mirror of
https://github.com/MercuryWorkshop/scramjet.git
synced 2025-05-13 14:30:02 -04:00
initial work on websocket wrapper
This commit is contained in:
parent
a8169c4cde
commit
05795969a7
3 changed files with 212 additions and 11 deletions
|
@ -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> = {};
|
||||||
|
|
|
@ -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]));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue