mirror of
https://github.com/MercuryWorkshop/scramjet.git
synced 2025-05-14 15:00: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;
|
windowProxy: any;
|
||||||
locationProxy: any;
|
locationProxy: any;
|
||||||
|
|
||||||
|
eventcallbacks: WeakMap<
|
||||||
|
any,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
event: string;
|
||||||
|
originalCallback: AnyFunction;
|
||||||
|
proxiedCallback: AnyFunction;
|
||||||
|
},
|
||||||
|
]
|
||||||
|
> = new WeakMap();
|
||||||
|
|
||||||
constructor(public global: typeof globalThis) {
|
constructor(public global: typeof globalThis) {
|
||||||
if ("document" in self) {
|
if ("document" in self) {
|
||||||
this.documentProxy = createDocumentProxy(this, global);
|
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", {
|
client.Trap("document.domain", {
|
||||||
get() {
|
get() {
|
||||||
return client.url.hostname;
|
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) {
|
apply(ctx) {
|
||||||
if (fakeregistrations.has(ctx.this)) {
|
if (fakeregistrations.has(ctx.this)) {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@ -22,7 +22,7 @@ export default function (client: ScramjetClient, self: Self) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
client.Proxy("removeEventListener", {
|
client.Proxy("EventTarget.prototype.removeEventListener", {
|
||||||
apply(ctx) {
|
apply(ctx) {
|
||||||
if (fakeregistrations.has(ctx.this)) {
|
if (fakeregistrations.has(ctx.this)) {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@ -38,9 +38,10 @@ export default function (client: ScramjetClient, self: Self) {
|
||||||
if (ctx.args[1] && ctx.args[1].type === "module") {
|
if (ctx.args[1] && ctx.args[1].type === "module") {
|
||||||
url += "&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(
|
navigator.serviceWorker.controller.postMessage(
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// entrypoint for scramjet.client.js
|
// entrypoint for scramjet.client.js
|
||||||
|
|
||||||
import { ScramjetClient } from "./client";
|
import { ScramjetClient } from "./client";
|
||||||
|
import { ScramjetServiceWorkerRuntime } from "./swruntime";
|
||||||
|
|
||||||
export const iswindow = "window" in self;
|
export const iswindow = "window" in self;
|
||||||
export const isworker = "WorkerGlobalScope" in self;
|
export const isworker = "WorkerGlobalScope" in self;
|
||||||
|
@ -13,4 +14,9 @@ dbg.log("scrammin");
|
||||||
if (!(ScramjetClient.SCRAMJET in self)) {
|
if (!(ScramjetClient.SCRAMJET in self)) {
|
||||||
const client = new ScramjetClient(self);
|
const client = new ScramjetClient(self);
|
||||||
client.hook();
|
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) {
|
export default function (client: ScramjetClient, self: Self) {
|
||||||
const handlers = {
|
const handlers = {
|
||||||
message: {
|
message: {
|
||||||
|
_init() {
|
||||||
|
if (typeof this.data === "object" && "$scramjet$type" in this.data) {
|
||||||
|
// this is a ctl message
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
origin() {
|
origin() {
|
||||||
if (typeof this.data === "object" && "$scramjet$origin" in this.data)
|
if (typeof this.data === "object" && "$scramjet$origin" in this.data)
|
||||||
return this.data.$scramjet$origin;
|
return this.data.$scramjet$origin;
|
||||||
|
@ -33,7 +41,12 @@ export default function (client: ScramjetClient, self: Self) {
|
||||||
const type = realEvent.type;
|
const type = realEvent.type;
|
||||||
|
|
||||||
if (type in handlers) {
|
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, {
|
argArray[0] = new Proxy(realEvent, {
|
||||||
get(_target, prop, reciever) {
|
get(_target, prop, reciever) {
|
||||||
if (prop in handler) {
|
if (prop in handler) {
|
||||||
|
@ -54,9 +67,41 @@ export default function (client: ScramjetClient, self: Self) {
|
||||||
client.Proxy("EventTarget.prototype.addEventListener", {
|
client.Proxy("EventTarget.prototype.addEventListener", {
|
||||||
apply(ctx) {
|
apply(ctx) {
|
||||||
unproxy(ctx, client);
|
unproxy(ctx, client);
|
||||||
// if (ctx.args[0] === "message" && iswindow) debugger;
|
if (typeof ctx.args[1] !== "function") return;
|
||||||
if (typeof ctx.args[1] === "function")
|
|
||||||
ctx.args[1] = wraplistener(ctx.args[1]);
|
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;
|
if (!iswindow) return;
|
||||||
|
|
||||||
const targets = [self.window, self.HTMLElement.prototype];
|
const targets = [self.window, self.HTMLElement.prototype];
|
||||||
|
|
|
@ -1,21 +1,121 @@
|
||||||
|
import { ScramjetClient } from "./client";
|
||||||
import { encodeUrl } from "./shared";
|
import { encodeUrl } from "./shared";
|
||||||
|
|
||||||
class ScramjetServiceWorkerRuntime {
|
export class ScramjetServiceWorkerRuntime {
|
||||||
constructor() {
|
constructor(public client: ScramjetClient) {
|
||||||
addEventListener("message", (event) => {
|
addEventListener("connect", (cevent: MessageEvent) => {
|
||||||
if ("scramjet$type" in event.data) {
|
const port = cevent.ports[0];
|
||||||
event.stopImmediatePropagation();
|
|
||||||
|
|
||||||
return;
|
port.addEventListener("message", (event) => {
|
||||||
}
|
if ("scramjet$type" in event.data) {
|
||||||
|
handleMessage(client, event.data, port);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
port.start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hook() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
function handleMessage(
|
||||||
interface Window {
|
client: ScramjetClient,
|
||||||
ScramjetServiceWorkerRuntime: typeof ScramjetServiceWorkerRuntime;
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ScramjetServiceWorkerRuntime = 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);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
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);
|
const urlParam = new URLSearchParams(new URL(request.url).search);
|
||||||
|
|
||||||
if (urlParam.has("url")) {
|
if (urlParam.has("url")) {
|
||||||
|
|
|
@ -24,24 +24,17 @@ export class ScramjetServiceWorker {
|
||||||
|
|
||||||
this.threadpool = new ScramjetThreadpool();
|
this.threadpool = new ScramjetThreadpool();
|
||||||
|
|
||||||
addEventListener("message", ({ data }) => {
|
addEventListener("message", ({ data }: { data: MessageC2W }) => {
|
||||||
if (!("scramjet$type" in data)) return;
|
if (!("scramjet$type" in data)) return;
|
||||||
|
|
||||||
if (data.scramjet$type === "registerServiceWorker") {
|
if (data.scramjet$type === "registerServiceWorker") {
|
||||||
this.serviceWorkers.push(new FakeServiceWorker(data.port));
|
this.serviceWorkers.push(new FakeServiceWorker(data.port, data.origin));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!("scramjet$token" in data)) return;
|
|
||||||
|
|
||||||
const resolve = this.syncPool[data.scramjet$token];
|
const resolve = this.syncPool[data.scramjet$token];
|
||||||
delete 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;
|
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