@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.
- package/dist/.downloads/controller/package/dist/controller.api.js +44 -0
- package/dist/.downloads/controller/package/dist/controller.api.js.map +1 -0
- package/dist/.downloads/controller/package/dist/controller.inject.js +2 -0
- package/dist/.downloads/controller/package/dist/controller.inject.js.map +1 -0
- package/dist/.downloads/controller/package/dist/controller.sw.js +2 -0
- package/dist/.downloads/controller/package/dist/controller.sw.js.map +1 -0
- package/dist/.downloads/controller/package/dist/types/cache.d.ts +39 -0
- package/dist/.downloads/controller/package/dist/types/index.d.ts +74 -0
- package/dist/.downloads/controller/package/dist/types/inject.d.ts +16 -0
- package/dist/.downloads/controller/package/dist/types/sw.d.ts +2 -0
- package/dist/.downloads/controller/package/dist/types/symbols.d.ts +1 -0
- package/dist/.downloads/controller/package/dist/types/typesEntry.d.ts +5 -0
- package/dist/.downloads/controller/package/package.json +16 -0
- package/dist/.downloads/controller/package/src/cache.ts +473 -0
- package/dist/.downloads/controller/package/src/index.ts +809 -0
- package/dist/.downloads/controller/package/src/inject.ts +370 -0
- package/dist/.downloads/controller/package/src/sw.ts +231 -0
- package/dist/.downloads/controller/package/src/symbols.ts +1 -0
- package/dist/.downloads/controller/package/src/types.d.ts +139 -0
- package/dist/.downloads/controller/package/src/typesEntry.ts +6 -0
- package/dist/.downloads/controller/package/tsconfig.json +24 -0
- package/dist/.downloads/controller/package/tsconfig.types.json +16 -0
- package/dist/.downloads/libcurl-transport/package/LICENSE +661 -0
- package/dist/.downloads/libcurl-transport/package/README.md +52 -0
- package/dist/.downloads/libcurl-transport/package/dist/index.d.ts +25 -0
- package/dist/.downloads/libcurl-transport/package/dist/index.js +6500 -0
- package/dist/.downloads/libcurl-transport/package/dist/index.mjs +6481 -0
- package/dist/.downloads/libcurl-transport/package/package.json +37 -0
- package/dist/.downloads/scramjet/package/dist/167400cb144aab22.wasm +0 -0
- package/dist/.downloads/scramjet/package/dist/2919e49b986edf8c.wasm +0 -0
- package/dist/.downloads/scramjet/package/dist/5aed1d5e48aab205.wasm +0 -0
- package/dist/.downloads/scramjet/package/dist/882d77912a3c8e3a.wasm +0 -0
- package/dist/.downloads/scramjet/package/dist/ac6aa30297a80464.wasm +0 -0
- package/dist/.downloads/scramjet/package/dist/c10a57758af882c8.wasm +0 -0
- package/dist/.downloads/scramjet/package/dist/cfd04aaae6955b67.wasm +0 -0
- package/dist/.downloads/scramjet/package/dist/d06a90fd413b36cf.wasm +0 -0
- package/dist/.downloads/scramjet/package/dist/dda06914899a6c28.wasm +0 -0
- package/dist/.downloads/scramjet/package/dist/scramjet.js +34 -0
- package/dist/.downloads/scramjet/package/dist/scramjet.js.map +1 -0
- package/dist/.downloads/scramjet/package/dist/scramjet.mjs +34 -0
- package/dist/.downloads/scramjet/package/dist/scramjet.mjs.map +1 -0
- package/dist/.downloads/scramjet/package/dist/scramjet.wasm +0 -0
- package/dist/.downloads/scramjet/package/dist/scramjet_bundled.js +34 -0
- package/dist/.downloads/scramjet/package/dist/scramjet_bundled.js.map +1 -0
- package/dist/.downloads/scramjet/package/dist/scramjet_bundled.mjs +34 -0
- package/dist/.downloads/scramjet/package/dist/scramjet_bundled.mjs.map +1 -0
- package/dist/.downloads/scramjet/package/dist/types/Tap.d.ts +32 -0
- package/dist/.downloads/scramjet/package/dist/types/client/client.d.ts +115 -0
- package/dist/.downloads/scramjet/package/dist/types/client/entry.d.ts +5 -0
- package/dist/.downloads/scramjet/package/dist/types/client/events.d.ts +10 -0
- package/dist/.downloads/scramjet/package/dist/types/client/global.d.ts +4 -0
- package/dist/.downloads/scramjet/package/dist/types/client/helpers.d.ts +1 -0
- package/dist/.downloads/scramjet/package/dist/types/client/index.d.ts +7 -0
- package/dist/.downloads/scramjet/package/dist/types/client/location.d.ts +2 -0
- package/dist/.downloads/scramjet/package/dist/types/client/shared/eval.d.ts +3 -0
- package/dist/.downloads/scramjet/package/dist/types/client/shared/sourcemaps.d.ts +19 -0
- package/dist/.downloads/scramjet/package/dist/types/client/shared/unproxy.d.ts +19 -0
- package/dist/.downloads/scramjet/package/dist/types/client/shared/wrap.d.ts +4 -0
- package/dist/.downloads/scramjet/package/dist/types/client/singletonbox.d.ts +16 -0
- package/dist/.downloads/scramjet/package/dist/types/client/unproxy.generated.d.ts +50 -0
- package/dist/.downloads/scramjet/package/dist/types/fetch/body.d.ts +3 -0
- package/dist/.downloads/scramjet/package/dist/types/fetch/fetch.d.ts +7 -0
- package/dist/.downloads/scramjet/package/dist/types/fetch/headers.d.ts +19 -0
- package/dist/.downloads/scramjet/package/dist/types/fetch/index.d.ts +128 -0
- package/dist/.downloads/scramjet/package/dist/types/fetch/parse.d.ts +22 -0
- package/dist/.downloads/scramjet/package/dist/types/fetch/util.d.ts +7 -0
- package/dist/.downloads/scramjet/package/dist/types/index.d.ts +11 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/cookie.d.ts +26 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/headers.d.ts +13 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/htmlRules.d.ts +6 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/index.d.ts +51 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/mime.d.ts +39 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/refresh.d.ts +7 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/css.d.ts +4 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/html.d.ts +33 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/index.d.ts +6 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/js.d.ts +11 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/url.d.ts +25 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/wasm.d.ts +7 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/rewriters/worker.d.ts +3 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/set-cookie-parser.d.ts +20 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/snapshot.d.ts +236 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/sniffEncoding.d.ts +65 -0
- package/dist/.downloads/scramjet/package/dist/types/shared/util.d.ts +2 -0
- package/dist/.downloads/scramjet/package/dist/types/symbols.d.ts +6 -0
- package/dist/.downloads/scramjet/package/dist/types/types.d.ts +68 -0
- package/dist/.downloads/scramjet/package/lib/index.cjs +7 -0
- package/dist/.downloads/scramjet/package/lib/index.d.ts +8 -0
- package/dist/.downloads/scramjet/package/lib/types.d.ts +20 -0
- package/dist/.downloads/scramjet/package/package.json +93 -0
- package/dist/bootstrap-client.js +169 -0
- package/dist/bootstrap-client.js.map +1 -0
- package/dist/bootstrap-server.js +406 -0
- package/dist/bootstrap-server.js.map +1 -0
- package/dist/bootstrap-static.js +476 -0
- package/dist/bootstrap-static.js.map +1 -0
- package/dist/types/client.d.ts +4 -0
- package/dist/types/clientcommon.d.ts +2 -0
- package/dist/types/common.d.ts +30 -0
- package/dist/types/server.d.ts +24 -0
- package/dist/types/static.d.ts +1 -0
- 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");
|