mirror of
https://github.com/MercuryWorkshop/scramjet.git
synced 2025-05-14 06:50:01 -04:00
swruntime: implement fetch event
This commit is contained in:
parent
14a0305bdb
commit
15bc9598c9
9 changed files with 273 additions and 31 deletions
|
@ -46,6 +46,17 @@ export class ScramjetClient {
|
|||
windowProxy: any;
|
||||
locationProxy: any;
|
||||
|
||||
eventcallbacks: WeakMap<
|
||||
any,
|
||||
[
|
||||
{
|
||||
event: string;
|
||||
originalCallback: AnyFunction;
|
||||
proxiedCallback: AnyFunction;
|
||||
},
|
||||
]
|
||||
> = new WeakMap();
|
||||
|
||||
constructor(public global: typeof globalThis) {
|
||||
if ("document" in self) {
|
||||
this.documentProxy = createDocumentProxy(this, global);
|
||||
|
|
|
@ -29,6 +29,15 @@ export default function (client: ScramjetClient, self: typeof window) {
|
|||
},
|
||||
});
|
||||
|
||||
client.Trap("document.documentURI", {
|
||||
get() {
|
||||
return decodeUrl(self.location.href);
|
||||
},
|
||||
set() {
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
client.Trap("document.domain", {
|
||||
get() {
|
||||
return client.url.hostname;
|
||||
|
|
|
@ -13,7 +13,7 @@ export default function (client: ScramjetClient, self: Self) {
|
|||
},
|
||||
});
|
||||
|
||||
client.Proxy("addEventListener", {
|
||||
client.Proxy("EventTarget.prototype.addEventListener", {
|
||||
apply(ctx) {
|
||||
if (fakeregistrations.has(ctx.this)) {
|
||||
// do nothing
|
||||
|
@ -22,7 +22,7 @@ export default function (client: ScramjetClient, self: Self) {
|
|||
},
|
||||
});
|
||||
|
||||
client.Proxy("removeEventListener", {
|
||||
client.Proxy("EventTarget.prototype.removeEventListener", {
|
||||
apply(ctx) {
|
||||
if (fakeregistrations.has(ctx.this)) {
|
||||
// do nothing
|
||||
|
@ -38,9 +38,10 @@ export default function (client: ScramjetClient, self: Self) {
|
|||
if (ctx.args[1] && ctx.args[1].type === "module") {
|
||||
url += "&type=module";
|
||||
}
|
||||
let worker = new SharedWorker(url);
|
||||
|
||||
let handle = worker.port;
|
||||
const worker = new SharedWorker(url);
|
||||
|
||||
const handle = worker.port;
|
||||
|
||||
navigator.serviceWorker.controller.postMessage(
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// entrypoint for scramjet.client.js
|
||||
|
||||
import { ScramjetClient } from "./client";
|
||||
import { ScramjetServiceWorkerRuntime } from "./swruntime";
|
||||
|
||||
export const iswindow = "window" in self;
|
||||
export const isworker = "WorkerGlobalScope" in self;
|
||||
|
@ -13,4 +14,9 @@ dbg.log("scrammin");
|
|||
if (!(ScramjetClient.SCRAMJET in self)) {
|
||||
const client = new ScramjetClient(self);
|
||||
client.hook();
|
||||
|
||||
if (issw) {
|
||||
const runtime = new ScramjetServiceWorkerRuntime(client);
|
||||
runtime.hook();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,14 @@ const realOnEvent = Symbol.for("scramjet original onevent function");
|
|||
export default function (client: ScramjetClient, self: Self) {
|
||||
const handlers = {
|
||||
message: {
|
||||
_init() {
|
||||
if (typeof this.data === "object" && "$scramjet$type" in this.data) {
|
||||
// this is a ctl message
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
origin() {
|
||||
if (typeof this.data === "object" && "$scramjet$origin" in this.data)
|
||||
return this.data.$scramjet$origin;
|
||||
|
@ -33,7 +41,12 @@ export default function (client: ScramjetClient, self: Self) {
|
|||
const type = realEvent.type;
|
||||
|
||||
if (type in handlers) {
|
||||
let handler = handlers[type];
|
||||
const handler = handlers[type];
|
||||
|
||||
if (handler._init) {
|
||||
if (handler._init.call(realEvent) === false) return;
|
||||
}
|
||||
|
||||
argArray[0] = new Proxy(realEvent, {
|
||||
get(_target, prop, reciever) {
|
||||
if (prop in handler) {
|
||||
|
@ -54,9 +67,41 @@ export default function (client: ScramjetClient, self: Self) {
|
|||
client.Proxy("EventTarget.prototype.addEventListener", {
|
||||
apply(ctx) {
|
||||
unproxy(ctx, client);
|
||||
// if (ctx.args[0] === "message" && iswindow) debugger;
|
||||
if (typeof ctx.args[1] === "function")
|
||||
ctx.args[1] = wraplistener(ctx.args[1]);
|
||||
if (typeof ctx.args[1] !== "function") return;
|
||||
|
||||
const origlistener = ctx.args[1];
|
||||
const proxylistener = wraplistener(origlistener);
|
||||
|
||||
ctx.args[1] = proxylistener;
|
||||
|
||||
let arr = client.eventcallbacks.get(ctx.this);
|
||||
arr ||= [] as any;
|
||||
arr.push({
|
||||
event: ctx.args[0] as string,
|
||||
originalCallback: origlistener,
|
||||
proxiedCallback: proxylistener,
|
||||
});
|
||||
client.eventcallbacks.set(ctx.this, arr);
|
||||
},
|
||||
});
|
||||
|
||||
client.Proxy("EventTarget.prototype.removeEventListener", {
|
||||
apply(ctx) {
|
||||
unproxy(ctx, client);
|
||||
if (typeof ctx.args[1] !== "function") return;
|
||||
|
||||
const arr = client.eventcallbacks.get(ctx.this);
|
||||
if (!arr) return;
|
||||
|
||||
const i = arr.findIndex(
|
||||
(e) => e.event === ctx.args[0] && e.originalCallback === ctx.args[1]
|
||||
);
|
||||
if (i === -1) return;
|
||||
|
||||
arr.splice(i, 1);
|
||||
client.eventcallbacks.set(ctx.this, arr);
|
||||
|
||||
ctx.args[1] = arr[i].proxiedCallback;
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -66,8 +111,6 @@ export default function (client: ScramjetClient, self: Self) {
|
|||
},
|
||||
});
|
||||
|
||||
// TODO: removeEventListener
|
||||
|
||||
if (!iswindow) return;
|
||||
|
||||
const targets = [self.window, self.HTMLElement.prototype];
|
||||
|
|
|
@ -1,21 +1,121 @@
|
|||
import { ScramjetClient } from "./client";
|
||||
import { encodeUrl } from "./shared";
|
||||
|
||||
class ScramjetServiceWorkerRuntime {
|
||||
constructor() {
|
||||
addEventListener("message", (event) => {
|
||||
if ("scramjet$type" in event.data) {
|
||||
event.stopImmediatePropagation();
|
||||
export class ScramjetServiceWorkerRuntime {
|
||||
constructor(public client: ScramjetClient) {
|
||||
addEventListener("connect", (cevent: MessageEvent) => {
|
||||
const port = cevent.ports[0];
|
||||
|
||||
return;
|
||||
port.addEventListener("message", (event) => {
|
||||
if ("scramjet$type" in event.data) {
|
||||
handleMessage(client, event.data, port);
|
||||
}
|
||||
});
|
||||
|
||||
port.start();
|
||||
});
|
||||
}
|
||||
|
||||
hook() {}
|
||||
}
|
||||
|
||||
function handleMessage(
|
||||
client: ScramjetClient,
|
||||
data: MessageW2R,
|
||||
port: MessagePort
|
||||
) {
|
||||
const type = data.scramjet$type;
|
||||
const token = data.scramjet$token;
|
||||
|
||||
if (type === "fetch") {
|
||||
const fetchhandlers = client.eventcallbacks.get("fetch");
|
||||
if (!fetchhandlers) return;
|
||||
|
||||
for (const handler of fetchhandlers) {
|
||||
const request = data.scramjet$request;
|
||||
const fakeRequest = new Request(request.url, {
|
||||
body: request.body,
|
||||
headers: new Headers(request.headers),
|
||||
method: request.method,
|
||||
mode: request.mode,
|
||||
});
|
||||
|
||||
Object.defineProperty(fakeRequest, "destination", {
|
||||
value: request.destinitation,
|
||||
});
|
||||
|
||||
const fakeFetchEvent = new FetchEvent("fetch", {
|
||||
request: fakeRequest,
|
||||
});
|
||||
|
||||
fakeFetchEvent.respondWith = async (
|
||||
response: Response | Promise<Response>
|
||||
) => {
|
||||
response = await response;
|
||||
|
||||
response.body;
|
||||
port.postMessage({
|
||||
scramjet$type: "fetch",
|
||||
scramjet$token: token,
|
||||
scramjet$response: {
|
||||
body: response.body,
|
||||
headers: Array.from(response.headers.entries()),
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
},
|
||||
} as MessageR2W);
|
||||
};
|
||||
|
||||
handler.proxiedCallback(trustEvent(fakeFetchEvent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
ScramjetServiceWorkerRuntime: typeof ScramjetServiceWorkerRuntime;
|
||||
}
|
||||
function trustEvent(event: Event): Event {
|
||||
return new Proxy(event, {
|
||||
get(target, prop, reciever) {
|
||||
if (prop === "isTrusted") return true;
|
||||
|
||||
return Reflect.get(target, prop, reciever);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
self.ScramjetServiceWorkerRuntime = ScramjetServiceWorkerRuntime;
|
||||
export type TransferrableResponse = {
|
||||
body: ReadableStream;
|
||||
headers: [string, string][];
|
||||
status: number;
|
||||
statusText: string;
|
||||
};
|
||||
|
||||
export type TransferrableRequest = {
|
||||
body: ReadableStream;
|
||||
headers: [string, string][];
|
||||
destinitation: RequestDestination;
|
||||
method: Request["method"];
|
||||
mode: Request["mode"];
|
||||
url: string;
|
||||
};
|
||||
|
||||
type FetchResponseMessage = {
|
||||
scramjet$type: "fetch";
|
||||
scramjet$response: TransferrableResponse;
|
||||
};
|
||||
|
||||
type FetchRequestMessage = {
|
||||
scramjet$type: "fetch";
|
||||
scramjet$request: TransferrableRequest;
|
||||
};
|
||||
|
||||
// r2w = runtime to (service) worker
|
||||
|
||||
type MessageTypeR2W = FetchResponseMessage;
|
||||
type MessageTypeW2R = FetchRequestMessage;
|
||||
|
||||
type MessageCommon = {
|
||||
scramjet$type: string;
|
||||
scramjet$token: number;
|
||||
};
|
||||
|
||||
export type MessageR2W = MessageCommon & MessageTypeR2W;
|
||||
export type MessageW2R = MessageCommon & MessageTypeW2R;
|
||||
|
|
|
@ -1,3 +1,56 @@
|
|||
import { type MessageW2R, type MessageR2W } from "../client/swruntime";
|
||||
|
||||
export class FakeServiceWorker {
|
||||
constructor(public handle: MessagePort) {}
|
||||
syncToken = 0;
|
||||
promises: Record<number, (val?: MessageR2W) => void> = {};
|
||||
|
||||
constructor(
|
||||
public handle: MessagePort,
|
||||
public origin: string
|
||||
) {
|
||||
this.handle.start();
|
||||
|
||||
this.handle.addEventListener("message", (event) => {
|
||||
if ("scramjet$type" in event.data) {
|
||||
this.handleMessage(event.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleMessage(data: MessageR2W) {
|
||||
const cb = this.promises[data.scramjet$token];
|
||||
if (cb) {
|
||||
cb(data);
|
||||
delete this.promises[data.scramjet$token];
|
||||
}
|
||||
}
|
||||
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const token = this.syncToken++;
|
||||
|
||||
const message: MessageW2R = {
|
||||
scramjet$type: "fetch",
|
||||
scramjet$token: token,
|
||||
scramjet$request: {
|
||||
url: request.url,
|
||||
body: request.body,
|
||||
headers: Array.from(request.headers.entries()),
|
||||
method: request.method,
|
||||
mode: request.mode,
|
||||
destinitation: request.destination,
|
||||
},
|
||||
};
|
||||
|
||||
this.handle.postMessage(message);
|
||||
|
||||
const { scramjet$response: r } = (await new Promise((resolve) => {
|
||||
this.promises[token] = resolve;
|
||||
})) as MessageR2W;
|
||||
|
||||
return new Response(r.body, {
|
||||
headers: r.headers,
|
||||
status: r.status,
|
||||
statusText: r.statusText,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,14 @@ export async function swfetch(
|
|||
});
|
||||
}
|
||||
|
||||
const activeWorker = this.serviceWorkers.find(
|
||||
(w) => w.origin === new URL(request.url).origin
|
||||
);
|
||||
if (activeWorker) {
|
||||
// TODO: check scope
|
||||
return await activeWorker.fetch(request);
|
||||
}
|
||||
|
||||
const urlParam = new URLSearchParams(new URL(request.url).search);
|
||||
|
||||
if (urlParam.has("url")) {
|
||||
|
|
|
@ -24,24 +24,17 @@ export class ScramjetServiceWorker {
|
|||
|
||||
this.threadpool = new ScramjetThreadpool();
|
||||
|
||||
addEventListener("message", ({ data }) => {
|
||||
addEventListener("message", ({ data }: { data: MessageC2W }) => {
|
||||
if (!("scramjet$type" in data)) return;
|
||||
|
||||
if (data.scramjet$type === "registerServiceWorker") {
|
||||
this.serviceWorkers.push(new FakeServiceWorker(data.port));
|
||||
this.serviceWorkers.push(new FakeServiceWorker(data.port, data.origin));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!("scramjet$token" in data)) return;
|
||||
|
||||
const resolve = this.syncPool[data.scramjet$token];
|
||||
delete this.syncPool[data.scramjet$token];
|
||||
if (data.scramjet$type === "getLocalStorage") {
|
||||
resolve(data.data);
|
||||
} else if (data.scramjet$type === "setLocalStorage") {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -117,3 +110,21 @@ export class ScramjetServiceWorker {
|
|||
}
|
||||
|
||||
self.ScramjetServiceWorker = ScramjetServiceWorker;
|
||||
|
||||
type RegisterServiceWorkerMessage = {
|
||||
scramjet$type: "registerServiceWorker";
|
||||
port: MessagePort;
|
||||
origin: string;
|
||||
};
|
||||
|
||||
type MessageCommon = {
|
||||
scramjet$type: string;
|
||||
scramjet$token: number;
|
||||
};
|
||||
|
||||
type MessageTypeC2W = RegisterServiceWorkerMessage;
|
||||
type MessageTypeW2C = never;
|
||||
|
||||
// c2w: client to (service) worker
|
||||
export type MessageC2W = MessageCommon & MessageTypeC2W;
|
||||
export type MessageW2C = MessageCommon & MessageTypeW2C;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue