diff --git a/package.json b/package.json
index 34fb584c..208ca8cc 100644
--- a/package.json
+++ b/package.json
@@ -75,6 +75,21 @@
},
"peerDependencyRules": {
"allowAny": [
+ "@penumbra-zone/bech32m",
+ "@penumbra-zone/client",
+ "@penumbra-zone/crypto-web",
+ "@penumbra-zone/getters",
+ "@penumbra-zone/keys",
+ "@penumbra-zone/perspective",
+ "@penumbra-zone/protobuf",
+ "@penumbra-zone/query",
+ "@penumbra-zone/services",
+ "@penumbra-zone/storage",
+ "@penumbra-zone/transport-chrome",
+ "@penumbra-zone/transport-dom",
+ "@penumbra-zone/types",
+ "@penumbra-zone/ui",
+ "@penumbra-zone/wasm",
"@penumbra-zone/bech32m",
"@penumbra-zone/client",
"@penumbra-zone/crypto-web",
diff --git a/packages/chrome-offscreen-worker/src/controller.ts b/packages/chrome-offscreen-worker/src/controller.ts
index 76dc74ac..3a59a378 100644
--- a/packages/chrome-offscreen-worker/src/controller.ts
+++ b/packages/chrome-offscreen-worker/src/controller.ts
@@ -1,5 +1,6 @@
///
+import { OffscreenControl } from './messages/offscreen-control.js';
import { WorkerConstructorParamsPrimitive } from './messages/primitive.js';
export class OffscreenController {
@@ -91,6 +92,11 @@ export class OffscreenController {
const workerId = crypto.randomUUID();
+ const newWorker: OffscreenControl<'new-Worker'> = {
+ control: 'new-Worker',
+ data: { workerId, init },
+ };
+
const {
promise: workerConnection,
resolve: resolveConnection,
@@ -111,10 +117,7 @@ export class OffscreenController {
chrome.runtime.onConnect.addListener(workerConnect);
- (await session).postMessage({
- control: 'new',
- data: { workerId, init },
- });
+ (await session).postMessage(newWorker);
void workerConnection.then(workerPort => {
this.workers.set(workerId, workerPort);
diff --git a/packages/chrome-offscreen-worker/src/entry.ts b/packages/chrome-offscreen-worker/src/entry.ts
index d5512a42..e94befd3 100644
--- a/packages/chrome-offscreen-worker/src/entry.ts
+++ b/packages/chrome-offscreen-worker/src/entry.ts
@@ -1,5 +1,5 @@
import { isOffscreenControl, validOffscreenControlData } from './messages/offscreen-control';
-import { isWorkerEvent, validWorkerEventInit } from './messages/worker-event';
+import { isWorkerEvent, validWorkerEventInit, WorkerEvent } from './messages/worker-event';
import { toErrorEventInit, toMessageEventInit } from './to-init';
declare global {
@@ -48,15 +48,23 @@ const constructWorker = ({
});
const caller = chrome.runtime.connect({ name: workerId });
- worker.addEventListener('error', event =>
- caller.postMessage({ event: 'error', init: toErrorEventInit(event) }),
- );
- worker.addEventListener('messageerror', event =>
- caller.postMessage({ event: 'messageerror', init: toMessageEventInit(event) }),
- );
- worker.addEventListener('message', event =>
- caller.postMessage({ event: 'message', init: toMessageEventInit(event) }),
- );
+ worker.addEventListener('error', event => {
+ console.log('-- worker output error ', event);
+ const json: WorkerEvent<'error'> = ['error', toErrorEventInit(event)];
+ caller.postMessage(json);
+ });
+
+ worker.addEventListener('messageerror', event => {
+ console.log('-- worker output messageerror ', event);
+ const json: WorkerEvent<'messageerror'> = ['messageerror', toMessageEventInit(event)];
+ caller.postMessage(json);
+ });
+
+ worker.addEventListener('message', event => {
+ console.log('-- worker output message ', event);
+ const json: WorkerEvent<'message'> = ['message', toMessageEventInit(event)];
+ caller.postMessage(json);
+ });
// setup disconnect handler
caller.onDisconnect.addListener(() => {
@@ -68,9 +76,10 @@ const constructWorker = ({
caller.onMessage.addListener((json: unknown) => {
console.log('entry callerInputListener', json, workerId);
if (isWorkerEvent(json)) {
- switch (json.event) {
+ const [event, init] = json;
+ switch (event) {
case 'message': {
- const { data } = validWorkerEventInit('message', json);
+ const { data } = validWorkerEventInit('message', init);
worker.postMessage(data);
return;
}
diff --git a/packages/chrome-offscreen-worker/src/messages/worker-event.ts b/packages/chrome-offscreen-worker/src/messages/worker-event.ts
index 4e703935..1a8d8ef0 100644
--- a/packages/chrome-offscreen-worker/src/messages/worker-event.ts
+++ b/packages/chrome-offscreen-worker/src/messages/worker-event.ts
@@ -10,29 +10,26 @@ interface WorkerEventInitMap extends Required
export type WorkerEventType = keyof WorkerEventMap;
-export interface WorkerEvent {
- event: T;
- init: T extends WorkerEventType ? WorkerEventInitMap[T] : unknown;
-}
-
-export const isWorkerEvent = (message: unknown): message is WorkerEvent =>
- typeof message === 'object' &&
- message != null &&
- 'event' in message &&
- typeof message.event === 'string' &&
- 'init' in message &&
- typeof message.init === 'object' &&
- message.init != null;
+export type WorkerEvent = [
+ T,
+ T extends WorkerEventType ? WorkerEventInitMap[T] : unknown,
+];
+
+export const isWorkerEvent = (message: unknown): message is WorkerEvent => {
+ if (Array.isArray(message) && message.length === 2) {
+ const [event, init] = message as [unknown, unknown];
+ return typeof event === 'string' && typeof init === 'object' && init != null;
+ }
+ return false;
+};
-export const hasValidWorkerEventInit = (message: {
- event: string | T;
- init: unknown;
-}): message is WorkerEvent => {
- if (typeof message.init !== 'object' || message.init == null) {
+export const hasValidWorkerEventInit = (
+ params: WorkerEvent,
+): params is WorkerEvent => {
+ const [event, init] = params;
+ if (typeof init !== 'object' || init == null) {
return false;
}
-
- const { event, init } = message;
switch (event) {
case 'error':
return isErrorEventInitPrimitive(init);
@@ -45,11 +42,12 @@ export const hasValidWorkerEventInit = (message: {
};
export const validWorkerEventInit = (
- type: T,
- message: WorkerEvent,
-): WorkerEvent['init'] => {
- if (message.event !== type || !hasValidWorkerEventInit(message)) {
+ event: T,
+ init: unknown,
+): WorkerEvent[1] => {
+ const message = [event, init] satisfies WorkerEvent;
+ if (!hasValidWorkerEventInit(message)) {
throw new TypeError('invalid WorkerEvent');
}
- return message.init;
+ return message[1];
};
diff --git a/packages/chrome-offscreen-worker/src/worker.ts b/packages/chrome-offscreen-worker/src/worker.ts
index 8709c863..7677b57a 100644
--- a/packages/chrome-offscreen-worker/src/worker.ts
+++ b/packages/chrome-offscreen-worker/src/worker.ts
@@ -1,16 +1,8 @@
import { OffscreenController } from './controller';
import type { WorkerConstructorParamsPrimitive } from './messages/primitive';
-import {
- hasValidWorkerEventInit,
- isWorkerEvent,
- WorkerEvent,
- WorkerEventType,
-} from './messages/worker-event';
+import { isWorkerEvent, validWorkerEventInit, WorkerEvent } from './messages/worker-event';
-type ErrorEventInitUnknown = Omit & { error?: unknown };
-type MessageEventInitUnknown = Omit & { data?: unknown };
-
-export class OffscreenWorker implements Worker {
+export class OffscreenWorker extends EventTarget implements Worker {
private static control?: OffscreenController;
public static configure(...params: ConstructorParameters) {
@@ -19,9 +11,7 @@ export class OffscreenWorker implements Worker {
private params: WorkerConstructorParamsPrimitive;
- private outgoing = new EventTarget();
- private incoming = new EventTarget();
- private workerPort: Promise;
+ private worker: Promise;
// user-assignable callback properties
onerror: Worker['onerror'] = null;
@@ -29,96 +19,91 @@ export class OffscreenWorker implements Worker {
onmessageerror: Worker['onmessageerror'] = null;
constructor(...[scriptURL, options]: ConstructorParameters) {
- this.params = [
- String(scriptURL),
- {
- name: `${this.constructor.name} ${Date.now()} ${String(scriptURL)}`,
- ...options,
- },
- ];
-
if (!OffscreenWorker.control) {
throw new Error(
- `${this.constructor.name + '.configure'} must be called before constructing ${this.constructor.name}`,
+ 'The static configure method must be called before constructing an instance of this class.',
);
}
- this.workerPort = OffscreenWorker.control.constructWorker(...this.params);
-
- this.outgoing.addEventListener(
- 'error',
- evt => void this.onerror?.call(this, evt as ErrorEvent),
- );
- this.outgoing.addEventListener(
- 'message',
- evt => void this.onmessage?.call(this, evt as MessageEvent),
- );
- this.outgoing.addEventListener(
- 'messageerror',
- evt => void this.onmessageerror?.call(this, evt as MessageEvent),
- );
-
- void this.workerPort.then(
- port => {
- console.log(this.params[1].name, 'got chromePort');
- port.onMessage.addListener(this.workerOutputListener);
-
- this.incoming.addEventListener('error', this.callerInputListener);
- this.incoming.addEventListener('message', this.callerInputListener);
- this.incoming.addEventListener('messageerror', this.callerInputListener);
- },
- (error: unknown) => {
- this.outgoing.dispatchEvent(new ErrorEvent('error', { error }));
- throw new Error('Failed to attach worker port', { cause: error });
- },
- );
+ console.log('offscreen worker super');
+ super();
+
+ this.params = [
+ String(scriptURL),
+ { name: `${this.constructor.name} ${Date.now()} ${String(scriptURL)}`, ...options },
+ ];
+
+ console.log('calling offscreen worker construct', this.params[1].name);
+ this.worker = OffscreenWorker.control.constructWorker(...this.params);
+
+ void this.worker
+ .then(
+ workerPort => {
+ console.log('got worker port', this.params[1].name);
+ workerPort.onMessage.addListener((...params) => {
+ console.log('activated worker output listener in background', ...params);
+ this.workerDispatch(...params);
+ });
+ },
+ (error: unknown) => {
+ this.dispatchEvent(new ErrorEvent('error', { error }));
+ throw new Error('Failed to attach worker port', { cause: error });
+ },
+ )
+ .finally(() => {
+ console.log('worker promise settled', this.params[1].name, this.worker);
+ });
+
+ console.log('exit constructor');
}
- private workerOutputListener = (json: unknown) => {
- console.debug('worker workerOutputListener', json);
- debugger;
+ private workerDispatch = (...[json]: [unknown, chrome.runtime.Port]) => {
+ console.debug('worker output', json);
if (isWorkerEvent(json)) {
- switch (json.event) {
+ const [event, init] = json;
+ switch (event) {
case 'error': {
- const { colno, filename, lineno, message } = validateEventInit<'error'>(json);
- this.outgoing.dispatchEvent(
- new ErrorEvent(json.event, { colno, filename, lineno, message }),
- );
- return;
- }
- case 'message': {
- const { data } = validateEventInit<'message'>(json);
- this.outgoing.dispatchEvent(new MessageEvent(json.event, { data }));
- return;
+ const { colno, filename, lineno, message } = validWorkerEventInit(event, init);
+ const dispatch = new ErrorEvent(event, { colno, filename, lineno, message });
+ this.dispatchEvent(dispatch);
+ this.onerror?.(dispatch);
+ break;
}
+ case 'message':
case 'messageerror': {
- const { data } = validateEventInit<'messageerror'>(json);
- this.outgoing.dispatchEvent(new MessageEvent(json.event, { data }));
- return;
+ const { data } = validWorkerEventInit(event, init);
+ const dispatch = new MessageEvent(event, { data });
+ this.dispatchEvent(dispatch);
+ this[`on${event}`]?.(dispatch);
+ break;
}
default:
throw new Error('Unknown event from worker', { cause: json });
- //this.outgoing.dispatchEvent(new Event(json.event, json.init));
}
}
};
- private callerInputListener = (evt: Event) => {
- console.debug('worker callerInputListener', [evt]);
+ private callerDispatch = (evt: Event) => {
+ console.debug('worker callerInputListener', evt.type);
switch (evt.type) {
case 'message': {
const { data } = evt as MessageEvent;
- void this.workerPort.then(port => port.postMessage({ event: 'message', init: { data } }));
+ const workerEventMessage: WorkerEvent<'message'> = ['message', { data }];
+ void this.worker.then(port => port.postMessage(workerEventMessage));
return;
}
- default:
+ case 'error':
+ case 'messageerror':
throw new Error('Unexpected event from caller', { cause: evt });
+ default:
+ throw new Error('Unknown event from caller', { cause: evt });
}
};
terminate: Worker['terminate'] = () => {
console.warn('worker terminate', this.params[1].name);
- void this.workerPort.then(port => port.disconnect());
+ void this.worker.then(port => port.disconnect());
+ this.postMessage = () => void 0;
};
postMessage: Worker['postMessage'] = (...args) => {
@@ -133,35 +118,6 @@ export class OffscreenWorker implements Worker {
const messageEvent = new MessageEvent('message', { data });
- this.incoming.dispatchEvent(messageEvent);
- };
-
- dispatchEvent: Worker['dispatchEvent'] = event => {
- console.debug('worker dispatchEvent', this.params[1].name, [event]);
- return this.incoming.dispatchEvent(event);
- };
-
- addEventListener: Worker['addEventListener'] = (
- ...args: Parameters
- ) => {
- console.debug('worker addEventListener', this.params[1].name, args);
- this.outgoing.addEventListener(...args);
- };
-
- removeEventListener: Worker['removeEventListener'] = (
- ...args: Parameters
- ) => {
- console.debug('worker removeEventListener', this.params[1].name, args);
- this.outgoing.removeEventListener(...args);
+ this.callerDispatch(messageEvent);
};
}
-
-const validateEventInit = (message: {
- event: T | string;
- init: NonNullable