Skip to content

Commit bf9b586

Browse files
committed
[scramjet] start writing new controller logic
1 parent b1fa27e commit bf9b586

File tree

4 files changed

+187
-2
lines changed

4 files changed

+187
-2
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import {
2+
CookieJar,
3+
ScramjetFetchHandler,
4+
ScramjetHeaders,
5+
type ScramjetFetchContext,
6+
} from "@mercuryworkshop/scramjet";
7+
8+
declare var clients: Clients;
9+
10+
const cookieJar = new CookieJar();
11+
12+
type Config = {
13+
wasmPath: string;
14+
scramjetPath: string;
15+
virtualWasmPath: string;
16+
prefix: string;
17+
};
18+
19+
const config: Config = {
20+
prefix: "~/sj",
21+
virtualWasmPath: "/scramjet.wasm.js",
22+
};
23+
24+
type Frame = {
25+
fetchHandler: ScramjetFetchHandler;
26+
};
27+
28+
const frames: Record<string, Frame>;
29+
30+
function shouldRoute(fetch: FetchEvent): boolean {
31+
const url = new URL(fetch.request.url);
32+
if (url.pathname === config.prefix + config.virtualWasmPath) {
33+
return true;
34+
}
35+
36+
for (let id in frames) {
37+
if (url.pathname.startsWith(config.prefix + "/" + id + "/")) {
38+
return true;
39+
}
40+
}
41+
42+
return false;
43+
}
44+
45+
async function handleFetch(fetch: FetchEvent) {
46+
if (fetch.request.url.startsWith(config.prefix + config.virtualWasmPath)) {
47+
return await makeWasmResponse();
48+
}
49+
50+
let frame;
51+
for (let id in frames) {
52+
if (fetch.request.url.startsWith(config.prefix + "/" + id + "/")) {
53+
frame = frames[id];
54+
break;
55+
}
56+
}
57+
if (!frame) throw new Error("No frame found for fetch");
58+
59+
// create fetch context
60+
61+
let headers = new ScramjetHeaders();
62+
for (let [k, v] of Object.entries(fetch.request.headers)) {
63+
headers.set(k, v);
64+
}
65+
66+
const client = await clients.get(fetch.clientId);
67+
68+
const context: ScramjetFetchContext = {
69+
initialHeaders: headers,
70+
rawClientUrl: new URL(client.url),
71+
rawUrl: new URL(fetch.request.url),
72+
destination: fetch.request.destination,
73+
method: fetch.request.method,
74+
mode: fetch.request.mode,
75+
referrer: fetch.request.referrer,
76+
forceCrossOriginIsolated: crossOriginIsolated,
77+
body: fetch.request.body,
78+
cache: fetch.request.cache,
79+
cookieStore: cookieJar,
80+
};
81+
82+
const fetchresponse = await frame.fetchHandler.handleFetch(context);
83+
}
84+
window.addEventListener("message", async (event) => {
85+
let data = event.data;
86+
if (!(data && "$sandboxsw$type" in data)) return;
87+
let controller = controllers.find(
88+
(c) => c.controllerframe.contentWindow == event.source
89+
);
90+
if (!controller) {
91+
console.error("No controller found for message", data);
92+
return;
93+
}
94+
95+
try {
96+
if (data.$sandboxsw$type == "request") {
97+
let domain = data.$sandboxsw$domain;
98+
let message = data.$sandboxsw$message;
99+
let token = data.$sandboxsw$token;
100+
101+
let fn = (methods as any)[domain];
102+
103+
let [result, transfer] = await fn(message, controller);
104+
controller.window.postMessage(
105+
{
106+
$sandboxsw$type: "response",
107+
$sandboxsw$token: token,
108+
$sandboxsw$message: result,
109+
},
110+
controller.baseurl.origin,
111+
transfer
112+
);
113+
} else if (data.$sandboxsw$type == "confirm") {
114+
console.log(controller.rootdomain + " controller activated");
115+
controller.readyResolve();
116+
}
117+
} catch (e) {
118+
console.log(e);
119+
console.error("error in response", e);
120+
}
121+
});
122+
123+
let wasmPayload: string | null = null;
124+
125+
async function makeWasmResponse() {
126+
if (!wasmPayload) {
127+
const resp = await fetch(scramjetWASM);
128+
const buf = await resp.arrayBuffer();
129+
const b64 = btoa(
130+
new Uint8Array(buf)
131+
.reduce(
132+
(data, byte) => (data.push(String.fromCharCode(byte)), data),
133+
[] as any
134+
)
135+
.join("")
136+
);
137+
138+
let payload = "";
139+
payload +=
140+
"if ('document' in self && document.currentScript) { document.currentScript.remove(); }\n";
141+
payload += `self.WASM = '${b64}';`;
142+
wasmPayload = payload;
143+
}
144+
145+
return {
146+
body: wasmPayload,
147+
headers: { "Content-Type": "application/javascript" },
148+
status: 200,
149+
statusText: "OK",
150+
};
151+
}
152+
153+
let synctoken = 0;
154+
let syncPool: { [token: number]: (val: any) => void } = {};
155+
export function sendFrame<T extends keyof Framebound>(
156+
tab: Tab,
157+
type: T,
158+
message: Framebound[T][0]
159+
): Promise<Framebound[T][1]> {
160+
let token = synctoken++;
161+
162+
tab.frame.frame.contentWindow!.postMessage(
163+
{
164+
$ipc$type: "request",
165+
$ipc$token: token,
166+
$ipc$message: {
167+
type,
168+
message,
169+
},
170+
},
171+
"*"
172+
);
173+
174+
return new Promise((res) => {
175+
syncPool[token] = res;
176+
});
177+
}

packages/scramjet/packages/controller/src/types.d.ts

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2022",
4+
"lib": ["es2022", "webworker"],
5+
"moduleResolution": "node"
6+
}
7+
}

packages/scramjet/rspack.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,10 +288,11 @@ const bootstrapConfig = createGenericConfig({
288288

289289
const controllerConfig = createGenericConfig({
290290
entry: {
291-
main: join(controllerdir, "src/index.ts"),
291+
api: join(controllerdir, "src/index.ts"),
292+
sw: join(controllerdir, "src/sw.ts"),
292293
},
293294
output: {
294-
filename: "controller.js",
295+
filename: "controller.[name].js",
295296
path: join(controllerdir, "dist"),
296297
iife: true,
297298
},

0 commit comments

Comments
 (0)