Last active
September 9, 2025 16:58
-
-
Save zicklag/2d7e139ee8eeeed78eb5697980a5da54 to your computer and use it in GitHub Desktop.
Simple helper to make it easy to communicate with typed interfaces over browser MessagePorts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import MySharedWorker from './mySharedWorker.ts?sharedworker'; // using Vite shared worker import | |
import { messagePortInterface } from './messagePortInterface'; | |
// | |
// We need to define the interfaces that we use on both sides of the message port. | |
// | |
// This is the interface we use for the frontend, i.e. when the worker wants to call | |
// a function remotely against the UI thread. | |
export type MainThreadInterface = { | |
getServiceAuth: (aud: string) => Promise<string>; | |
}; | |
// This is the interface we use for the "backend" | |
export type SharedWorkerInterface = { | |
sayHello: (name: string, name2: string) => Promise<string>; | |
logout: () => Promise<void>; | |
}; | |
// Initialize shared worker | |
const worker = new MySharedWorker(); | |
// Now we can create our message port interface. We need to specify the local interface | |
// first, and then the remote interface as type parameters. | |
export const workerInterface = messagePortInterface<MainThreadInterface, SharedWorkerInterface>( | |
worker.port, // Provide the port to communicate on | |
// And now we have to implement the MainThreadInterface | |
{ | |
async getServiceAuth(aud) { | |
const resp = await atproto.agent?.com.atproto.server.getServiceAuth({ | |
aud | |
}); | |
if (!resp) throw 'Could not get service auth'; | |
return resp?.data.token; | |
} | |
} | |
); | |
// We can now call the worker interface | |
const response = await workerInterface.sayHello("John"); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type HalfInterface = { [key: string]: (...args: any[]) => Promise<unknown> }; | |
type IncomingMessage<In extends HalfInterface, Out extends HalfInterface> = | |
| { | |
[K in keyof In]: ['call', K, string, ...Parameters<In[K]>]; | |
}[keyof In] | |
| { [K in keyof Out]: ['response', string, 'resolve' | 'reject', ReturnType<Out[K]>] }[keyof Out]; | |
export function messagePortInterface<Local extends HalfInterface, Remote extends HalfInterface>( | |
messagePort: MessagePort, | |
handlers: Local | |
): Remote { | |
const pendingResponseResolers: { | |
[key: string]: { | |
resolve: (resp: ReturnType<Remote[keyof Remote]>) => void; | |
reject: (error: any) => void; | |
}; | |
} = {}; | |
messagePort.onmessage = async (ev: MessageEvent<IncomingMessage<Local, Remote>>) => { | |
const type = ev.data[0]; | |
if (type == 'call') { | |
const [_, name, requestId, ...parameters] = ev.data; | |
for (const [event, handler] of Object.entries(handlers)) { | |
if (event == name) { | |
try { | |
const resp = await handler(...parameters); | |
messagePort.postMessage(['response', requestId, 'resolve', resp]); | |
} catch (e) { | |
messagePort.postMessage(['response', requestId, 'reject', e]); | |
} | |
} | |
} | |
} else if (type == 'response') { | |
const [_, requestId, action, data] = ev.data; | |
pendingResponseResolers[requestId][action](data); | |
delete pendingResponseResolers[requestId]; | |
} | |
}; | |
return new Proxy( | |
{ | |
messagePort | |
}, | |
{ | |
get({ messagePort }, name) { | |
const n = name as keyof Remote; | |
return (...args: Parameters<Remote[typeof n]>): ReturnType<Remote[typeof n]> => { | |
let reqId = crypto.randomUUID(); | |
const respPromise = new Promise( | |
(resolve, reject) => (pendingResponseResolers[reqId] = { resolve, reject }) | |
); | |
messagePort.postMessage(['call', n, reqId, ...args]); | |
return respPromise as any; | |
}; | |
} | |
} | |
) as unknown as Remote; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment