@statewalker/webrun-http-browser 0.9.0 → 0.12.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/README.md +155 -10
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/service-worker-control.d.ts +2 -1
- package/dist/core/service-worker-control.d.ts.map +1 -1
- package/dist/core/sw-scope.d.ts +31 -0
- package/dist/core/sw-scope.d.ts.map +1 -0
- package/dist/index.js +908 -221
- package/dist/link/http-link.d.ts +16 -3
- package/dist/link/http-link.d.ts.map +1 -1
- package/dist/relay/index-sw.d.ts +8 -2
- package/dist/relay/index-sw.d.ts.map +1 -1
- package/dist/relay/index.d.ts +1 -1
- package/dist/relay/index.d.ts.map +1 -1
- package/dist/relay-sw.js +470 -81
- package/dist/relay-worker.d.ts +1 -0
- package/dist/relay-worker.d.ts.map +1 -1
- package/dist/relay-worker.js +470 -81
- package/dist/sw/handler-endpoint.d.ts +5 -2
- package/dist/sw/handler-endpoint.d.ts.map +1 -1
- package/dist/sw/http-sw-dispatcher.d.ts +114 -7
- package/dist/sw/http-sw-dispatcher.d.ts.map +1 -1
- package/dist/sw/index.d.ts +1 -0
- package/dist/sw/index.d.ts.map +1 -1
- package/dist/sw/protocol.d.ts +3 -0
- package/dist/sw/protocol.d.ts.map +1 -1
- package/dist/sw/sw-dispatcher.d.ts +5 -2
- package/dist/sw/sw-dispatcher.d.ts.map +1 -1
- package/dist/sw/sw-http-adapter.d.ts +19 -4
- package/dist/sw/sw-http-adapter.d.ts.map +1 -1
- package/dist/sw/worker-service.d.ts +5 -2
- package/dist/sw/worker-service.d.ts.map +1 -1
- package/dist/sw-worker.js +681 -141
- package/dist/sw.js +896 -218
- package/dist/worker.js +689 -115
- package/dist/ws/server.d.ts +50 -2
- package/dist/ws/server.d.ts.map +1 -1
- package/dist/ws/socket-core.d.ts +5 -2
- package/dist/ws/socket-core.d.ts.map +1 -1
- package/dist/ws/upgrade.d.ts +12 -1
- package/dist/ws/upgrade.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/core/index.ts +1 -0
- package/src/core/service-worker-control.ts +5 -3
- package/src/core/sw-scope.ts +27 -0
- package/src/link/http-link.ts +20 -4
- package/src/relay/index-sw.ts +15 -2
- package/src/relay/index.ts +2 -2
- package/src/relay-worker.ts +1 -0
- package/src/sw/handler-endpoint.ts +17 -4
- package/src/sw/http-sw-dispatcher.ts +267 -67
- package/src/sw/index.ts +5 -0
- package/src/sw/protocol.ts +11 -1
- package/src/sw/sw-dispatcher.ts +17 -11
- package/src/sw/sw-http-adapter.ts +36 -10
- package/src/sw/worker-service.ts +11 -4
- package/src/ws/server.ts +166 -11
- package/src/ws/socket-core.ts +7 -2
- package/src/ws/upgrade.ts +22 -4
package/dist/index.js
CHANGED
|
@@ -1272,8 +1272,9 @@ async function awaitServiceWorkerControl(registration, { timeout = DEFAULT_SERVI
|
|
|
1272
1272
|
* Returns a function that stops answering.
|
|
1273
1273
|
*/
|
|
1274
1274
|
function handleClaimRequests(self) {
|
|
1275
|
-
|
|
1276
|
-
|
|
1275
|
+
const sw = self;
|
|
1276
|
+
return handleChannelCalls(sw, CLAIM_CALL, (event) => {
|
|
1277
|
+
const claimed = sw.clients.claim().then(() => true);
|
|
1277
1278
|
event.waitUntil?.(claimed);
|
|
1278
1279
|
return claimed;
|
|
1279
1280
|
});
|
|
@@ -2126,6 +2127,12 @@ function asyncDisposable$2(target, dispose) {
|
|
|
2126
2127
|
}
|
|
2127
2128
|
/** Carries the serialized JS error between two webrun peers. */
|
|
2128
2129
|
const PEER_ERROR_HEADER = "x-webrun-error";
|
|
2130
|
+
/**
|
|
2131
|
+
* The reason of `HttpExchange.signal` when the exchange is abandoned with no
|
|
2132
|
+
* error of its own (`return()`); one abandoned by `throw(error)` aborts with
|
|
2133
|
+
* that error.
|
|
2134
|
+
*/
|
|
2135
|
+
const EXCHANGE_ABANDONED_MESSAGE = "webrun-http-streams: the exchange was abandoned before it was over";
|
|
2129
2136
|
/** Hard cap on the peer-error header (M3): see `encodeErrorResponse`. */
|
|
2130
2137
|
const MAX_DETAIL_CHARS = 4096;
|
|
2131
2138
|
/**
|
|
@@ -2306,7 +2313,11 @@ async function drain(output) {
|
|
|
2306
2313
|
*/
|
|
2307
2314
|
function httpServe(handler, options = {}) {
|
|
2308
2315
|
const codec = options.codec ?? defaultCodec;
|
|
2309
|
-
return
|
|
2316
|
+
return (input) => {
|
|
2317
|
+
const abandoned = new AbortController();
|
|
2318
|
+
return abortingOnReturn(exchange(input, { signal: abandoned.signal }), (reason) => abandoned.abort(reason ?? /* @__PURE__ */ new Error("webrun-http-streams: the exchange was abandoned before it was over")));
|
|
2319
|
+
};
|
|
2320
|
+
async function* exchange(input, context) {
|
|
2310
2321
|
let decoded;
|
|
2311
2322
|
try {
|
|
2312
2323
|
decoded = await codec.decodeRequest(input);
|
|
@@ -2318,7 +2329,7 @@ function httpServe(handler, options = {}) {
|
|
|
2318
2329
|
const replyCodec = decoded.codec ?? codec;
|
|
2319
2330
|
let result;
|
|
2320
2331
|
try {
|
|
2321
|
-
result = await handler(decoded.envelope, decoded.body);
|
|
2332
|
+
result = await handler(decoded.envelope, decoded.body, context);
|
|
2322
2333
|
} catch (error) {
|
|
2323
2334
|
yield* encodeErrorResponse(replyCodec, error, decoded.envelope.method);
|
|
2324
2335
|
return;
|
|
@@ -2333,7 +2344,36 @@ function httpServe(handler, options = {}) {
|
|
|
2333
2344
|
await result.release?.();
|
|
2334
2345
|
} catch {}
|
|
2335
2346
|
}
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* `gen`, except that `return()` and `throw()` from its consumer call `abandon`
|
|
2351
|
+
* at once — before they are queued behind a `next()` still parked inside the
|
|
2352
|
+
* generator (the handler's `await`) — unless the generator already finished.
|
|
2353
|
+
*/
|
|
2354
|
+
function abortingOnReturn(gen, abandon) {
|
|
2355
|
+
let finished = false;
|
|
2356
|
+
const settle = (r) => {
|
|
2357
|
+
if (r.done) finished = true;
|
|
2358
|
+
return r;
|
|
2336
2359
|
};
|
|
2360
|
+
const wrapped = {
|
|
2361
|
+
next: (...args) => gen.next(...args).then(settle),
|
|
2362
|
+
return(value) {
|
|
2363
|
+
if (!finished) abandon();
|
|
2364
|
+
finished = true;
|
|
2365
|
+
return gen.return(value);
|
|
2366
|
+
},
|
|
2367
|
+
throw(error) {
|
|
2368
|
+
if (!finished) abandon(error);
|
|
2369
|
+
finished = true;
|
|
2370
|
+
return gen.throw(error);
|
|
2371
|
+
},
|
|
2372
|
+
[Symbol.asyncIterator]() {
|
|
2373
|
+
return wrapped;
|
|
2374
|
+
}
|
|
2375
|
+
};
|
|
2376
|
+
return asyncDisposable$2(wrapped, () => wrapped.return(void 0));
|
|
2337
2377
|
}
|
|
2338
2378
|
/**
|
|
2339
2379
|
* The one place that knows a runtime may not implement request body streams,
|
|
@@ -2605,10 +2645,11 @@ async function fetchOverDuplex(call, request, options = {}) {
|
|
|
2605
2645
|
* registered with any `webrun-streams-*` adapter's `serve`.
|
|
2606
2646
|
*/
|
|
2607
2647
|
function serveFetchOverDuplex(handler, options = {}) {
|
|
2608
|
-
return httpServe(async (env, body) => {
|
|
2648
|
+
return httpServe(async (env, body, exchange) => {
|
|
2609
2649
|
const reqInit = {
|
|
2610
2650
|
method: env.method,
|
|
2611
|
-
headers: forwardableHeaders(env.headers)
|
|
2651
|
+
headers: forwardableHeaders(env.headers),
|
|
2652
|
+
signal: exchange?.signal
|
|
2612
2653
|
};
|
|
2613
2654
|
let upload;
|
|
2614
2655
|
if (env.method !== "GET" && env.method !== "HEAD") {
|
|
@@ -3121,6 +3162,99 @@ async function* throughAbort(input, signal) {
|
|
|
3121
3162
|
}
|
|
3122
3163
|
}
|
|
3123
3164
|
/**
|
|
3165
|
+
* Runs `fn` on a later task, never a microtask: a producer looping on
|
|
3166
|
+
* `whenPortReady` must let the transport's own events run. `setImmediate`
|
|
3167
|
+
* where it exists; elsewhere a `MessageChannel`, because nested `setTimeout`s
|
|
3168
|
+
* are clamped to 4 ms in browsers.
|
|
3169
|
+
*/
|
|
3170
|
+
const setImmediateFn = globalThis.setImmediate;
|
|
3171
|
+
/** @internal */
|
|
3172
|
+
const nextTask = setImmediateFn ? (fn) => {
|
|
3173
|
+
setImmediateFn(fn);
|
|
3174
|
+
} : (() => {
|
|
3175
|
+
let channel;
|
|
3176
|
+
const queue = [];
|
|
3177
|
+
return (fn) => {
|
|
3178
|
+
if (!channel) {
|
|
3179
|
+
channel = new MessageChannel();
|
|
3180
|
+
channel.port1.onmessage = () => queue.shift()?.();
|
|
3181
|
+
}
|
|
3182
|
+
queue.push(fn);
|
|
3183
|
+
channel.port2.postMessage(0);
|
|
3184
|
+
};
|
|
3185
|
+
})();
|
|
3186
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
3187
|
+
/** @internal Registered by the multiplexer for each port with a window. */
|
|
3188
|
+
function setPortSendState(port, state) {
|
|
3189
|
+
states.set(port, state);
|
|
3190
|
+
}
|
|
3191
|
+
/**
|
|
3192
|
+
* Bytes queued on `port` because its window is spent (0 for a port without
|
|
3193
|
+
* flow control). A sender that should not queue without bound waits for
|
|
3194
|
+
* `whenPortReady` before posting more.
|
|
3195
|
+
*/
|
|
3196
|
+
function portBufferedAmount(port) {
|
|
3197
|
+
return states.get(port)?.bufferedAmount() ?? 0;
|
|
3198
|
+
}
|
|
3199
|
+
/**
|
|
3200
|
+
* Resolves once at most `below` bytes are queued on `port`, always on a later
|
|
3201
|
+
* task (for a port without flow control, on the next one).
|
|
3202
|
+
*/
|
|
3203
|
+
function whenPortReady(port, below = 0) {
|
|
3204
|
+
return states.get(port)?.ready(below) ?? new Promise((resolve) => nextTask(resolve));
|
|
3205
|
+
}
|
|
3206
|
+
const agreements = /* @__PURE__ */ new WeakMap();
|
|
3207
|
+
/** @internal Registered by the multiplexer for each port with a window. */
|
|
3208
|
+
function setPortAgreement(port, agreement) {
|
|
3209
|
+
agreements.set(port, agreement);
|
|
3210
|
+
}
|
|
3211
|
+
/**
|
|
3212
|
+
* @internal What has been agreed with the peer on `port` so far; `undefined`
|
|
3213
|
+
* on a port without a window. Not exported from the package.
|
|
3214
|
+
*/
|
|
3215
|
+
function portAgreement(port) {
|
|
3216
|
+
return agreements.get(port)?.();
|
|
3217
|
+
}
|
|
3218
|
+
const ends = /* @__PURE__ */ new WeakMap();
|
|
3219
|
+
/** @internal Registered by the multiplexer for each port with a window. */
|
|
3220
|
+
function setPortEndState(port, state) {
|
|
3221
|
+
ends.set(port, state);
|
|
3222
|
+
}
|
|
3223
|
+
/**
|
|
3224
|
+
* @internal End this side's direction of `port`: an `end` is queued behind
|
|
3225
|
+
* everything already posted on it, and anything posted afterwards is dropped.
|
|
3226
|
+
* `false`, and nothing changes, when `end` was not agreed (no window, the
|
|
3227
|
+
* handshake not answered yet, or a peer that does not speak it): the caller
|
|
3228
|
+
* then closes the port. Never waits on the peer. Not exported from the package.
|
|
3229
|
+
*/
|
|
3230
|
+
function endPort(port) {
|
|
3231
|
+
return ends.get(port)?.end() ?? false;
|
|
3232
|
+
}
|
|
3233
|
+
/**
|
|
3234
|
+
* @internal Resolves once the peer has ended its direction of `port`. It never
|
|
3235
|
+
* settles otherwise: not on a port where `end` is not agreed when it is asked
|
|
3236
|
+
* (no window, the handshake not answered yet, a peer or codec without it), and
|
|
3237
|
+
* not when the port closes first, so a caller races it against the port's
|
|
3238
|
+
* close signal. Not exported from the package.
|
|
3239
|
+
*/
|
|
3240
|
+
function whenPortEnded(port) {
|
|
3241
|
+
let waiting = peerEnds.get(port);
|
|
3242
|
+
if (!waiting) {
|
|
3243
|
+
let resolve = () => {};
|
|
3244
|
+
const promise = new Promise((r) => {
|
|
3245
|
+
resolve = r;
|
|
3246
|
+
});
|
|
3247
|
+
if (!ends.get(port)?.watchPeerEnd(resolve)) return promise;
|
|
3248
|
+
waiting = {
|
|
3249
|
+
promise,
|
|
3250
|
+
resolve
|
|
3251
|
+
};
|
|
3252
|
+
peerEnds.set(port, waiting);
|
|
3253
|
+
}
|
|
3254
|
+
return waiting.promise;
|
|
3255
|
+
}
|
|
3256
|
+
const peerEnds = /* @__PURE__ */ new WeakMap();
|
|
3257
|
+
/**
|
|
3124
3258
|
* Makes `target` disposable with `await using`: installs `dispose` as its
|
|
3125
3259
|
* `[Symbol.asyncDispose]`, where the runtime defines the symbol. Hand-written
|
|
3126
3260
|
* iterators lose what a native generator gets from `AsyncIteratorPrototype`;
|
|
@@ -3192,6 +3326,61 @@ function returningAtOnce(gen, abandon) {
|
|
|
3192
3326
|
* tests and adapters assert on it.
|
|
3193
3327
|
*/
|
|
3194
3328
|
const STREAM_ABORT = "webrun-rpc:stream-abort";
|
|
3329
|
+
/**
|
|
3330
|
+
* The `type` of a pipelined chunk: a plain port message, not a `callPort`
|
|
3331
|
+
* request, sent only on a port where both ends agreed `pipeline`. The window's
|
|
3332
|
+
* credit is its confirmation.
|
|
3333
|
+
*/
|
|
3334
|
+
const CHUNK = "webrun-rpc:chunk";
|
|
3335
|
+
/**
|
|
3336
|
+
* What a pipelined chunk counts beyond its bytes, against the window, on the
|
|
3337
|
+
* receiving side. Both codecs' windows count more for the chunk's envelope
|
|
3338
|
+
* (the estimate at least 64, msgpack's keys alone more than this), so an honest
|
|
3339
|
+
* sender never makes the receiver count past the window; a sender of empty
|
|
3340
|
+
* chunks still cannot have it hold an unbounded number of them.
|
|
3341
|
+
*/
|
|
3342
|
+
const CHUNK_OVERHEAD = 32;
|
|
3343
|
+
/**
|
|
3344
|
+
* The bytes of `view` as a plain `Uint8Array` that owns its whole buffer:
|
|
3345
|
+
* `view` itself when it already is one, else a copy. Structured clone carries
|
|
3346
|
+
* and keeps a view's whole `ArrayBuffer`, and a byte codec may decode into
|
|
3347
|
+
* views of the frame, while the window and the receiver's count see only the
|
|
3348
|
+
* view: a 4 KiB `subarray` of a 4 MiB buffer would cost 4 MiB per chunk.
|
|
3349
|
+
* Copied with the `Uint8Array` constructor, not `slice()`: a Node `Buffer`'s
|
|
3350
|
+
* `slice()` is another view on the same memory.
|
|
3351
|
+
*/
|
|
3352
|
+
function ownBytes(view) {
|
|
3353
|
+
if (view instanceof Uint8Array && view.constructor === Uint8Array && view.byteOffset === 0 && view.byteLength === view.buffer.byteLength) return view;
|
|
3354
|
+
return new Uint8Array(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
|
|
3355
|
+
}
|
|
3356
|
+
/**
|
|
3357
|
+
* A producer's chunk as bytes this end may send, in either mode: any view or
|
|
3358
|
+
* `ArrayBuffer`, normalised by `ownBytes`. Anything else fails the stream here,
|
|
3359
|
+
* as the producer's own error, rather than reaching a peer that would refuse it.
|
|
3360
|
+
*/
|
|
3361
|
+
/**
|
|
3362
|
+
* A received confirmed chunk's value, as its own bytes, put back in place of
|
|
3363
|
+
* the original in `chunk`: `listenPort` keeps the request until the handler
|
|
3364
|
+
* returns, and the original would keep its whole buffer that long. A function
|
|
3365
|
+
* of its own, so that no temporary of the suspended handler holds the
|
|
3366
|
+
* original either.
|
|
3367
|
+
*/
|
|
3368
|
+
function adoptValue(chunk) {
|
|
3369
|
+
const original = chunk.value;
|
|
3370
|
+
if (ArrayBuffer.isView(original)) chunk.value = ownBytes(original);
|
|
3371
|
+
return chunk.value;
|
|
3372
|
+
}
|
|
3373
|
+
function chunkBytes(value) {
|
|
3374
|
+
if (ArrayBuffer.isView(value)) return ownBytes(value);
|
|
3375
|
+
if (value instanceof ArrayBuffer || Object.prototype.toString.call(value) === "[object ArrayBuffer]") return new Uint8Array(value);
|
|
3376
|
+
throw new TypeError(`webrun-rpc: a stream chunk must be bytes (a typed array, a DataView or an ArrayBuffer), got ${value === null ? "null" : typeof value}`);
|
|
3377
|
+
}
|
|
3378
|
+
const MIXED_MESSAGE = "webrun-rpc: peer mixed confirmed and pipelined chunks; the stream port is closed";
|
|
3379
|
+
/** This end may pipeline its own chunks: agreed, and the window holds them. */
|
|
3380
|
+
const mayPipeline = (port) => {
|
|
3381
|
+
const agreement = portAgreement(port);
|
|
3382
|
+
return !!agreement?.enforced && agreement.caps.has("pipeline");
|
|
3383
|
+
};
|
|
3195
3384
|
/** Caller's input travels on this channel; the handler listens on it. */
|
|
3196
3385
|
const CHANNEL_IN = "in";
|
|
3197
3386
|
/** The handler's output travels on this channel; the caller listens on it. */
|
|
@@ -3204,7 +3393,9 @@ const CHANNEL_OUT = "out";
|
|
|
3204
3393
|
* same way on the other channel. Within each direction the next chunk is never
|
|
3205
3394
|
* sent until the previous one has been delivered *and* pulled past by the
|
|
3206
3395
|
* consumer (spec D11) — the reply to a chunk call *is* the confirmation, and
|
|
3207
|
-
* `listenPort` withholds it until then.
|
|
3396
|
+
* `listenPort` withholds it until then. On a port whose window both ends
|
|
3397
|
+
* speak and that agreed `pipeline`, chunks go as plain messages instead, and
|
|
3398
|
+
* the window's credit is the confirmation: up to a window is in flight.
|
|
3208
3399
|
*
|
|
3209
3400
|
* A stream port carries exactly one invocation. To make several calls, open
|
|
3210
3401
|
* several ports: `mux.openPort({ kind: "stream" })` per call.
|
|
@@ -3221,22 +3412,49 @@ function serveDuplexOverPort(port, handler, options = {}) {
|
|
|
3221
3412
|
const notice = installAbortNotice(port, controller);
|
|
3222
3413
|
abortOnPortClose(port, controller);
|
|
3223
3414
|
const clock = installStreamTimeout(controller, options.timeout);
|
|
3224
|
-
const inbound = receiveChunks(port, CHANNEL_IN, controller, clock.touch);
|
|
3415
|
+
const inbound = receiveChunks(port, CHANNEL_IN, controller, clock.touch, notice);
|
|
3225
3416
|
let output;
|
|
3226
3417
|
try {
|
|
3227
3418
|
output = handler(inbound.stream);
|
|
3228
3419
|
} catch (err) {
|
|
3229
|
-
const pump = sendChunks(port, CHANNEL_OUT, failing(err), options, controller.signal, clock.touch);
|
|
3420
|
+
const pump = sendChunks(port, CHANNEL_OUT, failing(err), options, controller.signal, clock.touch, () => {});
|
|
3230
3421
|
pump.catch(() => {});
|
|
3231
3422
|
disarmClockWhenBothSidesSettle(clock, pump, inbound.ended);
|
|
3232
3423
|
return teardownOnce(controller, notice, clock, inbound, void 0);
|
|
3233
3424
|
}
|
|
3234
|
-
|
|
3425
|
+
let onSent = () => {};
|
|
3426
|
+
const sent = new Promise((resolve) => {
|
|
3427
|
+
onSent = resolve;
|
|
3428
|
+
});
|
|
3429
|
+
const pump = sendChunks(port, CHANNEL_OUT, output, options, controller.signal, clock.touch, onSent);
|
|
3235
3430
|
pump.catch(() => {});
|
|
3236
3431
|
disarmClockWhenBothSidesSettle(clock, pump, inbound.ended);
|
|
3432
|
+
endServedStream(port, sent, inbound.over);
|
|
3237
3433
|
return teardownOnce(controller, notice, clock, inbound, output);
|
|
3238
3434
|
}
|
|
3239
3435
|
/**
|
|
3436
|
+
* The serving side's normal finish. Once the handler's last chunk has gone
|
|
3437
|
+
* without an error, and no more input is coming (`over`: the caller's last
|
|
3438
|
+
* chunk arrived without an error and was read, or the caller ended its
|
|
3439
|
+
* direction behind it, for a handler that did not read all its input), this
|
|
3440
|
+
* side ends its direction too, where `end` was agreed. Without `end` nothing
|
|
3441
|
+
* happens here: the caller closes the port. A stream that never gets that far
|
|
3442
|
+
* was abandoned, and the caller's notice and `close` free the port; this then
|
|
3443
|
+
* never resumes. It needs no race against the port's close signal: nothing
|
|
3444
|
+
* awaits it, and what keeps it waiting is the input's watcher, a listener of
|
|
3445
|
+
* the port that a close drops, after which the wait is collected.
|
|
3446
|
+
*
|
|
3447
|
+
* Nothing this side still posts is lost behind its `end`. The caller ends only
|
|
3448
|
+
* once its own last chunk was confirmed, so no confirmation is owed to it
|
|
3449
|
+
* here, and what the freed port's close signal aborts has settled: the last
|
|
3450
|
+
* chunk has left, and input nobody reads is dropped, as on a close.
|
|
3451
|
+
*/
|
|
3452
|
+
async function endServedStream(port, sent, over) {
|
|
3453
|
+
await sent;
|
|
3454
|
+
await over;
|
|
3455
|
+
endPort(port);
|
|
3456
|
+
}
|
|
3457
|
+
/**
|
|
3240
3458
|
* A stream that completes normally has nobody left to call the returned
|
|
3241
3459
|
* teardown — the caller only knows the stream ended, not that it must also
|
|
3242
3460
|
* dispose the serve-side handle. Without this, the last `touch()`'s timer
|
|
@@ -3276,22 +3494,31 @@ function runCallerSide(port, input, options) {
|
|
|
3276
3494
|
const notice = installAbortNotice(port, controller);
|
|
3277
3495
|
abortOnPortClose(port, controller);
|
|
3278
3496
|
const clock = installStreamTimeout(controller, options.timeout);
|
|
3279
|
-
const inbound = receiveChunks(port, CHANNEL_OUT, controller, clock.touch);
|
|
3280
|
-
|
|
3497
|
+
const inbound = receiveChunks(port, CHANNEL_OUT, controller, clock.touch, notice);
|
|
3498
|
+
let sent = false;
|
|
3499
|
+
const pump = sendChunks(port, CHANNEL_IN, input, options, controller.signal, clock.touch, () => {
|
|
3500
|
+
sent = true;
|
|
3501
|
+
});
|
|
3281
3502
|
pump.catch(() => {});
|
|
3282
3503
|
return returningAtOnce((async function* () {
|
|
3504
|
+
let received = false;
|
|
3283
3505
|
try {
|
|
3284
3506
|
yield* inbound.stream;
|
|
3507
|
+
received = true;
|
|
3285
3508
|
} finally {
|
|
3509
|
+
const ended = received && sent;
|
|
3286
3510
|
if (!controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("webrun-rpc: the caller abandoned the stream"));
|
|
3287
|
-
notice.post();
|
|
3511
|
+
if (!ended) notice.post();
|
|
3288
3512
|
notice.stop();
|
|
3289
3513
|
clock.stop();
|
|
3290
3514
|
inbound.stop();
|
|
3291
3515
|
await pump.catch(() => {});
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3516
|
+
if (!(ended && endPort(port))) {
|
|
3517
|
+
notice.post();
|
|
3518
|
+
try {
|
|
3519
|
+
await port.close?.();
|
|
3520
|
+
} catch {}
|
|
3521
|
+
}
|
|
3295
3522
|
}
|
|
3296
3523
|
})(), () => {
|
|
3297
3524
|
if (!controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("webrun-rpc: the caller abandoned the stream"));
|
|
@@ -3385,7 +3612,7 @@ function installStreamTimeout(controller, timeout) {
|
|
|
3385
3612
|
* the local consumer has not started iterating, an inbound chunk waits for it —
|
|
3386
3613
|
* which is the correct backpressure, and different from having no listener.
|
|
3387
3614
|
*/
|
|
3388
|
-
function receiveChunks(port, channelName, controller, touch) {
|
|
3615
|
+
function receiveChunks(port, channelName, controller, touch, notice) {
|
|
3389
3616
|
let deliver;
|
|
3390
3617
|
const waiting = [];
|
|
3391
3618
|
let finished = false;
|
|
@@ -3395,25 +3622,64 @@ function receiveChunks(port, channelName, controller, touch) {
|
|
|
3395
3622
|
const ended = new Promise((resolve) => {
|
|
3396
3623
|
resolveEnded = resolve;
|
|
3397
3624
|
});
|
|
3625
|
+
let resolveOver = () => {};
|
|
3626
|
+
const over = new Promise((resolve) => {
|
|
3627
|
+
resolveOver = resolve;
|
|
3628
|
+
});
|
|
3398
3629
|
let aborted = false;
|
|
3399
3630
|
let abortReason;
|
|
3400
3631
|
const ready = () => deliver || finished ? Promise.resolve() : new Promise((r) => waiting.push(r));
|
|
3401
3632
|
const wake = () => {
|
|
3402
3633
|
for (const r of waiting.splice(0)) r();
|
|
3403
3634
|
};
|
|
3404
|
-
const
|
|
3635
|
+
const violate = (message) => {
|
|
3636
|
+
poison = new Error(message);
|
|
3637
|
+
controller.abort(poison);
|
|
3638
|
+
setTimeout(() => {
|
|
3639
|
+
try {
|
|
3640
|
+
port.close?.();
|
|
3641
|
+
} catch {}
|
|
3642
|
+
}, 0);
|
|
3643
|
+
return poison;
|
|
3644
|
+
};
|
|
3645
|
+
let lastChunkArrived = false;
|
|
3646
|
+
let watching = false;
|
|
3647
|
+
const watchPeerEnd = () => {
|
|
3648
|
+
if (watching) return true;
|
|
3649
|
+
if (!portAgreement(port)?.caps.has("end")) return false;
|
|
3650
|
+
watching = true;
|
|
3651
|
+
whenPortEnded(port).then(() => {
|
|
3652
|
+
port.removeEventListener("message", watcher);
|
|
3653
|
+
if (lastChunkArrived) {
|
|
3654
|
+
resolveOver();
|
|
3655
|
+
return;
|
|
3656
|
+
}
|
|
3657
|
+
notice.post();
|
|
3658
|
+
violate("webrun-rpc: the peer ended its direction without finishing it; the stream port is closed");
|
|
3659
|
+
});
|
|
3660
|
+
return true;
|
|
3661
|
+
};
|
|
3662
|
+
const watcher = (event) => {
|
|
3663
|
+
const data = event.data;
|
|
3664
|
+
if (data?.channelName !== channelName) return;
|
|
3665
|
+
const chunk = data.type === CHUNK ? data.chunk : data.type === "request" ? data.params : void 0;
|
|
3666
|
+
if (!chunk) return;
|
|
3667
|
+
if (!watchPeerEnd()) {
|
|
3668
|
+
port.removeEventListener("message", watcher);
|
|
3669
|
+
return;
|
|
3670
|
+
}
|
|
3671
|
+
if (chunk.done && !chunk.error) lastChunkArrived = true;
|
|
3672
|
+
};
|
|
3673
|
+
port.addEventListener("message", watcher);
|
|
3674
|
+
watchPeerEnd();
|
|
3675
|
+
let held = 0;
|
|
3676
|
+
const off = listenPort(port, async (params) => {
|
|
3677
|
+
const { done, error } = params;
|
|
3678
|
+
const value = adoptValue(params);
|
|
3405
3679
|
touch();
|
|
3406
3680
|
if (poison) throw poison;
|
|
3407
|
-
if (
|
|
3408
|
-
|
|
3409
|
-
controller.abort(poison);
|
|
3410
|
-
setTimeout(() => {
|
|
3411
|
-
try {
|
|
3412
|
-
port.close?.();
|
|
3413
|
-
} catch {}
|
|
3414
|
-
}, 0);
|
|
3415
|
-
throw poison;
|
|
3416
|
-
}
|
|
3681
|
+
if (held > 0) throw violate(MIXED_MESSAGE);
|
|
3682
|
+
if (outstanding) throw violate("webrun-rpc: peer sent a second unconfirmed chunk; the stream port is closed");
|
|
3417
3683
|
outstanding = true;
|
|
3418
3684
|
try {
|
|
3419
3685
|
await ready();
|
|
@@ -3423,15 +3689,60 @@ function receiveChunks(port, channelName, controller, touch) {
|
|
|
3423
3689
|
value,
|
|
3424
3690
|
error: error ? deserializeError(error) : void 0
|
|
3425
3691
|
});
|
|
3692
|
+
if (done && !error) resolveOver();
|
|
3426
3693
|
} finally {
|
|
3427
3694
|
outstanding = false;
|
|
3428
3695
|
}
|
|
3429
3696
|
if (done) resolveEnded();
|
|
3430
3697
|
}, { channelName });
|
|
3698
|
+
const onChunk = (event) => {
|
|
3699
|
+
const data = event.data;
|
|
3700
|
+
if (data?.type !== CHUNK || data.channelName !== channelName || !data.chunk) return;
|
|
3701
|
+
if (poison) return;
|
|
3702
|
+
const agreement = portAgreement(port);
|
|
3703
|
+
if (!agreement?.caps.has("pipeline")) {
|
|
3704
|
+
violate("webrun-rpc: peer sent a pipelined chunk where pipelining was not agreed");
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
if (outstanding) {
|
|
3708
|
+
violate(MIXED_MESSAGE);
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
if (data.chunk.value !== void 0 && !ArrayBuffer.isView(data.chunk.value)) {
|
|
3712
|
+
violate("webrun-rpc: peer sent a pipelined chunk whose value is not bytes");
|
|
3713
|
+
return;
|
|
3714
|
+
}
|
|
3715
|
+
if (held > agreement.window) {
|
|
3716
|
+
violate(`webrun-rpc: peer sent past the window (${agreement.window} bytes) without waiting for credit; the stream port is closed`);
|
|
3717
|
+
return;
|
|
3718
|
+
}
|
|
3719
|
+
touch();
|
|
3720
|
+
const { done, error } = data.chunk;
|
|
3721
|
+
const value = data.chunk.value && ownBytes(data.chunk.value);
|
|
3722
|
+
const size = (value?.byteLength ?? 0) + CHUNK_OVERHEAD;
|
|
3723
|
+
held += size;
|
|
3724
|
+
return (async () => {
|
|
3725
|
+
try {
|
|
3726
|
+
await ready();
|
|
3727
|
+
if (finished) return;
|
|
3728
|
+
await deliver?.({
|
|
3729
|
+
done,
|
|
3730
|
+
value,
|
|
3731
|
+
error: error ? deserializeError(error) : void 0
|
|
3732
|
+
});
|
|
3733
|
+
if (done && !error) resolveOver();
|
|
3734
|
+
if (done) resolveEnded();
|
|
3735
|
+
} finally {
|
|
3736
|
+
held -= size;
|
|
3737
|
+
}
|
|
3738
|
+
})();
|
|
3739
|
+
};
|
|
3740
|
+
port.addEventListener("message", onChunk);
|
|
3431
3741
|
const detach = () => {
|
|
3432
3742
|
finished = true;
|
|
3433
3743
|
wake();
|
|
3434
3744
|
off();
|
|
3745
|
+
port.removeEventListener("message", onChunk);
|
|
3435
3746
|
controller.signal.removeEventListener("abort", onAbort);
|
|
3436
3747
|
resolveEnded();
|
|
3437
3748
|
};
|
|
@@ -3461,21 +3772,35 @@ function receiveChunks(port, channelName, controller, touch) {
|
|
|
3461
3772
|
return detach;
|
|
3462
3773
|
}),
|
|
3463
3774
|
stop: detach,
|
|
3464
|
-
ended
|
|
3775
|
+
ended,
|
|
3776
|
+
over
|
|
3465
3777
|
};
|
|
3466
3778
|
}
|
|
3467
3779
|
/**
|
|
3468
3780
|
* The sending half of one direction: one `callPort` per chunk, one call
|
|
3469
|
-
* outstanding at a time (spec D11/D12), with no per-chunk deadline (spec D8)
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3781
|
+
* outstanding at a time (spec D11/D12), with no per-chunk deadline (spec D8) —
|
|
3782
|
+
* or, once pipelining is agreed, plain chunk messages held back by the window.
|
|
3783
|
+
* `onSent` runs once the end of the output has left without an error: posted
|
|
3784
|
+
* when pipelined, confirmed when not.
|
|
3785
|
+
*/
|
|
3786
|
+
async function sendChunks(port, channelName, output, { maxMessageSize, log }, signal, touch, onSent) {
|
|
3787
|
+
const framed = maxMessageSize ? toChunks(maxMessageSize)(output) : output;
|
|
3788
|
+
const stream = throughAbort(framed, signal);
|
|
3789
|
+
const producer = output;
|
|
3790
|
+
const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
|
|
3791
|
+
if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
|
|
3792
|
+
let onAborted = () => {};
|
|
3793
|
+
const aborted = new Promise((resolve) => {
|
|
3794
|
+
onAborted = () => resolve();
|
|
3795
|
+
});
|
|
3796
|
+
if (signal.aborted) onAborted();
|
|
3797
|
+
else signal.addEventListener("abort", onAborted, { once: true });
|
|
3473
3798
|
try {
|
|
3474
3799
|
await sendIterator(async ({ done, value, error }) => {
|
|
3475
3800
|
if (signal.aborted) return;
|
|
3476
3801
|
const chunk = {
|
|
3477
3802
|
done,
|
|
3478
|
-
value,
|
|
3803
|
+
value: done ? value : chunkBytes(value),
|
|
3479
3804
|
error: error === void 0 ? void 0 : serializeError(error)
|
|
3480
3805
|
};
|
|
3481
3806
|
log?.("[duplexOverPort] send", {
|
|
@@ -3483,46 +3808,34 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
|
|
|
3483
3808
|
done,
|
|
3484
3809
|
size: value?.byteLength
|
|
3485
3810
|
});
|
|
3811
|
+
if (mayPipeline(port)) {
|
|
3812
|
+
port.postMessage({
|
|
3813
|
+
type: CHUNK,
|
|
3814
|
+
channelName,
|
|
3815
|
+
chunk
|
|
3816
|
+
});
|
|
3817
|
+
touch();
|
|
3818
|
+
if (done && !error) onSent();
|
|
3819
|
+
if (portBufferedAmount(port) > 0) await Promise.race([whenPortReady(port), aborted]);
|
|
3820
|
+
return;
|
|
3821
|
+
}
|
|
3486
3822
|
await callPort(port, chunk, {
|
|
3487
3823
|
channelName,
|
|
3488
3824
|
timeout: NO_TIMEOUT,
|
|
3489
3825
|
signal
|
|
3490
3826
|
});
|
|
3491
3827
|
touch();
|
|
3828
|
+
if (done && !error) onSent();
|
|
3492
3829
|
}, stream);
|
|
3493
3830
|
} catch (err) {
|
|
3494
3831
|
if (signal.aborted) return;
|
|
3495
3832
|
throw err;
|
|
3833
|
+
} finally {
|
|
3834
|
+
signal.removeEventListener("abort", returnProducer);
|
|
3835
|
+
signal.removeEventListener("abort", onAborted);
|
|
3496
3836
|
}
|
|
3497
3837
|
}
|
|
3498
3838
|
/**
|
|
3499
|
-
* Runs `fn` on a later task, never a microtask: a producer looping on
|
|
3500
|
-
* `whenPortReady` must let the transport's own events run. `setImmediate`
|
|
3501
|
-
* where it exists; elsewhere a `MessageChannel`, because nested `setTimeout`s
|
|
3502
|
-
* are clamped to 4 ms in browsers.
|
|
3503
|
-
*/
|
|
3504
|
-
const setImmediateFn = globalThis.setImmediate;
|
|
3505
|
-
/** @internal */
|
|
3506
|
-
const nextTask = setImmediateFn ? (fn) => {
|
|
3507
|
-
setImmediateFn(fn);
|
|
3508
|
-
} : (() => {
|
|
3509
|
-
let channel;
|
|
3510
|
-
const queue = [];
|
|
3511
|
-
return (fn) => {
|
|
3512
|
-
if (!channel) {
|
|
3513
|
-
channel = new MessageChannel();
|
|
3514
|
-
channel.port1.onmessage = () => queue.shift()?.();
|
|
3515
|
-
}
|
|
3516
|
-
queue.push(fn);
|
|
3517
|
-
channel.port2.postMessage(0);
|
|
3518
|
-
};
|
|
3519
|
-
})();
|
|
3520
|
-
const states = /* @__PURE__ */ new WeakMap();
|
|
3521
|
-
/** @internal Registered by the multiplexer for each port with a window. */
|
|
3522
|
-
function setPortSendState(port, state) {
|
|
3523
|
-
states.set(port, state);
|
|
3524
|
-
}
|
|
3525
|
-
/**
|
|
3526
3839
|
* One virtual port.
|
|
3527
3840
|
*
|
|
3528
3841
|
* `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
|
|
@@ -3600,6 +3913,21 @@ function newVirtualPort(send, requestClose) {
|
|
|
3600
3913
|
const KEEPALIVE_ID = Number.MAX_SAFE_INTEGER;
|
|
3601
3914
|
/** The longest delay a timer holds; past it `setInterval` fires at once. */
|
|
3602
3915
|
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
3916
|
+
/**
|
|
3917
|
+
* What a windowed port offers in its `open`: every capability this end
|
|
3918
|
+
* speaks. `end` only over a codec that reads it (`PortCodec.halfClose`).
|
|
3919
|
+
*/
|
|
3920
|
+
function ownCaps(codec) {
|
|
3921
|
+
return codec.halfClose ? ["pipeline", "end"] : ["pipeline"];
|
|
3922
|
+
}
|
|
3923
|
+
/**
|
|
3924
|
+
* The capabilities in a peer's `caps` that this end speaks too, or
|
|
3925
|
+
* `undefined` when the peer listed none (it is then treated as an older peer).
|
|
3926
|
+
*/
|
|
3927
|
+
function sharedCaps(own, listed) {
|
|
3928
|
+
if (!Array.isArray(listed)) return void 0;
|
|
3929
|
+
return new Set(own.filter((cap) => listed.includes(cap)));
|
|
3930
|
+
}
|
|
3603
3931
|
/** Ceiling on concurrently open virtual ports. Bounds the id table only. */
|
|
3604
3932
|
const DEFAULT_MAX_PORTS = 1024;
|
|
3605
3933
|
/**
|
|
@@ -3631,10 +3959,18 @@ function estimateMessageSize(payload) {
|
|
|
3631
3959
|
*/
|
|
3632
3960
|
function multiplexPort(port, options) {
|
|
3633
3961
|
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize, signal, window, frameSize, keepalive, onLeak } = options;
|
|
3962
|
+
const maxReassembledBytes = options.maxReassembledBytes ?? 67108864;
|
|
3634
3963
|
const misses = keepalive?.misses ?? 3;
|
|
3635
3964
|
if (keepalive && !(Number.isFinite(keepalive.intervalMs) && keepalive.intervalMs > 0 && keepalive.intervalMs <= MAX_TIMER_MS && Number.isSafeInteger(misses) && misses > 0)) throw new RangeError(`webrun-rpc: keepalive needs an intervalMs in (0, ${MAX_TIMER_MS}] and a positive integer misses`);
|
|
3636
3965
|
if (frameSize != null && !(Number.isSafeInteger(frameSize) && frameSize > 0)) throw new RangeError(`webrun-rpc: frameSize must be a positive integer, got ${frameSize}`);
|
|
3966
|
+
if (!(Number.isSafeInteger(maxReassembledBytes) && maxReassembledBytes > 0)) throw new RangeError(`webrun-rpc: maxReassembledBytes must be a positive integer, got ${maxReassembledBytes}`);
|
|
3637
3967
|
const open = /* @__PURE__ */ new Map();
|
|
3968
|
+
let reassembling = 0;
|
|
3969
|
+
const freePartial = (entry) => {
|
|
3970
|
+
if (!entry?.partial) return;
|
|
3971
|
+
reassembling -= entry.partial.charged;
|
|
3972
|
+
entry.partial = void 0;
|
|
3973
|
+
};
|
|
3638
3974
|
const leaks = onLeak ? new FinalizationRegistry(({ id, record, release }) => {
|
|
3639
3975
|
const entry = open.get(id);
|
|
3640
3976
|
if (entry?.handle !== record) return;
|
|
@@ -3655,6 +3991,8 @@ function multiplexPort(port, options) {
|
|
|
3655
3991
|
let muxClosed = false;
|
|
3656
3992
|
/** This end counts encoded bytes and reassembles frames. */
|
|
3657
3993
|
const exactCodec = codec.encode != null && codec.decode != null;
|
|
3994
|
+
/** What this end offers on a windowed port. */
|
|
3995
|
+
const caps = ownCaps(codec);
|
|
3658
3996
|
const post = (envelope, transfer) => {
|
|
3659
3997
|
if (muxClosed) return;
|
|
3660
3998
|
codecPost(envelope, transfer);
|
|
@@ -3712,13 +4050,39 @@ function multiplexPort(port, options) {
|
|
|
3712
4050
|
port.postMessage(bytes);
|
|
3713
4051
|
} catch {}
|
|
3714
4052
|
};
|
|
4053
|
+
/**
|
|
4054
|
+
* Both directions have ended: free the id as a close would, with no `close`
|
|
4055
|
+
* on the wire (each end frees it on its own once it has seen both ends).
|
|
4056
|
+
* Aborting the close signal is how the port's consumer learns it is gone.
|
|
4057
|
+
*/
|
|
4058
|
+
const freeIfEnded = (id, c) => {
|
|
4059
|
+
const entry = open.get(id);
|
|
4060
|
+
if (!c.endSent || !c.peerEnded || entry?.credit !== c) return;
|
|
4061
|
+
freePartial(entry);
|
|
4062
|
+
open.delete(id);
|
|
4063
|
+
leaks?.unregister(entry.handle);
|
|
4064
|
+
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the port ended in both directions"));
|
|
4065
|
+
};
|
|
4066
|
+
/** A peer that broke the port's protocol: hold none of its traffic, and close. */
|
|
4067
|
+
const failPort = (id, entry, error, reason) => {
|
|
4068
|
+
freePartial(entry);
|
|
4069
|
+
dropOutgoing(id);
|
|
4070
|
+
open.delete(id);
|
|
4071
|
+
releaseWaiters(entry.credit);
|
|
4072
|
+
entry.handle.markClosed(error);
|
|
4073
|
+
post({
|
|
4074
|
+
type: "close",
|
|
4075
|
+
id,
|
|
4076
|
+
reason
|
|
4077
|
+
});
|
|
4078
|
+
};
|
|
3715
4079
|
const releaseWaiters = (c) => {
|
|
3716
4080
|
for (const w of c?.waiters.splice(0) ?? []) w.resolve();
|
|
3717
4081
|
};
|
|
3718
4082
|
const flush = (c) => {
|
|
3719
4083
|
while (c.queue.length) {
|
|
3720
4084
|
const next = c.queue[0];
|
|
3721
|
-
if (c.enforced && c.outstanding > 0 && c.outstanding + next.size > c.window) break;
|
|
4085
|
+
if (next.size > 0 && c.enforced && c.outstanding > 0 && c.outstanding + next.size > c.window) break;
|
|
3722
4086
|
c.queue.shift();
|
|
3723
4087
|
c.queued -= next.size;
|
|
3724
4088
|
c.outstanding += next.size;
|
|
@@ -3756,15 +4120,20 @@ function multiplexPort(port, options) {
|
|
|
3756
4120
|
enforced: false,
|
|
3757
4121
|
opener,
|
|
3758
4122
|
exact,
|
|
4123
|
+
caps: /* @__PURE__ */ new Set(),
|
|
3759
4124
|
outstanding: 0,
|
|
3760
4125
|
queue: [],
|
|
3761
4126
|
queued: 0,
|
|
3762
4127
|
unconsumed: 0,
|
|
3763
4128
|
waiters: [],
|
|
3764
4129
|
toGrant: 0,
|
|
3765
|
-
grantScheduled: false
|
|
4130
|
+
grantScheduled: false,
|
|
4131
|
+
ending: false,
|
|
4132
|
+
endSent: false,
|
|
4133
|
+
peerEnded: false
|
|
3766
4134
|
} : void 0;
|
|
3767
4135
|
const self = newVirtualPort((payload, transfer) => {
|
|
4136
|
+
if (c?.ending) return;
|
|
3768
4137
|
touch(id);
|
|
3769
4138
|
const envelope = {
|
|
3770
4139
|
type: "message",
|
|
@@ -3782,12 +4151,12 @@ function multiplexPort(port, options) {
|
|
|
3782
4151
|
...envelope,
|
|
3783
4152
|
payload: structuredClone(payload, { transfer })
|
|
3784
4153
|
} : void 0;
|
|
3785
|
-
const send = () => {
|
|
4154
|
+
const send = (whole = false) => {
|
|
3786
4155
|
if (!bytes) {
|
|
3787
4156
|
sendOnPort(id, held ? () => post(held) : () => post(envelope, transfer));
|
|
3788
4157
|
return;
|
|
3789
4158
|
}
|
|
3790
|
-
if (frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
4159
|
+
if (!whole && frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
3791
4160
|
for (let at = 0; at < bytes.byteLength; at += frameSize) {
|
|
3792
4161
|
const data = bytes.subarray(at, at + frameSize);
|
|
3793
4162
|
const frame = at + frameSize >= bytes.byteLength ? {
|
|
@@ -3817,10 +4186,11 @@ function multiplexPort(port, options) {
|
|
|
3817
4186
|
record.markClosed(reason);
|
|
3818
4187
|
});
|
|
3819
4188
|
function release(reason) {
|
|
4189
|
+
freePartial(open.get(id));
|
|
3820
4190
|
open.delete(id);
|
|
3821
4191
|
leaks?.unregister(record);
|
|
3822
4192
|
if (c?.queue.length) {
|
|
3823
|
-
for (const item of c.queue.splice(0)) item.send();
|
|
4193
|
+
for (const item of c.queue.splice(0)) item.send(true);
|
|
3824
4194
|
c.queued = 0;
|
|
3825
4195
|
}
|
|
3826
4196
|
sendOnPort(id, () => post({
|
|
@@ -3850,6 +4220,38 @@ function multiplexPort(port, options) {
|
|
|
3850
4220
|
});
|
|
3851
4221
|
})
|
|
3852
4222
|
});
|
|
4223
|
+
setPortEndState(self.port, {
|
|
4224
|
+
end: () => {
|
|
4225
|
+
if (isClosed() || !c.caps.has("end")) return false;
|
|
4226
|
+
if (c.ending) return true;
|
|
4227
|
+
c.ending = true;
|
|
4228
|
+
c.queue.push({
|
|
4229
|
+
size: 0,
|
|
4230
|
+
send: () => sendOnPort(id, () => {
|
|
4231
|
+
if (open.get(id)?.credit !== c) return;
|
|
4232
|
+
post({
|
|
4233
|
+
type: "end",
|
|
4234
|
+
id
|
|
4235
|
+
});
|
|
4236
|
+
c.endSent = true;
|
|
4237
|
+
freeIfEnded(id, c);
|
|
4238
|
+
})
|
|
4239
|
+
});
|
|
4240
|
+
flush(c);
|
|
4241
|
+
return true;
|
|
4242
|
+
},
|
|
4243
|
+
watchPeerEnd: (resolve) => {
|
|
4244
|
+
if (!c.caps.has("end")) return false;
|
|
4245
|
+
if (c.peerEnded) resolve();
|
|
4246
|
+
else c.onPeerEnd = new WeakRef(resolve);
|
|
4247
|
+
return true;
|
|
4248
|
+
}
|
|
4249
|
+
});
|
|
4250
|
+
setPortAgreement(self.port, () => ({
|
|
4251
|
+
window: c.window,
|
|
4252
|
+
caps: c.caps,
|
|
4253
|
+
enforced: c.enforced
|
|
4254
|
+
}));
|
|
3853
4255
|
}
|
|
3854
4256
|
const record = {
|
|
3855
4257
|
deliver: self.deliver,
|
|
@@ -3901,16 +4303,24 @@ function multiplexPort(port, options) {
|
|
|
3901
4303
|
}
|
|
3902
4304
|
const handle = attach(envelope.id, envelope.meta, envelope.window, false, envelope.exact === true && exactCodec);
|
|
3903
4305
|
const credit = open.get(envelope.id)?.credit;
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
4306
|
+
const listed = sharedCaps(caps, envelope.caps);
|
|
4307
|
+
if (credit) {
|
|
4308
|
+
const ack = credit.exact ? {
|
|
4309
|
+
type: "credit",
|
|
4310
|
+
id: envelope.id,
|
|
4311
|
+
bytes: 0,
|
|
4312
|
+
exact: true
|
|
4313
|
+
} : {
|
|
4314
|
+
type: "credit",
|
|
4315
|
+
id: envelope.id,
|
|
4316
|
+
bytes: 0
|
|
4317
|
+
};
|
|
4318
|
+
if (listed) credit.caps = listed;
|
|
4319
|
+
post(listed ? {
|
|
4320
|
+
...ack,
|
|
4321
|
+
caps: [...listed]
|
|
4322
|
+
} : ack);
|
|
4323
|
+
}
|
|
3914
4324
|
let accepted = false;
|
|
3915
4325
|
if (onPort) try {
|
|
3916
4326
|
accepted = onPort(handle.port, envelope.meta) !== false;
|
|
@@ -3938,6 +4348,7 @@ function multiplexPort(port, options) {
|
|
|
3938
4348
|
c.exact = false;
|
|
3939
4349
|
c.outstanding = 0;
|
|
3940
4350
|
}
|
|
4351
|
+
c.caps = sharedCaps(caps, envelope.caps) ?? /* @__PURE__ */ new Set();
|
|
3941
4352
|
post({
|
|
3942
4353
|
type: "credit",
|
|
3943
4354
|
id: envelope.id,
|
|
@@ -3949,6 +4360,10 @@ function multiplexPort(port, options) {
|
|
|
3949
4360
|
flush(c);
|
|
3950
4361
|
return;
|
|
3951
4362
|
}
|
|
4363
|
+
if ((envelope.type === "message" || envelope.type === "frame") && entry?.credit?.peerEnded) {
|
|
4364
|
+
failPort(envelope.id, entry, /* @__PURE__ */ new Error("webrun-rpc: the peer sent on a port after its end"), "protocol");
|
|
4365
|
+
return;
|
|
4366
|
+
}
|
|
3952
4367
|
if (envelope.type === "message") {
|
|
3953
4368
|
const sizeOf = (c) => (c.exact ? byteLengthOf(event.data) : void 0) ?? estimateMessageSize(envelope.payload);
|
|
3954
4369
|
if (!entry) return;
|
|
@@ -3959,15 +4374,29 @@ function multiplexPort(port, options) {
|
|
|
3959
4374
|
if (envelope.type === "frame") {
|
|
3960
4375
|
if (!entry?.credit?.exact || !codec.decode || !(envelope.data instanceof Uint8Array)) return;
|
|
3961
4376
|
touch(envelope.id);
|
|
4377
|
+
const c = entry.credit;
|
|
4378
|
+
const owed = c.unconsumed + c.toGrant;
|
|
3962
4379
|
const partial = entry.partial ?? {
|
|
3963
4380
|
chunks: [],
|
|
3964
|
-
bytes: 0
|
|
4381
|
+
bytes: 0,
|
|
4382
|
+
owed,
|
|
4383
|
+
charged: 0,
|
|
4384
|
+
...owed > 0 ? { room: c.window - owed } : {}
|
|
3965
4385
|
};
|
|
3966
4386
|
entry.partial = partial;
|
|
4387
|
+
const size = envelope.data.byteLength;
|
|
4388
|
+
const charged = partial.room == null ? Math.max(0, partial.bytes + size - c.window) : 0;
|
|
4389
|
+
const overrun = partial.room != null && partial.bytes + size > partial.room ? `what the window left (${partial.room} of ${c.window} bytes, ${partial.owed} still owed)` : reassembling - partial.charged + charged > maxReassembledBytes ? `maxReassembledBytes (${maxReassembledBytes} bytes past the window, across all ports)` : void 0;
|
|
4390
|
+
if (overrun) {
|
|
4391
|
+
failPort(envelope.id, entry, /* @__PURE__ */ new RangeError(`webrun-rpc: a framed message passed ${overrun}`), "max-reassembled-bytes");
|
|
4392
|
+
return;
|
|
4393
|
+
}
|
|
4394
|
+
partial.bytes += size;
|
|
4395
|
+
reassembling += charged - partial.charged;
|
|
4396
|
+
partial.charged = charged;
|
|
3967
4397
|
partial.chunks.push(envelope.data);
|
|
3968
|
-
partial.bytes += envelope.data.byteLength;
|
|
3969
4398
|
if (!envelope.last) return;
|
|
3970
|
-
entry
|
|
4399
|
+
freePartial(entry);
|
|
3971
4400
|
const whole = new Uint8Array(partial.bytes);
|
|
3972
4401
|
let at = 0;
|
|
3973
4402
|
for (const chunk of partial.chunks) {
|
|
@@ -3979,8 +4408,22 @@ function multiplexPort(port, options) {
|
|
|
3979
4408
|
entry.handle.deliver(inner.payload, whole.byteLength);
|
|
3980
4409
|
return;
|
|
3981
4410
|
}
|
|
4411
|
+
if (envelope.type === "end") {
|
|
4412
|
+
if (!entry) return;
|
|
4413
|
+
const c = entry.credit;
|
|
4414
|
+
if (!c?.caps.has("end")) {
|
|
4415
|
+
failPort(envelope.id, entry, /* @__PURE__ */ new Error("webrun-rpc: the peer ended a port where end was not agreed"), "protocol");
|
|
4416
|
+
return;
|
|
4417
|
+
}
|
|
4418
|
+
touch(envelope.id);
|
|
4419
|
+
c.peerEnded = true;
|
|
4420
|
+
c.onPeerEnd?.deref()?.();
|
|
4421
|
+
freeIfEnded(envelope.id, c);
|
|
4422
|
+
return;
|
|
4423
|
+
}
|
|
3982
4424
|
dropOutgoing(envelope.id);
|
|
3983
4425
|
if (!entry) return;
|
|
4426
|
+
freePartial(entry);
|
|
3984
4427
|
open.delete(envelope.id);
|
|
3985
4428
|
releaseWaiters(entry.credit);
|
|
3986
4429
|
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
|
@@ -4059,12 +4502,14 @@ function multiplexPort(port, options) {
|
|
|
4059
4502
|
id,
|
|
4060
4503
|
meta,
|
|
4061
4504
|
window,
|
|
4062
|
-
exact: true
|
|
4505
|
+
exact: true,
|
|
4506
|
+
caps: [...caps]
|
|
4063
4507
|
} : {
|
|
4064
4508
|
type: "open",
|
|
4065
4509
|
id,
|
|
4066
4510
|
meta,
|
|
4067
|
-
window
|
|
4511
|
+
window,
|
|
4512
|
+
caps: [...caps]
|
|
4068
4513
|
} : {
|
|
4069
4514
|
type: "open",
|
|
4070
4515
|
id,
|
|
@@ -4171,14 +4616,17 @@ const serve = async ({ mux: factory, timeout }, handler) => {
|
|
|
4171
4616
|
const mux = await factory((port) => {
|
|
4172
4617
|
let off;
|
|
4173
4618
|
let finished = false;
|
|
4174
|
-
const tracked = (input) =>
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4619
|
+
const tracked = (input) => {
|
|
4620
|
+
const output = handler(input);
|
|
4621
|
+
return returningAtOnce((async function* () {
|
|
4622
|
+
try {
|
|
4623
|
+
yield* output;
|
|
4624
|
+
} finally {
|
|
4625
|
+
finished = true;
|
|
4626
|
+
if (off) live.delete(off);
|
|
4627
|
+
}
|
|
4628
|
+
})(), () => void output.return(void 0).catch(() => {}));
|
|
4629
|
+
};
|
|
4182
4630
|
off = serveDuplexOverPort(port, tracked, streamOptions);
|
|
4183
4631
|
if (!finished) live.add(off);
|
|
4184
4632
|
});
|
|
@@ -4216,7 +4664,7 @@ function isEnvelope(value) {
|
|
|
4216
4664
|
const candidate = value;
|
|
4217
4665
|
if (typeof candidate.id !== "number") return false;
|
|
4218
4666
|
if (!Number.isInteger(candidate.id) || candidate.id < 0) return false;
|
|
4219
|
-
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close" || candidate.type === "credit" || candidate.type === "frame" || candidate.type === "ping" || candidate.type === "pong";
|
|
4667
|
+
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close" || candidate.type === "credit" || candidate.type === "frame" || candidate.type === "ping" || candidate.type === "pong" || candidate.type === "end";
|
|
4220
4668
|
}
|
|
4221
4669
|
/**
|
|
4222
4670
|
* For ports whose messages are structured values — a real `MessagePort`, a
|
|
@@ -4228,6 +4676,7 @@ function isEnvelope(value) {
|
|
|
4228
4676
|
* not send anything a byte codec could not also carry.
|
|
4229
4677
|
*/
|
|
4230
4678
|
const structuredCodec = {
|
|
4679
|
+
halfClose: true,
|
|
4231
4680
|
post(port, envelope, transfer) {
|
|
4232
4681
|
if (transfer && transfer.length > 0) port.postMessage(envelope, transfer);
|
|
4233
4682
|
else port.postMessage(envelope);
|
|
@@ -4265,10 +4714,15 @@ function asyncDisposable(target, dispose) {
|
|
|
4265
4714
|
* own `handleStreams` / `sendStream` (no backpressure); version 2 is
|
|
4266
4715
|
* webrun-rpc's `connect` / `serve` over `overPipe` with webrun-http-streams'
|
|
4267
4716
|
* `fetchOverDuplex` / `serveFetchOverDuplex`: one port per link, calls
|
|
4268
|
-
* multiplexed on it, each call a stream with
|
|
4269
|
-
* directions, and cancellation that reaches the handler.
|
|
4717
|
+
* multiplexed on it, each call a stream with backpressure in both
|
|
4718
|
+
* directions, and cancellation that reaches the handler. A call's port with a
|
|
4719
|
+
* window (`HttpLinkOptions.window`) pipelines its chunks where both ends speak
|
|
4720
|
+
* it, and goes one confirmed chunk at a time otherwise, so the window needs
|
|
4721
|
+
* no new version.
|
|
4270
4722
|
*/
|
|
4271
4723
|
const HTTP_LINK_PROTOCOL = "webrun-http-link/2";
|
|
4724
|
+
/** The calling end's default `HttpLinkOptions.window`: 256 KiB. */
|
|
4725
|
+
const DEFAULT_HTTP_LINK_WINDOW = 262144;
|
|
4272
4726
|
/**
|
|
4273
4727
|
* Why a request failed when its link went away under it: the far side (the
|
|
4274
4728
|
* handler, or the ServiceWorker) was lost. The request may or may not have
|
|
@@ -4287,12 +4741,13 @@ var LinkLostError = class extends Error {
|
|
|
4287
4741
|
*/
|
|
4288
4742
|
const LINK_CLOSE = "webrun-http:link-close";
|
|
4289
4743
|
/** Opens the calling end of a link on `port` (one port per link). */
|
|
4290
|
-
async function connectHttpLink(port, { streamTimeout, maxPorts } = {}) {
|
|
4744
|
+
async function connectHttpLink(port, { streamTimeout, maxPorts, window = DEFAULT_HTTP_LINK_WINDOW } = {}) {
|
|
4291
4745
|
const { call, close } = await connect({
|
|
4292
4746
|
mux: overPipe(port, {
|
|
4293
4747
|
codec: structuredCodec,
|
|
4294
4748
|
side: "initiator",
|
|
4295
|
-
maxPorts
|
|
4749
|
+
maxPorts,
|
|
4750
|
+
window
|
|
4296
4751
|
}),
|
|
4297
4752
|
timeout: streamTimeout
|
|
4298
4753
|
});
|
|
@@ -4818,6 +5273,7 @@ var PortSocketCore = class extends EventTarget {
|
|
|
4818
5273
|
code,
|
|
4819
5274
|
reason
|
|
4820
5275
|
});
|
|
5276
|
+
if (!Number.isFinite(this._closeTimeoutMs)) return;
|
|
4821
5277
|
this._closeTimer = this._timers.setTimeout(() => this._finish(1006, "", false), this._closeTimeoutMs);
|
|
4822
5278
|
}
|
|
4823
5279
|
_onCloseFrame(code, reason) {
|
|
@@ -5128,7 +5584,8 @@ function errorMessage(error) {
|
|
|
5128
5584
|
* `whenDrained(limit)` waits for it to fall: the backpressure a producer
|
|
5129
5585
|
* applies.
|
|
5130
5586
|
* - When the client's pings stop for three heartbeat periods (a closed tab, a
|
|
5131
|
-
* terminated worker), the socket closes with 1006.
|
|
5587
|
+
* terminated worker), the socket closes with 1006. With `watch: "first-ping"`
|
|
5588
|
+
* the watch starts only once the client pings.
|
|
5132
5589
|
*/
|
|
5133
5590
|
var ServerWebSocket = class extends PortSocketCore {
|
|
5134
5591
|
static CONNECTING = 0;
|
|
@@ -5137,55 +5594,150 @@ var ServerWebSocket = class extends PortSocketCore {
|
|
|
5137
5594
|
static CLOSED = 3;
|
|
5138
5595
|
url;
|
|
5139
5596
|
protocol;
|
|
5597
|
+
_binaryType = "arraybuffer";
|
|
5598
|
+
_options;
|
|
5140
5599
|
_accepted = false;
|
|
5600
|
+
_detached = false;
|
|
5141
5601
|
_queue = [];
|
|
5142
5602
|
_heartbeatMs = DEFAULT_HEARTBEAT_MS;
|
|
5143
5603
|
_lastPing = Date.now();
|
|
5604
|
+
/** Whether a ping (with a period a timer takes) arrived already. */
|
|
5605
|
+
_pinged = false;
|
|
5144
5606
|
_watch;
|
|
5145
5607
|
constructor(port, options = {}) {
|
|
5146
5608
|
super({
|
|
5147
5609
|
timers: options.timers,
|
|
5148
5610
|
closeTimeoutMs: options.closeTimeoutMs
|
|
5149
5611
|
});
|
|
5612
|
+
this._options = options;
|
|
5150
5613
|
this.url = options.url ?? "";
|
|
5151
5614
|
this.protocol = options.protocol ?? "";
|
|
5152
5615
|
this._attach(port);
|
|
5153
5616
|
this._state = 1;
|
|
5154
|
-
this._watchHeartbeat();
|
|
5617
|
+
if (options.watch !== "first-ping") this._watchHeartbeat();
|
|
5155
5618
|
}
|
|
5156
5619
|
/** Whether `accept()` was called. */
|
|
5157
5620
|
get accepted() {
|
|
5158
5621
|
return this._accepted;
|
|
5159
5622
|
}
|
|
5623
|
+
/** How binary messages arrive: `ArrayBuffer`s (the default) or `Blob`s. Other values are ignored. */
|
|
5624
|
+
get binaryType() {
|
|
5625
|
+
return this._binaryType;
|
|
5626
|
+
}
|
|
5627
|
+
set binaryType(value) {
|
|
5628
|
+
if (value === "blob" || value === "arraybuffer") this._binaryType = value;
|
|
5629
|
+
}
|
|
5630
|
+
/**
|
|
5631
|
+
* Hands this end's transport to another holder before it was accepted: the port to give the
|
|
5632
|
+
* client (a pair's end returned in a 101, say), with the frames that reached this end first
|
|
5633
|
+
* delivered ahead of the rest. This end is closed then, with no event, and answers nothing.
|
|
5634
|
+
*/
|
|
5635
|
+
detach() {
|
|
5636
|
+
const port = this._port;
|
|
5637
|
+
if (!port || this._accepted || this._state !== 1) throw new DOMException("Only an open end that was not accepted can be detached.", "InvalidStateError");
|
|
5638
|
+
this._detached = true;
|
|
5639
|
+
this._port = void 0;
|
|
5640
|
+
port.onmessage = null;
|
|
5641
|
+
let owed = this.bufferedAmount;
|
|
5642
|
+
this._state = 3;
|
|
5643
|
+
this._onClosed();
|
|
5644
|
+
this._wakeDrainWaiters();
|
|
5645
|
+
const queued = this._queue.splice(0);
|
|
5646
|
+
if (queued.length === 0 && owed === 0) return port;
|
|
5647
|
+
const relay = new MessageChannel();
|
|
5648
|
+
const holder = relay.port2;
|
|
5649
|
+
const closing = /* @__PURE__ */ new Set();
|
|
5650
|
+
const end = () => {
|
|
5651
|
+
port.onmessage = null;
|
|
5652
|
+
holder.onmessage = null;
|
|
5653
|
+
port.close();
|
|
5654
|
+
holder.close();
|
|
5655
|
+
};
|
|
5656
|
+
const sawClose = (way) => {
|
|
5657
|
+
closing.add(way);
|
|
5658
|
+
if (closing.size === 2) end();
|
|
5659
|
+
};
|
|
5660
|
+
for (const frame of queued) {
|
|
5661
|
+
postFrame(holder, frame);
|
|
5662
|
+
if (frame.t === "c") closing.add("in");
|
|
5663
|
+
}
|
|
5664
|
+
holder.onmessage = (event) => {
|
|
5665
|
+
port.postMessage(event.data, transferables(event.data));
|
|
5666
|
+
if (event.data?.t === "c") sawClose("out");
|
|
5667
|
+
};
|
|
5668
|
+
port.onmessage = (event) => {
|
|
5669
|
+
const data = event.data;
|
|
5670
|
+
if (data?.t === "a" && typeof data.n === "number" && owed > 0) {
|
|
5671
|
+
const absorbed = Math.min(owed, data.n);
|
|
5672
|
+
owed -= absorbed;
|
|
5673
|
+
if (data.n > absorbed) holder.postMessage({
|
|
5674
|
+
t: "a",
|
|
5675
|
+
n: data.n - absorbed
|
|
5676
|
+
});
|
|
5677
|
+
return;
|
|
5678
|
+
}
|
|
5679
|
+
holder.postMessage(event.data, transferables(event.data));
|
|
5680
|
+
if (data?.t === "c") sawClose("in");
|
|
5681
|
+
};
|
|
5682
|
+
port.addEventListener?.("close", end);
|
|
5683
|
+
holder.addEventListener?.("close", end);
|
|
5684
|
+
return relay.port1;
|
|
5685
|
+
}
|
|
5160
5686
|
/** Starts delivering events, the queued ones first. */
|
|
5161
5687
|
accept() {
|
|
5688
|
+
if (this._detached) throw new DOMException("This end was detached; it cannot be accepted.", "InvalidStateError");
|
|
5162
5689
|
if (this._accepted) return;
|
|
5163
5690
|
this._accepted = true;
|
|
5164
|
-
for (const frame of this._queue.splice(0))
|
|
5691
|
+
for (const frame of this._queue.splice(0)) this._deliver(frame);
|
|
5165
5692
|
}
|
|
5166
5693
|
send(data) {
|
|
5167
|
-
if (
|
|
5694
|
+
if (this._detached) throw new DOMException("This end was detached; it cannot send.", "InvalidStateError");
|
|
5695
|
+
if (!this._accepted && !this._options.sendBeforeAccept) throw new DOMException("accept() the socket before sending on it.", "InvalidStateError");
|
|
5168
5696
|
this._sendData(isBinary(data) ? toArrayBuffer(data) : String(data));
|
|
5169
5697
|
}
|
|
5170
|
-
close(code
|
|
5171
|
-
|
|
5698
|
+
close(code, reason = "") {
|
|
5699
|
+
this.checkClose(code, reason);
|
|
5700
|
+
this._startClose(code ?? this._options.defaultCloseCode ?? 1e3, reason);
|
|
5701
|
+
}
|
|
5702
|
+
/**
|
|
5703
|
+
* Refuses a `close()` the endpoint may not make: by default a code outside RFC 6455 §7.4's
|
|
5704
|
+
* sendable ones, or a reason over 123 bytes. A subclass with another platform's rules
|
|
5705
|
+
* overrides it.
|
|
5706
|
+
*/
|
|
5707
|
+
checkClose(code, reason) {
|
|
5708
|
+
if (code !== void 0 && !isSendableCloseCode(code)) throw new DOMException(`${code} is not a close code an endpoint may send.`, "InvalidAccessError");
|
|
5172
5709
|
if (utf8Length(reason) > 123) throw new DOMException("The close reason is longer than 123 bytes.", "SyntaxError");
|
|
5173
|
-
this._startClose(code, reason);
|
|
5174
5710
|
}
|
|
5175
5711
|
_deliver(frame) {
|
|
5176
5712
|
if (!this._accepted) {
|
|
5177
5713
|
this._queue.push(frame);
|
|
5178
5714
|
return;
|
|
5179
5715
|
}
|
|
5716
|
+
if (frame.t === "m" && this._state === 2 && this._options.deliverWhileClosing) {
|
|
5717
|
+
this._dispatchMessage(frame.d);
|
|
5718
|
+
this._consumed(messageSize(frame.d));
|
|
5719
|
+
return;
|
|
5720
|
+
}
|
|
5180
5721
|
super._deliver(frame);
|
|
5181
5722
|
}
|
|
5182
5723
|
_dispatchMessage(data) {
|
|
5183
|
-
this.
|
|
5724
|
+
const delivered = typeof data !== "string" && this._binaryType === "blob" ? new Blob([data]) : data;
|
|
5725
|
+
this.dispatchEvent(new MessageEvent("message", { data: delivered }));
|
|
5184
5726
|
}
|
|
5185
5727
|
_onPing(hb) {
|
|
5186
5728
|
this._post({ t: "q" });
|
|
5187
5729
|
this._lastPing = Date.now();
|
|
5188
|
-
if (
|
|
5730
|
+
if (this._state === 3) return;
|
|
5731
|
+
const keep = this._options.heartbeatPeriod === "first" && this._pinged;
|
|
5732
|
+
if (!(hb > 0 && hb < 2 ** 31 - 1)) {
|
|
5733
|
+
if (!keep && this._watch !== void 0) {
|
|
5734
|
+
this._timers.clearInterval(this._watch);
|
|
5735
|
+
this._watch = void 0;
|
|
5736
|
+
}
|
|
5737
|
+
return;
|
|
5738
|
+
}
|
|
5739
|
+
this._pinged = true;
|
|
5740
|
+
if (!keep && (this._watch === void 0 || hb !== this._heartbeatMs)) {
|
|
5189
5741
|
this._heartbeatMs = hb;
|
|
5190
5742
|
this._watchHeartbeat();
|
|
5191
5743
|
}
|
|
@@ -5207,6 +5759,11 @@ function isSendableCloseCode(code) {
|
|
|
5207
5759
|
if (code >= 3e3 && code <= 4999) return true;
|
|
5208
5760
|
return code >= 1e3 && code <= 1014 && code !== 1004 && code !== 1005 && code !== 1006;
|
|
5209
5761
|
}
|
|
5762
|
+
/** The buffers a relayed frame carries, to transfer rather than copy. */
|
|
5763
|
+
function transferables(data) {
|
|
5764
|
+
const d = data?.d;
|
|
5765
|
+
return d instanceof ArrayBuffer ? [d] : [];
|
|
5766
|
+
}
|
|
5210
5767
|
//#endregion
|
|
5211
5768
|
//#region src/ws/upgrade.ts
|
|
5212
5769
|
/**
|
|
@@ -5238,6 +5795,7 @@ function newWebSocketUpgradeRequest(url, { protocols = [], headers } = {}) {
|
|
|
5238
5795
|
all.set("connection", "Upgrade");
|
|
5239
5796
|
all.set("sec-websocket-version", "13");
|
|
5240
5797
|
if (!all.has("sec-websocket-key")) all.set("sec-websocket-key", randomKey());
|
|
5798
|
+
all.delete("sec-websocket-protocol");
|
|
5241
5799
|
if (protocols.length > 0) all.set("sec-websocket-protocol", protocols.join(", "));
|
|
5242
5800
|
if (!all.has("x-webrun-ws-version")) all.set(WS_VERSION_HEADER, `2`);
|
|
5243
5801
|
return withHeaders(new Request(httpUrl, {
|
|
@@ -5297,6 +5855,23 @@ function upgradeWebSocket(request, options = {}) {
|
|
|
5297
5855
|
url: request.url,
|
|
5298
5856
|
protocol
|
|
5299
5857
|
});
|
|
5858
|
+
const response = webSocketUpgradeResponse(channel.port2, protocol);
|
|
5859
|
+
Object.defineProperty(response, "webSocket", {
|
|
5860
|
+
value: socket,
|
|
5861
|
+
configurable: true
|
|
5862
|
+
});
|
|
5863
|
+
return {
|
|
5864
|
+
socket,
|
|
5865
|
+
response
|
|
5866
|
+
};
|
|
5867
|
+
}
|
|
5868
|
+
/**
|
|
5869
|
+
* A 101 for a socket whose server end the handler runs itself, on the other end of `port`
|
|
5870
|
+
* (a server of its own over this package's frames, say): what `upgradeWebSocket` returns, minus
|
|
5871
|
+
* the `ServerWebSocket`. The host hands `port` to the caller as it would for `upgradeWebSocket`
|
|
5872
|
+
* (`takePort`), and `protocol` (`""` for none) is the subprotocol picked.
|
|
5873
|
+
*/
|
|
5874
|
+
function webSocketUpgradeResponse(port, protocol = "") {
|
|
5300
5875
|
const response = new Response(null, {
|
|
5301
5876
|
status: 200,
|
|
5302
5877
|
headers: protocol ? { "sec-websocket-protocol": protocol } : {}
|
|
@@ -5310,23 +5885,16 @@ function upgradeWebSocket(request, options = {}) {
|
|
|
5310
5885
|
value: "Switching Protocols",
|
|
5311
5886
|
configurable: true
|
|
5312
5887
|
},
|
|
5313
|
-
webSocket: {
|
|
5314
|
-
value: socket,
|
|
5315
|
-
configurable: true
|
|
5316
|
-
},
|
|
5317
5888
|
[UPGRADE]: {
|
|
5318
5889
|
value: {
|
|
5319
|
-
port
|
|
5890
|
+
port,
|
|
5320
5891
|
protocol
|
|
5321
5892
|
},
|
|
5322
5893
|
configurable: true,
|
|
5323
5894
|
writable: true
|
|
5324
5895
|
}
|
|
5325
5896
|
});
|
|
5326
|
-
return
|
|
5327
|
-
socket,
|
|
5328
|
-
response
|
|
5329
|
-
};
|
|
5897
|
+
return response;
|
|
5330
5898
|
}
|
|
5331
5899
|
/**
|
|
5332
5900
|
* The port to hand to the caller, for a response from `upgradeWebSocket`.
|
|
@@ -5550,7 +6118,7 @@ async function initHttpService(handler, { key, path, port, streamTimeout }) {
|
|
|
5550
6118
|
* resolves with the corresponding `Response`, whose body streams with
|
|
5551
6119
|
* backpressure.
|
|
5552
6120
|
*/
|
|
5553
|
-
async function callHttpService(request, { key, port, streamTimeout }) {
|
|
6121
|
+
async function callHttpService(request, { key, port, streamTimeout, window }) {
|
|
5554
6122
|
const channel = new MessageChannel();
|
|
5555
6123
|
const reply = await callChannel(port, "CONNECT", {
|
|
5556
6124
|
key,
|
|
@@ -5561,7 +6129,10 @@ async function callHttpService(request, { key, port, streamTimeout }) {
|
|
|
5561
6129
|
channel.port1.close();
|
|
5562
6130
|
throw new Error(`No service with key "${key}"`);
|
|
5563
6131
|
}
|
|
5564
|
-
return await sendOverConnection(channel.port1, reply, request, {
|
|
6132
|
+
return await sendOverConnection(channel.port1, reply, request, {
|
|
6133
|
+
streamTimeout,
|
|
6134
|
+
window
|
|
6135
|
+
});
|
|
5565
6136
|
}
|
|
5566
6137
|
/**
|
|
5567
6138
|
* Sends `request` on a port a service accepted with `reply`: over an HTTP link
|
|
@@ -5785,6 +6356,51 @@ function handleKeyedChannelCalls(target, callType, key, handler) {
|
|
|
5785
6356
|
return () => target.removeEventListener("message", listener);
|
|
5786
6357
|
}
|
|
5787
6358
|
//#endregion
|
|
6359
|
+
//#region src/sw/protocol.ts
|
|
6360
|
+
/**
|
|
6361
|
+
* The messages between pages, Dedicated Workers and the same-origin
|
|
6362
|
+
* ServiceWorker. Internal: the shapes may change between versions, which is
|
|
6363
|
+
* why every hand-shake carries `HTTP_LINK_PROTOCOL`.
|
|
6364
|
+
*
|
|
6365
|
+
* THREE PORTS PER HANDLER LINK. A page makes the channels and hands one end of
|
|
6366
|
+
* each to the handler (itself, or a worker) and the other to the
|
|
6367
|
+
* ServiceWorker; after that it carries nothing:
|
|
6368
|
+
*
|
|
6369
|
+
* - `down`: ServiceWorker → handler, the requests the handler serves;
|
|
6370
|
+
* - `ctl`: a raw port next to them, for what HTTP over a stream cannot carry,
|
|
6371
|
+
* transferred `MessagePort`s (WebSocket hand-over);
|
|
6372
|
+
* - `up` (workers only): handler → ServiceWorker, the requests the handler
|
|
6373
|
+
* sends through the router (to other handlers).
|
|
6374
|
+
*/
|
|
6375
|
+
/** Page → ServiceWorker: attach a handler link. The name is the one version 1 used. */
|
|
6376
|
+
const ATTACH_CALL = "UPDATE_COMMUNICATION_PORT";
|
|
6377
|
+
/** Page → ServiceWorker: remove a handler link now. */
|
|
6378
|
+
const DETACH_CALL = "webrun-http:detach";
|
|
6379
|
+
/** Page → ServiceWorker: stop routing to a link, let its requests finish, then remove it. */
|
|
6380
|
+
const DRAIN_CALL = "webrun-http:drain";
|
|
6381
|
+
/** Page → ServiceWorker: open a WebSocket to a handler. */
|
|
6382
|
+
const WS_CONNECT_CALL = "webrun-http:ws-connect";
|
|
6383
|
+
/** ServiceWorker → pages: attach your links again (the ServiceWorker restarted). */
|
|
6384
|
+
const NEED_LINKS_CALL = "webrun-http:need-links";
|
|
6385
|
+
/**
|
|
6386
|
+
* Page → ServiceWorker, plain messages (no reply). A message to the
|
|
6387
|
+
* ServiceWorker object is an event: it starts a stopped worker and resets its
|
|
6388
|
+
* idle timer. Port messages do neither.
|
|
6389
|
+
*/
|
|
6390
|
+
const WAKE_MESSAGE = "webrun-http:wake";
|
|
6391
|
+
const KEEPALIVE_MESSAGE = "webrun-http:keepalive";
|
|
6392
|
+
/** Page ↔ worker: what `attachWorker` and `serveHttpInWorker` exchange. */
|
|
6393
|
+
const WORKER_HELLO = "webrun-http:worker-hello";
|
|
6394
|
+
/** `headers` as pairs a `postMessage` can carry (a `Headers` object cannot be cloned). */
|
|
6395
|
+
function headerPairs(headers) {
|
|
6396
|
+
if (headers === void 0) return void 0;
|
|
6397
|
+
const pairs = [];
|
|
6398
|
+
new Headers(headers).forEach((value, name) => {
|
|
6399
|
+
pairs.push([name, value]);
|
|
6400
|
+
});
|
|
6401
|
+
return pairs;
|
|
6402
|
+
}
|
|
6403
|
+
//#endregion
|
|
5788
6404
|
//#region src/sw/handler-endpoint.ts
|
|
5789
6405
|
/** The message calls through a lost ServiceWorker fail with. */
|
|
5790
6406
|
const ROUTER_LOST_MESSAGE = "The ServiceWorker was lost while this call was in flight";
|
|
@@ -5868,8 +6484,11 @@ var HandlerEndpoint = class {
|
|
|
5868
6484
|
this._startKeepAlive();
|
|
5869
6485
|
return await link.up.send(request);
|
|
5870
6486
|
}
|
|
5871
|
-
/**
|
|
5872
|
-
|
|
6487
|
+
/**
|
|
6488
|
+
* Opens a socket to the handler `url` routes to, through the ServiceWorker. The handler sees
|
|
6489
|
+
* `headers` on the upgrade request, and so does a `route` function choosing it.
|
|
6490
|
+
*/
|
|
6491
|
+
async connectWebSocket(url, protocols = [], headers) {
|
|
5873
6492
|
const link = await this._linkWith("ctl");
|
|
5874
6493
|
const id = this._sockets.newId();
|
|
5875
6494
|
const answer = this._sockets.wait(id, this.options.wsConnectTimeoutMs ?? 15e3, `the socket request for ${url}`);
|
|
@@ -5877,7 +6496,8 @@ var HandlerEndpoint = class {
|
|
|
5877
6496
|
t: "ws-open",
|
|
5878
6497
|
id,
|
|
5879
6498
|
url,
|
|
5880
|
-
protocols
|
|
6499
|
+
protocols,
|
|
6500
|
+
headers: headerPairs(headers)
|
|
5881
6501
|
});
|
|
5882
6502
|
return await answer;
|
|
5883
6503
|
}
|
|
@@ -6117,43 +6737,22 @@ function set(key, value, customStore = defaultGetStore()) {
|
|
|
6117
6737
|
});
|
|
6118
6738
|
}
|
|
6119
6739
|
//#endregion
|
|
6120
|
-
//#region src/sw/
|
|
6121
|
-
/**
|
|
6122
|
-
* The messages between pages, Dedicated Workers and the same-origin
|
|
6123
|
-
* ServiceWorker. Internal: the shapes may change between versions, which is
|
|
6124
|
-
* why every hand-shake carries `HTTP_LINK_PROTOCOL`.
|
|
6125
|
-
*
|
|
6126
|
-
* THREE PORTS PER HANDLER LINK. A page makes the channels and hands one end of
|
|
6127
|
-
* each to the handler (itself, or a worker) and the other to the
|
|
6128
|
-
* ServiceWorker; after that it carries nothing:
|
|
6129
|
-
*
|
|
6130
|
-
* - `down`: ServiceWorker → handler, the requests the handler serves;
|
|
6131
|
-
* - `ctl`: a raw port next to them, for what HTTP over a stream cannot carry,
|
|
6132
|
-
* transferred `MessagePort`s (WebSocket hand-over);
|
|
6133
|
-
* - `up` (workers only): handler → ServiceWorker, the requests the handler
|
|
6134
|
-
* sends through the router (to other handlers).
|
|
6135
|
-
*/
|
|
6136
|
-
/** Page → ServiceWorker: attach a handler link. The name is the one version 1 used. */
|
|
6137
|
-
const ATTACH_CALL = "UPDATE_COMMUNICATION_PORT";
|
|
6138
|
-
/** Page → ServiceWorker: remove a handler link now. */
|
|
6139
|
-
const DETACH_CALL = "webrun-http:detach";
|
|
6140
|
-
/** Page → ServiceWorker: stop routing to a link, let its requests finish, then remove it. */
|
|
6141
|
-
const DRAIN_CALL = "webrun-http:drain";
|
|
6142
|
-
/** Page → ServiceWorker: open a WebSocket to a handler. */
|
|
6143
|
-
const WS_CONNECT_CALL = "webrun-http:ws-connect";
|
|
6144
|
-
/** ServiceWorker → pages: attach your links again (the ServiceWorker restarted). */
|
|
6145
|
-
const NEED_LINKS_CALL = "webrun-http:need-links";
|
|
6740
|
+
//#region src/sw/http-sw-dispatcher.ts
|
|
6146
6741
|
/**
|
|
6147
|
-
*
|
|
6148
|
-
*
|
|
6149
|
-
* idle timer. Port messages do neither.
|
|
6742
|
+
* The default key: the url's first path segment under `scope`, decoded; none outside it, at the
|
|
6743
|
+
* scope itself, or for a segment that does not decode.
|
|
6150
6744
|
*/
|
|
6151
|
-
|
|
6152
|
-
const
|
|
6153
|
-
|
|
6154
|
-
const
|
|
6155
|
-
|
|
6156
|
-
|
|
6745
|
+
function scopeKey(url, scope) {
|
|
6746
|
+
const href = `${url}`;
|
|
6747
|
+
if (!href.startsWith(scope)) return void 0;
|
|
6748
|
+
const key = href.substring(scope.length).replace(/[?#].*$/, "").split("/")[0];
|
|
6749
|
+
if (!key) return void 0;
|
|
6750
|
+
try {
|
|
6751
|
+
return decodeURIComponent(key);
|
|
6752
|
+
} catch {
|
|
6753
|
+
return;
|
|
6754
|
+
}
|
|
6755
|
+
}
|
|
6157
6756
|
/** One attached handler link, as the ServiceWorker holds it. */
|
|
6158
6757
|
var HandlerEntry = class {
|
|
6159
6758
|
key;
|
|
@@ -6193,7 +6792,8 @@ const TIMED_OUT = Symbol("timed-out");
|
|
|
6193
6792
|
* (`SwHttpAdapter.register`) or in Dedicated Workers
|
|
6194
6793
|
* (`SwHttpAdapter.attachWorker` + `serveHttpInWorker`).
|
|
6195
6794
|
*
|
|
6196
|
-
* A url `<scope>/<key>/...` belongs to the handlers of `key
|
|
6795
|
+
* A url `<scope>/<key>/...` belongs to the handlers of `key` (the `key`
|
|
6796
|
+
* option maps urls to keys some other way). Of those, the
|
|
6197
6797
|
* `route` option picks one per request. Requests and responses stream with
|
|
6198
6798
|
* backpressure in both directions, and an aborted fetch reaches the handler
|
|
6199
6799
|
* as a cancellation (webrun-rpc + webrun-http-streams).
|
|
@@ -6212,6 +6812,8 @@ const TIMED_OUT = Symbol("timed-out");
|
|
|
6212
6812
|
*/
|
|
6213
6813
|
var SwHttpDispatcher = class {
|
|
6214
6814
|
self;
|
|
6815
|
+
/** `self`, with the WebWorker lib's types this module compiles with. */
|
|
6816
|
+
_sw;
|
|
6215
6817
|
log;
|
|
6216
6818
|
options;
|
|
6217
6819
|
table = new HandlerTable();
|
|
@@ -6234,19 +6836,22 @@ var SwHttpDispatcher = class {
|
|
|
6234
6836
|
constructor(options) {
|
|
6235
6837
|
this.options = options;
|
|
6236
6838
|
this.self = options.self;
|
|
6839
|
+
this._sw = options.self;
|
|
6237
6840
|
this.log = options.log ?? (() => {});
|
|
6238
6841
|
if (locksAvailable()) this.routerLock = newLockName("webrun-http-router");
|
|
6239
6842
|
}
|
|
6240
6843
|
get scope() {
|
|
6241
|
-
return `${new URL("./", this.
|
|
6844
|
+
return this._sw.registration?.scope ?? `${new URL("./", this._sw.location.href)}`;
|
|
6242
6845
|
}
|
|
6243
|
-
/** The key `url` belongs to
|
|
6846
|
+
/** The key `url` belongs to (the `key` option; by default its first path segment under the scope). */
|
|
6244
6847
|
keyOf(url) {
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6848
|
+
let parsed;
|
|
6849
|
+
try {
|
|
6850
|
+
parsed = new URL(`${url}`);
|
|
6851
|
+
} catch {
|
|
6852
|
+
return;
|
|
6853
|
+
}
|
|
6854
|
+
return (this.options.key ?? scopeKey)(parsed, this.scope);
|
|
6250
6855
|
}
|
|
6251
6856
|
/** Whether `key` has ever been served on this worker. */
|
|
6252
6857
|
isClaimedKey(key) {
|
|
@@ -6257,7 +6862,7 @@ var SwHttpDispatcher = class {
|
|
|
6257
6862
|
return this.claimedKeys.has(key) || this.table.all(key).length > 0;
|
|
6258
6863
|
}
|
|
6259
6864
|
start() {
|
|
6260
|
-
const self = this.
|
|
6865
|
+
const self = this._sw;
|
|
6261
6866
|
if (this.routerLock) this._routerHeld = holdLock(this.routerLock).then(() => {});
|
|
6262
6867
|
this._restored = this._restoreClaimedKeys();
|
|
6263
6868
|
const latch = () => {
|
|
@@ -6277,8 +6882,8 @@ var SwHttpDispatcher = class {
|
|
|
6277
6882
|
return true;
|
|
6278
6883
|
}), handleChannelCalls(self, WS_CONNECT_CALL, async (event, params) => {
|
|
6279
6884
|
if (!this._isOwnClient(event)) throw new Error("Only this origin's pages may open sockets");
|
|
6280
|
-
const { url, protocols } = params;
|
|
6281
|
-
const connection = await this.connectWebSocket(url, protocols ?? []);
|
|
6885
|
+
const { url, protocols, headers } = params;
|
|
6886
|
+
const connection = await this.connectWebSocket(url, protocols ?? [], this._socketHeaders(headers, "page"));
|
|
6282
6887
|
return withTransfer(connection, [connection.port]);
|
|
6283
6888
|
}));
|
|
6284
6889
|
const onMessage = (event) => {
|
|
@@ -6296,10 +6901,10 @@ var SwHttpDispatcher = class {
|
|
|
6296
6901
|
this.log("Claim control over all clients.");
|
|
6297
6902
|
event.waitUntil(self.clients.claim());
|
|
6298
6903
|
};
|
|
6299
|
-
const onFetch = (event) => this.
|
|
6904
|
+
const onFetch = (event) => void this.handleFetch(event);
|
|
6300
6905
|
self.addEventListener("install", onInstall);
|
|
6301
6906
|
self.addEventListener("activate", onActivate);
|
|
6302
|
-
self.addEventListener("fetch", onFetch);
|
|
6907
|
+
if (this.options.handleFetch !== false) self.addEventListener("fetch", onFetch);
|
|
6303
6908
|
this._cleanup.push(() => {
|
|
6304
6909
|
self.removeEventListener("install", onInstall);
|
|
6305
6910
|
self.removeEventListener("activate", onActivate);
|
|
@@ -6314,37 +6919,60 @@ var SwHttpDispatcher = class {
|
|
|
6314
6919
|
}
|
|
6315
6920
|
/**
|
|
6316
6921
|
* Answers `request` as a fetch under the scope would be: through the
|
|
6317
|
-
* handler its key routes to. A url
|
|
6318
|
-
* worker never served, goes to the network.
|
|
6922
|
+
* handler its key routes to. A url with no key (by default, one outside the
|
|
6923
|
+
* scope), or of a key this worker never served, goes to the network.
|
|
6319
6924
|
*/
|
|
6320
6925
|
async dispatch(request) {
|
|
6321
6926
|
const key = this.keyOf(request.url);
|
|
6322
6927
|
if (key === void 0) return await fetch(request);
|
|
6323
6928
|
await this._restored.catch(() => {});
|
|
6324
6929
|
const entry = await this._acquire(request, key);
|
|
6325
|
-
if (entry === TIMED_OUT) return
|
|
6326
|
-
status: 503,
|
|
6327
|
-
statusText: "Service Unavailable (no handler attached in time)"
|
|
6328
|
-
});
|
|
6930
|
+
if (entry === TIMED_OUT) return await this._failed(request, { kind: "timeout" });
|
|
6329
6931
|
if (!entry) {
|
|
6330
6932
|
if (!this.serves(key)) return await fetch(request);
|
|
6331
|
-
return
|
|
6332
|
-
status: 404,
|
|
6333
|
-
statusText: "Not Found (no active handler)"
|
|
6334
|
-
});
|
|
6933
|
+
return await this._failed(request, { kind: "gone" });
|
|
6335
6934
|
}
|
|
6336
6935
|
try {
|
|
6337
6936
|
return await entry.link.send(request);
|
|
6338
6937
|
} catch (error) {
|
|
6339
6938
|
this.log("[dispatch] failed", key, error);
|
|
6340
|
-
return
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
6939
|
+
return await this._failed(request, {
|
|
6940
|
+
kind: "lost",
|
|
6941
|
+
error
|
|
6344
6942
|
});
|
|
6345
6943
|
}
|
|
6346
6944
|
}
|
|
6347
6945
|
/**
|
|
6946
|
+
* After `start()`: settles once the keys this worker served before a restart are read back
|
|
6947
|
+
* (whatever the outcome). Until then `serves()` may answer `false` for a key it will serve.
|
|
6948
|
+
*/
|
|
6949
|
+
get ready() {
|
|
6950
|
+
return this._restored.then(() => {}, () => {});
|
|
6951
|
+
}
|
|
6952
|
+
_socketHeaders(headers, from) {
|
|
6953
|
+
return this.options.socketHeaders ? this.options.socketHeaders(headers, from) : headers;
|
|
6954
|
+
}
|
|
6955
|
+
async _failed(request, failure) {
|
|
6956
|
+
if (this.options.failure) try {
|
|
6957
|
+
return await this.options.failure(request, failure);
|
|
6958
|
+
} catch (error) {
|
|
6959
|
+
this.log("[dispatch] the failure option threw; answering by default", error);
|
|
6960
|
+
}
|
|
6961
|
+
if (failure.kind === "timeout") return new Response(null, {
|
|
6962
|
+
status: 503,
|
|
6963
|
+
statusText: "Service Unavailable (no handler attached in time)"
|
|
6964
|
+
});
|
|
6965
|
+
if (failure.kind === "gone") return new Response(null, {
|
|
6966
|
+
status: 404,
|
|
6967
|
+
statusText: "Not Found (no active handler)"
|
|
6968
|
+
});
|
|
6969
|
+
return new Response(errorMessage(failure.error), {
|
|
6970
|
+
status: 502,
|
|
6971
|
+
statusText: "Bad Gateway",
|
|
6972
|
+
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
6973
|
+
});
|
|
6974
|
+
}
|
|
6975
|
+
/**
|
|
6348
6976
|
* Opens a socket to the handler `url` routes to: the handler accepts or
|
|
6349
6977
|
* refuses it like an HTTP upgrade, and the socket's port comes back. After
|
|
6350
6978
|
* that the socket runs between the caller and the handler directly; this
|
|
@@ -6356,7 +6984,7 @@ var SwHttpDispatcher = class {
|
|
|
6356
6984
|
headers
|
|
6357
6985
|
});
|
|
6358
6986
|
const key = this.keyOf(request.url);
|
|
6359
|
-
if (key === void 0) throw new Error(`${url}
|
|
6987
|
+
if (key === void 0) throw new Error(`${url} belongs to no key of this ServiceWorker`);
|
|
6360
6988
|
await this._restored.catch(() => {});
|
|
6361
6989
|
const entry = await this._acquire(request, key);
|
|
6362
6990
|
if (!entry || entry === TIMED_OUT) throw new Error(`No handler serves ${url}`);
|
|
@@ -6372,20 +7000,47 @@ var SwHttpDispatcher = class {
|
|
|
6372
7000
|
});
|
|
6373
7001
|
return await answer;
|
|
6374
7002
|
}
|
|
6375
|
-
|
|
6376
|
-
|
|
7003
|
+
/**
|
|
7004
|
+
* Answers `event` if it is the dispatcher's, with `request` (default `event.request`; a host
|
|
7005
|
+
* may pass a rewritten one, with headers it adds). Returns whether it called `respondWith`;
|
|
7006
|
+
* when it did not, the event is the caller's to answer or to leave to the network. For a host
|
|
7007
|
+
* that listens to `fetch` itself (`handleFetch: false`); call it synchronously in the event's
|
|
7008
|
+
* listener, as `respondWith` must be.
|
|
7009
|
+
*
|
|
7010
|
+
* It takes a same-origin request whose key (the `key` option) a handler serves, and also:
|
|
7011
|
+
* - a navigation under the scope whose key nobody serves (answered by `unserved`);
|
|
7012
|
+
* - before the stored keys are read (the first requests after a restart), every request with
|
|
7013
|
+
* a key, since only then is it known whose it is: one nobody serves gets what it would have
|
|
7014
|
+
* got after, `unserved` for a navigation and the network for anything else.
|
|
7015
|
+
* So a host with a page of its own for urls nobody serves gives it as `unserved`, rather than
|
|
7016
|
+
* deciding before calling this.
|
|
7017
|
+
*/
|
|
7018
|
+
handleFetch(event, request = event.request) {
|
|
7019
|
+
const navigation = event.request.mode === "navigate";
|
|
7020
|
+
if (new URL(request.url).origin !== this._sw.location.origin) return false;
|
|
6377
7021
|
const key = this.keyOf(request.url);
|
|
6378
|
-
if (key === void 0) return;
|
|
7022
|
+
if (key === void 0) return false;
|
|
6379
7023
|
if (this._restoreSettled) {
|
|
6380
|
-
if (this.serves(key))
|
|
6381
|
-
|
|
6382
|
-
|
|
7024
|
+
if (this.serves(key)) {
|
|
7025
|
+
event.respondWith(this.dispatch(request));
|
|
7026
|
+
return true;
|
|
7027
|
+
}
|
|
7028
|
+
if (navigation) {
|
|
7029
|
+
event.respondWith(this._unserved(request));
|
|
7030
|
+
return true;
|
|
7031
|
+
}
|
|
7032
|
+
return false;
|
|
6383
7033
|
}
|
|
6384
7034
|
event.respondWith((async () => {
|
|
6385
7035
|
await this._restored.catch(() => {});
|
|
6386
|
-
if (
|
|
6387
|
-
return await this.
|
|
7036
|
+
if (this.serves(key)) return await this.dispatch(request);
|
|
7037
|
+
return navigation ? await this._unserved(request) : await fetch(request);
|
|
6388
7038
|
})());
|
|
7039
|
+
return true;
|
|
7040
|
+
}
|
|
7041
|
+
/** What a request the dispatcher took, and no handler serves, gets: `unserved`, or the network. */
|
|
7042
|
+
async _unserved(request) {
|
|
7043
|
+
return await (this.options.unserved ?? fetch)(request);
|
|
6389
7044
|
}
|
|
6390
7045
|
async _attach(event, params, ports) {
|
|
6391
7046
|
const clientId = event.source?.id;
|
|
@@ -6404,8 +7059,11 @@ var SwHttpDispatcher = class {
|
|
|
6404
7059
|
}
|
|
6405
7060
|
const ctl = params.ctl ? rest.shift() : void 0;
|
|
6406
7061
|
const up = params.up ? rest.shift() : void 0;
|
|
6407
|
-
const streamTimeout = this.options
|
|
6408
|
-
const link = await connectHttpLink(down, {
|
|
7062
|
+
const { streamTimeout, window } = this.options;
|
|
7063
|
+
const link = await connectHttpLink(down, {
|
|
7064
|
+
streamTimeout,
|
|
7065
|
+
window
|
|
7066
|
+
});
|
|
6409
7067
|
const entry = new HandlerEntry(params.key, params.id ?? clientId ?? crypto.randomUUID(), params.owner ?? crypto.randomUUID(), params.meta, link, ctl, clientId, params.ownerLock);
|
|
6410
7068
|
if (up) entry.closeUp = await serveHttpLink(up, (request) => this.dispatch(request), { streamTimeout });
|
|
6411
7069
|
if (ctl) ctl.onmessage = (message) => this._onControl(entry, message.data);
|
|
@@ -6484,20 +7142,35 @@ var SwHttpDispatcher = class {
|
|
|
6484
7142
|
* its key, waiting for one when there is none.
|
|
6485
7143
|
*/
|
|
6486
7144
|
async _acquire(request, key) {
|
|
7145
|
+
const deadline = Date.now() + (this.options.attachTimeoutMs ?? 15e3);
|
|
6487
7146
|
const picked = await this._pick(request, key);
|
|
6488
7147
|
if (picked) return picked;
|
|
6489
|
-
if (this.table.all(key).length
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
7148
|
+
if (this.table.all(key).length === 0) {
|
|
7149
|
+
const vacantUntil = this._vacant.get(key) ?? 0;
|
|
7150
|
+
if (vacantUntil > Date.now()) return await this._waitToPick(request, key, vacantUntil);
|
|
7151
|
+
if (!this.serves(key)) return void 0;
|
|
7152
|
+
await this._askForLinks(key);
|
|
7153
|
+
const relinked = await this._pick(request, key);
|
|
7154
|
+
if (relinked) return relinked;
|
|
7155
|
+
if (this.table.all(key).length === 0) return void 0;
|
|
7156
|
+
}
|
|
7157
|
+
return await this._waitToPick(request, key, deadline);
|
|
7158
|
+
}
|
|
7159
|
+
/** Waits for attaches until `route` accepts a handler, or `until` passes. */
|
|
7160
|
+
async _waitToPick(request, key, until) {
|
|
7161
|
+
for (;;) {
|
|
7162
|
+
const left = until - Date.now();
|
|
7163
|
+
if (left <= 0) return TIMED_OUT;
|
|
7164
|
+
await this.table.waitFor(key, Math.min(left, 1e3), this.table.live(key).length > 0);
|
|
7165
|
+
const picked = await this._pick(request, key);
|
|
7166
|
+
if (picked) return picked;
|
|
7167
|
+
}
|
|
6495
7168
|
}
|
|
6496
7169
|
async _pick(request, key) {
|
|
6497
7170
|
let live = this.table.live(key);
|
|
6498
7171
|
for (const entry of live) {
|
|
6499
7172
|
if (entry.ownerLock || !entry.clientId) continue;
|
|
6500
|
-
if (!await this.
|
|
7173
|
+
if (!await this._sw.clients.get(entry.clientId)) this._close(entry, new LinkLostError(HANDLER_LOST_MESSAGE));
|
|
6501
7174
|
}
|
|
6502
7175
|
live = this.table.live(key);
|
|
6503
7176
|
if (live.length === 0) return void 0;
|
|
@@ -6521,7 +7194,7 @@ var SwHttpDispatcher = class {
|
|
|
6521
7194
|
}
|
|
6522
7195
|
async _linkRound(key) {
|
|
6523
7196
|
const timeout = this.options.relinkTimeoutMs ?? 3e3;
|
|
6524
|
-
const windows = await this.
|
|
7197
|
+
const windows = await this._sw.clients.matchAll({
|
|
6525
7198
|
type: "window",
|
|
6526
7199
|
includeUncontrolled: true
|
|
6527
7200
|
});
|
|
@@ -6559,7 +7232,7 @@ var SwHttpDispatcher = class {
|
|
|
6559
7232
|
entry.sockets.fail(message.id, new Error(message.message));
|
|
6560
7233
|
return;
|
|
6561
7234
|
case "ws-open":
|
|
6562
|
-
this.connectWebSocket(message.url, message.protocols).then(({ port, protocol }) => entry.ctl?.postMessage({
|
|
7235
|
+
this.connectWebSocket(message.url, message.protocols, this._socketHeaders(message.headers, "handler")).then(({ port, protocol }) => entry.ctl?.postMessage({
|
|
6563
7236
|
t: "ws-opened",
|
|
6564
7237
|
id: message.id,
|
|
6565
7238
|
protocol,
|
|
@@ -6576,7 +7249,7 @@ var SwHttpDispatcher = class {
|
|
|
6576
7249
|
const source = event.source;
|
|
6577
7250
|
if (!source?.url) return false;
|
|
6578
7251
|
try {
|
|
6579
|
-
return new URL(source.url).origin === this.
|
|
7252
|
+
return new URL(source.url).origin === this._sw.location.origin;
|
|
6580
7253
|
} catch {
|
|
6581
7254
|
return false;
|
|
6582
7255
|
}
|
|
@@ -6600,6 +7273,7 @@ var SwHttpDispatcher = class {
|
|
|
6600
7273
|
* that stops it.
|
|
6601
7274
|
*/
|
|
6602
7275
|
function startHttpDispatcher(options) {
|
|
7276
|
+
if (options.handleFetch === false) throw new Error("startHttpDispatcher: handleFetch: false needs a dispatcher to call handleFetch on; construct it instead: new SwHttpDispatcher({ self, handleFetch: false }).start()");
|
|
6603
7277
|
const dispatcher = new SwHttpDispatcher(options);
|
|
6604
7278
|
dispatcher.start();
|
|
6605
7279
|
return () => {
|
|
@@ -6740,6 +7414,8 @@ var SwPortHandler = class {
|
|
|
6740
7414
|
*/
|
|
6741
7415
|
var SwPortDispatcher = class {
|
|
6742
7416
|
self;
|
|
7417
|
+
/** `self`, with the WebWorker lib's types this module compiles with. */
|
|
7418
|
+
_sw;
|
|
6743
7419
|
log;
|
|
6744
7420
|
handlersIndex = /* @__PURE__ */ new Map();
|
|
6745
7421
|
/**
|
|
@@ -6753,10 +7429,11 @@ var SwPortDispatcher = class {
|
|
|
6753
7429
|
_activationPromise;
|
|
6754
7430
|
constructor({ self, log = () => {} }) {
|
|
6755
7431
|
this.self = self;
|
|
7432
|
+
this._sw = self;
|
|
6756
7433
|
this.log = log;
|
|
6757
7434
|
}
|
|
6758
7435
|
get scope() {
|
|
6759
|
-
return `${new URL("./", this.
|
|
7436
|
+
return this._sw.registration?.scope ?? `${new URL("./", this._sw.location.href)}`;
|
|
6760
7437
|
}
|
|
6761
7438
|
/**
|
|
6762
7439
|
* Look up the active channel for a key. Returns `undefined` when there
|
|
@@ -6769,7 +7446,7 @@ var SwPortDispatcher = class {
|
|
|
6769
7446
|
if (channelInfo.key !== key) continue;
|
|
6770
7447
|
const clientId = channelInfo.clientId;
|
|
6771
7448
|
if (!clientId) continue;
|
|
6772
|
-
if (!await this.
|
|
7449
|
+
if (!await this._sw.clients.get(clientId)) {
|
|
6773
7450
|
this.handlersIndex.delete(clientId);
|
|
6774
7451
|
continue;
|
|
6775
7452
|
}
|
|
@@ -6781,8 +7458,8 @@ var SwPortDispatcher = class {
|
|
|
6781
7458
|
return this.claimedKeys.has(key);
|
|
6782
7459
|
}
|
|
6783
7460
|
start() {
|
|
6784
|
-
const stopClaims = handleClaimRequests(this.
|
|
6785
|
-
const stopPortUpdates = handleChannelCalls(this.
|
|
7461
|
+
const stopClaims = handleClaimRequests(this._sw);
|
|
7462
|
+
const stopPortUpdates = handleChannelCalls(this._sw, "UPDATE_COMMUNICATION_PORT", async (event, channelInfo, port) => {
|
|
6786
7463
|
const clientId = event.source?.id;
|
|
6787
7464
|
this.log("[UPDATE_COMMUNICATION_PORT]", clientId, channelInfo);
|
|
6788
7465
|
await this._updateChannelInfo({
|
|
@@ -6796,14 +7473,14 @@ var SwPortDispatcher = class {
|
|
|
6796
7473
|
stopClaims();
|
|
6797
7474
|
stopPortUpdates();
|
|
6798
7475
|
};
|
|
6799
|
-
this.
|
|
7476
|
+
this._sw.addEventListener("install", (event) => {
|
|
6800
7477
|
this.log("Skip waiting on install.", event);
|
|
6801
|
-
this.
|
|
7478
|
+
this._sw.skipWaiting();
|
|
6802
7479
|
});
|
|
6803
|
-
this.
|
|
7480
|
+
this._sw.addEventListener("activate", (event) => {
|
|
6804
7481
|
this.log("Claim control over all clients.", event);
|
|
6805
7482
|
event.waitUntil((async () => {
|
|
6806
|
-
await this.
|
|
7483
|
+
await this._sw.clients.claim();
|
|
6807
7484
|
await this._checkActivation();
|
|
6808
7485
|
})());
|
|
6809
7486
|
});
|
|
@@ -6853,7 +7530,7 @@ var SwPortDispatcher = class {
|
|
|
6853
7530
|
for (const clientId of this.handlersIndex.keys()) ids.add(clientId);
|
|
6854
7531
|
const index = /* @__PURE__ */ new Map();
|
|
6855
7532
|
for (const clientId of ids) {
|
|
6856
|
-
const client = await this.
|
|
7533
|
+
const client = await this._sw.clients.get(clientId);
|
|
6857
7534
|
if (client) index.set(clientId, client);
|
|
6858
7535
|
else this.handlersIndex.delete(clientId);
|
|
6859
7536
|
}
|
|
@@ -6906,7 +7583,10 @@ var SwHttpAdapter = class {
|
|
|
6906
7583
|
return this.options.key;
|
|
6907
7584
|
}
|
|
6908
7585
|
get scope() {
|
|
6909
|
-
|
|
7586
|
+
if (this.options.scope) return this.options.scope;
|
|
7587
|
+
const registration = this.options.registration;
|
|
7588
|
+
if (registration) return new URL(registration.scope).pathname;
|
|
7589
|
+
return new URL("./", this.serviceWorkerUrl).pathname;
|
|
6910
7590
|
}
|
|
6911
7591
|
/**
|
|
6912
7592
|
* The scope as a url on the worker's origin. Resolved against the worker's
|
|
@@ -6917,6 +7597,8 @@ var SwHttpAdapter = class {
|
|
|
6917
7597
|
* sits next to it).
|
|
6918
7598
|
*/
|
|
6919
7599
|
get rootUrl() {
|
|
7600
|
+
const registration = this.options.registration;
|
|
7601
|
+
if (registration && !this.options.serviceWorkerUrl) return new URL(this.scope, registration.scope);
|
|
6920
7602
|
const base = this.options.serviceWorkerUrl ? this.serviceWorkerUrl : import.meta.url;
|
|
6921
7603
|
return new URL(this.scope, base);
|
|
6922
7604
|
}
|
|
@@ -6938,7 +7620,10 @@ var SwHttpAdapter = class {
|
|
|
6938
7620
|
*/
|
|
6939
7621
|
async start() {
|
|
6940
7622
|
if (!this._registrationPromise) this._registrationPromise = (async () => {
|
|
6941
|
-
const registration = await navigator.serviceWorker.register(this.serviceWorkerUrl, {
|
|
7623
|
+
const registration = this.options.registration ?? await navigator.serviceWorker.register(this.serviceWorkerUrl, {
|
|
7624
|
+
scope: this.scope,
|
|
7625
|
+
...this.options.type ? { type: this.options.type } : {}
|
|
7626
|
+
});
|
|
6942
7627
|
this._registration = registration;
|
|
6943
7628
|
const stopListening = handleChannelCalls(navigator.serviceWorker, NEED_LINKS_CALL, async (_event, params) => {
|
|
6944
7629
|
await this.relink(params?.routerLock);
|
|
@@ -6968,7 +7653,7 @@ var SwHttpAdapter = class {
|
|
|
6968
7653
|
})();
|
|
6969
7654
|
return this._registrationPromise;
|
|
6970
7655
|
}
|
|
6971
|
-
/** Detaches every link, and unregisters the worker. */
|
|
7656
|
+
/** Detaches every link, and unregisters the worker (unless the page passed its `registration`). */
|
|
6972
7657
|
async stop() {
|
|
6973
7658
|
for (const cleanup of this._cleanup.splice(0)) cleanup();
|
|
6974
7659
|
for (const worker of [...this._workers]) await worker.detach().catch(() => {});
|
|
@@ -6985,7 +7670,7 @@ var SwHttpAdapter = class {
|
|
|
6985
7670
|
this._registration = void 0;
|
|
6986
7671
|
this._registrationPromise = void 0;
|
|
6987
7672
|
try {
|
|
6988
|
-
await registration?.unregister();
|
|
7673
|
+
if (!this.options.registration) await registration?.unregister();
|
|
6989
7674
|
} catch (error) {
|
|
6990
7675
|
console.log("Service Worker unregistration failed: ", error);
|
|
6991
7676
|
}
|
|
@@ -7059,10 +7744,10 @@ var SwHttpAdapter = class {
|
|
|
7059
7744
|
/**
|
|
7060
7745
|
* Opens the transport of a socket to the handler `url` routes to: a handler
|
|
7061
7746
|
* of this page directly, any other through the ServiceWorker. Resolves once
|
|
7062
|
-
* the handler accepted.
|
|
7063
|
-
* `createWebSocketClass(adapter.connectWebSocket)`.
|
|
7747
|
+
* the handler accepted. The handler sees `headers` on the upgrade request. An arrow function,
|
|
7748
|
+
* so it can be handed around: `createWebSocketClass(adapter.connectWebSocket)`.
|
|
7064
7749
|
*/
|
|
7065
|
-
connectWebSocket = async (url, protocols = []) => {
|
|
7750
|
+
connectWebSocket = async (url, protocols = [], headers) => {
|
|
7066
7751
|
const httpUrl = new URL(`${url}`, globalThis.location?.href);
|
|
7067
7752
|
if (httpUrl.protocol === "ws:") httpUrl.protocol = "http:";
|
|
7068
7753
|
else if (httpUrl.protocol === "wss:") httpUrl.protocol = "https:";
|
|
@@ -7070,7 +7755,8 @@ var SwHttpAdapter = class {
|
|
|
7070
7755
|
if (local) {
|
|
7071
7756
|
const result = await serveWebSocketUpgrade(local, {
|
|
7072
7757
|
url: `${httpUrl}`,
|
|
7073
|
-
protocols
|
|
7758
|
+
protocols,
|
|
7759
|
+
headers
|
|
7074
7760
|
});
|
|
7075
7761
|
if (!result.ok) throw new Error(result.message);
|
|
7076
7762
|
return {
|
|
@@ -7081,7 +7767,8 @@ var SwHttpAdapter = class {
|
|
|
7081
7767
|
await this.start();
|
|
7082
7768
|
return await callChannelWithin(this._callTimeout(), this._worker(), WS_CONNECT_CALL, {
|
|
7083
7769
|
url: `${httpUrl}`,
|
|
7084
|
-
protocols
|
|
7770
|
+
protocols,
|
|
7771
|
+
headers: headerPairs(headers)
|
|
7085
7772
|
});
|
|
7086
7773
|
};
|
|
7087
7774
|
/**
|
|
@@ -7377,11 +8064,11 @@ function serveHttpInWorker(handler, options = {}) {
|
|
|
7377
8064
|
};
|
|
7378
8065
|
scope.addEventListener("message", onMessage);
|
|
7379
8066
|
const baseUrl = () => globalThis.location?.href;
|
|
7380
|
-
const connectWebSocket = (url, protocols = []) => {
|
|
8067
|
+
const connectWebSocket = (url, protocols = [], headers) => {
|
|
7381
8068
|
const httpUrl = new URL(`${url}`, baseUrl());
|
|
7382
8069
|
if (httpUrl.protocol === "ws:") httpUrl.protocol = "http:";
|
|
7383
8070
|
else if (httpUrl.protocol === "wss:") httpUrl.protocol = "https:";
|
|
7384
|
-
return endpoint.connectWebSocket(`${httpUrl}`, protocols);
|
|
8071
|
+
return endpoint.connectWebSocket(`${httpUrl}`, protocols, headers);
|
|
7385
8072
|
};
|
|
7386
8073
|
let WebSocketClass;
|
|
7387
8074
|
return {
|
|
@@ -7408,4 +8095,4 @@ function serveHttpInWorker(handler, options = {}) {
|
|
|
7408
8095
|
};
|
|
7409
8096
|
}
|
|
7410
8097
|
//#endregion
|
|
7411
|
-
export { CLAIM_CALL, DEFAULT_CLOSE_TIMEOUT_MS, DEFAULT_HEARTBEAT_MS, DEFAULT_SERVICE_WORKER_TIMEOUT, DuplexSiteBuilder, HANDLER_LOST_MESSAGE, HTTP_LINK_PROTOCOL, HandlerEndpoint, HandlerTable, HttpError, HttpParseError, LinkLostError, PEER_ERROR_HEADER, PortRendezvous, ROUTER_LOST_MESSAGE, ServerWebSocket, ServiceWorkerControlError, SwHttpAdapter, SwHttpDispatcher, SwPortDispatcher, SwPortHandler, TransportClosedError, WS_PORT_PROTOCOL_VERSION, WS_VERSION_HEADER, WsCloseEvent, answerConnect, awaitActiveServiceWorker, awaitServiceWorkerControl, callChannel, callChannelWithin, callHttpService, callWebSocketService, collect, collectBytes, collectString, connectHttpLink, connectWebSocketThroughServiceWorker, createWebSocketClass, decodeJsonl, decodeMessage, decodeText, defaultCodec, deserializeError, emulateMux, encodeJsonl, encodeMessage, encodeText, fetchOverDuplex, fromReadableStream, getRelayWindowMessageHandler, handleChannelCalls, handleClaimRequests, handleHttpRequests, handleStreams, holdLock, httpCodec, httpFetch, httpServe, initHttpService, initServiceWorker, initializeConnection, isWebSocketUpgrade, joinLines, jsonEnvelopeCodec, leastInflightRoute, locksAvailable, map, newAsyncGenerator, newCreditGrantor, newCreditLedger, newHttpClientStub, newHttpCodec, newHttpServerStub, newInvokationChannel, newLockName, newRegistry, newRemoteRelayChannel, newServiceWorkerPort, newSniffingCodec, newWebSocketUpgradeRequest, normalizeToUint8Array, recieveIterator, registerConnectionsHandler, relayWebSocketClass, requestedProtocols, sendHttpRequest, sendIterator, sendOverConnection, sendStream, serializeError, serveFetchOverDuplex, serveHttpInWorker, serveHttpLink, serveWebSocketUpgrade, serviceWorkerWebSocketClass, splitLines, splitServiceUrl, startHttpDispatcher, takePort, takeUpgrade, toChunks, toReadableStream, trackedLink, unwrapTransfer, upgradeWebSocket, watchLock, withHeaders, withTransfer };
|
|
8098
|
+
export { CLAIM_CALL, DEFAULT_CLOSE_TIMEOUT_MS, DEFAULT_HEARTBEAT_MS, DEFAULT_SERVICE_WORKER_TIMEOUT, DuplexSiteBuilder, EXCHANGE_ABANDONED_MESSAGE, HANDLER_LOST_MESSAGE, HTTP_LINK_PROTOCOL, HandlerEndpoint, HandlerTable, HttpError, HttpParseError, LinkLostError, PEER_ERROR_HEADER, PortRendezvous, ROUTER_LOST_MESSAGE, ServerWebSocket, ServiceWorkerControlError, SwHttpAdapter, SwHttpDispatcher, SwPortDispatcher, SwPortHandler, TransportClosedError, WS_PORT_PROTOCOL_VERSION, WS_VERSION_HEADER, WsCloseEvent, answerConnect, awaitActiveServiceWorker, awaitServiceWorkerControl, callChannel, callChannelWithin, callHttpService, callWebSocketService, collect, collectBytes, collectString, connectHttpLink, connectWebSocketThroughServiceWorker, createWebSocketClass, decodeJsonl, decodeMessage, decodeText, defaultCodec, deserializeError, emulateMux, encodeJsonl, encodeMessage, encodeText, fetchOverDuplex, fromReadableStream, getRelayWindowMessageHandler, handleChannelCalls, handleClaimRequests, handleHttpRequests, handleStreams, holdLock, httpCodec, httpFetch, httpServe, initHttpService, initServiceWorker, initializeConnection, isWebSocketUpgrade, joinLines, jsonEnvelopeCodec, leastInflightRoute, locksAvailable, map, newAsyncGenerator, newCreditGrantor, newCreditLedger, newHttpClientStub, newHttpCodec, newHttpServerStub, newInvokationChannel, newLockName, newRegistry, newRemoteRelayChannel, newServiceWorkerPort, newSniffingCodec, newWebSocketUpgradeRequest, normalizeToUint8Array, recieveIterator, registerConnectionsHandler, relayWebSocketClass, requestedProtocols, scopeKey, sendHttpRequest, sendIterator, sendOverConnection, sendStream, serializeError, serveFetchOverDuplex, serveHttpInWorker, serveHttpLink, serveWebSocketUpgrade, serviceWorkerWebSocketClass, splitLines, splitServiceUrl, startHttpDispatcher, takePort, takeUpgrade, toChunks, toReadableStream, trackedLink, unwrapTransfer, upgradeWebSocket, watchLock, webSocketUpgradeResponse, withHeaders, withTransfer };
|