Skip to content

Commit

Permalink
working
Browse files Browse the repository at this point in the history
  • Loading branch information
turbocrime committed Sep 6, 2024
1 parent 0c48f8e commit 1d6bfa2
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 152 deletions.
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 7 additions & 4 deletions packages/chrome-offscreen-worker/src/controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="chrome" />

import { OffscreenControl } from './messages/offscreen-control.js';
import { WorkerConstructorParamsPrimitive } from './messages/primitive.js';

export class OffscreenController {
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
33 changes: 21 additions & 12 deletions packages/chrome-offscreen-worker/src/entry.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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(() => {
Expand All @@ -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;
}
Expand Down
48 changes: 23 additions & 25 deletions packages/chrome-offscreen-worker/src/messages/worker-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,26 @@ interface WorkerEventInitMap extends Required<Record<WorkerEventType, EventInit>

export type WorkerEventType = keyof WorkerEventMap;

export interface WorkerEvent<T extends string = WorkerEventType> {
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 extends string = WorkerEventType> = [
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 = <T extends WorkerEventType>(message: {
event: string | T;
init: unknown;
}): message is WorkerEvent<T> => {
if (typeof message.init !== 'object' || message.init == null) {
export const hasValidWorkerEventInit = <T extends WorkerEventType>(
params: WorkerEvent<T | string>,
): params is WorkerEvent<T> => {
const [event, init] = params;
if (typeof init !== 'object' || init == null) {
return false;
}

const { event, init } = message;
switch (event) {
case 'error':
return isErrorEventInitPrimitive(init);
Expand All @@ -45,11 +42,12 @@ export const hasValidWorkerEventInit = <T extends WorkerEventType>(message: {
};

export const validWorkerEventInit = <T extends WorkerEventType>(
type: T,
message: WorkerEvent<string>,
): WorkerEvent<T>['init'] => {
if (message.event !== type || !hasValidWorkerEventInit<T>(message)) {
event: T,
init: unknown,
): WorkerEvent<T>[1] => {
const message = [event, init] satisfies WorkerEvent<string>;
if (!hasValidWorkerEventInit<T>(message)) {
throw new TypeError('invalid WorkerEvent');
}
return message.init;
return message[1];
};
164 changes: 60 additions & 104 deletions packages/chrome-offscreen-worker/src/worker.ts
Original file line number Diff line number Diff line change
@@ -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<ErrorEventInit, 'error'> & { error?: unknown };
type MessageEventInitUnknown = Omit<MessageEventInit, 'data'> & { data?: unknown };

export class OffscreenWorker implements Worker {
export class OffscreenWorker extends EventTarget implements Worker {
private static control?: OffscreenController;

public static configure(...params: ConstructorParameters<typeof OffscreenController>) {
Expand All @@ -19,106 +11,99 @@ export class OffscreenWorker implements Worker {

private params: WorkerConstructorParamsPrimitive;

private outgoing = new EventTarget();
private incoming = new EventTarget();
private workerPort: Promise<chrome.runtime.Port>;
private worker: Promise<chrome.runtime.Port>;

// user-assignable callback properties
onerror: Worker['onerror'] = null;
onmessage: Worker['onmessage'] = null;
onmessageerror: Worker['onmessageerror'] = null;

constructor(...[scriptURL, options]: ConstructorParameters<typeof Worker>) {
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<unknown>;
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) => {
Expand All @@ -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<Worker['addEventListener']>
) => {
console.debug('worker addEventListener', this.params[1].name, args);
this.outgoing.addEventListener(...args);
};

removeEventListener: Worker['removeEventListener'] = (
...args: Parameters<Worker['removeEventListener']>
) => {
console.debug('worker removeEventListener', this.params[1].name, args);
this.outgoing.removeEventListener(...args);
this.callerDispatch(messageEvent);
};
}

const validateEventInit = <T extends WorkerEventType>(message: {
event: T | string;
init: NonNullable<object>;
}): WorkerEvent<T>['init'] => {
if (!hasValidWorkerEventInit(message)) {
throw new TypeError('Invalid event init', { cause: message });
}
return message.init;
};
Loading

0 comments on commit 1d6bfa2

Please sign in to comment.