lucent-pack 0.1.0__py3-none-any.whl
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.
- lucent_pack/__init__.py +3 -0
- lucent_pack/__main__.py +3 -0
- lucent_pack/build.py +244 -0
- lucent_pack/cli.py +119 -0
- lucent_pack/serve.py +41 -0
- lucent_pack/static/lucent/dist/httpuv-sw.d.ts +2 -0
- lucent_pack/static/lucent/dist/httpuv-sw.js +1478 -0
- lucent_pack/static/lucent/dist/httpuv-sw.js.map +1 -0
- lucent_pack/static/lucent/dist/httpuv-web.d.ts +263 -0
- lucent_pack/static/lucent/dist/httpuv-web.js +794 -0
- lucent_pack/static/lucent/dist/httpuv-web.js.map +1 -0
- lucent_pack/static/lucent/dist/rWasmWorker.d.ts +2 -0
- lucent_pack/static/lucent/dist/rWasmWorker.js +1662 -0
- lucent_pack/static/lucent/dist/rWasmWorker.js.map +1 -0
- lucent_pack/static/lucent/dist/runApp.d.ts +8 -0
- lucent_pack/static/lucent/dist/runApp.js +1056 -0
- lucent_pack/static/lucent/dist/runApp.js.map +1 -0
- lucent_pack/static/lucent/dist/shiny-socket.d.ts +53 -0
- lucent_pack/static/lucent/dist/shiny-socket.js +388 -0
- lucent_pack/static/lucent/dist/shiny-socket.js.map +1 -0
- lucent_pack/templates/favicon.svg +7 -0
- lucent_pack/templates/index.html +96 -0
- lucent_pack/templates/style.css +73 -0
- lucent_pack-0.1.0.dist-info/METADATA +90 -0
- lucent_pack-0.1.0.dist-info/RECORD +28 -0
- lucent_pack-0.1.0.dist-info/WHEEL +4 -0
- lucent_pack-0.1.0.dist-info/entry_points.txt +2 -0
- lucent_pack-0.1.0.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,1478 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/httpuv/constants.ts
|
|
8
|
+
var WS_FRAME = {
|
|
9
|
+
SEND: "websocket.send"};
|
|
10
|
+
var WASM_R_HOME = "/lib/R";
|
|
11
|
+
var REQUEST_TIMEOUT_MS = 18e4;
|
|
12
|
+
var MSG = {
|
|
13
|
+
REGISTER_HOST: "httpuv_register_host",
|
|
14
|
+
HTTP_REQUEST: "httpuv_http_request",
|
|
15
|
+
HTTP_RESPONSE: "httpuv_http_response",
|
|
16
|
+
WS_PUSH: "httpuv_ws_push",
|
|
17
|
+
/** Iframe → SW (on session port): frame delivered to Shiny.createSocket. */
|
|
18
|
+
WS_PUSH_ACK: "httpuv_ws_push_ack",
|
|
19
|
+
STOP: "httpuv_stop",
|
|
20
|
+
/** Drop cached GET /shiny/ without tearing down the R worker (app restart). */
|
|
21
|
+
CLEAR_APP_CACHE: "httpuv_clear_app_cache",
|
|
22
|
+
/** Host -> SW: full shiny::resourcePaths() map (warmup). */
|
|
23
|
+
REGISTER_RESOURCE_PATHS: "httpuv_register_resource_paths",
|
|
24
|
+
/** Host -> SW: upsert or remove one addResourcePath prefix (empty dir removes). */
|
|
25
|
+
UPDATE_RESOURCE_PATH: "httpuv_update_resource_path",
|
|
26
|
+
/** SW -> host: Comlink to the R worker was lost (e.g. after SW update). */
|
|
27
|
+
REQUEST_COMLINK: "httpuv_request_comlink",
|
|
28
|
+
/** Iframe → SW: transfer a MessagePort for session WS push delivery. */
|
|
29
|
+
REGISTER_SESSION: "httpuv_register_session",
|
|
30
|
+
/** SW → iframe (on session port): registration accepted. */
|
|
31
|
+
SESSION_ACK: "httpuv_session_ack",
|
|
32
|
+
/** SW → iframe client: session MessagePort was lost (e.g. SW idle restart); re-REGISTER_SESSION. */
|
|
33
|
+
REQUEST_SESSION_PORT: "httpuv_request_session_port"
|
|
34
|
+
};
|
|
35
|
+
var WARMUP_REQUEST_HEADER = "X-Shiny-Forge-Warmup";
|
|
36
|
+
var COMLINK = {
|
|
37
|
+
PORT_HANDOFF: "httpuv_comlink_port",
|
|
38
|
+
/**
|
|
39
|
+
* SW -> host: the handoff port arrived. Sent before the Comlink round-trip so
|
|
40
|
+
* the host can distinguish a dropped handoff (worker evicted mid-boot) from an
|
|
41
|
+
* R worker that never finished setup.
|
|
42
|
+
*/
|
|
43
|
+
PORT_HANDOFF_ACK: "httpuv_comlink_port_ack"
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// node_modules/comlink/dist/esm/comlink.mjs
|
|
47
|
+
var comlink_exports = {};
|
|
48
|
+
__export(comlink_exports, {
|
|
49
|
+
createEndpoint: () => createEndpoint,
|
|
50
|
+
expose: () => expose,
|
|
51
|
+
finalizer: () => finalizer,
|
|
52
|
+
proxy: () => proxy,
|
|
53
|
+
proxyMarker: () => proxyMarker,
|
|
54
|
+
releaseProxy: () => releaseProxy,
|
|
55
|
+
transfer: () => transfer,
|
|
56
|
+
transferHandlers: () => transferHandlers,
|
|
57
|
+
windowEndpoint: () => windowEndpoint,
|
|
58
|
+
wrap: () => wrap
|
|
59
|
+
});
|
|
60
|
+
var proxyMarker = /* @__PURE__ */ Symbol("Comlink.proxy");
|
|
61
|
+
var createEndpoint = /* @__PURE__ */ Symbol("Comlink.endpoint");
|
|
62
|
+
var releaseProxy = /* @__PURE__ */ Symbol("Comlink.releaseProxy");
|
|
63
|
+
var finalizer = /* @__PURE__ */ Symbol("Comlink.finalizer");
|
|
64
|
+
var throwMarker = /* @__PURE__ */ Symbol("Comlink.thrown");
|
|
65
|
+
var isObject = (val) => typeof val === "object" && val !== null || typeof val === "function";
|
|
66
|
+
var proxyTransferHandler = {
|
|
67
|
+
canHandle: (val) => isObject(val) && val[proxyMarker],
|
|
68
|
+
serialize(obj) {
|
|
69
|
+
const { port1, port2 } = new MessageChannel();
|
|
70
|
+
expose(obj, port1);
|
|
71
|
+
return [port2, [port2]];
|
|
72
|
+
},
|
|
73
|
+
deserialize(port) {
|
|
74
|
+
port.start();
|
|
75
|
+
return wrap(port);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var throwTransferHandler = {
|
|
79
|
+
canHandle: (value) => isObject(value) && throwMarker in value,
|
|
80
|
+
serialize({ value }) {
|
|
81
|
+
let serialized;
|
|
82
|
+
if (value instanceof Error) {
|
|
83
|
+
serialized = {
|
|
84
|
+
isError: true,
|
|
85
|
+
value: {
|
|
86
|
+
message: value.message,
|
|
87
|
+
name: value.name,
|
|
88
|
+
stack: value.stack
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
} else {
|
|
92
|
+
serialized = { isError: false, value };
|
|
93
|
+
}
|
|
94
|
+
return [serialized, []];
|
|
95
|
+
},
|
|
96
|
+
deserialize(serialized) {
|
|
97
|
+
if (serialized.isError) {
|
|
98
|
+
throw Object.assign(new Error(serialized.value.message), serialized.value);
|
|
99
|
+
}
|
|
100
|
+
throw serialized.value;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var transferHandlers = /* @__PURE__ */ new Map([
|
|
104
|
+
["proxy", proxyTransferHandler],
|
|
105
|
+
["throw", throwTransferHandler]
|
|
106
|
+
]);
|
|
107
|
+
function isAllowedOrigin(allowedOrigins, origin) {
|
|
108
|
+
for (const allowedOrigin of allowedOrigins) {
|
|
109
|
+
if (origin === allowedOrigin || allowedOrigin === "*") {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
function expose(obj, ep = globalThis, allowedOrigins = ["*"]) {
|
|
119
|
+
ep.addEventListener("message", function callback(ev) {
|
|
120
|
+
if (!ev || !ev.data) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (!isAllowedOrigin(allowedOrigins, ev.origin)) {
|
|
124
|
+
console.warn(`Invalid origin '${ev.origin}' for comlink proxy`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const { id, type, path } = Object.assign({ path: [] }, ev.data);
|
|
128
|
+
const argumentList = (ev.data.argumentList || []).map(fromWireValue);
|
|
129
|
+
let returnValue;
|
|
130
|
+
try {
|
|
131
|
+
const parent = path.slice(0, -1).reduce((obj2, prop) => obj2[prop], obj);
|
|
132
|
+
const rawValue = path.reduce((obj2, prop) => obj2[prop], obj);
|
|
133
|
+
switch (type) {
|
|
134
|
+
case "GET":
|
|
135
|
+
{
|
|
136
|
+
returnValue = rawValue;
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
case "SET":
|
|
140
|
+
{
|
|
141
|
+
parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
|
|
142
|
+
returnValue = true;
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
case "APPLY":
|
|
146
|
+
{
|
|
147
|
+
returnValue = rawValue.apply(parent, argumentList);
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
case "CONSTRUCT":
|
|
151
|
+
{
|
|
152
|
+
const value = new rawValue(...argumentList);
|
|
153
|
+
returnValue = proxy(value);
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
case "ENDPOINT":
|
|
157
|
+
{
|
|
158
|
+
const { port1, port2 } = new MessageChannel();
|
|
159
|
+
expose(obj, port2);
|
|
160
|
+
returnValue = transfer(port1, [port1]);
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
case "RELEASE":
|
|
164
|
+
{
|
|
165
|
+
returnValue = void 0;
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
default:
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
} catch (value) {
|
|
172
|
+
returnValue = { value, [throwMarker]: 0 };
|
|
173
|
+
}
|
|
174
|
+
Promise.resolve(returnValue).catch((value) => {
|
|
175
|
+
return { value, [throwMarker]: 0 };
|
|
176
|
+
}).then((returnValue2) => {
|
|
177
|
+
const [wireValue, transferables] = toWireValue(returnValue2);
|
|
178
|
+
ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
|
|
179
|
+
if (type === "RELEASE") {
|
|
180
|
+
ep.removeEventListener("message", callback);
|
|
181
|
+
closeEndPoint(ep);
|
|
182
|
+
if (finalizer in obj && typeof obj[finalizer] === "function") {
|
|
183
|
+
obj[finalizer]();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}).catch((error) => {
|
|
187
|
+
const [wireValue, transferables] = toWireValue({
|
|
188
|
+
value: new TypeError("Unserializable return value"),
|
|
189
|
+
[throwMarker]: 0
|
|
190
|
+
});
|
|
191
|
+
ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
if (ep.start) {
|
|
195
|
+
ep.start();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function isMessagePort(endpoint) {
|
|
199
|
+
return endpoint.constructor.name === "MessagePort";
|
|
200
|
+
}
|
|
201
|
+
function closeEndPoint(endpoint) {
|
|
202
|
+
if (isMessagePort(endpoint))
|
|
203
|
+
endpoint.close();
|
|
204
|
+
}
|
|
205
|
+
function wrap(ep, target) {
|
|
206
|
+
const pendingListeners = /* @__PURE__ */ new Map();
|
|
207
|
+
ep.addEventListener("message", function handleMessage(ev) {
|
|
208
|
+
const { data } = ev;
|
|
209
|
+
if (!data || !data.id) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const resolver = pendingListeners.get(data.id);
|
|
213
|
+
if (!resolver) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
resolver(data);
|
|
218
|
+
} finally {
|
|
219
|
+
pendingListeners.delete(data.id);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
return createProxy(ep, pendingListeners, [], target);
|
|
223
|
+
}
|
|
224
|
+
function throwIfProxyReleased(isReleased) {
|
|
225
|
+
if (isReleased) {
|
|
226
|
+
throw new Error("Proxy has been released and is not useable");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function releaseEndpoint(ep) {
|
|
230
|
+
return requestResponseMessage(ep, /* @__PURE__ */ new Map(), {
|
|
231
|
+
type: "RELEASE"
|
|
232
|
+
}).then(() => {
|
|
233
|
+
closeEndPoint(ep);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
var proxyCounter = /* @__PURE__ */ new WeakMap();
|
|
237
|
+
var proxyFinalizers = "FinalizationRegistry" in globalThis && new FinalizationRegistry((ep) => {
|
|
238
|
+
const newCount = (proxyCounter.get(ep) || 0) - 1;
|
|
239
|
+
proxyCounter.set(ep, newCount);
|
|
240
|
+
if (newCount === 0) {
|
|
241
|
+
releaseEndpoint(ep);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
function registerProxy(proxy2, ep) {
|
|
245
|
+
const newCount = (proxyCounter.get(ep) || 0) + 1;
|
|
246
|
+
proxyCounter.set(ep, newCount);
|
|
247
|
+
if (proxyFinalizers) {
|
|
248
|
+
proxyFinalizers.register(proxy2, ep, proxy2);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function unregisterProxy(proxy2) {
|
|
252
|
+
if (proxyFinalizers) {
|
|
253
|
+
proxyFinalizers.unregister(proxy2);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function createProxy(ep, pendingListeners, path = [], target = function() {
|
|
257
|
+
}) {
|
|
258
|
+
let isProxyReleased = false;
|
|
259
|
+
const proxy2 = new Proxy(target, {
|
|
260
|
+
get(_target, prop) {
|
|
261
|
+
throwIfProxyReleased(isProxyReleased);
|
|
262
|
+
if (prop === releaseProxy) {
|
|
263
|
+
return () => {
|
|
264
|
+
unregisterProxy(proxy2);
|
|
265
|
+
releaseEndpoint(ep);
|
|
266
|
+
pendingListeners.clear();
|
|
267
|
+
isProxyReleased = true;
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (prop === "then") {
|
|
271
|
+
if (path.length === 0) {
|
|
272
|
+
return { then: () => proxy2 };
|
|
273
|
+
}
|
|
274
|
+
const r = requestResponseMessage(ep, pendingListeners, {
|
|
275
|
+
type: "GET",
|
|
276
|
+
path: path.map((p) => p.toString())
|
|
277
|
+
}).then(fromWireValue);
|
|
278
|
+
return r.then.bind(r);
|
|
279
|
+
}
|
|
280
|
+
return createProxy(ep, pendingListeners, [...path, prop]);
|
|
281
|
+
},
|
|
282
|
+
set(_target, prop, rawValue) {
|
|
283
|
+
throwIfProxyReleased(isProxyReleased);
|
|
284
|
+
const [value, transferables] = toWireValue(rawValue);
|
|
285
|
+
return requestResponseMessage(ep, pendingListeners, {
|
|
286
|
+
type: "SET",
|
|
287
|
+
path: [...path, prop].map((p) => p.toString()),
|
|
288
|
+
value
|
|
289
|
+
}, transferables).then(fromWireValue);
|
|
290
|
+
},
|
|
291
|
+
apply(_target, _thisArg, rawArgumentList) {
|
|
292
|
+
throwIfProxyReleased(isProxyReleased);
|
|
293
|
+
const last = path[path.length - 1];
|
|
294
|
+
if (last === createEndpoint) {
|
|
295
|
+
return requestResponseMessage(ep, pendingListeners, {
|
|
296
|
+
type: "ENDPOINT"
|
|
297
|
+
}).then(fromWireValue);
|
|
298
|
+
}
|
|
299
|
+
if (last === "bind") {
|
|
300
|
+
return createProxy(ep, pendingListeners, path.slice(0, -1));
|
|
301
|
+
}
|
|
302
|
+
const [argumentList, transferables] = processArguments(rawArgumentList);
|
|
303
|
+
return requestResponseMessage(ep, pendingListeners, {
|
|
304
|
+
type: "APPLY",
|
|
305
|
+
path: path.map((p) => p.toString()),
|
|
306
|
+
argumentList
|
|
307
|
+
}, transferables).then(fromWireValue);
|
|
308
|
+
},
|
|
309
|
+
construct(_target, rawArgumentList) {
|
|
310
|
+
throwIfProxyReleased(isProxyReleased);
|
|
311
|
+
const [argumentList, transferables] = processArguments(rawArgumentList);
|
|
312
|
+
return requestResponseMessage(ep, pendingListeners, {
|
|
313
|
+
type: "CONSTRUCT",
|
|
314
|
+
path: path.map((p) => p.toString()),
|
|
315
|
+
argumentList
|
|
316
|
+
}, transferables).then(fromWireValue);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
registerProxy(proxy2, ep);
|
|
320
|
+
return proxy2;
|
|
321
|
+
}
|
|
322
|
+
function myFlat(arr) {
|
|
323
|
+
return Array.prototype.concat.apply([], arr);
|
|
324
|
+
}
|
|
325
|
+
function processArguments(argumentList) {
|
|
326
|
+
const processed = argumentList.map(toWireValue);
|
|
327
|
+
return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];
|
|
328
|
+
}
|
|
329
|
+
var transferCache = /* @__PURE__ */ new WeakMap();
|
|
330
|
+
function transfer(obj, transfers) {
|
|
331
|
+
transferCache.set(obj, transfers);
|
|
332
|
+
return obj;
|
|
333
|
+
}
|
|
334
|
+
function proxy(obj) {
|
|
335
|
+
return Object.assign(obj, { [proxyMarker]: true });
|
|
336
|
+
}
|
|
337
|
+
function windowEndpoint(w, context = globalThis, targetOrigin = "*") {
|
|
338
|
+
return {
|
|
339
|
+
postMessage: (msg, transferables) => w.postMessage(msg, targetOrigin, transferables),
|
|
340
|
+
addEventListener: context.addEventListener.bind(context),
|
|
341
|
+
removeEventListener: context.removeEventListener.bind(context)
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function toWireValue(value) {
|
|
345
|
+
for (const [name, handler] of transferHandlers) {
|
|
346
|
+
if (handler.canHandle(value)) {
|
|
347
|
+
const [serializedValue, transferables] = handler.serialize(value);
|
|
348
|
+
return [
|
|
349
|
+
{
|
|
350
|
+
type: "HANDLER",
|
|
351
|
+
name,
|
|
352
|
+
value: serializedValue
|
|
353
|
+
},
|
|
354
|
+
transferables
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return [
|
|
359
|
+
{
|
|
360
|
+
type: "RAW",
|
|
361
|
+
value
|
|
362
|
+
},
|
|
363
|
+
transferCache.get(value) || []
|
|
364
|
+
];
|
|
365
|
+
}
|
|
366
|
+
function fromWireValue(value) {
|
|
367
|
+
switch (value.type) {
|
|
368
|
+
case "HANDLER":
|
|
369
|
+
return transferHandlers.get(value.name).deserialize(value.value);
|
|
370
|
+
case "RAW":
|
|
371
|
+
return value.value;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function requestResponseMessage(ep, pendingListeners, msg, transfers) {
|
|
375
|
+
return new Promise((resolve) => {
|
|
376
|
+
const id = generateUUID();
|
|
377
|
+
pendingListeners.set(id, resolve);
|
|
378
|
+
if (ep.start) {
|
|
379
|
+
ep.start();
|
|
380
|
+
}
|
|
381
|
+
ep.postMessage(Object.assign({ id }, msg), transfers);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function generateUUID() {
|
|
385
|
+
return new Array(4).fill(0).map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16)).join("-");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// src/httpuv/comlink.ts
|
|
389
|
+
function createSwDeliveryApi(deliverOutbound) {
|
|
390
|
+
return {
|
|
391
|
+
deliverHttpResponse(resp) {
|
|
392
|
+
deliverOutbound({
|
|
393
|
+
type: MSG.HTTP_RESPONSE,
|
|
394
|
+
uuid: resp.uuid,
|
|
395
|
+
status: resp.status ?? 500,
|
|
396
|
+
headers: resp.headers ?? {},
|
|
397
|
+
body: resp.body ?? null
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/httpuv/debug.ts
|
|
404
|
+
function isHttpuvDebug() {
|
|
405
|
+
if (globalThis.__HTTPUV_DEBUG__) {
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
if (typeof location !== "undefined") {
|
|
410
|
+
const params = new URLSearchParams(location.search);
|
|
411
|
+
if (params.has("httpuvDebug") || params.get("debug") === "httpuv") {
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (typeof localStorage !== "undefined" && localStorage.getItem("shinyForgeDebug") === "1") {
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
if (typeof self !== "undefined" && self.location?.href) {
|
|
419
|
+
const params = new URL(self.location.href).searchParams;
|
|
420
|
+
if (params.has("httpuvDebug") || params.get("debug") === "httpuv") {
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
} catch {
|
|
425
|
+
}
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
function httpuvDebugLog(stage, ...args) {
|
|
429
|
+
if (!isHttpuvDebug()) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
console.info(`[httpuv-debug:${stage}]`, ...args);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/httpuv/prefix.ts
|
|
436
|
+
function resolveShinyPrefix(fromUrl) {
|
|
437
|
+
const prefix = new URL("shiny/", fromUrl).pathname;
|
|
438
|
+
return prefix.endsWith("/") ? prefix : `${prefix}/`;
|
|
439
|
+
}
|
|
440
|
+
var SESSION_DIR = "__session__";
|
|
441
|
+
var HOST_DIR = "__host__";
|
|
442
|
+
function normalizeSessionHandle(handle) {
|
|
443
|
+
let s = String(handle ?? "").trim();
|
|
444
|
+
while (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
|
|
445
|
+
s = s.slice(1, -1);
|
|
446
|
+
}
|
|
447
|
+
return s;
|
|
448
|
+
}
|
|
449
|
+
function resolveSessionPrefix(fromUrl) {
|
|
450
|
+
return `${resolveShinyPrefix(fromUrl)}${SESSION_DIR}/`;
|
|
451
|
+
}
|
|
452
|
+
function parseSessionAction(urlString, prefix) {
|
|
453
|
+
const url = new URL(urlString);
|
|
454
|
+
const sessionPrefix = `${prefix}${SESSION_DIR}/`;
|
|
455
|
+
if (!url.pathname.startsWith(sessionPrefix)) {
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
const action = url.pathname.slice(sessionPrefix.length).replace(/\/$/, "");
|
|
459
|
+
if (!["open", "send", "recv", "close"].includes(action)) {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
const rawHandle = url.searchParams.get("handle");
|
|
463
|
+
return {
|
|
464
|
+
action,
|
|
465
|
+
handle: rawHandle ? normalizeSessionHandle(rawHandle) : null
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function isHostPushUrl(urlString, prefix) {
|
|
469
|
+
const url = new URL(urlString);
|
|
470
|
+
return url.pathname === `${prefix}${HOST_DIR}/push`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/httpuv/htmlwidget-deps-patch.ts
|
|
474
|
+
var LUCENT_PATCH_VERSION = "htmlwidget-async-v2";
|
|
475
|
+
var HTMLWIDGET_ASYNC_DEPS_PATCH = `<script id="lucent-htmlwidget-async-deps">(function(){
|
|
476
|
+
window.__lucentEvalCache=window.__lucentEvalCache||{};
|
|
477
|
+
function cacheScript(url,text){
|
|
478
|
+
try{
|
|
479
|
+
var abs=new URL(url,document.baseURI).href;
|
|
480
|
+
window.__lucentEvalCache[abs]=text;
|
|
481
|
+
window.__lucentEvalCache[url]=text;
|
|
482
|
+
}catch(e){}
|
|
483
|
+
}
|
|
484
|
+
(function(){
|
|
485
|
+
var ofetch=window.fetch;
|
|
486
|
+
if(!ofetch)return;
|
|
487
|
+
var oAppend=Element.prototype.appendChild;
|
|
488
|
+
Element.prototype.appendChild=function(node){
|
|
489
|
+
if(node&&node.tagName==='SCRIPT'&&node.src){
|
|
490
|
+
var src=node.src;
|
|
491
|
+
ofetch(src).then(function(r){return r.ok?r.text():null;}).then(function(t){if(t)cacheScript(src,t);}).catch(function(){});
|
|
492
|
+
}
|
|
493
|
+
return oAppend.apply(this,arguments);
|
|
494
|
+
};
|
|
495
|
+
})();
|
|
496
|
+
function installEvalUrlPatch(){
|
|
497
|
+
if(!window.jQuery||!jQuery._evalUrl||jQuery._evalUrl.__lucentPatched)return;
|
|
498
|
+
var orig=jQuery._evalUrl;
|
|
499
|
+
jQuery._evalUrl=function(url,options,doc){
|
|
500
|
+
var abs;
|
|
501
|
+
try{abs=new URL(url,document.baseURI).href;}catch(e){abs=url;}
|
|
502
|
+
var cached=window.__lucentEvalCache[abs]||window.__lucentEvalCache[url];
|
|
503
|
+
if(typeof cached==='string'){
|
|
504
|
+
jQuery.globalEval(cached,options,doc);
|
|
505
|
+
return {status:200,responseText:cached};
|
|
506
|
+
}
|
|
507
|
+
return orig.apply(this,arguments);
|
|
508
|
+
};
|
|
509
|
+
jQuery._evalUrl.__lucentPatched=1;
|
|
510
|
+
}
|
|
511
|
+
function wrap(binding){
|
|
512
|
+
if(!binding||!binding.renderValue||binding.__lucentAsyncDeps)return;
|
|
513
|
+
binding.__lucentAsyncDeps=1;
|
|
514
|
+
try{
|
|
515
|
+
if(binding.renderValue.constructor&&binding.renderValue.constructor.name==='AsyncFunction')return;
|
|
516
|
+
}catch(e){}
|
|
517
|
+
var rv=binding.renderValue.bind(binding);
|
|
518
|
+
binding.renderValue=function(el,data){
|
|
519
|
+
if(data&&data.x!=null&&data.deps&&data.deps.length){
|
|
520
|
+
return Shiny.renderDependenciesAsync(data.deps).then(function(){return rv(el,data);});
|
|
521
|
+
}
|
|
522
|
+
return rv(el,data);
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function installBindingWrap(){
|
|
526
|
+
if(!window.Shiny||!Shiny.outputBindings||!Shiny.renderDependenciesAsync)return;
|
|
527
|
+
if(!Shiny.outputBindings.__lucentAsyncDepsReg){
|
|
528
|
+
Shiny.outputBindings.__lucentAsyncDepsReg=1;
|
|
529
|
+
var reg=Shiny.outputBindings.register.bind(Shiny.outputBindings);
|
|
530
|
+
Shiny.outputBindings.register=function(binding,name){
|
|
531
|
+
wrap(binding);
|
|
532
|
+
return reg(binding,name);
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
var list=Shiny.outputBindings.getBindings?Shiny.outputBindings.getBindings():[];
|
|
536
|
+
for(var i=0;i<list.length;i++){
|
|
537
|
+
wrap(list[i].binding||list[i]);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function install(){
|
|
541
|
+
installEvalUrlPatch();
|
|
542
|
+
installBindingWrap();
|
|
543
|
+
}
|
|
544
|
+
install();
|
|
545
|
+
var n=0,t=setInterval(function(){install();if(++n>400)clearInterval(t);},5);
|
|
546
|
+
})();<\/script>`;
|
|
547
|
+
function responseBodyToText(body) {
|
|
548
|
+
if (body == null) {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
if (typeof body === "string") {
|
|
552
|
+
return body;
|
|
553
|
+
}
|
|
554
|
+
if (body instanceof ArrayBuffer) {
|
|
555
|
+
return new TextDecoder().decode(body);
|
|
556
|
+
}
|
|
557
|
+
if (ArrayBuffer.isView(body)) {
|
|
558
|
+
return new TextDecoder().decode(body);
|
|
559
|
+
}
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
function isHtmlContentType(headers) {
|
|
563
|
+
if (!headers) {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
const ct = headers["content-type"] ?? headers["Content-Type"] ?? "";
|
|
567
|
+
return ct.includes("text/html");
|
|
568
|
+
}
|
|
569
|
+
function injectHtmlwidgetAsyncDepsPatch(html) {
|
|
570
|
+
let out = html.replace(/<script id="lucent-htmlwidget-async-deps"[\s\S]*?<\/script>\s*/g, "");
|
|
571
|
+
const patch = HTMLWIDGET_ASYNC_DEPS_PATCH.replace(
|
|
572
|
+
'id="lucent-htmlwidget-async-deps"',
|
|
573
|
+
`id="lucent-htmlwidget-async-deps" data-lucent-patch="${LUCENT_PATCH_VERSION}"`
|
|
574
|
+
);
|
|
575
|
+
const shinyScript = out.match(/<script src="[^"]*\/shiny(?:\.min)?\.js"><\/script>/);
|
|
576
|
+
if (shinyScript) {
|
|
577
|
+
return out.replace(shinyScript[0], `${shinyScript[0]}
|
|
578
|
+
${patch}`);
|
|
579
|
+
}
|
|
580
|
+
if (out.includes("<head>")) {
|
|
581
|
+
return out.replace("<head>", `<head>
|
|
582
|
+
${patch}`);
|
|
583
|
+
}
|
|
584
|
+
if (out.includes("</head>")) {
|
|
585
|
+
return out.replace("</head>", `${patch}
|
|
586
|
+
</head>`);
|
|
587
|
+
}
|
|
588
|
+
return patch + out;
|
|
589
|
+
}
|
|
590
|
+
function maybePatchAppDocumentResponse(resp, url, method, isAppDocument) {
|
|
591
|
+
if (method !== "GET" || resp.status !== 200 || !isAppDocument(url) || !isHtmlContentType(resp.headers)) {
|
|
592
|
+
return resp;
|
|
593
|
+
}
|
|
594
|
+
const html = responseBodyToText(resp.body);
|
|
595
|
+
if (!html || !html.includes("htmlwidgets")) {
|
|
596
|
+
return resp;
|
|
597
|
+
}
|
|
598
|
+
const patched = injectHtmlwidgetAsyncDepsPatch(html);
|
|
599
|
+
if (patched === html) {
|
|
600
|
+
return resp;
|
|
601
|
+
}
|
|
602
|
+
return {
|
|
603
|
+
status: resp.status,
|
|
604
|
+
headers: resp.headers,
|
|
605
|
+
body: patched
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/httpuv/sw.ts
|
|
610
|
+
var swSelf = self;
|
|
611
|
+
function initialShinyPrefix() {
|
|
612
|
+
try {
|
|
613
|
+
const own = new URL(swSelf.location.href);
|
|
614
|
+
const declared = own.searchParams.get("shinyPrefix");
|
|
615
|
+
if (declared) {
|
|
616
|
+
return declared.endsWith("/") ? declared : `${declared}/`;
|
|
617
|
+
}
|
|
618
|
+
} catch {
|
|
619
|
+
}
|
|
620
|
+
return resolveShinyPrefix(new URL("/", swSelf.location.href).href);
|
|
621
|
+
}
|
|
622
|
+
var SHINY_PREFIX = initialShinyPrefix();
|
|
623
|
+
resolveSessionPrefix(new URL("/", swSelf.location.href).href);
|
|
624
|
+
var shinyAppPrefix = SHINY_PREFIX;
|
|
625
|
+
var hostClientId = null;
|
|
626
|
+
var rwasmHost = null;
|
|
627
|
+
function createRwasmHostReady() {
|
|
628
|
+
let resolve;
|
|
629
|
+
let reject;
|
|
630
|
+
const promise = new Promise((res, rej) => {
|
|
631
|
+
resolve = res;
|
|
632
|
+
reject = rej;
|
|
633
|
+
});
|
|
634
|
+
return { promise, resolve, reject };
|
|
635
|
+
}
|
|
636
|
+
var rwasmHostReady = createRwasmHostReady();
|
|
637
|
+
async function connectSwToWorker(port) {
|
|
638
|
+
const workerHost = comlink_exports.wrap(port);
|
|
639
|
+
const deliveryChannel = new MessageChannel();
|
|
640
|
+
const wsPushChannel = new MessageChannel();
|
|
641
|
+
comlink_exports.expose(
|
|
642
|
+
createSwDeliveryApi((msg) => handleHostOutboundMessage(msg)),
|
|
643
|
+
deliveryChannel.port1
|
|
644
|
+
);
|
|
645
|
+
wsPushChannel.port1.onmessage = (event) => {
|
|
646
|
+
const msg = event.data;
|
|
647
|
+
if (!msg || typeof msg !== "object" || typeof msg.type !== "string") {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
handleHostOutboundMessage(msg);
|
|
651
|
+
};
|
|
652
|
+
wsPushChannel.port1.start();
|
|
653
|
+
try {
|
|
654
|
+
await workerHost.registerSwDelivery(
|
|
655
|
+
comlink_exports.transfer(deliveryChannel.port2, [deliveryChannel.port2]),
|
|
656
|
+
comlink_exports.transfer(wsPushChannel.port2, [wsPushChannel.port2])
|
|
657
|
+
);
|
|
658
|
+
rwasmHost = workerHost;
|
|
659
|
+
markRwasmHostReady();
|
|
660
|
+
httpuvDebugLog("sw-comlink-connected");
|
|
661
|
+
} catch (err) {
|
|
662
|
+
console.error("[httpuv-sw] Comlink unified setup failed", err);
|
|
663
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
664
|
+
resetRwasmHostWaiter(new Error(`R worker Comlink setup failed: ${message}`));
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function markRwasmHostReady() {
|
|
668
|
+
if (!rwasmHost) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
rwasmHostReady.resolve(rwasmHost);
|
|
672
|
+
}
|
|
673
|
+
function resetRwasmHostWaiter(reason) {
|
|
674
|
+
const previous = rwasmHostReady;
|
|
675
|
+
rwasmHost = null;
|
|
676
|
+
rwasmHostReady = createRwasmHostReady();
|
|
677
|
+
previous.reject(reason ?? new Error("R worker Comlink not ready"));
|
|
678
|
+
}
|
|
679
|
+
function rollRwasmHostWaiter() {
|
|
680
|
+
const previous = rwasmHostReady;
|
|
681
|
+
rwasmHost = null;
|
|
682
|
+
rwasmHostReady = createRwasmHostReady();
|
|
683
|
+
void rwasmHostReady.promise.then(
|
|
684
|
+
(host) => {
|
|
685
|
+
previous.resolve(host);
|
|
686
|
+
},
|
|
687
|
+
(err) => {
|
|
688
|
+
previous.reject(err instanceof Error ? err : new Error(String(err)));
|
|
689
|
+
}
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
async function waitForRwasmHost() {
|
|
693
|
+
for (; ; ) {
|
|
694
|
+
if (rwasmHost) {
|
|
695
|
+
return rwasmHost;
|
|
696
|
+
}
|
|
697
|
+
const ready = rwasmHostReady;
|
|
698
|
+
try {
|
|
699
|
+
return await ready.promise;
|
|
700
|
+
} catch (err) {
|
|
701
|
+
if (rwasmHost) {
|
|
702
|
+
return rwasmHost;
|
|
703
|
+
}
|
|
704
|
+
if (rwasmHostReady !== ready) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
throw err;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
async function requestComlinkFromHost() {
|
|
712
|
+
const clients = await swSelf.clients.matchAll({
|
|
713
|
+
type: "window",
|
|
714
|
+
includeUncontrolled: true
|
|
715
|
+
});
|
|
716
|
+
httpuvDebugLog("sw-request-comlink", {
|
|
717
|
+
clients: clients.length,
|
|
718
|
+
hostClientId,
|
|
719
|
+
urls: clients.map((c) => c.url)
|
|
720
|
+
});
|
|
721
|
+
for (const client of clients) {
|
|
722
|
+
client.postMessage({ type: MSG.REQUEST_COMLINK });
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
var pendingHttp = /* @__PURE__ */ new Map();
|
|
726
|
+
var sessionPorts = /* @__PURE__ */ new Map();
|
|
727
|
+
var sessionClientIds = /* @__PURE__ */ new Map();
|
|
728
|
+
var queuedWsPush = /* @__PURE__ */ new Map();
|
|
729
|
+
var sessionPortReregisterPending = /* @__PURE__ */ new Set();
|
|
730
|
+
var sessionDeliverChain = /* @__PURE__ */ new Map();
|
|
731
|
+
var pendingSessionAcks = /* @__PURE__ */ new Map();
|
|
732
|
+
var nextSessionPushId = 1;
|
|
733
|
+
var SESSION_PUSH_ACK_TIMEOUT_MS = 8e3;
|
|
734
|
+
var cachedAppDocument = null;
|
|
735
|
+
var LUCENT_APP_PATCH_VERSION = "htmlwidget-async-v2";
|
|
736
|
+
var shinyResourcePaths = /* @__PURE__ */ new Map();
|
|
737
|
+
function isAppDocumentRequest(urlString) {
|
|
738
|
+
const url = new URL(urlString);
|
|
739
|
+
if (!url.pathname.startsWith(shinyAppPrefix)) {
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
const rest = url.pathname.slice(shinyAppPrefix.length).replace(/\/$/, "");
|
|
743
|
+
return rest === "" || rest === "index.html";
|
|
744
|
+
}
|
|
745
|
+
function pathUnderShinyPrefix(pathname) {
|
|
746
|
+
return pathname.startsWith(shinyAppPrefix) || pathname.startsWith(SHINY_PREFIX);
|
|
747
|
+
}
|
|
748
|
+
function clonePendingResponse(resp) {
|
|
749
|
+
let body = resp.body;
|
|
750
|
+
if (body instanceof ArrayBuffer) {
|
|
751
|
+
body = body.slice(0);
|
|
752
|
+
} else if (body instanceof Uint8Array) {
|
|
753
|
+
body = body.slice();
|
|
754
|
+
}
|
|
755
|
+
return {
|
|
756
|
+
status: resp.status,
|
|
757
|
+
headers: { ...resp.headers ?? {} },
|
|
758
|
+
body
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function clearCachedAppDocument() {
|
|
762
|
+
cachedAppDocument = null;
|
|
763
|
+
}
|
|
764
|
+
function clearShinyResourcePaths() {
|
|
765
|
+
shinyResourcePaths = /* @__PURE__ */ new Map();
|
|
766
|
+
}
|
|
767
|
+
function setShinyResourcePaths(paths) {
|
|
768
|
+
shinyResourcePaths = /* @__PURE__ */ new Map();
|
|
769
|
+
for (const [prefix, dir] of Object.entries(paths ?? {})) {
|
|
770
|
+
if (prefix && dir) {
|
|
771
|
+
shinyResourcePaths.set(prefix, dir);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (shinyResourcePaths.size > 0) {
|
|
775
|
+
httpuvDebugLog("sw-resource-paths", {
|
|
776
|
+
count: shinyResourcePaths.size,
|
|
777
|
+
keys: [...shinyResourcePaths.keys()]
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function updateShinyResourcePath(prefix, dir) {
|
|
782
|
+
if (!prefix) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (!dir) {
|
|
786
|
+
shinyResourcePaths.delete(prefix);
|
|
787
|
+
} else {
|
|
788
|
+
shinyResourcePaths.set(prefix, dir);
|
|
789
|
+
}
|
|
790
|
+
httpuvDebugLog("sw-resource-path-update", {
|
|
791
|
+
prefix,
|
|
792
|
+
dir: dir || null,
|
|
793
|
+
count: shinyResourcePaths.size
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
function mimeForAssetSuffix(suffix) {
|
|
797
|
+
if (suffix.endsWith(".js") || suffix.endsWith(".mjs")) {
|
|
798
|
+
return "application/javascript";
|
|
799
|
+
}
|
|
800
|
+
if (suffix.endsWith(".css")) {
|
|
801
|
+
return "text/css";
|
|
802
|
+
}
|
|
803
|
+
if (suffix.endsWith(".svg")) {
|
|
804
|
+
return "image/svg+xml";
|
|
805
|
+
}
|
|
806
|
+
if (suffix.endsWith(".png")) {
|
|
807
|
+
return "image/png";
|
|
808
|
+
}
|
|
809
|
+
if (suffix.endsWith(".woff2")) {
|
|
810
|
+
return "font/woff2";
|
|
811
|
+
}
|
|
812
|
+
if (suffix.endsWith(".woff")) {
|
|
813
|
+
return "font/woff";
|
|
814
|
+
}
|
|
815
|
+
return "application/octet-stream";
|
|
816
|
+
}
|
|
817
|
+
function pathnameRelativeToScope(pathname) {
|
|
818
|
+
const scopeUrl = swSelf.registration?.scope;
|
|
819
|
+
if (!scopeUrl) {
|
|
820
|
+
return pathname;
|
|
821
|
+
}
|
|
822
|
+
let scopePath = new URL(scopeUrl).pathname;
|
|
823
|
+
if (!scopePath.endsWith("/")) {
|
|
824
|
+
scopePath += "/";
|
|
825
|
+
}
|
|
826
|
+
if (scopePath !== "/" && pathname.startsWith(scopePath)) {
|
|
827
|
+
return `/${pathname.slice(scopePath.length)}`;
|
|
828
|
+
}
|
|
829
|
+
return pathname;
|
|
830
|
+
}
|
|
831
|
+
function vfsPathFromLibRRequest(pathname) {
|
|
832
|
+
const rest = pathnameRelativeToScope(pathname);
|
|
833
|
+
const marker = `${WASM_R_HOME}/`;
|
|
834
|
+
if (rest !== WASM_R_HOME && !rest.startsWith(marker)) {
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
if (rest.includes("..")) {
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
840
|
+
return rest;
|
|
841
|
+
}
|
|
842
|
+
function vfsStaticResponse(body, suffix, source, method) {
|
|
843
|
+
const headers = new Headers({
|
|
844
|
+
"Content-Type": mimeForAssetSuffix(suffix),
|
|
845
|
+
"X-Httpuv-Static": source
|
|
846
|
+
});
|
|
847
|
+
if (suffix.toLowerCase().endsWith(".png") || source === "vfs") {
|
|
848
|
+
headers.set("Cache-Control", "no-store");
|
|
849
|
+
}
|
|
850
|
+
if (method === "HEAD") {
|
|
851
|
+
return new Response(null, { status: 200, headers });
|
|
852
|
+
}
|
|
853
|
+
return new Response(body, { status: 200, headers });
|
|
854
|
+
}
|
|
855
|
+
async function tryServeLibRAsset(request) {
|
|
856
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
857
|
+
return null;
|
|
858
|
+
}
|
|
859
|
+
const url = new URL(request.url);
|
|
860
|
+
const vfsPath = vfsPathFromLibRRequest(url.pathname);
|
|
861
|
+
if (!vfsPath) {
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
const suffix = vfsPath.slice(vfsPath.lastIndexOf("/") + 1);
|
|
865
|
+
try {
|
|
866
|
+
if (!rwasmHost) {
|
|
867
|
+
void requestComlinkFromHost();
|
|
868
|
+
}
|
|
869
|
+
const host = await waitForRwasmHost();
|
|
870
|
+
const body = await host.readVfsFileAt(vfsPath);
|
|
871
|
+
if (!body) {
|
|
872
|
+
httpuvDebugLog("sw-vfs-read-miss", { vfsPath });
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
httpuvDebugLog("sw-static-hit", { vfsPath, source: "lib-r-vfs" });
|
|
876
|
+
return vfsStaticResponse(body, suffix, "lib-r-vfs", request.method);
|
|
877
|
+
} catch (err) {
|
|
878
|
+
httpuvDebugLog("sw-vfs-read-fail", { vfsPath, err: String(err) });
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
async function tryServeShinyStaticAsset(request) {
|
|
883
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
const url = new URL(request.url);
|
|
887
|
+
if (!url.pathname.startsWith(shinyAppPrefix)) {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
const rest = url.pathname.slice(shinyAppPrefix.length);
|
|
891
|
+
const slash = rest.indexOf("/");
|
|
892
|
+
if (slash <= 0) {
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
const prefix = rest.slice(0, slash);
|
|
896
|
+
const suffix = rest.slice(slash + 1);
|
|
897
|
+
if (!suffix || suffix.includes("..")) {
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
const localDir = shinyResourcePaths.get(prefix);
|
|
901
|
+
if (!localDir || !rwasmHost) {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
try {
|
|
905
|
+
const body = await rwasmHost.readVfsFile(localDir, suffix);
|
|
906
|
+
if (!body) {
|
|
907
|
+
httpuvDebugLog("sw-vfs-read-miss", { prefix, suffix, vfsDir: localDir });
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
httpuvDebugLog("sw-static-hit", { prefix, suffix, source: "vfs", path: localDir });
|
|
911
|
+
return vfsStaticResponse(body, suffix, "vfs", request.method);
|
|
912
|
+
} catch (err) {
|
|
913
|
+
httpuvDebugLog("sw-vfs-read-fail", { prefix, suffix, vfsDir: localDir, err: String(err) });
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
swSelf.addEventListener("install", (event) => {
|
|
918
|
+
httpuvDebugLog("sw-install", { shinyPrefix: SHINY_PREFIX });
|
|
919
|
+
event.waitUntil(swSelf.skipWaiting());
|
|
920
|
+
});
|
|
921
|
+
swSelf.addEventListener("activate", (event) => {
|
|
922
|
+
httpuvDebugLog("sw-activate", { shinyPrefix: SHINY_PREFIX });
|
|
923
|
+
resetRwasmHostWaiter();
|
|
924
|
+
event.waitUntil(
|
|
925
|
+
(async () => {
|
|
926
|
+
await swSelf.clients.claim();
|
|
927
|
+
await requestComlinkFromHost();
|
|
928
|
+
})()
|
|
929
|
+
);
|
|
930
|
+
});
|
|
931
|
+
function waitForHttpResponse(uuid, url, method) {
|
|
932
|
+
return new Promise((resolve, reject) => {
|
|
933
|
+
const timer = setTimeout(() => {
|
|
934
|
+
pendingHttp.delete(uuid);
|
|
935
|
+
httpuvDebugLog("sw-timeout", { uuid, timeoutMs: REQUEST_TIMEOUT_MS });
|
|
936
|
+
reject(new Error(`httpuv request ${uuid} timed out after ${REQUEST_TIMEOUT_MS}ms`));
|
|
937
|
+
}, REQUEST_TIMEOUT_MS);
|
|
938
|
+
pendingHttp.set(uuid, { resolve, reject, timer, url, method });
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
function maybeCacheAppDocument(resp, url, method) {
|
|
942
|
+
if (url && method === "GET" && isAppDocumentRequest(url) && resp.status === 200) {
|
|
943
|
+
cachedAppDocument = clonePendingResponse(resp);
|
|
944
|
+
if (cachedAppDocument.headers) {
|
|
945
|
+
cachedAppDocument.headers["x-lucent-app-patch"] = LUCENT_APP_PATCH_VERSION;
|
|
946
|
+
}
|
|
947
|
+
httpuvDebugLog("sw-app-cache-store", { url, patch: LUCENT_APP_PATCH_VERSION });
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
function toFetchResponse(resp) {
|
|
951
|
+
const headers = new Headers(resp.headers ?? {});
|
|
952
|
+
if (resp.body == null) {
|
|
953
|
+
return new Response(null, { status: resp.status, headers });
|
|
954
|
+
}
|
|
955
|
+
return new Response(resp.body, { status: resp.status, headers });
|
|
956
|
+
}
|
|
957
|
+
async function headersToObject(request) {
|
|
958
|
+
const headers = {};
|
|
959
|
+
request.headers.forEach((value, key) => {
|
|
960
|
+
headers[key] = value;
|
|
961
|
+
});
|
|
962
|
+
return headers;
|
|
963
|
+
}
|
|
964
|
+
function messageBodyLength(message) {
|
|
965
|
+
if (typeof message === "string") {
|
|
966
|
+
return message.length;
|
|
967
|
+
}
|
|
968
|
+
if (message instanceof ArrayBuffer) {
|
|
969
|
+
return message.byteLength;
|
|
970
|
+
}
|
|
971
|
+
if (ArrayBuffer.isView(message)) {
|
|
972
|
+
return message.byteLength;
|
|
973
|
+
}
|
|
974
|
+
return 0;
|
|
975
|
+
}
|
|
976
|
+
function cloneWsMessage(message, binary) {
|
|
977
|
+
if (message == null) {
|
|
978
|
+
return null;
|
|
979
|
+
}
|
|
980
|
+
if (!binary) {
|
|
981
|
+
if (typeof message === "string") {
|
|
982
|
+
return message;
|
|
983
|
+
}
|
|
984
|
+
if (message instanceof ArrayBuffer) {
|
|
985
|
+
return new TextDecoder().decode(message);
|
|
986
|
+
}
|
|
987
|
+
if (ArrayBuffer.isView(message)) {
|
|
988
|
+
return new TextDecoder().decode(message);
|
|
989
|
+
}
|
|
990
|
+
return String(message);
|
|
991
|
+
}
|
|
992
|
+
if (message instanceof ArrayBuffer) {
|
|
993
|
+
return message.slice(0);
|
|
994
|
+
}
|
|
995
|
+
if (ArrayBuffer.isView(message)) {
|
|
996
|
+
return message.buffer.slice(message.byteOffset, message.byteOffset + message.byteLength);
|
|
997
|
+
}
|
|
998
|
+
if (Array.isArray(message)) {
|
|
999
|
+
return new Uint8Array(message).buffer;
|
|
1000
|
+
}
|
|
1001
|
+
return message;
|
|
1002
|
+
}
|
|
1003
|
+
function postWsPushToPort(port, handle, msg, pushId) {
|
|
1004
|
+
const binary = Boolean(msg.binary);
|
|
1005
|
+
const payload = {
|
|
1006
|
+
type: MSG.WS_PUSH,
|
|
1007
|
+
handle,
|
|
1008
|
+
wsType: msg.wsType ?? WS_FRAME.SEND,
|
|
1009
|
+
binary,
|
|
1010
|
+
message: cloneWsMessage(msg.message ?? null, binary),
|
|
1011
|
+
pushId
|
|
1012
|
+
};
|
|
1013
|
+
try {
|
|
1014
|
+
port.postMessage(payload);
|
|
1015
|
+
return true;
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
console.warn("[httpuv-sw] session port postMessage failed", err);
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function rejectPendingSessionAcks(reason) {
|
|
1022
|
+
for (const [id, pending] of pendingSessionAcks) {
|
|
1023
|
+
pendingSessionAcks.delete(id);
|
|
1024
|
+
pending.reject(new Error(reason));
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
function waitForSessionPushAck(pushId) {
|
|
1028
|
+
return new Promise((resolve, reject) => {
|
|
1029
|
+
const timer = setTimeout(() => {
|
|
1030
|
+
pendingSessionAcks.delete(pushId);
|
|
1031
|
+
reject(new Error(`session WS_PUSH_ACK timed out (id=${pushId})`));
|
|
1032
|
+
}, SESSION_PUSH_ACK_TIMEOUT_MS);
|
|
1033
|
+
pendingSessionAcks.set(pushId, {
|
|
1034
|
+
resolve: () => {
|
|
1035
|
+
clearTimeout(timer);
|
|
1036
|
+
resolve();
|
|
1037
|
+
},
|
|
1038
|
+
reject: (err) => {
|
|
1039
|
+
clearTimeout(timer);
|
|
1040
|
+
reject(err);
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
function handleSessionPortInbound(key, event) {
|
|
1046
|
+
const data = event.data;
|
|
1047
|
+
if (!data || typeof data !== "object") {
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
if (data.type === MSG.WS_PUSH_ACK && typeof data.pushId === "number") {
|
|
1051
|
+
const pending = pendingSessionAcks.get(data.pushId);
|
|
1052
|
+
if (pending) {
|
|
1053
|
+
pendingSessionAcks.delete(data.pushId);
|
|
1054
|
+
pending.resolve();
|
|
1055
|
+
}
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
httpuvDebugLog("sw-session-port-unknown", { handle: key, type: data.type });
|
|
1059
|
+
}
|
|
1060
|
+
function clearSessionPort(handle) {
|
|
1061
|
+
const key = normalizeSessionHandle(handle);
|
|
1062
|
+
const port = sessionPorts.get(key);
|
|
1063
|
+
if (!port) {
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
sessionPorts.delete(key);
|
|
1067
|
+
rejectPendingSessionAcks(`session port cleared (${key})`);
|
|
1068
|
+
try {
|
|
1069
|
+
port.onmessage = null;
|
|
1070
|
+
port.close();
|
|
1071
|
+
} catch {
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
function requestSessionPortReregister(handle) {
|
|
1075
|
+
const key = normalizeSessionHandle(handle);
|
|
1076
|
+
if (!key || sessionPortReregisterPending.has(key)) {
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
sessionPortReregisterPending.add(key);
|
|
1080
|
+
const storedClientId = sessionClientIds.get(key);
|
|
1081
|
+
void (async () => {
|
|
1082
|
+
try {
|
|
1083
|
+
let client;
|
|
1084
|
+
if (storedClientId) {
|
|
1085
|
+
client = await swSelf.clients.get(storedClientId);
|
|
1086
|
+
}
|
|
1087
|
+
if (!client) {
|
|
1088
|
+
const clients = await swSelf.clients.matchAll({
|
|
1089
|
+
type: "window",
|
|
1090
|
+
includeUncontrolled: true
|
|
1091
|
+
});
|
|
1092
|
+
client = clients.find((c) => isShinyAppClientUrl(c.url)) ?? clients.find((c) => !isShinyAppClientUrl(c.url)) ?? clients[0];
|
|
1093
|
+
}
|
|
1094
|
+
if (!client) {
|
|
1095
|
+
httpuvDebugLog("sw-session-reregister-no-client", { handle: key });
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
httpuvDebugLog("sw-session-reregister", { handle: key, clientId: client.id });
|
|
1099
|
+
client.postMessage({ type: MSG.REQUEST_SESSION_PORT, handle: key });
|
|
1100
|
+
} catch (err) {
|
|
1101
|
+
console.warn("[httpuv-sw] REQUEST_SESSION_PORT failed", err);
|
|
1102
|
+
} finally {
|
|
1103
|
+
setTimeout(() => {
|
|
1104
|
+
sessionPortReregisterPending.delete(key);
|
|
1105
|
+
}, 2e3);
|
|
1106
|
+
}
|
|
1107
|
+
})();
|
|
1108
|
+
}
|
|
1109
|
+
function queueWsPush(key, msg) {
|
|
1110
|
+
const existing = queuedWsPush.get(key);
|
|
1111
|
+
if (existing) {
|
|
1112
|
+
existing.push(msg);
|
|
1113
|
+
} else {
|
|
1114
|
+
queuedWsPush.set(key, [msg]);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
function registerSessionPort(handle, port, clientId) {
|
|
1118
|
+
const key = normalizeSessionHandle(handle);
|
|
1119
|
+
clearSessionPort(key);
|
|
1120
|
+
if (clientId) {
|
|
1121
|
+
sessionClientIds.set(key, clientId);
|
|
1122
|
+
}
|
|
1123
|
+
sessionPortReregisterPending.delete(key);
|
|
1124
|
+
port.start();
|
|
1125
|
+
port.onmessage = (event) => {
|
|
1126
|
+
handleSessionPortInbound(key, event);
|
|
1127
|
+
};
|
|
1128
|
+
sessionPorts.set(key, port);
|
|
1129
|
+
httpuvDebugLog("sw-session-registered", { handle: key, clientId: sessionClientIds.get(key) });
|
|
1130
|
+
const queued = queuedWsPush.get(key);
|
|
1131
|
+
if (queued && queued.length > 0) {
|
|
1132
|
+
queuedWsPush.delete(key);
|
|
1133
|
+
for (const next of queued) {
|
|
1134
|
+
deliverWsPush(key, next);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
try {
|
|
1138
|
+
port.postMessage({ type: MSG.SESSION_ACK, handle: key });
|
|
1139
|
+
} catch (err) {
|
|
1140
|
+
console.warn("[httpuv-sw] session ACK failed", err);
|
|
1141
|
+
clearSessionPort(key);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
async function deliverWsPushOne(key, msg) {
|
|
1145
|
+
const port = sessionPorts.get(key);
|
|
1146
|
+
if (!port) {
|
|
1147
|
+
queueWsPush(key, msg);
|
|
1148
|
+
requestSessionPortReregister(key);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
const pushId = nextSessionPushId++;
|
|
1152
|
+
const ackPromise = waitForSessionPushAck(pushId);
|
|
1153
|
+
if (!postWsPushToPort(port, key, msg, pushId)) {
|
|
1154
|
+
pendingSessionAcks.delete(pushId);
|
|
1155
|
+
clearSessionPort(key);
|
|
1156
|
+
queueWsPush(key, msg);
|
|
1157
|
+
requestSessionPortReregister(key);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
try {
|
|
1161
|
+
await ackPromise;
|
|
1162
|
+
} catch (err) {
|
|
1163
|
+
console.warn("[httpuv-sw] session WS_PUSH_ACK failed; re-queueing frame", err);
|
|
1164
|
+
queueWsPush(key, msg);
|
|
1165
|
+
requestSessionPortReregister(key);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function deliverWsPush(handle, msg) {
|
|
1169
|
+
const key = normalizeSessionHandle(handle);
|
|
1170
|
+
httpuvDebugLog("sw-ws-push", {
|
|
1171
|
+
handle: key,
|
|
1172
|
+
wsType: msg.wsType,
|
|
1173
|
+
messageLen: messageBodyLength(msg.message),
|
|
1174
|
+
hasPort: Boolean(sessionPorts.get(key)),
|
|
1175
|
+
queuedBefore: queuedWsPush.get(key)?.length ?? 0
|
|
1176
|
+
});
|
|
1177
|
+
const prev = sessionDeliverChain.get(key) ?? Promise.resolve();
|
|
1178
|
+
const next = prev.then(() => deliverWsPushOne(key, msg)).catch((err) => {
|
|
1179
|
+
console.warn("[httpuv-sw] session deliver chain error", err);
|
|
1180
|
+
});
|
|
1181
|
+
sessionDeliverChain.set(key, next);
|
|
1182
|
+
}
|
|
1183
|
+
function handleSessionRecv() {
|
|
1184
|
+
return new Response("session recv long-poll is retired; use REGISTER_SESSION MessagePort", {
|
|
1185
|
+
status: 410,
|
|
1186
|
+
headers: { "Content-Type": "text/plain" }
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
function isShinyAppClientUrl(url) {
|
|
1190
|
+
try {
|
|
1191
|
+
const pathname = new URL(url).pathname;
|
|
1192
|
+
return pathname.startsWith(shinyAppPrefix) || pathname.startsWith(SHINY_PREFIX);
|
|
1193
|
+
} catch {
|
|
1194
|
+
return false;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
function handleHostOutboundMessage(msg) {
|
|
1198
|
+
switch (msg.type) {
|
|
1199
|
+
case MSG.HTTP_RESPONSE: {
|
|
1200
|
+
httpuvDebugLog("sw-response", { uuid: msg.uuid, status: msg.status });
|
|
1201
|
+
const pending = msg.uuid ? pendingHttp.get(msg.uuid) : void 0;
|
|
1202
|
+
if (!pending || !msg.uuid) {
|
|
1203
|
+
httpuvDebugLog("sw-response-orphan", { uuid: msg.uuid });
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
clearTimeout(pending.timer);
|
|
1207
|
+
pendingHttp.delete(msg.uuid);
|
|
1208
|
+
const resp = maybePatchAppDocumentResponse(
|
|
1209
|
+
{
|
|
1210
|
+
status: msg.status ?? 500,
|
|
1211
|
+
headers: msg.headers ?? {},
|
|
1212
|
+
body: msg.body ?? null
|
|
1213
|
+
},
|
|
1214
|
+
pending.url,
|
|
1215
|
+
pending.method,
|
|
1216
|
+
isAppDocumentRequest
|
|
1217
|
+
);
|
|
1218
|
+
maybeCacheAppDocument(resp, pending.url, pending.method);
|
|
1219
|
+
pending.resolve(resp);
|
|
1220
|
+
break;
|
|
1221
|
+
}
|
|
1222
|
+
case MSG.WS_PUSH: {
|
|
1223
|
+
if (!msg.handle) {
|
|
1224
|
+
httpuvDebugLog("sw-ws-push-missing-handle");
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
httpuvDebugLog("sw-ws-push-inbound", {
|
|
1228
|
+
handle: normalizeSessionHandle(msg.handle),
|
|
1229
|
+
wsType: msg.wsType,
|
|
1230
|
+
messageLen: messageBodyLength(msg.message)
|
|
1231
|
+
});
|
|
1232
|
+
deliverWsPush(normalizeSessionHandle(msg.handle), msg);
|
|
1233
|
+
break;
|
|
1234
|
+
}
|
|
1235
|
+
default:
|
|
1236
|
+
httpuvDebugLog("sw-unknown-push", { type: msg.type });
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
async function handleHostPush(event) {
|
|
1240
|
+
try {
|
|
1241
|
+
const msg = await event.request.json();
|
|
1242
|
+
handleHostOutboundMessage(msg);
|
|
1243
|
+
return new Response(null, { status: 204 });
|
|
1244
|
+
} catch (err) {
|
|
1245
|
+
console.error("[httpuv-sw] host push failed", err);
|
|
1246
|
+
return new Response("bad host push payload", { status: 400 });
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
async function handleShinyFetch(event) {
|
|
1250
|
+
const request = event.request;
|
|
1251
|
+
const uuid = crypto.randomUUID();
|
|
1252
|
+
httpuvDebugLog("sw-request", { uuid, method: request.method, url: request.url });
|
|
1253
|
+
const bypassAppCache = request.headers.get(WARMUP_REQUEST_HEADER) === "1";
|
|
1254
|
+
if (request.method === "GET" && isAppDocumentRequest(request.url) && cachedAppDocument && !bypassAppCache) {
|
|
1255
|
+
const cachedPatch = cachedAppDocument.headers?.["x-lucent-app-patch"];
|
|
1256
|
+
if (cachedPatch !== LUCENT_APP_PATCH_VERSION) {
|
|
1257
|
+
httpuvDebugLog("sw-app-cache-stale", {
|
|
1258
|
+
uuid,
|
|
1259
|
+
cachedPatch: cachedPatch ?? null,
|
|
1260
|
+
want: LUCENT_APP_PATCH_VERSION
|
|
1261
|
+
});
|
|
1262
|
+
clearCachedAppDocument();
|
|
1263
|
+
} else {
|
|
1264
|
+
httpuvDebugLog("sw-app-cache-hit", { uuid, url: request.url });
|
|
1265
|
+
const refreshed = maybePatchAppDocumentResponse(
|
|
1266
|
+
clonePendingResponse(cachedAppDocument),
|
|
1267
|
+
request.url,
|
|
1268
|
+
request.method,
|
|
1269
|
+
isAppDocumentRequest
|
|
1270
|
+
);
|
|
1271
|
+
return toFetchResponse(refreshed);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
const staticRes = await tryServeShinyStaticAsset(request);
|
|
1275
|
+
if (staticRes) {
|
|
1276
|
+
return staticRes;
|
|
1277
|
+
}
|
|
1278
|
+
if (!rwasmHost) {
|
|
1279
|
+
void requestComlinkFromHost();
|
|
1280
|
+
try {
|
|
1281
|
+
await waitForRwasmHost();
|
|
1282
|
+
} catch (err) {
|
|
1283
|
+
console.error("[httpuv-sw] R worker not ready for", request.url, err);
|
|
1284
|
+
return new Response("Shiny R worker is not ready", {
|
|
1285
|
+
status: 503,
|
|
1286
|
+
headers: { "Content-Type": "text/plain" }
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
const host = rwasmHost;
|
|
1291
|
+
if (!host) {
|
|
1292
|
+
return new Response("Shiny R worker is not ready", {
|
|
1293
|
+
status: 503,
|
|
1294
|
+
headers: { "Content-Type": "text/plain" }
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
const body = request.method === "GET" || request.method === "HEAD" ? null : await request.arrayBuffer();
|
|
1298
|
+
const responsePromise = waitForHttpResponse(uuid, request.url, request.method);
|
|
1299
|
+
const payload = {
|
|
1300
|
+
uuid,
|
|
1301
|
+
method: request.method,
|
|
1302
|
+
url: request.url,
|
|
1303
|
+
headers: await headersToObject(request),
|
|
1304
|
+
body,
|
|
1305
|
+
clientId: event.clientId
|
|
1306
|
+
};
|
|
1307
|
+
const delivery = host.deliverHttpRequest(body ? comlink_exports.transfer(payload, [body]) : payload).catch((err) => {
|
|
1308
|
+
console.error("[httpuv-sw] R worker request failed", err);
|
|
1309
|
+
throw err;
|
|
1310
|
+
});
|
|
1311
|
+
try {
|
|
1312
|
+
const resp = await responsePromise;
|
|
1313
|
+
await delivery;
|
|
1314
|
+
return toFetchResponse(resp);
|
|
1315
|
+
} catch (err) {
|
|
1316
|
+
const pending = pendingHttp.get(uuid);
|
|
1317
|
+
if (pending) {
|
|
1318
|
+
clearTimeout(pending.timer);
|
|
1319
|
+
pendingHttp.delete(uuid);
|
|
1320
|
+
}
|
|
1321
|
+
console.error("[httpuv-sw]", err);
|
|
1322
|
+
const failed = err instanceof Error && err.message.includes("request failed");
|
|
1323
|
+
return new Response(failed ? "Bad Gateway" : "Gateway Timeout", {
|
|
1324
|
+
status: failed ? 502 : 504,
|
|
1325
|
+
headers: { "Content-Type": "text/plain" }
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
swSelf.addEventListener("fetch", (event) => {
|
|
1330
|
+
const url = new URL(event.request.url);
|
|
1331
|
+
if (event.request.method === "GET" || event.request.method === "HEAD") {
|
|
1332
|
+
if (vfsPathFromLibRRequest(url.pathname)) {
|
|
1333
|
+
event.respondWith(
|
|
1334
|
+
(async () => {
|
|
1335
|
+
if (!rwasmHost) {
|
|
1336
|
+
void requestComlinkFromHost();
|
|
1337
|
+
}
|
|
1338
|
+
const served = await tryServeLibRAsset(event.request);
|
|
1339
|
+
if (!served) {
|
|
1340
|
+
return new Response("Not Found", { status: 404 });
|
|
1341
|
+
}
|
|
1342
|
+
return served;
|
|
1343
|
+
})()
|
|
1344
|
+
);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
if (!pathUnderShinyPrefix(url.pathname)) {
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
if (isHostPushUrl(event.request.url, shinyAppPrefix) || isHostPushUrl(event.request.url, SHINY_PREFIX)) {
|
|
1352
|
+
event.respondWith(handleHostPush(event));
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
const session = parseSessionAction(event.request.url, shinyAppPrefix) ?? parseSessionAction(event.request.url, SHINY_PREFIX);
|
|
1356
|
+
if (session?.action === "recv") {
|
|
1357
|
+
event.respondWith(handleSessionRecv());
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
event.respondWith(handleShinyFetch(event));
|
|
1361
|
+
});
|
|
1362
|
+
function replyToMessageSource(event, msg) {
|
|
1363
|
+
const source = event.source;
|
|
1364
|
+
if (!source) {
|
|
1365
|
+
httpuvDebugLog("sw-reply-no-source", { type: msg.type });
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
try {
|
|
1369
|
+
source.postMessage(msg);
|
|
1370
|
+
} catch (err) {
|
|
1371
|
+
httpuvDebugLog("sw-reply-failed", { type: msg.type, err: String(err) });
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
swSelf.addEventListener("message", (event) => {
|
|
1375
|
+
const msg = event.data;
|
|
1376
|
+
if (msg === "skipWaiting" || msg?.type === "SKIP_WAITING") {
|
|
1377
|
+
void swSelf.skipWaiting();
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (!msg || typeof msg !== "object") {
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
if (msg.type === COMLINK.PORT_HANDOFF && event.ports[0]) {
|
|
1384
|
+
const port = event.ports[0];
|
|
1385
|
+
port.start();
|
|
1386
|
+
httpuvDebugLog("sw-port-handoff");
|
|
1387
|
+
replyToMessageSource(event, { type: COMLINK.PORT_HANDOFF_ACK });
|
|
1388
|
+
rollRwasmHostWaiter();
|
|
1389
|
+
void connectSwToWorker(port);
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
if (msg.type === MSG.REGISTER_SESSION && event.ports[0]) {
|
|
1393
|
+
const handle = normalizeSessionHandle(msg.handle);
|
|
1394
|
+
if (!handle) {
|
|
1395
|
+
httpuvDebugLog("sw-session-register-missing-handle");
|
|
1396
|
+
try {
|
|
1397
|
+
event.ports[0].close();
|
|
1398
|
+
} catch {
|
|
1399
|
+
}
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
const source = event.source;
|
|
1403
|
+
const clientId = source && "id" in source ? String(source.id) : void 0;
|
|
1404
|
+
registerSessionPort(handle, event.ports[0], clientId);
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
if (typeof msg.type !== "string") {
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
switch (msg.type) {
|
|
1411
|
+
case MSG.REGISTER_HOST: {
|
|
1412
|
+
if (typeof msg.shinyPrefix === "string" && msg.shinyPrefix) {
|
|
1413
|
+
shinyAppPrefix = msg.shinyPrefix.endsWith("/") ? msg.shinyPrefix : `${msg.shinyPrefix}/`;
|
|
1414
|
+
}
|
|
1415
|
+
const source = event.source;
|
|
1416
|
+
if (source && "id" in source) {
|
|
1417
|
+
hostClientId = source.id;
|
|
1418
|
+
httpuvDebugLog("sw-host-registered", { hostClientId });
|
|
1419
|
+
}
|
|
1420
|
+
break;
|
|
1421
|
+
}
|
|
1422
|
+
case MSG.HTTP_RESPONSE: {
|
|
1423
|
+
handleHostOutboundMessage(msg);
|
|
1424
|
+
break;
|
|
1425
|
+
}
|
|
1426
|
+
case MSG.WS_PUSH: {
|
|
1427
|
+
handleHostOutboundMessage(msg);
|
|
1428
|
+
break;
|
|
1429
|
+
}
|
|
1430
|
+
case MSG.CLEAR_APP_CACHE: {
|
|
1431
|
+
clearCachedAppDocument();
|
|
1432
|
+
clearShinyResourcePaths();
|
|
1433
|
+
break;
|
|
1434
|
+
}
|
|
1435
|
+
case MSG.REGISTER_RESOURCE_PATHS: {
|
|
1436
|
+
setShinyResourcePaths(msg.paths);
|
|
1437
|
+
break;
|
|
1438
|
+
}
|
|
1439
|
+
case MSG.UPDATE_RESOURCE_PATH: {
|
|
1440
|
+
updateShinyResourcePath(String(msg.prefix ?? ""), msg.dir == null ? "" : String(msg.dir));
|
|
1441
|
+
break;
|
|
1442
|
+
}
|
|
1443
|
+
case MSG.STOP: {
|
|
1444
|
+
clearCachedAppDocument();
|
|
1445
|
+
clearShinyResourcePaths();
|
|
1446
|
+
for (const [uuid, pending] of pendingHttp) {
|
|
1447
|
+
clearTimeout(pending.timer);
|
|
1448
|
+
pending.reject(new Error("httpuv stopped"));
|
|
1449
|
+
pendingHttp.delete(uuid);
|
|
1450
|
+
}
|
|
1451
|
+
for (const handle of [...sessionPorts.keys()]) {
|
|
1452
|
+
clearSessionPort(handle);
|
|
1453
|
+
}
|
|
1454
|
+
sessionClientIds.clear();
|
|
1455
|
+
sessionPortReregisterPending.clear();
|
|
1456
|
+
queuedWsPush.clear();
|
|
1457
|
+
hostClientId = null;
|
|
1458
|
+
if (rwasmHost) {
|
|
1459
|
+
void rwasmHost.stop().catch((err) => {
|
|
1460
|
+
console.warn("[httpuv-sw] R worker stop failed", err);
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
resetRwasmHostWaiter(new Error("httpuv stopped"));
|
|
1464
|
+
break;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
/*! Bundled license information:
|
|
1469
|
+
|
|
1470
|
+
comlink/dist/esm/comlink.mjs:
|
|
1471
|
+
(**
|
|
1472
|
+
* @license
|
|
1473
|
+
* Copyright 2019 Google LLC
|
|
1474
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
1475
|
+
*)
|
|
1476
|
+
*/
|
|
1477
|
+
//# sourceMappingURL=httpuv-sw.js.map
|
|
1478
|
+
//# sourceMappingURL=httpuv-sw.js.map
|