add getTransport, add retrying to the sw messageport promise

This commit is contained in:
Toshit Chawda 2024-07-07 19:38:15 -07:00
parent c540450b66
commit dc9f4a7e39
No known key found for this signature in database
GPG key ID: 91480ED99E2B3D9D
6 changed files with 43 additions and 19 deletions

View file

@ -5,6 +5,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "rollup -c", "build": "rollup -c",
"watch": "rollup -cw",
"prepare": "npm run build" "prepare": "npm run build"
}, },
"author": "", "author": "",

View file

@ -44,7 +44,7 @@ const configs = [
format: 'umd', format: 'umd',
name: 'BareMux', name: 'BareMux',
sourcemap: true, sourcemap: true,
exports: 'auto', exports: 'named',
}, },
plugins: commonPlugins(), plugins: commonPlugins(),
}, },

View file

@ -107,10 +107,14 @@ export class BareMuxConnection {
this.worker = new WorkerConnection(workerPath); this.worker = new WorkerConnection(workerPath);
} }
async getTransport(): Promise<string> {
return (await this.worker.sendMessage({ type: "get" })).name;
}
async setTransport(path: string, options: any[]) { async setTransport(path: string, options: any[]) {
await this.setManualTransport(` await this.setManualTransport(`
const { default: BareTransport } = await import("${path}"); const { default: BareTransport } = await import("${path}");
return new BareTransport(${options.map(x => JSON.stringify(x)).join(", ")}); return [new BareTransport(${options.map(x => JSON.stringify(x)).join(", ")}), "${path}"];
`); `);
} }

View file

@ -2,18 +2,8 @@ import { BareHeaders, TransferrableResponse } from "./baretypes";
type SWClient = { postMessage: typeof MessagePort.prototype.postMessage }; type SWClient = { postMessage: typeof MessagePort.prototype.postMessage };
function tryGetPort(client: SWClient): Promise<MessagePort> {
let channel = new MessageChannel();
return new Promise(resolve => {
client.postMessage({ type: "getPort", port: channel.port2 }, [channel.port2]);
channel.port1.onmessage = event => {
resolve(event.data)
}
});
}
export type WorkerMessage = { export type WorkerMessage = {
type: "fetch" | "websocket" | "set", type: "fetch" | "websocket" | "set" | "get",
fetch?: { fetch?: {
remote: string, remote: string,
method: string, method: string,
@ -36,8 +26,9 @@ export type WorkerRequest = {
} }
export type WorkerResponse = { export type WorkerResponse = {
type: "fetch" | "websocket" | "set" | "error", type: "fetch" | "websocket" | "set" | "get" | "error",
fetch?: TransferrableResponse, fetch?: TransferrableResponse,
name?: string,
error?: Error, error?: Error,
} }
@ -46,6 +37,28 @@ type BroadcastMessage = {
path?: string, path?: string,
} }
async function searchForPort(): Promise<MessagePort> {
// @ts-expect-error
const clients: SWClient[] = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
const promise: Promise<MessagePort> = Promise.race([...clients.map((x: SWClient) => tryGetPort(x)), new Promise((_, reject) => setTimeout(reject, 1000, new Error("")))]) as Promise<MessagePort>;
try {
return await promise;
} catch {
console.warn("bare-mux: failed to get a bare-mux SharedWorker MessagePort within 1s, retrying");
return await searchForPort();
}
}
function tryGetPort(client: SWClient): Promise<MessagePort> {
let channel = new MessageChannel();
return new Promise(resolve => {
client.postMessage({ type: "getPort", port: channel.port2 }, [channel.port2]);
channel.port1.onmessage = event => {
resolve(event.data)
}
});
}
function createPort(path: string, channel: BroadcastChannel): MessagePort { function createPort(path: string, channel: BroadcastChannel): MessagePort {
const worker = new SharedWorker(path, "bare-mux-worker"); const worker = new SharedWorker(path, "bare-mux-worker");
// uv removes navigator.serviceWorker so this errors // uv removes navigator.serviceWorker so this errors
@ -77,12 +90,12 @@ export class WorkerConnection {
if (self.clients) { if (self.clients) {
// running in a ServiceWorker // running in a ServiceWorker
// ask a window for the worker port // ask a window for the worker port
// @ts-expect-error this.port = searchForPort();
const clients: Promise<SWClient[]> = self.clients.matchAll({ type: "window", includeUncontrolled: true });
this.port = clients.then(clients => Promise.any(clients.map((x: SWClient) => tryGetPort(x))));
} else if (workerPath && SharedWorker) { } else if (workerPath && SharedWorker) {
// running in a window, was passed a workerPath // running in a window, was passed a workerPath
// create the SharedWorker and help other bare-mux clients get the workerPath // create the SharedWorker and help other bare-mux clients get the workerPath
if (!workerPath.startsWith("/") && !workerPath.includes("://")) throw new Error("Invalid URL. Must be absolute or start at the root.");
this.port = createPort(workerPath, this.channel); this.port = createPort(workerPath, this.channel);
} else if (SharedWorker) { } else if (SharedWorker) {
// running in a window, was not passed a workerPath // running in a window, was not passed a workerPath

View file

@ -6,6 +6,7 @@ export const WebSocket = globalThis.WebSocket;
export const Request = globalThis.Request; export const Request = globalThis.Request;
export const Response = globalThis.Response; export const Response = globalThis.Response;
export const XMLHttpRequest = globalThis.XMLHttpRequest; export const XMLHttpRequest = globalThis.XMLHttpRequest;
export const SharedWorker = globalThis.SharedWorker;
export const WebSocketFields = { export const WebSocketFields = {
prototype: { prototype: {

View file

@ -2,6 +2,7 @@ import { BareTransport } from "./baretypes";
import { WorkerMessage, WorkerResponse } from "./connection" import { WorkerMessage, WorkerResponse } from "./connection"
let currentTransport: BareTransport | null = null; let currentTransport: BareTransport | null = null;
let currentTransportName: string = "";
function noClients(): Error { function noClients(): Error {
// @ts-expect-error mdn error constructor: new Error(message, options) // @ts-expect-error mdn error constructor: new Error(message, options)
@ -21,13 +22,17 @@ function handleConnection(port: MessagePort) {
// @ts-expect-error // @ts-expect-error
const func = new AsyncFunction(message.client); const func = new AsyncFunction(message.client);
currentTransport = await func(); const [newTransport, name] = await func();
console.log("set transport to ", currentTransport); currentTransport = newTransport;
currentTransportName = name;
console.log("set transport to ", currentTransport, name);
port.postMessage(<WorkerResponse>{ type: "set" }); port.postMessage(<WorkerResponse>{ type: "set" });
} catch (err) { } catch (err) {
port.postMessage(<WorkerResponse>{ type: "error", error: err }); port.postMessage(<WorkerResponse>{ type: "error", error: err });
} }
} else if (message.type === "get") {
port.postMessage(<WorkerResponse>{ type: "get", name: currentTransportName });
} else if (message.type === "fetch") { } else if (message.type === "fetch") {
try { try {
if (!currentTransport) throw noClients(); if (!currentTransport) throw noClients();