@mercuryworkshop/proxy-bootstrap 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/.downloads/controller/package/dist/controller.api.js +44 -0
  2. package/dist/.downloads/controller/package/dist/controller.api.js.map +1 -0
  3. package/dist/.downloads/controller/package/dist/controller.inject.js +2 -0
  4. package/dist/.downloads/controller/package/dist/controller.inject.js.map +1 -0
  5. package/dist/.downloads/controller/package/dist/controller.sw.js +2 -0
  6. package/dist/.downloads/controller/package/dist/controller.sw.js.map +1 -0
  7. package/dist/.downloads/controller/package/dist/types/cache.d.ts +39 -0
  8. package/dist/.downloads/controller/package/dist/types/index.d.ts +74 -0
  9. package/dist/.downloads/controller/package/dist/types/inject.d.ts +16 -0
  10. package/dist/.downloads/controller/package/dist/types/sw.d.ts +2 -0
  11. package/dist/.downloads/controller/package/dist/types/symbols.d.ts +1 -0
  12. package/dist/.downloads/controller/package/dist/types/typesEntry.d.ts +5 -0
  13. package/dist/.downloads/controller/package/package.json +16 -0
  14. package/dist/.downloads/controller/package/src/cache.ts +473 -0
  15. package/dist/.downloads/controller/package/src/index.ts +809 -0
  16. package/dist/.downloads/controller/package/src/inject.ts +370 -0
  17. package/dist/.downloads/controller/package/src/sw.ts +231 -0
  18. package/dist/.downloads/controller/package/src/symbols.ts +1 -0
  19. package/dist/.downloads/controller/package/src/types.d.ts +139 -0
  20. package/dist/.downloads/controller/package/src/typesEntry.ts +6 -0
  21. package/dist/.downloads/controller/package/tsconfig.json +24 -0
  22. package/dist/.downloads/controller/package/tsconfig.types.json +16 -0
  23. package/dist/.downloads/libcurl-transport/package/LICENSE +661 -0
  24. package/dist/.downloads/libcurl-transport/package/README.md +52 -0
  25. package/dist/.downloads/libcurl-transport/package/dist/index.d.ts +25 -0
  26. package/dist/.downloads/libcurl-transport/package/dist/index.js +6500 -0
  27. package/dist/.downloads/libcurl-transport/package/dist/index.mjs +6481 -0
  28. package/dist/.downloads/libcurl-transport/package/package.json +37 -0
  29. package/dist/.downloads/scramjet/package/dist/167400cb144aab22.wasm +0 -0
  30. package/dist/.downloads/scramjet/package/dist/2919e49b986edf8c.wasm +0 -0
  31. package/dist/.downloads/scramjet/package/dist/5aed1d5e48aab205.wasm +0 -0
  32. package/dist/.downloads/scramjet/package/dist/882d77912a3c8e3a.wasm +0 -0
  33. package/dist/.downloads/scramjet/package/dist/ac6aa30297a80464.wasm +0 -0
  34. package/dist/.downloads/scramjet/package/dist/c10a57758af882c8.wasm +0 -0
  35. package/dist/.downloads/scramjet/package/dist/cfd04aaae6955b67.wasm +0 -0
  36. package/dist/.downloads/scramjet/package/dist/d06a90fd413b36cf.wasm +0 -0
  37. package/dist/.downloads/scramjet/package/dist/dda06914899a6c28.wasm +0 -0
  38. package/dist/.downloads/scramjet/package/dist/scramjet.js +34 -0
  39. package/dist/.downloads/scramjet/package/dist/scramjet.js.map +1 -0
  40. package/dist/.downloads/scramjet/package/dist/scramjet.mjs +34 -0
  41. package/dist/.downloads/scramjet/package/dist/scramjet.mjs.map +1 -0
  42. package/dist/.downloads/scramjet/package/dist/scramjet.wasm +0 -0
  43. package/dist/.downloads/scramjet/package/dist/scramjet_bundled.js +34 -0
  44. package/dist/.downloads/scramjet/package/dist/scramjet_bundled.js.map +1 -0
  45. package/dist/.downloads/scramjet/package/dist/scramjet_bundled.mjs +34 -0
  46. package/dist/.downloads/scramjet/package/dist/scramjet_bundled.mjs.map +1 -0
  47. package/dist/.downloads/scramjet/package/dist/types/Tap.d.ts +32 -0
  48. package/dist/.downloads/scramjet/package/dist/types/client/client.d.ts +115 -0
  49. package/dist/.downloads/scramjet/package/dist/types/client/entry.d.ts +5 -0
  50. package/dist/.downloads/scramjet/package/dist/types/client/events.d.ts +10 -0
  51. package/dist/.downloads/scramjet/package/dist/types/client/global.d.ts +4 -0
  52. package/dist/.downloads/scramjet/package/dist/types/client/helpers.d.ts +1 -0
  53. package/dist/.downloads/scramjet/package/dist/types/client/index.d.ts +7 -0
  54. package/dist/.downloads/scramjet/package/dist/types/client/location.d.ts +2 -0
  55. package/dist/.downloads/scramjet/package/dist/types/client/shared/eval.d.ts +3 -0
  56. package/dist/.downloads/scramjet/package/dist/types/client/shared/sourcemaps.d.ts +19 -0
  57. package/dist/.downloads/scramjet/package/dist/types/client/shared/unproxy.d.ts +19 -0
  58. package/dist/.downloads/scramjet/package/dist/types/client/shared/wrap.d.ts +4 -0
  59. package/dist/.downloads/scramjet/package/dist/types/client/singletonbox.d.ts +16 -0
  60. package/dist/.downloads/scramjet/package/dist/types/client/unproxy.generated.d.ts +50 -0
  61. package/dist/.downloads/scramjet/package/dist/types/fetch/body.d.ts +3 -0
  62. package/dist/.downloads/scramjet/package/dist/types/fetch/fetch.d.ts +7 -0
  63. package/dist/.downloads/scramjet/package/dist/types/fetch/headers.d.ts +19 -0
  64. package/dist/.downloads/scramjet/package/dist/types/fetch/index.d.ts +128 -0
  65. package/dist/.downloads/scramjet/package/dist/types/fetch/parse.d.ts +22 -0
  66. package/dist/.downloads/scramjet/package/dist/types/fetch/util.d.ts +7 -0
  67. package/dist/.downloads/scramjet/package/dist/types/index.d.ts +11 -0
  68. package/dist/.downloads/scramjet/package/dist/types/shared/cookie.d.ts +26 -0
  69. package/dist/.downloads/scramjet/package/dist/types/shared/headers.d.ts +13 -0
  70. package/dist/.downloads/scramjet/package/dist/types/shared/htmlRules.d.ts +6 -0
  71. package/dist/.downloads/scramjet/package/dist/types/shared/index.d.ts +51 -0
  72. package/dist/.downloads/scramjet/package/dist/types/shared/mime.d.ts +39 -0
  73. package/dist/.downloads/scramjet/package/dist/types/shared/refresh.d.ts +7 -0
  74. package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/css.d.ts +4 -0
  75. package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/html.d.ts +33 -0
  76. package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/index.d.ts +6 -0
  77. package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/js.d.ts +11 -0
  78. package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/url.d.ts +25 -0
  79. package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/wasm.d.ts +7 -0
  80. package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/worker.d.ts +3 -0
  81. package/dist/.downloads/scramjet/package/dist/types/shared/set-cookie-parser.d.ts +20 -0
  82. package/dist/.downloads/scramjet/package/dist/types/shared/snapshot.d.ts +236 -0
  83. package/dist/.downloads/scramjet/package/dist/types/shared/sniffEncoding.d.ts +65 -0
  84. package/dist/.downloads/scramjet/package/dist/types/shared/util.d.ts +2 -0
  85. package/dist/.downloads/scramjet/package/dist/types/symbols.d.ts +6 -0
  86. package/dist/.downloads/scramjet/package/dist/types/types.d.ts +68 -0
  87. package/dist/.downloads/scramjet/package/lib/index.cjs +7 -0
  88. package/dist/.downloads/scramjet/package/lib/index.d.ts +8 -0
  89. package/dist/.downloads/scramjet/package/lib/types.d.ts +20 -0
  90. package/dist/.downloads/scramjet/package/package.json +93 -0
  91. package/dist/bootstrap-client.js +169 -0
  92. package/dist/bootstrap-client.js.map +1 -0
  93. package/dist/bootstrap-server.js +406 -0
  94. package/dist/bootstrap-server.js.map +1 -0
  95. package/dist/bootstrap-static.js +476 -0
  96. package/dist/bootstrap-static.js.map +1 -0
  97. package/dist/types/client.d.ts +4 -0
  98. package/dist/types/clientcommon.d.ts +2 -0
  99. package/dist/types/common.d.ts +30 -0
  100. package/dist/types/server.d.ts +24 -0
  101. package/dist/types/static.d.ts +1 -0
  102. package/package.json +30 -0
