diff --git a/src/client/client.ts b/src/client/client.ts index 9eb9758..300824e 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -97,7 +97,7 @@ export class ScramjetClient { constructor(public global: typeof globalThis) { if (SCRAMJETCLIENT in global) { console.error( - "attempted to initialize a scramjet cl ient, but one is already loaded - this is very bad" + "attempted to initialize a scramjet client, but one is already loaded - this is very bad" ); throw new Error(); } diff --git a/src/client/index.ts b/src/client/index.ts index e692055..f4becff 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -14,7 +14,7 @@ export const isshared = "SharedWorkerGlobalScope" in self; export const isemulatedsw = new URL(self.location.href).searchParams.get("dest") === "serviceworker"; -dbg.log("scrammin"); +dbg.log("initializing scramjet client"); // if it already exists, that means the handlers have probably already been setup by the parent document if (!(SCRAMJETCLIENT in >self)) { loadCodecs(); diff --git a/src/client/shared/requests/websocket.ts b/src/client/shared/requests/websocket.ts index 1a5362f..eb6d3ed 100644 --- a/src/client/shared/requests/websocket.ts +++ b/src/client/shared/requests/websocket.ts @@ -13,8 +13,21 @@ type FakeWebSocketState = { onmessage?: (ev: MessageEvent) => any; onopen?: (ev: Event) => any; }; +type FakeWebSocketStreamState = { + extensions: string; + protocol: string; + url: string; + barews: BareWebSocket; + + opened: any; + closed: any; + readable: ReadableStream; + writable: WritableStream; +}; export default function (client: ScramjetClient, self: typeof globalThis) { const socketmap: WeakMap = new WeakMap(); + const socketstreammap: WeakMap = + new WeakMap(); client.Proxy("WebSocket", { construct(ctx) { const fakeWebSocket = new EventTarget() as WebSocket; @@ -215,4 +228,111 @@ export default function (client: ScramjetClient, self: typeof globalThis) { ctx.return(ws.barews.close(ctx.args[0], ctx.args[1])); }, }); + + client.Proxy("WebSocketStream", { + construct(ctx) { + const fakeWebSocket = {}; + Object.setPrototypeOf(fakeWebSocket, ctx.fn.prototype); + fakeWebSocket.constructor = ctx.fn; + + const barews = client.bare.createWebSocket( + ctx.args[0], + ctx.args[1], + null, + { + "User-Agent": self.navigator.userAgent, + Origin: client.url.origin, + } + ); + ctx.args[1]?.signal.addEventListener("abort", () => { + barews.close(1000, ""); + }); + let openResolver, closeResolver; + const state: FakeWebSocketStreamState = { + extensions: "", + protocol: "", + url: ctx.args[0], + barews, + + opened: new Promise((resolve) => { + openResolver = resolve; + }), + closed: new Promise((resolve) => { + closeResolver = resolve; + }), + readable: new ReadableStream({ + start(controller) { + barews.addEventListener("message", (ev: MessageEvent) => { + const payload = ev.data; + if (typeof payload === "string") { + // DO NOTHING + } else if ("byteLength" in payload) { + Object.setPrototypeOf(payload, ArrayBuffer.prototype); + } + controller.enqueue(payload); + }); + }, + }), + writable: new WritableStream({ + write(chunk) { + barews.send(chunk); + }, + }), + }; + barews.addEventListener("open", () => { + openResolver({ + readable: state.readable, + writable: state.writable, + extensions: state.extensions, + protocol: state.protocol, + }); + }); + barews.addEventListener("close", (ev: CloseEvent) => { + closeResolver({ code: ev.code, reason: ev.reason }); + }); + + socketstreammap.set(fakeWebSocket, state); + ctx.return(fakeWebSocket); + }, + }); + + client.Trap("WebSocketStream.prototype.closed", { + get(ctx) { + const ws = socketstreammap.get(ctx.this); + + return ws.closed; + }, + }); + + client.Trap("WebSocketStream.prototype.opened", { + get(ctx) { + const ws = socketstreammap.get(ctx.this); + + return ws.opened; + }, + }); + + client.Trap("WebSocketStream.prototype.url", { + get(ctx) { + const ws = socketstreammap.get(ctx.this); + + return ws.url; + }, + }); + + client.Proxy("WebSocketStream.prototype.close", { + apply(ctx) { + const ws = socketstreammap.get(ctx.this); + if (ctx.args[0]) { + if (ctx.args[0].closeCode === undefined) ctx.args[0].closeCode = 1000; + if (ctx.args[0].reason === undefined) ctx.args[0].reason = ""; + + return ctx.return( + ws.barews.close(ctx.args[0].closeCode, ctx.args[0].reason) + ); + } + + return ctx.return(ws.barews.close(1000, "")); + }, + }); } diff --git a/src/controller/index.ts b/src/controller/index.ts index e8b9fa7..857d108 100644 --- a/src/controller/index.ts +++ b/src/controller/index.ts @@ -59,15 +59,11 @@ export class ScramjetController { $scramjet.config = deepMerge(defaultConfig, config); } - async init(serviceWorkerPath: string): Promise { + async init(): Promise { loadCodecs(); await this.openIDB(); - - const reg = await navigator.serviceWorker.register(serviceWorkerPath); - dbg.log("service worker registered"); - - return reg; + dbg.log("config loaded"); } createFrame(frame?: HTMLIFrameElement): ScramjetFrame {