initial work on websocket wrapper

This commit is contained in:
velzie 2024-09-07 17:37:35 -04:00
parent a8169c4cde
commit 05795969a7
No known key found for this signature in database
GPG key ID: 048413F95F0DDE1F
3 changed files with 212 additions and 11 deletions

View file

@ -13,6 +13,7 @@ import {
decodeUrl, decodeUrl,
encodeUrl, encodeUrl,
} from "../shared"; } from "../shared";
import type BareClientType from "@mercuryworkshop/bare-mux";
import { createWrapFn } from "./shared/wrap"; import { createWrapFn } from "./shared/wrap";
import { NavigateEvent } from "./events"; import { NavigateEvent } from "./events";
import type { URLMeta } from "../shared/rewriters/url"; import type { URLMeta } from "../shared/rewriters/url";
@ -60,7 +61,7 @@ export class ScramjetClient {
globalProxy: any; globalProxy: any;
locationProxy: any; locationProxy: any;
serviceWorker: ServiceWorkerContainer; serviceWorker: ServiceWorkerContainer;
bare: any; bare: BareClientType;
descriptors: Record<string, PropertyDescriptor> = {}; descriptors: Record<string, PropertyDescriptor> = {};
natives: Record<string, any> = {}; natives: Record<string, any> = {};

View file

@ -1,20 +1,219 @@
import { type BareWebSocket } from "@mercuryworkshop/bare-mux";
import { ScramjetClient } from "../../client"; import { ScramjetClient } from "../../client";
type FakeWebSocketState = {
extensions: string;
protocol: string;
url: string;
binaryType: string;
barews: BareWebSocket;
captureListeners: Record<string, EventListener[]>;
listeners: Record<string, EventListener[]>;
onclose?: (ev: CloseEvent) => any;
onerror?: (ev: Event) => any;
onmessage?: (ev: MessageEvent) => any;
onopen?: (ev: Event) => any;
};
export default function (client: ScramjetClient, self: typeof globalThis) { export default function (client: ScramjetClient, self: typeof globalThis) {
const socketmap: WeakMap<WebSocket, FakeWebSocketState> = new WeakMap();
client.Proxy("WebSocket", { client.Proxy("WebSocket", {
construct(ctx) { construct(ctx) {
ctx.return( const fakeWebSocket = new EventTarget() as WebSocket;
client.bare.createWebSocket( Object.setPrototypeOf(fakeWebSocket, self.WebSocket.prototype);
fakeWebSocket.constructor = ctx.fn;
const trustEvent = (ev: Event) =>
new Proxy(ev, {
get(target, prop) {
if (prop === "isTrusted") return true;
return Reflect.get(target, prop);
},
});
const barews = client.bare.createWebSocket(
ctx.args[0], ctx.args[0],
ctx.args[1], ctx.args[1],
ctx.fn as typeof WebSocket, null,
{ {
"User-Agent": self.navigator.userAgent, "User-Agent": self.navigator.userAgent,
Origin: client.url.origin, Origin: client.url.origin,
}, }
ArrayBuffer.prototype
)
); );
const state: FakeWebSocketState = {
extensions: "",
protocol: "",
url: ctx.args[0],
binaryType: "blob",
barews,
captureListeners: {},
listeners: {},
};
barews.addEventListener("open", (ev) => {
const fakeev = new Event("open");
state.onopen?.(trustEvent(fakeev));
fakeWebSocket.dispatchEvent(fakeev);
});
socketmap.set(fakeWebSocket, state);
},
});
client.Proxy("EventTarget.prototype.addEventListener", {
apply(ctx) {
const ws = socketmap.get(ctx.this);
if (!ws) return; // it's not a websocket ignore it
const [type, listener, opts] = ctx.args;
if (
(typeof opts === "object" && opts.capture) ||
(typeof opts === "boolean" && opts)
) {
const listeners = (ws.captureListeners[type] ??= []);
listeners.push(listener);
ws.captureListeners[type] = listeners;
} else {
const listeners = (ws.listeners[type] ??= []);
listeners.push(listener);
ws.listeners[type] = listeners;
}
ctx.return(undefined);
},
});
client.Proxy("EventTarget.prototype.removeEventListener", {
apply(ctx) {
const ws = socketmap.get(ctx.this);
if (!ws) return;
const [type, listener, opts] = ctx.args;
if (
(typeof opts === "object" && opts.capture) ||
(typeof opts === "boolean" && opts)
) {
const listeners = (ws.captureListeners[type] ??= []);
const idx = listeners.indexOf(listener);
if (idx !== -1) listeners.splice(idx, 1);
ws.captureListeners[type] = listeners;
} else {
const listeners = (ws.listeners[type] ??= []);
const idx = listeners.indexOf(listener);
if (idx !== -1) listeners.splice(idx, 1);
ws.listeners[type] = listeners;
}
ctx.return(undefined);
},
});
client.Trap("WebSocket.prototype.binaryType", {
get(ctx) {
const ws = socketmap.get(ctx.this);
return ws.binaryType;
},
set(ctx, v: string) {
const ws = socketmap.get(ctx.this);
if (v === "blob" || v === "arraybuffer") ws.binaryType = v;
},
});
client.Trap("WebSocket.prototype.bufferedAmount", {
get() {
return 0;
},
});
client.Trap("WebSocket.prototype.extensions", {
get(ctx) {
const ws = socketmap.get(ctx.this);
return ws.extensions;
},
});
client.Trap("WebSocket.prototype.onclose", {
get(ctx) {
const ws = socketmap.get(ctx.this);
return ws.onclose;
},
set(ctx, v: (ev: CloseEvent) => any) {
const ws = socketmap.get(ctx.this);
ws.onclose = v;
},
});
client.Trap("WebSocket.prototype.onerror", {
get(ctx) {
const ws = socketmap.get(ctx.this);
return ws.onerror;
},
set(ctx, v: (ev: Event) => any) {
const ws = socketmap.get(ctx.this);
ws.onerror = v;
},
});
client.Trap("WebSocket.prototype.onmessage", {
get(ctx) {
const ws = socketmap.get(ctx.this);
return ws.onmessage;
},
set(ctx, v: (ev: MessageEvent) => any) {
const ws = socketmap.get(ctx.this);
ws.onmessage = v;
},
});
client.Trap("WebSocket.prototype.onopen", {
get(ctx) {
const ws = socketmap.get(ctx.this);
return ws.onopen;
},
set(ctx, v: (ev: Event) => any) {
const ws = socketmap.get(ctx.this);
ws.onopen = v;
},
});
client.Trap("WebSocket.prototype.url", {
get(ctx) {
const ws = socketmap.get(ctx.this);
return ws.url;
},
});
client.Trap("WebSocket.prototype.protocol", {
get(ctx) {
const ws = socketmap.get(ctx.this);
return ws.protocol;
},
});
client.Trap("WebSocket.prototype.readyState", {
get(ctx) {
const ws = socketmap.get(ctx.this);
return ws.barews.readyState;
},
});
client.Proxy("WebSocket.prototype.send", {
apply(ctx) {
const ws = socketmap.get(ctx.this);
ctx.return(ws.barews.send(ctx.args[0]));
},
});
client.Proxy("WebSocket.prototype.close", {
apply(ctx) {
const ws = socketmap.get(ctx.this);
ctx.return(ws.barews.close(ctx.args[0], ctx.args[1]));
}, },
}); });
} }

View file

@ -7,6 +7,7 @@ export const order = 3;
export default function (client: ScramjetClient, self: typeof window) { export default function (client: ScramjetClient, self: typeof window) {
// an automated approach to cleaning the documentProxy from dom functions // an automated approach to cleaning the documentProxy from dom functions
// it will trigger an illegal invocation if you pass the proxy to c++ code, we gotta hotswap it out with the real one // it will trigger an illegal invocation if you pass the proxy to c++ code, we gotta hotswap it out with the real one
// admittedly this is pretty slow. worth investigating if there's ways to get back some of the lost performance
for (const target of [self]) { for (const target of [self]) {
for (const prop in target) { for (const prop in target) {