@@ -0,0 +1,370 @@
1
+ import type * as ScramjetGlobal from "@mercuryworkshop/scramjet";
2
+ declare const $scramjet: typeof ScramjetGlobal;
3
+ import type {
4
+ RawHeaders,
5
+ ProxyTransport,
6
+ TransferrableResponse,
7
+ } from "@mercuryworkshop/proxy-transports";
8
+
9
+ import { RpcHelper } from "@mercuryworkshop/rpc";
10
+ import type { Config } from ".";
11
+ import { CONTROLLERFRAME } from "./symbols";
12
+ import type {
13
+ SerializedCookieSyncEntry,
14
+ ControllerToTransport,
15
+ TransportToController,
16
+ WebSocketMessage,
17
+ } from "./types";
18
+
19
+ const MessagePort_postMessage = MessagePort.prototype.postMessage;
20
+ const postMessage = (
21
+ port: MessagePort,
22
+ data: any,
23
+ transfer?: Transferable[]
24
+ ) => {
25
+ MessagePort_postMessage.call(port, data, transfer as any);
26
+ };
27
+
28
+ class RemoteTransport implements ProxyTransport {
29
+ private readyResolve!: () => void;
30
+ private readyPromise: Promise<void> = new Promise((resolve) => {
31
+ this.readyResolve = resolve;
32
+ });
33
+
34
+ public ready = false;
35
+ async init() {
36
+ await this.readyPromise;
37
+ this.ready = true;
38
+ }
39
+
40
+ private rpc: RpcHelper<ControllerToTransport, TransportToController>;
41
+ constructor(public port: MessagePort) {
42
+ this.rpc = new RpcHelper<ControllerToTransport, TransportToController>(
43
+ {
44
+ ready: async () => {
45
+ this.readyResolve();
46
+ },
47
+ },
48
+ "transport",
49
+ (data, transfer) => {
50
+ postMessage(port, data, transfer);
51
+ }
52
+ );
53
+ port.onmessageerror = (ev) => {
54
+ console.error("onmessageerror (this should never happen!)", ev);
55
+ };
56
+ port.onmessage = (ev) => {
57
+ this.rpc.recieve(ev.data);
58
+ };
59
+ port.start();
60
+ }
61
+ connect(
62
+ url: URL,
63
+ protocols: string[],
64
+ requestHeaders: RawHeaders,
65
+ onopen: (protocol: string, extensions: string) => void,
66
+ onmessage: (data: Blob | ArrayBuffer | string) => void,
67
+ onclose: (code: number, reason: string) => void,
68
+ onerror: (error: string) => void
69
+ ): [
70
+ (data: Blob | ArrayBuffer | string) => void,
71
+ (code: number, reason: string) => void,
72
+ ] {
73
+ const channel = new MessageChannel();
74
+ const port = channel.port1;
75
+ console.warn("connecting");
76
+ this.rpc
77
+ .call(
78
+ "connect",
79
+ {
80
+ url: url.href,
81
+ protocols,
82
+ requestHeaders,
83
+ port: channel.port2,
84
+ },
85
+ [channel.port2]
86
+ )
87
+ .then((response) => {
88
+ console.log(response);
89
+ if (response.result === "success") {
90
+ onopen(response.protocol, response.extensions);
91
+ } else {
92
+ onerror(response.error);
93
+ }
94
+ });
95
+ port.onmessage = (ev) => {
96
+ const message = ev.data as WebSocketMessage;
97
+ if (message.type === "data") {
98
+ onmessage(message.data);
99
+ } else if (message.type === "close") {
100
+ onclose(message.code, message.reason);
101
+ }
102
+ };
103
+ port.onmessageerror = (ev) => {
104
+ console.error("onmessageerror (this should never happen!)", ev);
105
+ onerror("Message error in transport port");
106
+ };
107
+
108
+ return [
109
+ (data) => {
110
+ postMessage(
111
+ port,
112
+ {
113
+ type: "data",
114
+ data: data,
115
+ },
116
+ data instanceof ArrayBuffer ? [data] : []
117
+ );
118
+ },
119
+ (code) => {
120
+ postMessage(port, {
121
+ type: "close",
122
+ code: code,
123
+ });
124
+ },
125
+ ];
126
+ }
127
+
128
+ async request(
129
+ remote: URL,
130
+ method: string,
131
+ body: BodyInit | null,
132
+ headers: RawHeaders,
133
+ _signal: AbortSignal | undefined
134
+ ): Promise<TransferrableResponse> {
135
+ return await this.rpc.call("request", {
136
+ remote: remote.href,
137
+ method,
138
+ body,
139
+ headers,
140
+ });
141
+ }
142
+
143
+ async sendSetCookie(
144
+ cookies: Array<{ url: URL; cookie: string }>,
145
+ options: ScramjetGlobal.CookieSyncOptions = {}
146
+ ): Promise<void> {
147
+ await this.rpc.call("sendSetCookie", {
148
+ cookies: cookies.map(({ url, cookie }) => ({
149
+ url: url.href,
150
+ cookie,
151
+ })),
152
+ options,
153
+ });
154
+ }
155
+ }
156
+
157
+ const sw = navigator.serviceWorker.controller;
158
+ const { SCRAMJETCLIENT, ScramjetClient, CookieJar, setWasm } = $scramjet;
159
+
160
+ type Init = {
161
+ config: Config;
162
+ sjconfig: ScramjetGlobal.ScramjetConfig;
163
+ prefix: URL;
164
+ cookies: string;
165
+ yieldGetInjectScripts: (
166
+ config: Config,
167
+ sjconfig: ScramjetGlobal.ScramjetConfig,
168
+ prefix: URL,
169
+ cookieJar: ScramjetGlobal.CookieJar,
170
+ codecEncode: (input: string) => string,
171
+ codecDecode: (input: string) => string
172
+ ) => any;
173
+ codecEncode: (input: string) => string;
174
+ codecDecode: (input: string) => string;
175
+ initHeaders: RawHeaders;
176
+ history: ScramjetGlobal.TrackedHistoryState[];
177
+ };
178
+
179
+ export function load(init: Init) {
180
+ if (SCRAMJETCLIENT in globalThis) {
181
+ (
182
+ (globalThis as any)[SCRAMJETCLIENT] as ScramjetGlobal.ScramjetClient
183
+ ).syncDocumentInit({
184
+ initHeaders: init.initHeaders,
185
+ history: init.history,
186
+ cookies: init.cookies,
187
+ });
188
+ return;
189
+ }
190
+ if (!("WASM" in self)) {
191
+ throw new Error("WASM not found in global scope!");
192
+ }
193
+ const wasm = Uint8Array.from(atob(self.WASM), (c) => c.charCodeAt(0));
194
+ delete (self as any).WASM;
195
+ setWasm(wasm);
196
+
197
+ new ExecutionContextWrapper(globalThis, init);
198
+ }
199
+
200
+ function createFrameId() {
201
+ return `${Array(8)
202
+ .fill(0)
203
+ .map(() => Math.floor(Math.random() * 36).toString(36))
204
+ .join("")}`;
205
+ }
206
+
207
+ class ExecutionContextWrapper {
208
+ client!: ScramjetGlobal.ScramjetClient;
209
+ cookieJar: ScramjetGlobal.CookieJar;
210
+ transport: RemoteTransport;
211
+ private handleServiceWorkerCookieMessage: (event: MessageEvent) => void;
212
+
213
+ constructor(
214
+ public global: typeof globalThis,
215
+ public init: Init
216
+ ) {
217
+ const channel = new MessageChannel();
218
+ this.transport = new RemoteTransport(channel.port1);
219
+ sw?.postMessage(
220
+ {
221
+ $sw$initRemoteTransport: {
222
+ port: channel.port2,
223
+ prefix: this.init.prefix.href,
224
+ },
225
+ },
226
+ [channel.port2]
227
+ );
228
+
229
+ this.cookieJar = new CookieJar();
230
+ this.cookieJar.load(this.init.cookies);
231
+
232
+ this.handleServiceWorkerCookieMessage = (event: MessageEvent) => {
233
+ if (
234
+ !event.data?.$controller$setCookie ||
235
+ typeof event.data.$controller$setCookie !== "object"
236
+ ) {
237
+ return;
238
+ }
239
+
240
+ const payload = event.data.$controller$setCookie as {
241
+ cookies?: SerializedCookieSyncEntry[];
242
+ options?: ScramjetGlobal.CookieSyncOptions;
243
+ id?: string;
244
+ };
245
+
246
+ if (payload.options?.clear) {
247
+ this.cookieJar.clear();
248
+ }
249
+
250
+ if (Array.isArray(payload.cookies)) {
251
+ for (const cookie of payload.cookies) {
252
+ if (
253
+ typeof cookie?.url !== "string" ||
254
+ typeof cookie.cookie !== "string"
255
+ ) {
256
+ continue;
257
+ }
258
+
259
+ try {
260
+ this.cookieJar.setCookies(cookie.cookie, new URL(cookie.url));
261
+ } catch {
262
+ console.error("Failed to set cookie", cookie);
263
+ }
264
+ }
265
+ }
266
+
267
+ if (typeof payload.id === "string") {
268
+ const targetSw = navigator.serviceWorker?.controller ?? sw;
269
+ targetSw?.postMessage({
270
+ $sw$setCookieDone: {
271
+ id: payload.id,
272
+ },
273
+ });
274
+ }
275
+ };
276
+
277
+ navigator.serviceWorker?.addEventListener(
278
+ "message",
279
+ this.handleServiceWorkerCookieMessage
280
+ );
281
+
282
+ this.injectScramjet();
283
+ }
284
+
285
+ injectScramjet() {
286
+ const frame = this.global.frameElement as HTMLIFrameElement | null;
287
+ if (frame && !frame.name) {
288
+ window.name = frame.name = createFrameId();
289
+ }
290
+ let controllerFrame = frame?.[CONTROLLERFRAME];
291
+ let isTopLevel = true;
292
+ if (!controllerFrame) {
293
+ isTopLevel = false;
294
+ let currentwin = this.global.window;
295
+ while (currentwin.parent !== currentwin) {
296
+ const currentclient = currentwin[$scramjet.SCRAMJETCLIENT];
297
+ if (!currentclient) {
298
+ currentwin = currentwin.parent.window;
299
+ continue;
300
+ }
301
+ const currentFrame = currentclient.descriptors.get(
302
+ "window.frameElement",
303
+ currentwin
304
+ );
305
+ if (currentFrame && currentFrame[CONTROLLERFRAME]) {
306
+ controllerFrame = currentFrame[CONTROLLERFRAME];
307
+ break;
308
+ }
309
+ currentwin = currentwin.parent.window;
310
+ }
311
+ }
312
+ const context: ScramjetGlobal.ScramjetContext = {
313
+ config: this.init.sjconfig,
314
+ prefix: this.init.prefix,
315
+ cookieJar: this.cookieJar,
316
+ interface: {
317
+ getInjectScripts: this.init.yieldGetInjectScripts(
318
+ this.init.config,
319
+ this.init.sjconfig,
320
+ this.init.prefix,
321
+ this.cookieJar,
322
+ this.init.codecEncode,
323
+ this.init.codecDecode
324
+ ),
325
+ codecEncode: this.init.codecEncode,
326
+ codecDecode: this.init.codecDecode,
327
+ },
328
+ };
329
+ this.client = new ScramjetClient(this.global, {
330
+ context,
331
+ transport: this.transport,
332
+ sendSetCookie: async (cookies, options) => {
333
+ await this.transport.sendSetCookie(cookies, options);
334
+ },
335
+ shouldPassthroughWebsocket: () => {
336
+ return false;
337
+ },
338
+ shouldBlockMessageEvent: () => {
339
+ return false;
340
+ },
341
+ hookSubcontext: (frameself) => {
342
+ const context = new ExecutionContextWrapper(frameself, {
343
+ ...this.init,
344
+ cookies: this.cookieJar.dump(),
345
+ });
346
+ return context.client;
347
+ },
348
+ initHeaders: this.init.initHeaders,
349
+ history: this.init.history,
350
+ });
351
+ const frameInitContext = {
352
+ window: this.global.window,
353
+ client: this.client,
354
+ isTopLevel,
355
+ };
356
+ if (controllerFrame)
357
+ $scramjet.Tap.dispatch(
358
+ controllerFrame.hooks.init.pre,
359
+ frameInitContext,
360
+ {}
361
+ );
362
+ this.client.hook();
363
+ if (controllerFrame)
364
+ $scramjet.Tap.dispatch(
365
+ controllerFrame.hooks.init.post,
366
+ frameInitContext,
367
+ {}
368
+ );
369
+ }
370
+ }
@@ -0,0 +1,231 @@
1
+ /// <reference lib="WebWorker" />
2
+ /// <reference types="@types/serviceworker" />
3
+ import { RpcHelper } from "@mercuryworkshop/rpc";
4
+ import type { Controllerbound, SWbound } from "./types";
5
+ import type { RawHeaders } from "@mercuryworkshop/proxy-transports";
6
+
7
+ function makeId(): string {
8
+ return Math.random().toString(36).substring(2, 10);
9
+ }
10
+
11
+ const cookieResolvers: Record<string, (value: void) => void> = {};
12
+ addEventListener("message", (e) => {
13
+ if (!e.data) return;
14
+ if (typeof e.data != "object") return;
15
+ if (e.data.$sw$setCookieDone && typeof e.data.$sw$setCookieDone == "object") {
16
+ const done = e.data.$sw$setCookieDone;
17
+
18
+ const resolver = cookieResolvers[done.id];
19
+ if (resolver) {
20
+ resolver();
21
+ delete cookieResolvers[done.id];
22
+ }
23
+ }
24
+
25
+ if (
26
+ e.data.$sw$initRemoteTransport &&
27
+ typeof e.data.$sw$initRemoteTransport == "object"
28
+ ) {
29
+ const { port, prefix } = e.data.$sw$initRemoteTransport;
30
+
31
+ const relevantcontroller = tabs.find((tab) =>
32
+ new URL(prefix).pathname.startsWith(tab.prefix)
33
+ );
34
+ if (!relevantcontroller) {
35
+ console.error("No relevant controller found for transport init");
36
+ return;
37
+ }
38
+ relevantcontroller.rpc.call("initRemoteTransport", port, [port]);
39
+ }
40
+ });
41
+
42
+ class ControllerReference {
43
+ rpc: RpcHelper<SWbound, Controllerbound>;
44
+
45
+ constructor(
46
+ public prefix: string,
47
+ public id: string,
48
+ port: MessagePort
49
+ ) {
50
+ this.rpc = new RpcHelper(
51
+ {
52
+ sendSetCookie: async ({ cookies, options }) => {
53
+ const clients = await self.clients.matchAll();
54
+ const ids: string[] = [];
55
+ const promises: Promise<string>[] = [];
56
+
57
+ // Navigation fetches (document/iframe) deliver cookies via the inject
58
+ // script's embedded cookieJar dump — the destination page doesn't have
59
+ // inject.ts loaded yet to ack, so awaiting would deadlock. Broadcast
60
+ // so any already-loaded clients can update their jars, but don't wait.
61
+ const isNavigation =
62
+ options?.destination === "document" ||
63
+ options?.destination === "iframe";
64
+
65
+ for (const client of clients) {
66
+ const id = makeId();
67
+ ids.push(id);
68
+ client.postMessage({
69
+ $controller$setCookie: {
70
+ cookies,
71
+ options,
72
+ id,
73
+ },
74
+ });
75
+ if (!isNavigation) {
76
+ promises.push(
77
+ new Promise<string>((resolve) => {
78
+ // Resolve with the id so we know which client replied.
79
+ cookieResolvers[id] = () => resolve(id);
80
+ })
81
+ );
82
+ }
83
+ }
84
+ // Wait for the first client to acknowledge the cookie sync.
85
+ // Using Promise.any (not Promise.all) so that extra SW clients created by
86
+ // window.open (e.g. test popup windows) don't cause timeouts — only the
87
+ // main controller client needs to respond.
88
+ if (promises.length > 0) {
89
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
90
+ let responded = false;
91
+ const timeoutPromise = new Promise<void>((resolve) => {
92
+ timeoutId = setTimeout(() => {
93
+ if (!responded) {
94
+ const pending = ids.filter(
95
+ (id) => cookieResolvers[id] !== undefined
96
+ );
97
+ console.error(
98
+ "timed out waiting for set cookie response (deadlock?): " +
99
+ `cookies=${cookies.length} clients=${clients.length} ` +
100
+ `pending=${pending.length}/${ids.length} ` +
101
+ `clientUrls=${clients.map((c) => c.url).join(",")}`
102
+ );
103
+ }
104
+ resolve();
105
+ }, 1000);
106
+ });
107
+
108
+ try {
109
+ await Promise.race([
110
+ timeoutPromise,
111
+ Promise.any(promises)
112
+ .then(() => {
113
+ responded = true;
114
+ })
115
+ .catch(() => {}),
116
+ ]);
117
+ } finally {
118
+ // Clear the timeout so it doesn't fire spuriously after the
119
+ // race has already been won by Promise.any.
120
+ if (timeoutId !== undefined) clearTimeout(timeoutId);
121
+ // Clean up any pending resolvers so clients that never
122
+ // responded don't leak entries in cookieResolvers.
123
+ for (const id of ids) {
124
+ delete cookieResolvers[id];
125
+ }
126
+ }
127
+ }
128
+ },
129
+ },
130
+ "tabchannel-" + id,
131
+ (data, transfer) => {
132
+ port.postMessage(data, transfer);
133
+ }
134
+ );
135
+ port.onmessage = (e: MessageEvent) => {
136
+ this.rpc.recieve(e.data);
137
+ };
138
+ port.onmessageerror = console.error;
139
+
140
+ this.rpc.call("ready", undefined);
141
+ }
142
+ }
143
+
144
+ const tabs: ControllerReference[] = [];
145
+
146
+ addEventListener("message", (e) => {
147
+ if (!e.data) return;
148
+ if (typeof e.data != "object") return;
149
+ if (!e.data.$controller$init) return;
150
+ if (typeof e.data.$controller$init != "object") return;
151
+ const init = e.data.$controller$init;
152
+
153
+ const existing = tabs.findIndex((t) => t.id === init.id);
154
+ if (existing !== -1) {
155
+ tabs.splice(existing, 1);
156
+ }
157
+ tabs.push(new ControllerReference(init.prefix, init.id, e.ports[0]));
158
+ });
159
+
160
+ export function shouldRoute(event: FetchEvent): boolean {
161
+ const url = new URL(event.request.url);
162
+ const tab = tabs.find((tab) => url.pathname.startsWith(tab.prefix));
163
+ return tab !== undefined;
164
+ }
165
+
166
+ export async function route(event: FetchEvent): Promise<Response> {
167
+ try {
168
+ const url = new URL(event.request.url);
169
+ const tab = tabs.find((tab) => url.pathname.startsWith(tab.prefix))!;
170
+ const client = await clients.get(event.clientId);
171
+
172
+ const rawheaders: RawHeaders = [...event.request.headers];
173
+
174
+ const response = await tab.rpc.call(
175
+ "request",
176
+ {
177
+ rawUrl: event.request.url,
178
+ rawReferrer: event.request.referrer,
179
+ destination: event.request.destination,
180
+ mode: event.request.mode,
181
+ referrer: event.request.referrer,
182
+ method: event.request.method,
183
+ body: event.request.body,
184
+ cache: event.request.cache,
185
+ forceCrossOriginIsolated: false,
186
+ initialHeaders: rawheaders,
187
+ rawClientUrl: client ? client.url : undefined,
188
+ clientId: event.clientId || event.resultingClientId,
189
+ },
190
+ event.request.body instanceof ReadableStream ||
191
+ // @ts-expect-error the types for fetchevent are messed up
192
+ event.request.body instanceof ArrayBuffer
193
+ ? [event.request.body]
194
+ : undefined
195
+ );
196
+
197
+ return new Response(response.body, {
198
+ status: response.status,
199
+ statusText: response.statusText,
200
+ headers: response.headers,
201
+ });
202
+ } catch (e) {
203
+ console.error("Service Worker error:", e);
204
+ return new Response(
205
+ "Internal Service Worker Error: " + (e as Error).message,
206
+ {
207
+ status: 500,
208
+ }
209
+ );
210
+ }
211
+ }
212
+
213
+ addEventListener("install", () => {
214
+ self.skipWaiting();
215
+ });
216
+
217
+ addEventListener("activate", (event: ExtendableEvent) => {
218
+ event.waitUntil(clients.claim());
219
+ });
220
+
221
+ // the only way to know if a service worker has suddenly died is if this code runs again
222
+ // notify all clients to send over their messageports again
223
+ setTimeout(async () => {
224
+ console.log("service worker activated, notifying clients to revive");
225
+ for (const client of await clients.matchAll()) {
226
+ client.postMessage({
227
+ $controller$swrevive: {},
228
+ });
229
+ }
230
+ // short delay is apparently needed
231
+ }, 100);
@@ -0,0 +1 @@
1
+ export const CONTROLLERFRAME = Symbol.for("controller frame handle");