@volter/tabnode 0.5.25 → 0.5.27

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.
@@ -0,0 +1,655 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ const CHUNK = 8192;
5
+ function uint8ToBase64(bytes) {
6
+ const parts = [];
7
+ for (let i = 0; i < bytes.length; i += CHUNK) {
8
+ parts.push(String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + CHUNK))));
9
+ }
10
+ return btoa(parts.join(""));
11
+ }
12
+ function base64ToUint8(base64) {
13
+ const binary = atob(base64);
14
+ const bytes = new Uint8Array(binary.length);
15
+ for (let i = 0; i < binary.length; i++) {
16
+ bytes[i] = binary.charCodeAt(i);
17
+ }
18
+ return bytes;
19
+ }
20
+ function uint8ToHex(bytes) {
21
+ const hex = new Array(bytes.length);
22
+ for (let i = 0; i < bytes.length; i++) {
23
+ hex[i] = bytes[i].toString(16).padStart(2, "0");
24
+ }
25
+ return hex.join("");
26
+ }
27
+ function uint8ToBinaryString(bytes) {
28
+ const parts = [];
29
+ for (let i = 0; i < bytes.length; i += CHUNK) {
30
+ parts.push(String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + CHUNK))));
31
+ }
32
+ return parts.join("");
33
+ }
34
+ const _encoder = new TextEncoder();
35
+ const FLOW_MAX_REQUEST_BYTES = 64 * 1024 * 1024;
36
+ const FLOW_MAX_CHUNK_BYTES = 65536;
37
+ const FLOW_QUEUED_CHUNKS = 4;
38
+ function ownedBytes(view) {
39
+ const copy = new Uint8Array(view.byteLength);
40
+ copy.set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
41
+ return copy;
42
+ }
43
+ class BridgeEvents {
44
+ constructor() {
45
+ __publicField(this, "listenersByEvent", /* @__PURE__ */ new Map());
46
+ }
47
+ on(event, listener) {
48
+ const list = this.listenersByEvent.get(event) ?? [];
49
+ list.push(listener);
50
+ this.listenersByEvent.set(event, list);
51
+ return this;
52
+ }
53
+ addListener(event, listener) {
54
+ return this.on(event, listener);
55
+ }
56
+ once(event, listener) {
57
+ const wrapped = (...args) => {
58
+ this.off(event, wrapped);
59
+ listener(...args);
60
+ };
61
+ wrapped.listener = listener;
62
+ return this.on(event, wrapped);
63
+ }
64
+ off(event, listener) {
65
+ const list = this.listenersByEvent.get(event);
66
+ if (!list) return this;
67
+ const at = list.findIndex((candidate) => candidate === listener || candidate.listener === listener);
68
+ if (at >= 0) list.splice(at, 1);
69
+ if (list.length === 0) this.listenersByEvent.delete(event);
70
+ return this;
71
+ }
72
+ removeListener(event, listener) {
73
+ return this.off(event, listener);
74
+ }
75
+ removeAllListeners(event) {
76
+ if (event === void 0) this.listenersByEvent.clear();
77
+ else this.listenersByEvent.delete(event);
78
+ return this;
79
+ }
80
+ listenerCount(event) {
81
+ var _a;
82
+ return ((_a = this.listenersByEvent.get(event)) == null ? void 0 : _a.length) ?? 0;
83
+ }
84
+ emit(event, ...args) {
85
+ const list = this.listenersByEvent.get(event);
86
+ if (!list || list.length === 0) return false;
87
+ for (const listener of [...list]) listener(...args);
88
+ return true;
89
+ }
90
+ }
91
+ const _PortBridge = class _PortBridge extends BridgeEvents {
92
+ constructor(options = {}) {
93
+ super();
94
+ __publicField(this, "servers", /* @__PURE__ */ new Map());
95
+ __publicField(this, "baseUrl");
96
+ __publicField(this, "options");
97
+ __publicField(this, "messageChannel", null);
98
+ __publicField(this, "serviceWorkerReady", false);
99
+ __publicField(this, "keepaliveInterval", null);
100
+ /** The server the service worker answers at the origin's root, the preview's; null for none. */
101
+ __publicField(this, "primaryPort", null);
102
+ __publicField(this, "ownDocuments");
103
+ /** Flow-controlled requests in flight: a pull is one chunk's credit, a cancel ends the upstream. */
104
+ __publicField(this, "controlledStreams", /* @__PURE__ */ new Map());
105
+ this.options = options;
106
+ if (typeof location !== "undefined") {
107
+ this.baseUrl = options.baseUrl || `${location.protocol}//${location.host}`;
108
+ } else {
109
+ this.baseUrl = options.baseUrl || "http://localhost";
110
+ }
111
+ }
112
+ /**
113
+ * Give back everything this bridge opened: the service worker keepalive,
114
+ * and in an engine's bridge its upgrade channel. A host that is done with a
115
+ * container calls it and has its process back; calling it twice is nothing.
116
+ */
117
+ close() {
118
+ if (this.keepaliveInterval !== null) {
119
+ clearInterval(this.keepaliveInterval);
120
+ this.keepaliveInterval = null;
121
+ }
122
+ }
123
+ /**
124
+ * Register a server on a port
125
+ */
126
+ registerServer(server, port, hostname = "0.0.0.0") {
127
+ this.servers.set(port, { server, port, hostname });
128
+ const url = this.getServerUrl(port);
129
+ this.emit("server-ready", port, url);
130
+ if (this.options.onServerReady) {
131
+ this.options.onServerReady(port, url);
132
+ }
133
+ this.notifyServiceWorker("server-registered", { port, hostname, primary: port === this.primaryPort });
134
+ }
135
+ /**
136
+ * Unregister a server
137
+ */
138
+ unregisterServer(port) {
139
+ this.servers.delete(port);
140
+ if (this.primaryPort === port) this.primaryPort = null;
141
+ this.notifyServiceWorker("server-unregistered", { port });
142
+ }
143
+ /**
144
+ * Names the server the service worker serves at the origin's root, as an
145
+ * app is served at its own origin's root: its documents read their routes
146
+ * from `location.pathname`, which under /__virtual__/<port>/ is not the
147
+ * path they expect. Told to the worker now and again whenever the worker
148
+ * is (re)initialized, since a worker that restarted remembers nothing.
149
+ */
150
+ setPrimaryPort(port) {
151
+ const previous = this.primaryPort;
152
+ this.primaryPort = port;
153
+ if (previous !== null && previous !== port) {
154
+ const entry = this.servers.get(previous);
155
+ if (entry) this.notifyServiceWorker("server-registered", { port: previous, hostname: entry.hostname, primary: false });
156
+ }
157
+ if (port !== null) {
158
+ const entry = this.servers.get(port);
159
+ this.notifyServiceWorker("server-registered", { port, hostname: (entry == null ? void 0 : entry.hostname) ?? "0.0.0.0", primary: true });
160
+ }
161
+ }
162
+ /** Every registration the worker should hold, sent again to a worker that was (re)initialized. */
163
+ announceServers() {
164
+ if (this.ownDocuments) this.notifyServiceWorker("own-documents", { paths: this.ownDocuments });
165
+ for (const [port, entry] of this.servers) {
166
+ this.notifyServiceWorker("server-registered", { port, hostname: entry.hostname, primary: port === this.primaryPort });
167
+ }
168
+ }
169
+ /**
170
+ * Get server URL for a port
171
+ */
172
+ getServerUrl(port) {
173
+ return `${this.baseUrl}/__virtual__/${port}`;
174
+ }
175
+ /**
176
+ * Get all registered server ports
177
+ */
178
+ getServerPorts() {
179
+ return [...this.servers.keys()];
180
+ }
181
+ /**
182
+ * Handle an incoming request from Service Worker
183
+ */
184
+ async handleRequest(port, method, url, headers, body) {
185
+ const virtualServer = this.servers.get(port);
186
+ if (!virtualServer) {
187
+ return {
188
+ statusCode: 503,
189
+ statusMessage: "Service Unavailable",
190
+ headers: { "Content-Type": "text/plain" },
191
+ body: _encoder.encode(`No server listening on port ${port}`)
192
+ };
193
+ }
194
+ try {
195
+ const bodyBuffer = body ? this.bodyOf(new Uint8Array(body)) : void 0;
196
+ const named = Object.keys(headers).some((name) => name.toLowerCase() === "host");
197
+ const authority = `${virtualServer.hostname && virtualServer.hostname !== "0.0.0.0" && virtualServer.hostname !== "::" ? virtualServer.hostname : "127.0.0.1"}:${port}`;
198
+ const withHost = named ? headers : { ...headers, host: authority };
199
+ if (virtualServer.server) return await virtualServer.server.handleRequest(method, url, withHost, bodyBuffer);
200
+ return await this.requestGuest(port, method, url, withHost, bodyBuffer);
201
+ } catch (error) {
202
+ const message = error instanceof Error ? error.message : "Internal Server Error";
203
+ return {
204
+ statusCode: 500,
205
+ statusMessage: "Internal Server Error",
206
+ headers: { "Content-Type": "text/plain" },
207
+ body: _encoder.encode(message)
208
+ };
209
+ }
210
+ }
211
+ /**
212
+ * Initialize Service Worker communication
213
+ * @param options - Configuration options for the service worker
214
+ * @param options.swUrl - Custom URL path to the service worker file (default: '/__sw__.js')
215
+ */
216
+ async initServiceWorker(options) {
217
+ if (!("serviceWorker" in navigator)) {
218
+ throw new Error("Service Workers not supported");
219
+ }
220
+ const swUrl = (options == null ? void 0 : options.swUrl) ?? "/__sw__.js";
221
+ const controllerReady = navigator.serviceWorker.controller ? Promise.resolve() : new Promise((resolve) => {
222
+ navigator.serviceWorker.addEventListener("controllerchange", () => resolve(), { once: true });
223
+ });
224
+ const registration = await navigator.serviceWorker.register(swUrl, {
225
+ scope: "/"
226
+ });
227
+ const sw = registration.active || registration.waiting || registration.installing;
228
+ if (!sw) {
229
+ throw new Error("Service Worker registration failed");
230
+ }
231
+ await new Promise((resolve) => {
232
+ if (sw.state === "activated") {
233
+ resolve();
234
+ } else {
235
+ const handler = () => {
236
+ if (sw.state === "activated") {
237
+ sw.removeEventListener("statechange", handler);
238
+ resolve();
239
+ }
240
+ };
241
+ sw.addEventListener("statechange", handler);
242
+ }
243
+ });
244
+ this.messageChannel = new MessageChannel();
245
+ this.messageChannel.port1.onmessage = this.handleServiceWorkerMessage.bind(this);
246
+ this.ownDocuments = options == null ? void 0 : options.ownDocuments;
247
+ sw.postMessage({ type: "init", port: this.messageChannel.port2, data: { ownDocuments: this.ownDocuments ?? [] } }, [
248
+ this.messageChannel.port2
249
+ ]);
250
+ await controllerReady;
251
+ const reinit = () => {
252
+ if (navigator.serviceWorker.controller) {
253
+ for (const stream of [...this.controlledStreams.values()]) stream.cancel();
254
+ this.messageChannel = new MessageChannel();
255
+ this.messageChannel.port1.onmessage = this.handleServiceWorkerMessage.bind(this);
256
+ navigator.serviceWorker.controller.postMessage(
257
+ { type: "init", port: this.messageChannel.port2, data: { ownDocuments: this.ownDocuments ?? [] } },
258
+ [this.messageChannel.port2]
259
+ );
260
+ this.announceServers();
261
+ }
262
+ };
263
+ navigator.serviceWorker.addEventListener("controllerchange", reinit);
264
+ navigator.serviceWorker.addEventListener("message", (event) => {
265
+ var _a;
266
+ if (((_a = event.data) == null ? void 0 : _a.type) === "sw-needs-init") {
267
+ reinit();
268
+ }
269
+ });
270
+ this.keepaliveInterval = setInterval(() => {
271
+ var _a;
272
+ (_a = this.messageChannel) == null ? void 0 : _a.port1.postMessage({ type: "keepalive" });
273
+ }, 2e4);
274
+ this.serviceWorkerReady = true;
275
+ this.announceServers();
276
+ this.emit("sw-ready");
277
+ }
278
+ /**
279
+ * Handle messages from Service Worker
280
+ */
281
+ async handleServiceWorkerMessage(event) {
282
+ var _a, _b, _c;
283
+ const { type, id, data } = event.data;
284
+ _PortBridge.DEBUG && console.log("[ServerBridge] SW message:", type, id, data == null ? void 0 : data.url);
285
+ if (type === "stream-pull" || type === "stream-cancel") {
286
+ const stream = this.controlledStreams.get(id);
287
+ if (type === "stream-pull") stream == null ? void 0 : stream.pull();
288
+ else stream == null ? void 0 : stream.cancel();
289
+ return;
290
+ }
291
+ if (type === "request" && (data == null ? void 0 : data.flowControl) === 1) {
292
+ const { port, method, url, headers, body } = data;
293
+ await this.controlledStream(id, port, method, url, headers, body);
294
+ return;
295
+ }
296
+ if (type === "request") {
297
+ const { port, method, url, headers, body, streaming } = data;
298
+ _PortBridge.DEBUG && console.log("[ServerBridge] Handling request:", port, method, url, "streaming:", streaming);
299
+ if (streaming) {
300
+ _PortBridge.DEBUG && console.log("[ServerBridge] 🔴 Will use streaming handler");
301
+ }
302
+ try {
303
+ if (streaming) {
304
+ await this.streamToServiceWorker(id, port, method, url, headers, body);
305
+ } else {
306
+ const response = await this.handleRequest(port, method, url, headers, body);
307
+ _PortBridge.DEBUG && console.log("[ServerBridge] Response:", response.statusCode, "body length:", (_a = response.body) == null ? void 0 : _a.length);
308
+ let bodyBase64 = "";
309
+ if (response.body && response.body.length > 0) {
310
+ const bytes = response.body instanceof Uint8Array ? response.body : new Uint8Array(0);
311
+ bodyBase64 = uint8ToBase64(bytes);
312
+ }
313
+ _PortBridge.DEBUG && console.log("[ServerBridge] Sending response to SW, body base64 length:", bodyBase64.length);
314
+ (_b = this.messageChannel) == null ? void 0 : _b.port1.postMessage({
315
+ type: "response",
316
+ id,
317
+ data: {
318
+ statusCode: response.statusCode,
319
+ statusMessage: response.statusMessage,
320
+ headers: response.headers,
321
+ bodyBase64
322
+ }
323
+ });
324
+ }
325
+ } catch (error) {
326
+ (_c = this.messageChannel) == null ? void 0 : _c.port1.postMessage({
327
+ type: "response",
328
+ id,
329
+ error: error instanceof Error ? error.message : "Unknown error"
330
+ });
331
+ }
332
+ }
333
+ }
334
+ /**
335
+ * Handle a streaming request - sends chunks as they arrive
336
+ */
337
+ /**
338
+ * A page-side request streamed to the caller as it arrives, the door a host
339
+ * reads a guest's server through when it wants chunks rather than a body:
340
+ * a guest's own server is reached over the loopback as any client reaches
341
+ * it; a server the host registered answers through its own streaming
342
+ * method where it has one, else its buffered answer is delivered whole.
343
+ */
344
+ async handleStreamingRequest(port, method, url, headers, body, callbacks, flow) {
345
+ const virtualServer = this.servers.get(port);
346
+ if (!virtualServer) return false;
347
+ const bodyBuffer = body ? this.bodyOf(new Uint8Array(body)) : void 0;
348
+ if (this.isGuest(virtualServer)) {
349
+ const named = Object.keys(headers).some((name) => name.toLowerCase() === "host");
350
+ const authority = `${virtualServer.hostname && virtualServer.hostname !== "0.0.0.0" && virtualServer.hostname !== "::" ? virtualServer.hostname : "127.0.0.1"}:${port}`;
351
+ await this.streamGuest(
352
+ port,
353
+ method,
354
+ url,
355
+ named ? headers : { ...headers, host: authority },
356
+ bodyBuffer,
357
+ (statusCode, statusMessage, respHeaders) => callbacks.start(statusCode, statusMessage, respHeaders),
358
+ (chunk) => callbacks.chunk(ownedBytes(chunk)),
359
+ () => callbacks.end(),
360
+ flow
361
+ );
362
+ return true;
363
+ }
364
+ const server = virtualServer.server;
365
+ if (typeof server.handleStreamingRequest === "function") {
366
+ await server.handleStreamingRequest(
367
+ method,
368
+ url,
369
+ headers,
370
+ bodyBuffer,
371
+ (statusCode, statusMessage, respHeaders) => callbacks.start(statusCode, statusMessage, respHeaders),
372
+ (chunk) => callbacks.chunk(typeof chunk === "string" ? _encoder.encode(chunk) : ownedBytes(chunk)),
373
+ () => callbacks.end(),
374
+ flow
375
+ );
376
+ return true;
377
+ }
378
+ const response = await this.handleRequest(port, method, url, headers, body);
379
+ callbacks.start(response.statusCode, response.statusMessage ?? "", response.headers);
380
+ if (response.body) {
381
+ const body2 = typeof response.body === "string" ? _encoder.encode(response.body) : response.body;
382
+ callbacks.chunk(ownedBytes(body2));
383
+ }
384
+ callbacks.end();
385
+ return true;
386
+ }
387
+ async streamToServiceWorker(id, port, method, url, headers, body) {
388
+ var _a, _b, _c, _d, _e;
389
+ const virtualServer = this.servers.get(port);
390
+ if (!virtualServer) {
391
+ (_a = this.messageChannel) == null ? void 0 : _a.port1.postMessage({
392
+ type: "stream-start",
393
+ id,
394
+ data: { statusCode: 503, statusMessage: "Service Unavailable", headers: {} }
395
+ });
396
+ (_b = this.messageChannel) == null ? void 0 : _b.port1.postMessage({ type: "stream-end", id });
397
+ return;
398
+ }
399
+ if (this.isGuest(virtualServer)) {
400
+ const bodyBuffer = body ? this.bodyOf(new Uint8Array(body)) : void 0;
401
+ const named = Object.keys(headers).some((name) => name.toLowerCase() === "host");
402
+ const authority = `${virtualServer.hostname && virtualServer.hostname !== "0.0.0.0" && virtualServer.hostname !== "::" ? virtualServer.hostname : "127.0.0.1"}:${port}`;
403
+ await this.streamGuest(
404
+ port,
405
+ method,
406
+ url,
407
+ named ? headers : { ...headers, host: authority },
408
+ bodyBuffer,
409
+ (statusCode, statusMessage, respHeaders) => {
410
+ var _a2;
411
+ (_a2 = this.messageChannel) == null ? void 0 : _a2.port1.postMessage({ type: "stream-start", id, data: { statusCode, statusMessage, headers: respHeaders } });
412
+ },
413
+ (chunk) => {
414
+ var _a2;
415
+ const copy = ownedBytes(chunk);
416
+ (_a2 = this.messageChannel) == null ? void 0 : _a2.port1.postMessage({ type: "stream-chunk", id, data: copy.buffer }, [copy.buffer]);
417
+ },
418
+ () => {
419
+ var _a2;
420
+ (_a2 = this.messageChannel) == null ? void 0 : _a2.port1.postMessage({ type: "stream-end", id });
421
+ }
422
+ );
423
+ return;
424
+ }
425
+ const server = virtualServer.server;
426
+ if (!server) return;
427
+ if (typeof server.handleStreamingRequest === "function") {
428
+ _PortBridge.DEBUG && console.log("[ServerBridge] 🟢 Server has streaming support, calling handleStreamingRequest");
429
+ const bodyBuffer = body ? this.bodyOf(new Uint8Array(body)) : void 0;
430
+ await server.handleStreamingRequest(
431
+ method,
432
+ url,
433
+ headers,
434
+ bodyBuffer,
435
+ // onStart - called with headers
436
+ (statusCode, statusMessage, respHeaders) => {
437
+ var _a2;
438
+ _PortBridge.DEBUG && console.log("[ServerBridge] 🟢 onStart called, sending stream-start");
439
+ (_a2 = this.messageChannel) == null ? void 0 : _a2.port1.postMessage({
440
+ type: "stream-start",
441
+ id,
442
+ data: { statusCode, statusMessage, headers: respHeaders }
443
+ });
444
+ },
445
+ // onChunk - called for each chunk
446
+ (chunk) => {
447
+ var _a2;
448
+ const bytes = typeof chunk === "string" ? _encoder.encode(chunk) : chunk;
449
+ const chunkBase64 = uint8ToBase64(bytes);
450
+ _PortBridge.DEBUG && console.log("[ServerBridge] 🟡 onChunk called, sending stream-chunk, size:", chunkBase64.length);
451
+ (_a2 = this.messageChannel) == null ? void 0 : _a2.port1.postMessage({
452
+ type: "stream-chunk",
453
+ id,
454
+ data: { chunkBase64 }
455
+ });
456
+ },
457
+ // onEnd - called when response is complete
458
+ () => {
459
+ var _a2;
460
+ _PortBridge.DEBUG && console.log("[ServerBridge] 🟢 onEnd called, sending stream-end");
461
+ (_a2 = this.messageChannel) == null ? void 0 : _a2.port1.postMessage({ type: "stream-end", id });
462
+ }
463
+ );
464
+ } else {
465
+ const bodyBuffer = body ? this.bodyOf(new Uint8Array(body)) : void 0;
466
+ const response = await server.handleRequest(method, url, headers, bodyBuffer);
467
+ (_c = this.messageChannel) == null ? void 0 : _c.port1.postMessage({
468
+ type: "stream-start",
469
+ id,
470
+ data: {
471
+ statusCode: response.statusCode,
472
+ statusMessage: response.statusMessage,
473
+ headers: response.headers
474
+ }
475
+ });
476
+ if (response.body && response.body.length > 0) {
477
+ const bytes = response.body instanceof Uint8Array ? response.body : new Uint8Array(0);
478
+ (_d = this.messageChannel) == null ? void 0 : _d.port1.postMessage({
479
+ type: "stream-chunk",
480
+ id,
481
+ data: { chunkBase64: uint8ToBase64(bytes) }
482
+ });
483
+ }
484
+ (_e = this.messageChannel) == null ? void 0 : _e.port1.postMessage({ type: "stream-end", id });
485
+ }
486
+ }
487
+ /**
488
+ * Send message to Service Worker
489
+ */
490
+ notifyServiceWorker(type, data) {
491
+ if (this.serviceWorkerReady && this.messageChannel) {
492
+ const flow = type === "server-registered" ? { flowControl: 1, maxRequestBytes: FLOW_MAX_REQUEST_BYTES, maxChunkBytes: FLOW_MAX_CHUNK_BYTES } : {};
493
+ this.messageChannel.port1.postMessage({ type, data: { ...data, ...flow } });
494
+ }
495
+ }
496
+ /**
497
+ * A request the worker sent under flow control: the response starts with
498
+ * its head, then goes one chunk (at most FLOW_MAX_CHUNK_BYTES) for each
499
+ * `stream-pull`, and ends with `stream-end` once the server finished and
500
+ * every chunk went out. The upstream connection is paused while chunks wait
501
+ * for credit, so a slow reader holds the server back; `stream-cancel` ends it.
502
+ */
503
+ async controlledStream(id, port, method, url, headers, body) {
504
+ const channel = this.messageChannel;
505
+ const post = (type, data) => {
506
+ channel == null ? void 0 : channel.port1.postMessage({ type, id, ...data === void 0 ? {} : { data } });
507
+ };
508
+ const queue = [];
509
+ const abort = new AbortController();
510
+ let credits = 0;
511
+ let upstreamEnded = false;
512
+ let finished = false;
513
+ let paused = false;
514
+ let pause = () => void 0;
515
+ let resume = () => void 0;
516
+ const finish = () => {
517
+ finished = true;
518
+ this.controlledStreams.delete(id);
519
+ };
520
+ const flush = () => {
521
+ if (finished) return;
522
+ while (credits > 0 && queue.length > 0) {
523
+ credits -= 1;
524
+ post("stream-chunk", { chunkBase64: uint8ToBase64(queue.shift()) });
525
+ }
526
+ if (upstreamEnded && queue.length === 0) {
527
+ post("stream-end");
528
+ finish();
529
+ return;
530
+ }
531
+ if (queue.length >= FLOW_QUEUED_CHUNKS && !paused) {
532
+ paused = true;
533
+ pause();
534
+ } else if (queue.length < FLOW_QUEUED_CHUNKS && paused) {
535
+ paused = false;
536
+ resume();
537
+ }
538
+ };
539
+ this.controlledStreams.set(id, {
540
+ pull: () => {
541
+ credits += 1;
542
+ flush();
543
+ },
544
+ cancel: () => {
545
+ if (finished) return;
546
+ finish();
547
+ queue.length = 0;
548
+ abort.abort();
549
+ }
550
+ });
551
+ try {
552
+ const answered = await this.handleStreamingRequest(port, method, url, headers, body, {
553
+ start: (statusCode, statusMessage, respHeaders) => post("stream-start", { statusCode, statusMessage, headers: respHeaders }),
554
+ chunk: (chunk) => {
555
+ if (finished) return;
556
+ for (let at = 0; at < chunk.byteLength; at += FLOW_MAX_CHUNK_BYTES) queue.push(chunk.slice(at, at + FLOW_MAX_CHUNK_BYTES));
557
+ flush();
558
+ },
559
+ end: () => {
560
+ upstreamEnded = true;
561
+ flush();
562
+ }
563
+ }, { signal: abort.signal, control: (p, r) => {
564
+ pause = p;
565
+ resume = r;
566
+ } });
567
+ if (!answered) {
568
+ post("stream-start", { statusCode: 503, statusMessage: "Service Unavailable", headers: {} });
569
+ post("stream-end");
570
+ finish();
571
+ }
572
+ } catch (error) {
573
+ if (!finished) {
574
+ post("stream-error", { message: error instanceof Error ? error.message : String(error) });
575
+ finish();
576
+ }
577
+ }
578
+ }
579
+ /**
580
+ * Create a mock request handler for testing without Service Worker
581
+ */
582
+ createFetchHandler() {
583
+ return async (request) => {
584
+ const url = new URL(request.url);
585
+ const match = url.pathname.match(/^\/__virtual__\/(\d+)(\/.*)?$/);
586
+ if (!match) {
587
+ throw new Error("Not a virtual server request");
588
+ }
589
+ const port = parseInt(match[1], 10);
590
+ const path = match[2] || "/";
591
+ const headers = {};
592
+ request.headers.forEach((value, key) => {
593
+ headers[key] = value;
594
+ });
595
+ let body;
596
+ if (request.method !== "GET" && request.method !== "HEAD") {
597
+ body = await request.arrayBuffer();
598
+ }
599
+ const response = await this.handleRequest(
600
+ port,
601
+ request.method,
602
+ path + url.search,
603
+ headers,
604
+ body
605
+ );
606
+ const answerHeaders = new Headers();
607
+ for (const [name, value] of Object.entries(response.headers)) {
608
+ for (const each of Array.isArray(value) ? value : [value]) answerHeaders.append(name, each);
609
+ }
610
+ return new Response(response.body, {
611
+ status: response.statusCode,
612
+ statusText: response.statusMessage ?? "",
613
+ headers: answerHeaders
614
+ });
615
+ };
616
+ }
617
+ // ---------------------------------------------------------------- an engine's own servers
618
+ /**
619
+ * Whether a registration is a guest's own listener, answered over the
620
+ * engine's loopback rather than by a server object the host registered. A
621
+ * bridge with no engine in its realm has none: every server it holds was
622
+ * registered by the host, a worker's proxies among them.
623
+ */
624
+ isGuest(entry) {
625
+ return entry.server === null;
626
+ }
627
+ /** A request to a guest's listener, over the engine's loopback. */
628
+ requestGuest(port, _method, _url, _headers, _body) {
629
+ return Promise.reject(new Error(`Port ${port} has no server in this realm.`));
630
+ }
631
+ /** A guest's answer streamed off its connection as it arrives. */
632
+ streamGuest(port, _method, _url, _headers, _body, _onStart, _onChunk, _onEnd, _flow) {
633
+ return Promise.reject(new Error(`Port ${port} has no server in this realm.`));
634
+ }
635
+ /** A request's body as the servers of this realm read it. */
636
+ bodyOf(bytes) {
637
+ return bytes;
638
+ }
639
+ };
640
+ __publicField(_PortBridge, "DEBUG", false);
641
+ let PortBridge = _PortBridge;
642
+ let globalPortBridge = null;
643
+ function getPortBridge(options) {
644
+ if (!globalPortBridge) globalPortBridge = new PortBridge(options);
645
+ return globalPortBridge;
646
+ }
647
+ export {
648
+ PortBridge as P,
649
+ uint8ToBinaryString as a,
650
+ base64ToUint8 as b,
651
+ uint8ToHex as c,
652
+ getPortBridge as g,
653
+ ownedBytes as o,
654
+ uint8ToBase64 as u
655
+ };