@sproutboat/runtime 0.16.0 → 0.17.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/CHANGELOG.md +68 -0
- package/WEB_STREAMS.md +7 -3
- package/package.json +1 -1
- package/src/asset-rules.test.js +125 -0
- package/src/egress.test.ts +248 -5
- package/src/gen-streams.ts +4 -5
- package/src/native-fetch-prelude.js +776 -112
- package/src/streams-entry.js +17 -3
- package/src/transport-broker.js +12 -0
- package/src/transport-embedded.js +403 -71
- package/test/native-bodies.ts +136 -0
- package/test/native-errors.ts +101 -0
- package/test/native-events.ts +102 -0
- package/test/native-iterators.ts +6 -1
- package/test/native-json.ts +102 -0
- package/test/native-raw-body.ts +118 -0
- package/test/native-request.ts +101 -0
- package/test/native-response-defaults.ts +98 -0
- package/test/native-streaming.ts +117 -0
- package/test/native-url.ts +145 -0
- package/test/native-waituntil.ts +107 -0
- package/test/native-web-apis.ts +110 -0
|
@@ -1,37 +1,6 @@
|
|
|
1
1
|
// WEB STREAMS: generated by gen-streams.ts, do not edit.
|
|
2
2
|
/* oxlint-disable -- generated third-party implementation */
|
|
3
|
-
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __returnValue = (v) => v;
|
|
6
|
-
function __exportSetter(name, newValue) {
|
|
7
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
8
|
-
}
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, {
|
|
12
|
-
get: all[name],
|
|
13
|
-
enumerable: true,
|
|
14
|
-
configurable: true,
|
|
15
|
-
set: __exportSetter.bind(all, name)
|
|
16
|
-
});
|
|
17
|
-
};
|
|
18
3
|
// web-streams-polyfill 4.3.0, MIT, copyright Mattias Buelens (see WEB_STREAMS_LICENSE)
|
|
19
|
-
var exports_ponyfill = {};
|
|
20
|
-
__export(exports_ponyfill, {
|
|
21
|
-
ByteLengthQueuingStrategy: () => ByteLengthQueuingStrategy,
|
|
22
|
-
CountQueuingStrategy: () => CountQueuingStrategy,
|
|
23
|
-
ReadableByteStreamController: () => ReadableByteStreamController,
|
|
24
|
-
ReadableStream: () => ReadableStream,
|
|
25
|
-
ReadableStreamBYOBReader: () => ReadableStreamBYOBReader,
|
|
26
|
-
ReadableStreamBYOBRequest: () => ReadableStreamBYOBRequest,
|
|
27
|
-
ReadableStreamDefaultController: () => ReadableStreamDefaultController,
|
|
28
|
-
ReadableStreamDefaultReader: () => ReadableStreamDefaultReader,
|
|
29
|
-
TransformStream: () => TransformStream,
|
|
30
|
-
TransformStreamDefaultController: () => TransformStreamDefaultController,
|
|
31
|
-
WritableStream: () => WritableStream,
|
|
32
|
-
WritableStreamDefaultController: () => WritableStreamDefaultController,
|
|
33
|
-
WritableStreamDefaultWriter: () => WritableStreamDefaultWriter
|
|
34
|
-
});
|
|
35
4
|
function e() {}
|
|
36
5
|
function t(e) {
|
|
37
6
|
return typeof e == "object" && e !== null || typeof e == "function";
|
|
@@ -332,8 +301,7 @@
|
|
|
332
301
|
return ue(n, 0, e, t, o), n;
|
|
333
302
|
}
|
|
334
303
|
function he(e, t) {
|
|
335
|
-
|
|
336
|
-
if (r == null && t === Symbol.iterator && Array.isArray(e)) r = () => e.values();
|
|
304
|
+
const r = e[t];
|
|
337
305
|
if (r != null) {
|
|
338
306
|
if (typeof r != "function")
|
|
339
307
|
throw new TypeError(`${String(t)} is not a function`);
|
|
@@ -2307,10 +2275,32 @@
|
|
|
2307
2275
|
}
|
|
2308
2276
|
Object.defineProperties(TransformStreamDefaultController.prototype, { enqueue: { enumerable: true }, error: { enumerable: true }, terminate: { enumerable: true }, desiredSize: { enumerable: true } }), o(TransformStreamDefaultController.prototype.enqueue, "enqueue"), o(TransformStreamDefaultController.prototype.error, "error"), o(TransformStreamDefaultController.prototype.terminate, "terminate"), typeof Symbol.toStringTag == "symbol" && Object.defineProperty(TransformStreamDefaultController.prototype, Symbol.toStringTag, { value: "TransformStreamDefaultController", configurable: true });
|
|
2309
2277
|
// streams-entry.js
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2278
|
+
if (globalThis.ByteLengthQueuingStrategy == null)
|
|
2279
|
+
globalThis.ByteLengthQueuingStrategy = ByteLengthQueuingStrategy;
|
|
2280
|
+
if (globalThis.CountQueuingStrategy == null)
|
|
2281
|
+
globalThis.CountQueuingStrategy = CountQueuingStrategy;
|
|
2282
|
+
if (globalThis.ReadableByteStreamController == null)
|
|
2283
|
+
globalThis.ReadableByteStreamController = ReadableByteStreamController;
|
|
2284
|
+
if (globalThis.ReadableStream == null)
|
|
2285
|
+
globalThis.ReadableStream = ReadableStream;
|
|
2286
|
+
if (globalThis.ReadableStreamBYOBReader == null)
|
|
2287
|
+
globalThis.ReadableStreamBYOBReader = ReadableStreamBYOBReader;
|
|
2288
|
+
if (globalThis.ReadableStreamBYOBRequest == null)
|
|
2289
|
+
globalThis.ReadableStreamBYOBRequest = ReadableStreamBYOBRequest;
|
|
2290
|
+
if (globalThis.ReadableStreamDefaultController == null)
|
|
2291
|
+
globalThis.ReadableStreamDefaultController = ReadableStreamDefaultController;
|
|
2292
|
+
if (globalThis.ReadableStreamDefaultReader == null)
|
|
2293
|
+
globalThis.ReadableStreamDefaultReader = ReadableStreamDefaultReader;
|
|
2294
|
+
if (globalThis.TransformStream == null)
|
|
2295
|
+
globalThis.TransformStream = TransformStream;
|
|
2296
|
+
if (globalThis.TransformStreamDefaultController == null)
|
|
2297
|
+
globalThis.TransformStreamDefaultController = TransformStreamDefaultController;
|
|
2298
|
+
if (globalThis.WritableStream == null)
|
|
2299
|
+
globalThis.WritableStream = WritableStream;
|
|
2300
|
+
if (globalThis.WritableStreamDefaultController == null)
|
|
2301
|
+
globalThis.WritableStreamDefaultController = WritableStreamDefaultController;
|
|
2302
|
+
if (globalThis.WritableStreamDefaultWriter == null)
|
|
2303
|
+
globalThis.WritableStreamDefaultWriter = WritableStreamDefaultWriter;
|
|
2314
2304
|
|
|
2315
2305
|
/* oxlint-enable */
|
|
2316
2306
|
// END WEB STREAMS
|
|
@@ -2435,8 +2425,38 @@ function __sbCollectionIterator(owner, kind, formData) {
|
|
|
2435
2425
|
|
|
2436
2426
|
class __SproutboatURLSearchParams {
|
|
2437
2427
|
constructor(init) {
|
|
2428
|
+
this._url = null;
|
|
2429
|
+
this._reset(init);
|
|
2430
|
+
}
|
|
2431
|
+
// A URL's searchParams writes back into it (_url), and is re-read when the URL's
|
|
2432
|
+
// search is assigned (_reset).
|
|
2433
|
+
_reset(init) {
|
|
2438
2434
|
this._keys = [];
|
|
2439
2435
|
this._vals = [];
|
|
2436
|
+
// The URLSearchParams init forms: another URLSearchParams, a sequence of [name, value]
|
|
2437
|
+
// pairs, a record of own string keys, or a query string. Only strings were read, so
|
|
2438
|
+
// new URLSearchParams({ a: "1" }) serialized as "%5Bobject+Object%5D=" (W2).
|
|
2439
|
+
if (init instanceof __SproutboatURLSearchParams) {
|
|
2440
|
+
this._keys = init._keys.slice();
|
|
2441
|
+
this._vals = init._vals.slice();
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
if (init != null && Object(init) === init) {
|
|
2445
|
+
if (__sbIsFn(init[Symbol.iterator])) {
|
|
2446
|
+
for (const pair of init) {
|
|
2447
|
+
const items = Array.from(pair);
|
|
2448
|
+
if (items.length !== 2) throw new TypeError("URLSearchParams: each pair must have exactly two items");
|
|
2449
|
+
this._keys.push(String(items[0]));
|
|
2450
|
+
this._vals.push(String(items[1]));
|
|
2451
|
+
}
|
|
2452
|
+
return;
|
|
2453
|
+
}
|
|
2454
|
+
for (const key of Object.keys(init)) {
|
|
2455
|
+
this._keys.push(key);
|
|
2456
|
+
this._vals.push(String(init[key]));
|
|
2457
|
+
}
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2440
2460
|
let raw = init == null ? "" : String(init);
|
|
2441
2461
|
if (raw.charCodeAt(0) === 63) raw = raw.slice(1); // strip a leading '?'
|
|
2442
2462
|
if (raw.length === 0) return;
|
|
@@ -2451,6 +2471,11 @@ class __SproutboatURLSearchParams {
|
|
|
2451
2471
|
this._vals.push(decodeURIComponent(v.split("+").join(" ")));
|
|
2452
2472
|
}
|
|
2453
2473
|
}
|
|
2474
|
+
_update() {
|
|
2475
|
+
if (this._url == null) return;
|
|
2476
|
+
const s = this.toString();
|
|
2477
|
+
this._url._query = s === "" ? null : s;
|
|
2478
|
+
}
|
|
2454
2479
|
get(name) {
|
|
2455
2480
|
for (let i = 0; i < this._keys.length; i++) if (this._keys[i] === name) return this._vals[i];
|
|
2456
2481
|
return null;
|
|
@@ -2467,12 +2492,10 @@ class __SproutboatURLSearchParams {
|
|
|
2467
2492
|
forEach(cb) {
|
|
2468
2493
|
for (let i = 0; i < this._keys.length; i++) cb(this._vals[i], this._keys[i], this);
|
|
2469
2494
|
}
|
|
2470
|
-
// Mutators: standalone `new URLSearchParams()` building works. They do NOT
|
|
2471
|
-
// write back into a URL's `search` (Porffor's URL has no setter) — build the
|
|
2472
|
-
// string with toString() and assign it yourself.
|
|
2473
2495
|
append(name, value) {
|
|
2474
2496
|
this._keys.push(String(name));
|
|
2475
2497
|
this._vals.push(String(value));
|
|
2498
|
+
this._update();
|
|
2476
2499
|
}
|
|
2477
2500
|
set(name, value) {
|
|
2478
2501
|
let found = false;
|
|
@@ -2487,7 +2510,11 @@ class __SproutboatURLSearchParams {
|
|
|
2487
2510
|
found = true;
|
|
2488
2511
|
}
|
|
2489
2512
|
}
|
|
2490
|
-
if (!found)
|
|
2513
|
+
if (!found) {
|
|
2514
|
+
this._keys.push(String(name));
|
|
2515
|
+
this._vals.push(String(value));
|
|
2516
|
+
}
|
|
2517
|
+
this._update();
|
|
2491
2518
|
}
|
|
2492
2519
|
delete(name) {
|
|
2493
2520
|
for (let i = 0; i < this._keys.length; i++)
|
|
@@ -2496,13 +2523,15 @@ class __SproutboatURLSearchParams {
|
|
|
2496
2523
|
this._vals.splice(i, 1);
|
|
2497
2524
|
i--;
|
|
2498
2525
|
}
|
|
2526
|
+
this._update();
|
|
2499
2527
|
}
|
|
2500
2528
|
sort() {
|
|
2501
2529
|
const idx = this._keys
|
|
2502
2530
|
.map((_, i) => i)
|
|
2503
|
-
.sort((a, b) => (this._keys[a] < this._keys[b] ? -1 : this._keys[a] > this._keys[b] ? 1 :
|
|
2531
|
+
.sort((a, b) => (this._keys[a] < this._keys[b] ? -1 : this._keys[a] > this._keys[b] ? 1 : a - b));
|
|
2504
2532
|
this._keys = idx.map((i) => this._keys[i]);
|
|
2505
2533
|
this._vals = idx.map((i) => this._vals[i]);
|
|
2534
|
+
this._update();
|
|
2506
2535
|
}
|
|
2507
2536
|
keys() {
|
|
2508
2537
|
return __sbCollectionIterator(this, 0, false);
|
|
@@ -2517,15 +2546,34 @@ class __SproutboatURLSearchParams {
|
|
|
2517
2546
|
get size() {
|
|
2518
2547
|
return this._keys.length;
|
|
2519
2548
|
}
|
|
2549
|
+
// application/x-www-form-urlencoded: space is "+", and ! ' ( ) ~ are escaped too.
|
|
2520
2550
|
toString() {
|
|
2521
2551
|
let out = "";
|
|
2522
2552
|
for (let i = 0; i < this._keys.length; i++) {
|
|
2523
2553
|
if (i > 0) out += "&";
|
|
2524
|
-
out +=
|
|
2554
|
+
out += __sbFormEncode(this._keys[i]) + "=" + __sbFormEncode(this._vals[i]);
|
|
2525
2555
|
}
|
|
2526
2556
|
return out;
|
|
2527
2557
|
}
|
|
2528
2558
|
}
|
|
2559
|
+
// One pass over encodeURIComponent's output (chained split/join doubled compile time).
|
|
2560
|
+
function __sbFormEncode(s) {
|
|
2561
|
+
const e = encodeURIComponent(s);
|
|
2562
|
+
let out = "";
|
|
2563
|
+
for (let i = 0; i < e.length; i++) {
|
|
2564
|
+
const c = e.charCodeAt(i);
|
|
2565
|
+
if (c === 33) out += "%21";
|
|
2566
|
+
else if (c === 39) out += "%27";
|
|
2567
|
+
else if (c === 40) out += "%28";
|
|
2568
|
+
else if (c === 41) out += "%29";
|
|
2569
|
+
else if (c === 126) out += "%7E";
|
|
2570
|
+
else if (c === 37 && e.charCodeAt(i + 1) === 50 && e.charCodeAt(i + 2) === 48) {
|
|
2571
|
+
out += "+";
|
|
2572
|
+
i += 2;
|
|
2573
|
+
} else out += e[i];
|
|
2574
|
+
}
|
|
2575
|
+
return out;
|
|
2576
|
+
}
|
|
2529
2577
|
|
|
2530
2578
|
// URL and Response are always defined by Porffor's fetch-globals.js banner.
|
|
2531
2579
|
if (globalThis.URLSearchParams == null) globalThis.URLSearchParams = __SproutboatURLSearchParams;
|
|
@@ -2542,19 +2590,39 @@ if (!("searchParams" in URL.prototype)) {
|
|
|
2542
2590
|
|
|
2543
2591
|
if (Response.json == null) {
|
|
2544
2592
|
Response.json = function (data, init) {
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2593
|
+
// Typed before construction: a string body would otherwise default to text/plain.
|
|
2594
|
+
const headers = new Headers(init?.headers);
|
|
2595
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
2596
|
+
return new Response(JSON.stringify(data), { status: init?.status ?? 200, statusText: init?.statusText, headers });
|
|
2548
2597
|
};
|
|
2549
2598
|
}
|
|
2550
2599
|
|
|
2600
|
+
// Error.stack: Porffor keeps no frames, so give V8's first line, "Name: message", rather
|
|
2601
|
+
// than undefined (code logs it or splits it on newlines).
|
|
2602
|
+
for (const E of [Error, TypeError, RangeError, ReferenceError, SyntaxError, EvalError, URIError]) {
|
|
2603
|
+
if (!("stack" in E.prototype)) {
|
|
2604
|
+
Object.defineProperty(E.prototype, "stack", {
|
|
2605
|
+
configurable: true,
|
|
2606
|
+
get() {
|
|
2607
|
+
const message = String(this.message);
|
|
2608
|
+
return message === "" ? String(this.name) : String(this.name) + ": " + message;
|
|
2609
|
+
},
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
// Fetch: Headers iterates like entries(), so new Map(headers), [...headers] and
|
|
2615
|
+
// Object.fromEntries(headers) work. Porffor's Headers has entries() but no iterator.
|
|
2616
|
+
if (Headers.prototype[Symbol.iterator] == null) Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
|
|
2617
|
+
|
|
2551
2618
|
// Porffor alpha-10 has no static Response.redirect().
|
|
2552
2619
|
if (Response.redirect == null) {
|
|
2553
2620
|
Response.redirect = function (url, status = 302) {
|
|
2554
2621
|
if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) {
|
|
2555
2622
|
throw new RangeError("invalid redirect status");
|
|
2556
2623
|
}
|
|
2557
|
-
|
|
2624
|
+
// Serialized like the URL parser does (a bare origin gains its "/"); invalid URLs throw.
|
|
2625
|
+
return new Response(null, { status, headers: { location: new URL(String(url)).href } });
|
|
2558
2626
|
};
|
|
2559
2627
|
}
|
|
2560
2628
|
|
|
@@ -2626,11 +2694,21 @@ if (globalThis.FormData == null) {
|
|
|
2626
2694
|
constructor() {
|
|
2627
2695
|
this._entries = [];
|
|
2628
2696
|
}
|
|
2697
|
+
// A Blob value is stored as a File named by filename ("blob" by default); anything
|
|
2698
|
+
// else as a string, as the FormData spec's entry list does.
|
|
2699
|
+
static _entryValue(value, filename) {
|
|
2700
|
+
if (value instanceof Blob) {
|
|
2701
|
+
if (filename === undefined && value instanceof File) return value;
|
|
2702
|
+
return new File([value], filename === undefined ? "blob" : String(filename), { type: value.type });
|
|
2703
|
+
}
|
|
2704
|
+
return String(value);
|
|
2705
|
+
}
|
|
2629
2706
|
append(name, value, filename) {
|
|
2630
|
-
this._entries.push([String(name), value, filename]);
|
|
2707
|
+
this._entries.push([String(name), __SproutboatFormData._entryValue(value, filename), filename]);
|
|
2631
2708
|
}
|
|
2632
2709
|
set(name, value, filename) {
|
|
2633
2710
|
name = String(name);
|
|
2711
|
+
value = __SproutboatFormData._entryValue(value, filename);
|
|
2634
2712
|
let replaced = false;
|
|
2635
2713
|
this._entries = this._entries.filter((e) => {
|
|
2636
2714
|
if (e[0] !== name) return true;
|
|
@@ -2786,7 +2864,12 @@ if (globalThis.crypto.getRandomValues == null) {
|
|
|
2786
2864
|
globalThis.crypto.getRandomValues = function (view) {
|
|
2787
2865
|
const n = view.length >>> 0;
|
|
2788
2866
|
// WebCrypto caps a single call at 65536 bytes.
|
|
2789
|
-
if (n > 65536)
|
|
2867
|
+
if (n > 65536) {
|
|
2868
|
+
// WebCrypto's error for an oversized request (a DOMException named QuotaExceededError).
|
|
2869
|
+
const e = new Error("crypto.getRandomValues: byte length exceeds 65536");
|
|
2870
|
+
e.name = "QuotaExceededError";
|
|
2871
|
+
throw e;
|
|
2872
|
+
}
|
|
2790
2873
|
if (n === 0) return view;
|
|
2791
2874
|
// One CSPRNG byte per element. Correct for Uint8Array (and randomUUID); a
|
|
2792
2875
|
// wider view gets its low byte filled, matching the previous polyfill's shape.
|
|
@@ -2796,17 +2879,282 @@ if (globalThis.crypto.getRandomValues == null) {
|
|
|
2796
2879
|
return view;
|
|
2797
2880
|
};
|
|
2798
2881
|
}
|
|
2799
|
-
//
|
|
2800
|
-
//
|
|
2882
|
+
// queueMicrotask, and atob/btoa input checks as the HTML spec has them: atob drops ASCII
|
|
2883
|
+
// whitespace and rejects anything that is not base64, btoa rejects characters above
|
|
2884
|
+
// U+00FF. Both threw nothing and returned garbage before (W2 conformance).
|
|
2885
|
+
{
|
|
2886
|
+
const invalidCharacter = (message) => {
|
|
2887
|
+
const e = new Error(message);
|
|
2888
|
+
e.name = "InvalidCharacterError";
|
|
2889
|
+
return e;
|
|
2890
|
+
};
|
|
2891
|
+
if (globalThis.queueMicrotask == null) {
|
|
2892
|
+
globalThis.queueMicrotask = function (callback) {
|
|
2893
|
+
if (!__sbIsFn(callback)) throw new TypeError("queueMicrotask needs a function");
|
|
2894
|
+
Promise.resolve().then(() => callback());
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
const nativeAtob = globalThis.atob, nativeBtoa = globalThis.btoa;
|
|
2898
|
+
globalThis.atob = function (data) {
|
|
2899
|
+
let text = String(data).replace(/[\t\n\f\r ]/g, "");
|
|
2900
|
+
if (text.length % 4 === 0) text = text.replace(/==?$/, "");
|
|
2901
|
+
if (text.length % 4 === 1 || !/^[A-Za-z0-9+/]*$/.test(text)) throw invalidCharacter("atob: the string is not valid base64");
|
|
2902
|
+
while (text.length % 4 !== 0) text += "=";
|
|
2903
|
+
return nativeAtob(text);
|
|
2904
|
+
};
|
|
2905
|
+
globalThis.btoa = function (data) {
|
|
2906
|
+
const text = String(data);
|
|
2907
|
+
for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) > 0xff) throw invalidCharacter("btoa: the string has characters outside Latin-1");
|
|
2908
|
+
return nativeBtoa(text);
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
/* oxlint-disable anti-slop/no-runtime-typeof -- these Web API polyfills branch on JavaScript types
|
|
2913
|
+
exactly where their specs do (listener: function or { handleEvent }; structured clone: by value type). */
|
|
2914
|
+
// Event, CustomEvent, EventTarget, AbortSignal and AbortController, as workerd has
|
|
2915
|
+
// them (no DOM tree, so no bubbling or capture phases). Found missing by the W2
|
|
2916
|
+
// conformance suite; libraries use AbortController for fetch timeouts and EventTarget
|
|
2917
|
+
// for their own events.
|
|
2918
|
+
if (typeof globalThis.EventTarget !== "function") {
|
|
2919
|
+
const namedError = (name, message) => {
|
|
2920
|
+
const e = new Error(message);
|
|
2921
|
+
e.name = name;
|
|
2922
|
+
return e;
|
|
2923
|
+
};
|
|
2924
|
+
class Event {
|
|
2925
|
+
constructor(type, init) {
|
|
2926
|
+
if (arguments.length === 0) throw new TypeError("Event requires a type");
|
|
2927
|
+
const options = init || {};
|
|
2928
|
+
this.type = String(type);
|
|
2929
|
+
this.bubbles = !!options.bubbles;
|
|
2930
|
+
this.cancelable = !!options.cancelable;
|
|
2931
|
+
this.composed = !!options.composed;
|
|
2932
|
+
this.defaultPrevented = false;
|
|
2933
|
+
this.target = null;
|
|
2934
|
+
this.currentTarget = null;
|
|
2935
|
+
this.eventPhase = 0;
|
|
2936
|
+
this.isTrusted = false;
|
|
2937
|
+
this.timeStamp = Date.now();
|
|
2938
|
+
this.__sbStopped = false;
|
|
2939
|
+
}
|
|
2940
|
+
preventDefault() { if (this.cancelable) this.defaultPrevented = true; }
|
|
2941
|
+
stopPropagation() {}
|
|
2942
|
+
stopImmediatePropagation() { this.__sbStopped = true; }
|
|
2943
|
+
composedPath() { return this.currentTarget ? [this.currentTarget] : []; }
|
|
2944
|
+
}
|
|
2945
|
+
class CustomEvent extends Event {
|
|
2946
|
+
constructor(type, init) {
|
|
2947
|
+
super(type, init);
|
|
2948
|
+
this.detail = init && init.detail !== undefined ? init.detail : null;
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
class EventTarget {
|
|
2952
|
+
constructor() { this.__sbListeners = {}; }
|
|
2953
|
+
addEventListener(type, listener, options) {
|
|
2954
|
+
if (listener == null) return;
|
|
2955
|
+
const capture = typeof options === "boolean" ? options : !!(options && options.capture);
|
|
2956
|
+
const once = !!(options && typeof options === "object" && options.once);
|
|
2957
|
+
const signal = options && typeof options === "object" ? options.signal : undefined;
|
|
2958
|
+
if (signal && signal.aborted) return;
|
|
2959
|
+
const list = this.__sbListeners[type] || (this.__sbListeners[type] = []);
|
|
2960
|
+
for (const entry of list) if (entry.listener === listener && entry.capture === capture) return;
|
|
2961
|
+
const entry = { listener, capture, once, removed: false };
|
|
2962
|
+
list.push(entry);
|
|
2963
|
+
if (signal) signal.addEventListener("abort", () => this.removeEventListener(type, listener, capture));
|
|
2964
|
+
}
|
|
2965
|
+
removeEventListener(type, listener, options) {
|
|
2966
|
+
const capture = typeof options === "boolean" ? options : !!(options && options.capture);
|
|
2967
|
+
const list = this.__sbListeners[type];
|
|
2968
|
+
if (!list) return;
|
|
2969
|
+
for (let i = 0; i < list.length; i++) {
|
|
2970
|
+
if (list[i].listener === listener && list[i].capture === capture) {
|
|
2971
|
+
list[i].removed = true;
|
|
2972
|
+
list.splice(i, 1);
|
|
2973
|
+
return;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
dispatchEvent(event) {
|
|
2978
|
+
if (!(event instanceof Event)) throw new TypeError("dispatchEvent needs an Event");
|
|
2979
|
+
event.target = this;
|
|
2980
|
+
event.currentTarget = this;
|
|
2981
|
+
event.eventPhase = 2;
|
|
2982
|
+
const list = (this.__sbListeners[event.type] || []).slice();
|
|
2983
|
+
for (const entry of list) {
|
|
2984
|
+
if (entry.removed) continue;
|
|
2985
|
+
if (entry.once) this.removeEventListener(event.type, entry.listener, entry.capture);
|
|
2986
|
+
const fn = entry.listener;
|
|
2987
|
+
if (typeof fn === "function") fn.call(this, event);
|
|
2988
|
+
else if (fn && typeof fn.handleEvent === "function") fn.handleEvent(event);
|
|
2989
|
+
if (event.__sbStopped) break;
|
|
2990
|
+
}
|
|
2991
|
+
event.currentTarget = null;
|
|
2992
|
+
event.eventPhase = 0;
|
|
2993
|
+
return !event.defaultPrevented;
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
class AbortSignal extends EventTarget {
|
|
2997
|
+
constructor() {
|
|
2998
|
+
super();
|
|
2999
|
+
this.aborted = false;
|
|
3000
|
+
this.reason = undefined;
|
|
3001
|
+
this.onabort = null;
|
|
3002
|
+
}
|
|
3003
|
+
throwIfAborted() { if (this.aborted) throw this.reason; }
|
|
3004
|
+
__sbAbort(reason) {
|
|
3005
|
+
if (this.aborted) return;
|
|
3006
|
+
this.aborted = true;
|
|
3007
|
+
this.reason = reason === undefined ? namedError("AbortError", "This operation was aborted") : reason;
|
|
3008
|
+
const event = new Event("abort");
|
|
3009
|
+
if (typeof this.onabort === "function") this.onabort.call(this, event);
|
|
3010
|
+
this.dispatchEvent(event);
|
|
3011
|
+
}
|
|
3012
|
+
static abort(reason) {
|
|
3013
|
+
const signal = new AbortSignal();
|
|
3014
|
+
signal.__sbAbort(reason);
|
|
3015
|
+
return signal;
|
|
3016
|
+
}
|
|
3017
|
+
static timeout(ms) {
|
|
3018
|
+
const signal = new AbortSignal();
|
|
3019
|
+
setTimeout(() => signal.__sbAbort(namedError("TimeoutError", "The operation timed out")), ms);
|
|
3020
|
+
return signal;
|
|
3021
|
+
}
|
|
3022
|
+
static any(signals) {
|
|
3023
|
+
const signal = new AbortSignal();
|
|
3024
|
+
for (const s of signals) {
|
|
3025
|
+
if (s.aborted) {
|
|
3026
|
+
signal.__sbAbort(s.reason);
|
|
3027
|
+
return signal;
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
for (const s of signals) s.addEventListener("abort", () => signal.__sbAbort(s.reason));
|
|
3031
|
+
return signal;
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
class AbortController {
|
|
3035
|
+
constructor() { this.signal = new AbortSignal(); }
|
|
3036
|
+
abort(reason) { this.signal.__sbAbort(reason); }
|
|
3037
|
+
}
|
|
3038
|
+
globalThis.Event = Event;
|
|
3039
|
+
globalThis.CustomEvent = CustomEvent;
|
|
3040
|
+
globalThis.EventTarget = EventTarget;
|
|
3041
|
+
globalThis.AbortSignal = AbortSignal;
|
|
3042
|
+
globalThis.AbortController = AbortController;
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
// TextEncoderStream and TextDecoderStream over the Web Streams ponyfill (W2 conformance).
|
|
3046
|
+
// The encoder holds back a lone high surrogate at the end of a chunk until the next one;
|
|
3047
|
+
// the decoder is TextDecoder in streaming mode, so a character split across chunks decodes.
|
|
3048
|
+
if (globalThis.TextEncoderStream == null) {
|
|
3049
|
+
class TextEncoderStream {
|
|
3050
|
+
constructor() {
|
|
3051
|
+
const encoder = new TextEncoder();
|
|
3052
|
+
let pending = "";
|
|
3053
|
+
const transform = new TransformStream({
|
|
3054
|
+
transform(chunk, controller) {
|
|
3055
|
+
let text = pending + String(chunk);
|
|
3056
|
+
pending = "";
|
|
3057
|
+
const last = text.charCodeAt(text.length - 1);
|
|
3058
|
+
if (last >= 0xd800 && last <= 0xdbff) {
|
|
3059
|
+
pending = text.slice(-1);
|
|
3060
|
+
text = text.slice(0, -1);
|
|
3061
|
+
}
|
|
3062
|
+
if (text.length > 0) controller.enqueue(encoder.encode(text));
|
|
3063
|
+
},
|
|
3064
|
+
flush(controller) {
|
|
3065
|
+
if (pending.length > 0) controller.enqueue(encoder.encode(pending));
|
|
3066
|
+
},
|
|
3067
|
+
});
|
|
3068
|
+
this.readable = transform.readable;
|
|
3069
|
+
this.writable = transform.writable;
|
|
3070
|
+
}
|
|
3071
|
+
get encoding() {
|
|
3072
|
+
return "utf-8";
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
class TextDecoderStream {
|
|
3076
|
+
constructor(label = "utf-8", options = undefined) {
|
|
3077
|
+
const decoder = new TextDecoder(label, options);
|
|
3078
|
+
this.encoding = decoder.encoding;
|
|
3079
|
+
this.fatal = decoder.fatal;
|
|
3080
|
+
this.ignoreBOM = decoder.ignoreBOM;
|
|
3081
|
+
const transform = new TransformStream({
|
|
3082
|
+
transform(chunk, controller) {
|
|
3083
|
+
const text = decoder.decode(chunk, { stream: true });
|
|
3084
|
+
if (text.length > 0) controller.enqueue(text);
|
|
3085
|
+
},
|
|
3086
|
+
flush(controller) {
|
|
3087
|
+
const text = decoder.decode();
|
|
3088
|
+
if (text.length > 0) controller.enqueue(text);
|
|
3089
|
+
},
|
|
3090
|
+
});
|
|
3091
|
+
this.readable = transform.readable;
|
|
3092
|
+
this.writable = transform.writable;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
globalThis.TextEncoderStream = TextEncoderStream;
|
|
3096
|
+
globalThis.TextDecoderStream = TextDecoderStream;
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3099
|
+
// structuredClone: the HTML structured-clone algorithm for the types Worker code
|
|
3100
|
+
// passes around. Keeps cycles and shared references (a memo per call), Date, RegExp,
|
|
3101
|
+
// Map, Set, ArrayBuffer, typed arrays, DataView, errors, arrays and plain objects;
|
|
3102
|
+
// functions and symbols throw DataCloneError. It replaced a JSON round-trip that lost
|
|
3103
|
+
// every non-JSON type and crashed on cycles (W2 conformance).
|
|
2801
3104
|
if (globalThis.structuredClone == null) {
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
3105
|
+
const dataCloneError = (what) => {
|
|
3106
|
+
const e = new Error(what + " could not be cloned");
|
|
3107
|
+
e.name = "DataCloneError";
|
|
3108
|
+
return e;
|
|
3109
|
+
};
|
|
3110
|
+
const errorTypes = { Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError };
|
|
3111
|
+
const clone = (value, memo) => {
|
|
3112
|
+
const type = typeof value;
|
|
3113
|
+
if (type === "function") throw dataCloneError("function");
|
|
3114
|
+
if (type === "symbol") throw dataCloneError("Symbol");
|
|
3115
|
+
if (value === null || type !== "object") return value;
|
|
3116
|
+
if (memo.has(value)) return memo.get(value);
|
|
3117
|
+
let out;
|
|
3118
|
+
if (value instanceof Date) out = new Date(value.getTime());
|
|
3119
|
+
else if (value instanceof RegExp) out = new RegExp(value.source, value.flags);
|
|
3120
|
+
else if (value instanceof ArrayBuffer) out = value.slice(0);
|
|
3121
|
+
else if (ArrayBuffer.isView(value)) {
|
|
3122
|
+
const buffer = clone(value.buffer, memo);
|
|
3123
|
+
out = value instanceof DataView ? new DataView(buffer, value.byteOffset, value.byteLength) : new value.constructor(buffer, value.byteOffset, value.length);
|
|
3124
|
+
} else if (value instanceof Map) {
|
|
3125
|
+
out = new Map();
|
|
3126
|
+
memo.set(value, out);
|
|
3127
|
+
for (const [k, v] of value) out.set(clone(k, memo), clone(v, memo));
|
|
3128
|
+
return out;
|
|
3129
|
+
} else if (value instanceof Set) {
|
|
3130
|
+
out = new Set();
|
|
3131
|
+
memo.set(value, out);
|
|
3132
|
+
for (const v of value) out.add(clone(v, memo));
|
|
3133
|
+
return out;
|
|
3134
|
+
} else if (value instanceof Error) {
|
|
3135
|
+
const Ctor = errorTypes[value.name] || Error;
|
|
3136
|
+
out = new Ctor(value.message);
|
|
3137
|
+
} else if (Array.isArray(value)) {
|
|
3138
|
+
out = new Array(value.length);
|
|
3139
|
+
memo.set(value, out);
|
|
3140
|
+
for (const k of Object.keys(value)) out[k] = clone(value[k], memo);
|
|
3141
|
+
return out;
|
|
3142
|
+
} else {
|
|
3143
|
+
out = {};
|
|
3144
|
+
memo.set(value, out);
|
|
3145
|
+
for (const k of Object.keys(value)) out[k] = clone(value[k], memo);
|
|
3146
|
+
return out;
|
|
3147
|
+
}
|
|
3148
|
+
memo.set(value, out);
|
|
3149
|
+
return out;
|
|
3150
|
+
};
|
|
2805
3151
|
globalThis.structuredClone = function (value) {
|
|
2806
|
-
return
|
|
3152
|
+
return clone(value, new Map());
|
|
2807
3153
|
};
|
|
2808
3154
|
}
|
|
2809
3155
|
|
|
3156
|
+
/* oxlint-enable anti-slop/no-runtime-typeof */
|
|
3157
|
+
|
|
2810
3158
|
if (globalThis.crypto.randomUUID == null) {
|
|
2811
3159
|
globalThis.crypto.randomUUID = function () {
|
|
2812
3160
|
const b = new Uint8Array(16);
|
|
@@ -2827,6 +3175,8 @@ if (globalThis.crypto.randomUUID == null) {
|
|
|
2827
3175
|
// dependency, so it works on the broker transport too.
|
|
2828
3176
|
function __sbHashBits(algo) {
|
|
2829
3177
|
const n = String((algo && algo.name) || algo || "").toUpperCase();
|
|
3178
|
+
if (n === "SHA-1" || n === "SHA1") return "1";
|
|
3179
|
+
if (n === "MD5") return "5";
|
|
2830
3180
|
if (n === "SHA-256" || n === "SHA256") return "256";
|
|
2831
3181
|
if (n === "SHA-384" || n === "SHA384") return "384";
|
|
2832
3182
|
if (n === "SHA-512" || n === "SHA512") return "512";
|
|
@@ -3053,42 +3403,77 @@ function __sbHexOrBytes(value) {
|
|
|
3053
3403
|
return __sbToBytes(value);
|
|
3054
3404
|
}
|
|
3055
3405
|
|
|
3406
|
+
// Errors as WebCrypto names them (DOMException names; plain Errors here).
|
|
3407
|
+
function __sbCryptoError(name, message) {
|
|
3408
|
+
const e = new Error(message);
|
|
3409
|
+
e.name = name;
|
|
3410
|
+
return e;
|
|
3411
|
+
}
|
|
3412
|
+
// An HMAC key as importKey and generateKey return it. MD5 is digest-only, as in workerd.
|
|
3413
|
+
function __sbHmacKey(bytes, bits, extractable, usages) {
|
|
3414
|
+
const hash = bits === "1" ? "SHA-1" : "SHA-" + bits;
|
|
3415
|
+
return {
|
|
3416
|
+
type: "secret",
|
|
3417
|
+
extractable: !!extractable,
|
|
3418
|
+
algorithm: { name: "HMAC", hash: { name: hash }, length: bytes.length * 8 },
|
|
3419
|
+
usages: usages || [],
|
|
3420
|
+
__sbKey: bytes,
|
|
3421
|
+
__sbBits: bits,
|
|
3422
|
+
};
|
|
3423
|
+
}
|
|
3424
|
+
function __sbHmacBits(algo) {
|
|
3425
|
+
const bits = __sbHashBits((algo && algo.hash) || "SHA-256");
|
|
3426
|
+
if (!bits || bits === "5") throw __sbCryptoError("NotSupportedError", "HMAC needs SHA-1, SHA-256, SHA-384 or SHA-512");
|
|
3427
|
+
return bits;
|
|
3428
|
+
}
|
|
3056
3429
|
if (globalThis.crypto.subtle == null) {
|
|
3057
3430
|
globalThis.crypto.subtle = {
|
|
3058
3431
|
async digest(algo, data) {
|
|
3059
3432
|
const bits = __sbHashBits(algo);
|
|
3060
|
-
if (!bits) throw
|
|
3433
|
+
if (!bits) throw __sbCryptoError("NotSupportedError", "crypto.subtle.digest: unsupported algorithm");
|
|
3061
3434
|
const out = __sbDigestRaw(bits, __sbToBytes(data));
|
|
3062
3435
|
if (!out) throw new Error("crypto.subtle.digest failed");
|
|
3063
3436
|
return __sbBufFrom(out);
|
|
3064
3437
|
},
|
|
3065
3438
|
async importKey(format, keyData, algo, extractable, usages) {
|
|
3066
|
-
if (format !== "raw") throw
|
|
3439
|
+
if (format !== "raw") throw __sbCryptoError("NotSupportedError", "crypto.subtle.importKey: only format 'raw' is supported");
|
|
3067
3440
|
const name = String((algo && algo.name) || algo || "").toUpperCase();
|
|
3068
|
-
if (name !== "HMAC") throw
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3441
|
+
if (name !== "HMAC") throw __sbCryptoError("NotSupportedError", "crypto.subtle.importKey: only HMAC keys are supported");
|
|
3442
|
+
return __sbHmacKey(__sbToBytes(keyData), __sbHmacBits(algo), extractable, usages);
|
|
3443
|
+
},
|
|
3444
|
+
async exportKey(format, key) {
|
|
3445
|
+
if (!key || key.__sbKey == null) throw new TypeError("crypto.subtle.exportKey: not a key from this runtime");
|
|
3446
|
+
if (format !== "raw") throw __sbCryptoError("NotSupportedError", "crypto.subtle.exportKey: only format 'raw' is supported");
|
|
3447
|
+
if (!key.extractable) throw __sbCryptoError("InvalidAccessError", "crypto.subtle.exportKey: the key is not extractable");
|
|
3448
|
+
return __sbBufFrom(key.__sbKey);
|
|
3449
|
+
},
|
|
3450
|
+
async generateKey(algo, extractable, usages) {
|
|
3451
|
+
const name = String((algo && algo.name) || algo || "").toUpperCase();
|
|
3452
|
+
if (name !== "HMAC") throw __sbCryptoError("NotSupportedError", "crypto.subtle.generateKey: only HMAC keys are supported");
|
|
3453
|
+
const bits = __sbHmacBits(algo);
|
|
3454
|
+
// The default length is the hash's block size: 512 bits, or 1024 for SHA-384/512.
|
|
3455
|
+
const length = (algo && algo.length) || (bits === "384" || bits === "512" ? 1024 : 512);
|
|
3456
|
+
const bytes = __sbRandomBytes(String(Math.ceil(length / 8)));
|
|
3457
|
+
return __sbHmacKey(bytes, bits, extractable, usages);
|
|
3079
3458
|
},
|
|
3080
3459
|
async sign(algo, key, data) {
|
|
3081
|
-
if (!key || key.__sbKey == null) throw
|
|
3460
|
+
if (!key || key.__sbKey == null) throw __sbCryptoError("InvalidAccessError", "crypto.subtle.sign: only HMAC keys are supported");
|
|
3082
3461
|
const out = __sbHmacRaw(key.__sbBits, key.__sbKey, __sbToBytes(data));
|
|
3083
3462
|
if (!out) throw new Error("crypto.subtle.sign failed");
|
|
3084
3463
|
return __sbBufFrom(out);
|
|
3085
3464
|
},
|
|
3086
3465
|
async verify(algo, key, signature, data) {
|
|
3087
|
-
if (!key || key.__sbKey == null) throw
|
|
3466
|
+
if (!key || key.__sbKey == null) throw __sbCryptoError("InvalidAccessError", "crypto.subtle.verify: only HMAC keys are supported");
|
|
3088
3467
|
const mac = __sbHmacRaw(key.__sbBits, key.__sbKey, __sbToBytes(data));
|
|
3089
3468
|
if (!mac) return false;
|
|
3090
3469
|
return __sbEqCt(mac, __sbToBytes(signature));
|
|
3091
3470
|
},
|
|
3471
|
+
// workerd's extension: constant-time comparison of two equal-length buffers.
|
|
3472
|
+
timingSafeEqual(a, b) {
|
|
3473
|
+
const x = __sbToBytes(a), y = __sbToBytes(b);
|
|
3474
|
+
if (x.length !== y.length) throw new TypeError("crypto.subtle.timingSafeEqual: the buffers differ in length");
|
|
3475
|
+
return __sbEqCt(x, y);
|
|
3476
|
+
},
|
|
3092
3477
|
};
|
|
3093
3478
|
}
|
|
3094
3479
|
|
|
@@ -3291,9 +3676,82 @@ static void sb_sha512_final(sb_sha512_ctx* c, unsigned char* out, int is384) {
|
|
|
3291
3676
|
for (int j = 0; j < 8; j++) out[i*8+j] = (unsigned char)(c->h[i] >> (56 - j*8));
|
|
3292
3677
|
}
|
|
3293
3678
|
|
|
3294
|
-
//
|
|
3679
|
+
// SHA-1 and MD5 (W2: workerd's digest has both; HMAC also takes SHA-1). One-shot over a
|
|
3680
|
+
// padded copy: inputs are request-sized, not streams.
|
|
3681
|
+
static uint32_t sb_rol32(uint32_t x, int n) { return (x << n) | (x >> (32 - n)); }
|
|
3682
|
+
static unsigned char* sb_md_pad(const unsigned char* msg, size_t len, size_t* total, int big_endian) {
|
|
3683
|
+
*total = ((len + 8) / 64 + 1) * 64;
|
|
3684
|
+
unsigned char* m = (unsigned char*)calloc(*total, 1);
|
|
3685
|
+
if (!m) return 0;
|
|
3686
|
+
memcpy(m, msg, len); m[len] = 0x80;
|
|
3687
|
+
uint64_t bits = (uint64_t)len * 8;
|
|
3688
|
+
for (int i = 0; i < 8; i++) m[big_endian ? *total - 1 - i : *total - 8 + i] = (unsigned char)(bits >> (8 * i));
|
|
3689
|
+
return m;
|
|
3690
|
+
}
|
|
3691
|
+
static size_t sb_sha1(const unsigned char* msg, size_t len, unsigned char out[20]) {
|
|
3692
|
+
size_t total; unsigned char* m = sb_md_pad(msg, len, &total, 1);
|
|
3693
|
+
if (!m) return 0;
|
|
3694
|
+
uint32_t h[5] = { 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 };
|
|
3695
|
+
for (size_t off = 0; off < total; off += 64) {
|
|
3696
|
+
uint32_t w[80];
|
|
3697
|
+
for (int i = 0; i < 16; i++) w[i] = ((uint32_t)m[off + 4*i] << 24) | ((uint32_t)m[off + 4*i + 1] << 16) | ((uint32_t)m[off + 4*i + 2] << 8) | m[off + 4*i + 3];
|
|
3698
|
+
for (int i = 16; i < 80; i++) w[i] = sb_rol32(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1);
|
|
3699
|
+
uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4];
|
|
3700
|
+
for (int i = 0; i < 80; i++) {
|
|
3701
|
+
uint32_t f, k;
|
|
3702
|
+
if (i < 20) { f = (b & c) | (~b & d); k = 0x5A827999; }
|
|
3703
|
+
else if (i < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1; }
|
|
3704
|
+
else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC; }
|
|
3705
|
+
else { f = b ^ c ^ d; k = 0xCA62C1D6; }
|
|
3706
|
+
uint32_t t = sb_rol32(a, 5) + f + e + k + w[i];
|
|
3707
|
+
e = d; d = c; c = sb_rol32(b, 30); b = a; a = t;
|
|
3708
|
+
}
|
|
3709
|
+
h[0] += a; h[1] += b; h[2] += c; h[3] += d; h[4] += e;
|
|
3710
|
+
}
|
|
3711
|
+
free(m);
|
|
3712
|
+
for (int i = 0; i < 5; i++) { out[4*i] = h[i] >> 24; out[4*i+1] = h[i] >> 16; out[4*i+2] = h[i] >> 8; out[4*i+3] = h[i]; }
|
|
3713
|
+
return 20;
|
|
3714
|
+
}
|
|
3715
|
+
static size_t sb_md5(const unsigned char* msg, size_t len, unsigned char out[16]) {
|
|
3716
|
+
static const uint32_t K[64] = {
|
|
3717
|
+
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
|
|
3718
|
+
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
|
|
3719
|
+
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
|
|
3720
|
+
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
|
|
3721
|
+
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
|
|
3722
|
+
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
|
|
3723
|
+
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
|
|
3724
|
+
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 };
|
|
3725
|
+
static const int S[16] = { 7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21 };
|
|
3726
|
+
size_t total; unsigned char* m = sb_md_pad(msg, len, &total, 0);
|
|
3727
|
+
if (!m) return 0;
|
|
3728
|
+
uint32_t h[4] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 };
|
|
3729
|
+
for (size_t off = 0; off < total; off += 64) {
|
|
3730
|
+
uint32_t w[16];
|
|
3731
|
+
for (int i = 0; i < 16; i++) w[i] = (uint32_t)m[off + 4*i] | ((uint32_t)m[off + 4*i + 1] << 8) | ((uint32_t)m[off + 4*i + 2] << 16) | ((uint32_t)m[off + 4*i + 3] << 24);
|
|
3732
|
+
uint32_t a = h[0], b = h[1], c = h[2], d = h[3];
|
|
3733
|
+
for (int i = 0; i < 64; i++) {
|
|
3734
|
+
uint32_t f; int g;
|
|
3735
|
+
if (i < 16) { f = (b & c) | (~b & d); g = i; }
|
|
3736
|
+
else if (i < 32) { f = (d & b) | (~d & c); g = (5*i + 1) % 16; }
|
|
3737
|
+
else if (i < 48) { f = b ^ c ^ d; g = (3*i + 5) % 16; }
|
|
3738
|
+
else { f = c ^ (b | ~d); g = (7*i) % 16; }
|
|
3739
|
+
f = f + a + K[i] + w[g];
|
|
3740
|
+
a = d; d = c; c = b;
|
|
3741
|
+
b = b + sb_rol32(f, S[(i / 16) * 4 + i % 4]);
|
|
3742
|
+
}
|
|
3743
|
+
h[0] += a; h[1] += b; h[2] += c; h[3] += d;
|
|
3744
|
+
}
|
|
3745
|
+
free(m);
|
|
3746
|
+
for (int i = 0; i < 4; i++) { out[4*i] = h[i]; out[4*i+1] = h[i] >> 8; out[4*i+2] = h[i] >> 16; out[4*i+3] = h[i] >> 24; }
|
|
3747
|
+
return 16;
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3750
|
+
// One-shot digest. algo: 1 (SHA-1) | 5 (MD5) | 256 | 384 | 512. out must hold up to 64 bytes.
|
|
3295
3751
|
// Returns the digest length, or 0 for an unknown algo.
|
|
3296
3752
|
static size_t sb_digest(int algo, const unsigned char* msg, size_t mlen, unsigned char* out) {
|
|
3753
|
+
if (algo == 1) return sb_sha1(msg, mlen, out);
|
|
3754
|
+
if (algo == 5) return sb_md5(msg, mlen, out);
|
|
3297
3755
|
if (algo == 256) { sb_sha256_ctx c; sb_sha256_init(&c); sb_sha256_update(&c, msg, mlen); sb_sha256_final(&c, out); return 32; }
|
|
3298
3756
|
if (algo == 384) { sb_sha512_ctx c; sb_sha512_init(&c, 1); sb_sha512_update(&c, msg, mlen); sb_sha512_final(&c, out, 1); return 48; }
|
|
3299
3757
|
if (algo == 512) { sb_sha512_ctx c; sb_sha512_init(&c, 0); sb_sha512_update(&c, msg, mlen); sb_sha512_final(&c, out, 0); return 64; }
|
|
@@ -3303,6 +3761,20 @@ static size_t sb_digest(int algo, const unsigned char* msg, size_t mlen, unsigne
|
|
|
3303
3761
|
// HMAC(algo) per RFC 2104. Block size is 64 for SHA-256, 128 for SHA-384/512.
|
|
3304
3762
|
static size_t sb_hmac(int algo, const unsigned char* key, size_t klen,
|
|
3305
3763
|
const unsigned char* msg, size_t mlen, unsigned char* out) {
|
|
3764
|
+
if (algo == 1) {
|
|
3765
|
+
// HMAC-SHA-1: block 64, over padded copies like the one-shot digest.
|
|
3766
|
+
unsigned char k1[64], inner1[20];
|
|
3767
|
+
memset(k1, 0, sizeof(k1));
|
|
3768
|
+
if (klen > 64) sb_sha1(key, klen, k1); else memcpy(k1, key, klen);
|
|
3769
|
+
unsigned char* buf = (unsigned char*)malloc(64 + (mlen > 20 ? mlen : 20));
|
|
3770
|
+
if (!buf) return 0;
|
|
3771
|
+
for (int i = 0; i < 64; i++) buf[i] = k1[i] ^ 0x36;
|
|
3772
|
+
memcpy(buf + 64, msg, mlen); sb_sha1(buf, 64 + mlen, inner1);
|
|
3773
|
+
for (int i = 0; i < 64; i++) buf[i] = k1[i] ^ 0x5c;
|
|
3774
|
+
memcpy(buf + 64, inner1, 20); sb_sha1(buf, 84, out);
|
|
3775
|
+
free(buf);
|
|
3776
|
+
return 20;
|
|
3777
|
+
}
|
|
3306
3778
|
size_t bs = algo == 256 ? 64 : 128;
|
|
3307
3779
|
unsigned char k[128], ipad[128], opad[128], inner[64];
|
|
3308
3780
|
memset(k, 0, sizeof(k));
|
|
@@ -3712,6 +4184,64 @@ function __sbR2Multipart(bucket, key, uploadId) {
|
|
|
3712
4184
|
};
|
|
3713
4185
|
}
|
|
3714
4186
|
|
|
4187
|
+
// Asset rules use the same parsed manifest in both transports. Matching mirrors
|
|
4188
|
+
// @sproutboat/assets; parity tests cover the portable native implementation.
|
|
4189
|
+
function __sbAssetRedirect(rule, path) {
|
|
4190
|
+
const star = rule.from.indexOf("*");
|
|
4191
|
+
if (star !== -1) {
|
|
4192
|
+
const prefix = rule.from.slice(0, star);
|
|
4193
|
+
if (!path.startsWith(prefix)) return null;
|
|
4194
|
+
return rule.to.split(":splat").join(path.slice(prefix.length));
|
|
4195
|
+
}
|
|
4196
|
+
const from = rule.from.split("/").filter(Boolean);
|
|
4197
|
+
const parts = path.split("/").filter(Boolean);
|
|
4198
|
+
if (from.length !== parts.length) return null;
|
|
4199
|
+
const params = {};
|
|
4200
|
+
for (let i = 0; i < from.length; i++) {
|
|
4201
|
+
if (from[i].startsWith(":")) params[from[i].slice(1)] = parts[i];
|
|
4202
|
+
else if (from[i] !== parts[i]) return null;
|
|
4203
|
+
}
|
|
4204
|
+
let to = rule.to;
|
|
4205
|
+
for (const name in params) to = to.split(":" + name).join(params[name]);
|
|
4206
|
+
return to;
|
|
4207
|
+
}
|
|
4208
|
+
|
|
4209
|
+
function __sbAssetHeaderMatches(pattern, path) {
|
|
4210
|
+
const from = pattern.split("/").filter(Boolean);
|
|
4211
|
+
const parts = path.split("/").filter(Boolean);
|
|
4212
|
+
for (let i = 0; i < from.length; i++) {
|
|
4213
|
+
if (from[i] === "*") return true;
|
|
4214
|
+
if (from[i] !== parts[i]) return false;
|
|
4215
|
+
}
|
|
4216
|
+
return from.length === parts.length;
|
|
4217
|
+
}
|
|
4218
|
+
|
|
4219
|
+
function __sbAssetResponse(path, r) {
|
|
4220
|
+
if (r.location != null) return new Response(null, { status: r.status, headers: { location: r.location } });
|
|
4221
|
+
const rules = r.rules || {};
|
|
4222
|
+
const redirects = rules.redirects || [];
|
|
4223
|
+
for (let i = 0; i < redirects.length; i++) {
|
|
4224
|
+
const to = __sbAssetRedirect(redirects[i], path);
|
|
4225
|
+
if (to !== null) return new Response(null, { status: redirects[i].status, headers: { location: to } });
|
|
4226
|
+
}
|
|
4227
|
+
const headers = new Headers();
|
|
4228
|
+
if (r.type) headers.set("content-type", r.type);
|
|
4229
|
+
if (r.found) headers.set("etag", '"' + r.hash + '"');
|
|
4230
|
+
// A custom 404 page is also an asset; a generated Not Found is not.
|
|
4231
|
+
if (r.type) {
|
|
4232
|
+
const list = rules.headers || [];
|
|
4233
|
+
for (let i = 0; i < list.length; i++) {
|
|
4234
|
+
const rule = list[i];
|
|
4235
|
+
if (!__sbAssetHeaderMatches(rule.pattern, path)) continue;
|
|
4236
|
+
for (let j = 0; j < rule.unset.length; j++) headers.delete(rule.unset[j]);
|
|
4237
|
+
for (let j = 0; j < rule.set.length; j++) headers.set(rule.set[j][0], rule.set[j][1]);
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
// Apply the internal byte marker after user rules so they cannot remove it.
|
|
4241
|
+
if (r.body != null) headers.set("x-sb-raw-body", "1");
|
|
4242
|
+
return __sbRawBodyResponse(r.body, { status: r.status || (r.found ? 200 : 404), headers });
|
|
4243
|
+
}
|
|
4244
|
+
|
|
3715
4245
|
// Installed only when the project declares bindings. `env` is the module-scoped
|
|
3716
4246
|
// object from compile.ts (a `const`, but mutable); we add the binding accessors
|
|
3717
4247
|
// to it in place. compile.ts emits `__sbInstallBindings(env, {...})` right after
|
|
@@ -3935,16 +4465,7 @@ globalThis.__sbInstallBindings = function (target, bindings) {
|
|
|
3935
4465
|
} catch {
|
|
3936
4466
|
/* use as-is */
|
|
3937
4467
|
}
|
|
3938
|
-
|
|
3939
|
-
const headers = {};
|
|
3940
|
-
if (r.type) headers["content-type"] = r.type;
|
|
3941
|
-
if (r.found) headers["etag"] = '"' + r.hash + '"';
|
|
3942
|
-
// #176: these bytes came off disk already-finished (UTF-8 text or
|
|
3943
|
-
// binary, doesn't matter which) -- the reserved x-sb-raw-body header
|
|
3944
|
-
// tells the C write path to pass them through untouched instead of
|
|
3945
|
-
// re-encoding as if this were a JS string a handler built.
|
|
3946
|
-
if (r.body != null) headers["x-sb-raw-body"] = "1";
|
|
3947
|
-
return __sbRawBodyResponse(r.body, { status: r.status || (r.found ? 200 : 404), headers });
|
|
4468
|
+
return __sbAssetResponse(path, globalThis.__sbAssetsGet(path));
|
|
3948
4469
|
},
|
|
3949
4470
|
};
|
|
3950
4471
|
}
|
|
@@ -3989,26 +4510,48 @@ globalThis.__sbInstallBindings = function (target, bindings) {
|
|
|
3989
4510
|
// prelude must not replace fetch just by being evaluated.
|
|
3990
4511
|
globalThis.__sbInstallFetch = function () {
|
|
3991
4512
|
globalThis.fetch = function (input, init) {
|
|
3992
|
-
|
|
4513
|
+
// A Request's method, headers and body apply unless init overrides them; a URL
|
|
4514
|
+
// object stands for its href.
|
|
4515
|
+
const req = input != null && !__sbIsStr(input) && input.url != null ? input : null;
|
|
4516
|
+
const url = req ? String(req.url) : input != null && input.href != null ? String(input.href) : String(input);
|
|
3993
4517
|
const opts = init || {};
|
|
4518
|
+
const source = opts.headers != null ? opts.headers : req ? req.headers : null;
|
|
3994
4519
|
const headers = [];
|
|
3995
|
-
if (
|
|
3996
|
-
if (__sbIsFn(
|
|
3997
|
-
else for (const k in
|
|
3998
|
-
}
|
|
3999
|
-
const
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4520
|
+
if (source) {
|
|
4521
|
+
if (__sbIsFn(source.forEach)) source.forEach((v, k) => headers.push([k, v]));
|
|
4522
|
+
else for (const k in source) headers.push([k, source[k]]);
|
|
4523
|
+
}
|
|
4524
|
+
const body = opts.body !== undefined ? opts.body : req && req.body !== "" ? req.body : null;
|
|
4525
|
+
const method = opts.method || (req && req.method) || "GET";
|
|
4526
|
+
const msg = { url, method, headers, ...__sbRequestBody(body) };
|
|
4527
|
+
const respond = (r) => {
|
|
4528
|
+
const respHeaders = new Headers();
|
|
4529
|
+
for (let j = 0; j < (r.headers || []).length; j++) respHeaders.set(r.headers[j][0], r.headers[j][1]);
|
|
4530
|
+
// #176: bytes from an outbound HTTP response, not a string this handler
|
|
4531
|
+
// built -- must not be re-encoded if the handler proxies it straight
|
|
4532
|
+
// through (see the assets binding for the same reasoning).
|
|
4533
|
+
if (r.body != null) respHeaders.set("x-sb-raw-body", "1");
|
|
4534
|
+
return __sbRawBodyResponse(r.body, { status: r.status || 502, headers: respHeaders });
|
|
4535
|
+
};
|
|
4536
|
+
if (!__sbHttpAsyncEnabled()) return respond(globalThis.__sbFetchUpstream("fetch", msg));
|
|
4537
|
+
// B18: the host runs the request off the event loop. Its reply fires a timer
|
|
4538
|
+
// that never fires on its own; the timer table keeps the callback alive.
|
|
4539
|
+
return new Promise((resolve, reject) => {
|
|
4540
|
+
const id = setTimeout(() => {
|
|
4541
|
+
try {
|
|
4542
|
+
resolve(respond(__sbFetchReply(__sbHttpAsyncResult(id))));
|
|
4543
|
+
} catch (e) {
|
|
4544
|
+
reject(new Error("sproutboat fetch: " + ((e && e.message) || e)));
|
|
4545
|
+
}
|
|
4546
|
+
}, 2147483647);
|
|
4547
|
+
try {
|
|
4548
|
+
msg.asyncId = id;
|
|
4549
|
+
globalThis.__sbFetchUpstream("fetch", msg);
|
|
4550
|
+
} catch (e) {
|
|
4551
|
+
clearTimeout(id);
|
|
4552
|
+
reject(e);
|
|
4553
|
+
}
|
|
4004
4554
|
});
|
|
4005
|
-
const respHeaders = new Headers();
|
|
4006
|
-
for (let j = 0; j < (r.headers || []).length; j++) respHeaders.set(r.headers[j][0], r.headers[j][1]);
|
|
4007
|
-
// #176: bytes from an outbound HTTP response, not a string this handler
|
|
4008
|
-
// built -- must not be re-encoded if the handler proxies it straight
|
|
4009
|
-
// through (see the assets binding for the same reasoning).
|
|
4010
|
-
if (r.body != null) respHeaders.set("x-sb-raw-body", "1");
|
|
4011
|
-
return __sbRawBodyResponse(r.body, { status: r.status || 502, headers: respHeaders });
|
|
4012
4555
|
};
|
|
4013
4556
|
};
|
|
4014
4557
|
|
|
@@ -4067,7 +4610,7 @@ function __sbMakeDONamespace(binding, className) {
|
|
|
4067
4610
|
}
|
|
4068
4611
|
const inst = __sbGetDOInstance(className, idStr);
|
|
4069
4612
|
const res = inst.fetch(req);
|
|
4070
|
-
if (inst.__sbTasks.length || (res && __sbIsFn(res.then))) return __sbFetchWithDrain(res, inst.__sbTasks);
|
|
4613
|
+
if (inst.__sbTasks.length || (res && __sbIsFn(res.then)) || __sbHasStreamBody(res)) return __sbFetchWithDrain(res, inst.__sbTasks);
|
|
4071
4614
|
return res;
|
|
4072
4615
|
},
|
|
4073
4616
|
};
|
|
@@ -4350,14 +4893,17 @@ function __sbTrustedProxyHeader(request, name) {
|
|
|
4350
4893
|
// own.
|
|
4351
4894
|
const __SB_WAITUNTIL_TIMEOUT_MS = 25000;
|
|
4352
4895
|
|
|
4353
|
-
function __sbTimeout(ms) {
|
|
4354
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4355
|
-
}
|
|
4356
|
-
|
|
4357
4896
|
async function __sbDrainWaitUntil(tasks) {
|
|
4358
4897
|
if (!tasks.length) return;
|
|
4359
4898
|
const pending = tasks.splice(0, tasks.length);
|
|
4360
|
-
|
|
4899
|
+
// Clear the cap's timer once the tasks finish: a pending timer kept the process
|
|
4900
|
+
// busy for the full 25 s (sproutd will not reap a worker with timers left, B22).
|
|
4901
|
+
let timer;
|
|
4902
|
+
const cap = new Promise((resolve) => {
|
|
4903
|
+
timer = setTimeout(resolve, __SB_WAITUNTIL_TIMEOUT_MS);
|
|
4904
|
+
});
|
|
4905
|
+
await Promise.race([__sbDrainAll(pending), cap]);
|
|
4906
|
+
clearTimeout(timer);
|
|
4361
4907
|
}
|
|
4362
4908
|
|
|
4363
4909
|
async function __sbDrainAll(pending) {
|
|
@@ -4373,15 +4919,130 @@ async function __sbDrainAll(pending) {
|
|
|
4373
4919
|
// Tail-called from `__sbEntry`/DO dispatch so the promise this returns is the
|
|
4374
4920
|
// one *this* async function creates, not a `.then()` derived from someone
|
|
4375
4921
|
// else's — see the note below on why that distinction matters here.
|
|
4922
|
+
// B19 (first step): a ReadableStream response body is read to the end and sent buffered;
|
|
4923
|
+
// the native writer had sent "[object ReadableStream]". Chunks must be bytes, as in workerd
|
|
4924
|
+
// (a string chunk is a TypeError), and the whole body is capped like a fetched one.
|
|
4925
|
+
const __SB_STREAM_BODY_MAX = 32 * 1024 * 1024;
|
|
4926
|
+
async function __sbCollectBody(stream) {
|
|
4927
|
+
const reader = stream.getReader();
|
|
4928
|
+
const chunks = [];
|
|
4929
|
+
let size = 0;
|
|
4930
|
+
for (;;) {
|
|
4931
|
+
const { value, done } = await reader.read();
|
|
4932
|
+
if (done) break;
|
|
4933
|
+
let bytes;
|
|
4934
|
+
if (value instanceof Uint8Array) bytes = value;
|
|
4935
|
+
else if (value instanceof ArrayBuffer) bytes = new Uint8Array(value);
|
|
4936
|
+
else if (ArrayBuffer.isView(value)) bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
4937
|
+
else throw new TypeError("This ReadableStream did not return bytes.");
|
|
4938
|
+
size += bytes.length;
|
|
4939
|
+
if (size > __SB_STREAM_BODY_MAX) throw new RangeError("Response body stream exceeds 32 MiB");
|
|
4940
|
+
chunks.push(bytes);
|
|
4941
|
+
}
|
|
4942
|
+
const out = new Uint8Array(size);
|
|
4943
|
+
let at = 0;
|
|
4944
|
+
for (const chunk of chunks) {
|
|
4945
|
+
out.set(chunk, at);
|
|
4946
|
+
at += chunk.length;
|
|
4947
|
+
}
|
|
4948
|
+
return out;
|
|
4949
|
+
}
|
|
4950
|
+
function __sbHasStreamBody(res) {
|
|
4951
|
+
return res != null && res.body instanceof ReadableStream;
|
|
4952
|
+
}
|
|
4953
|
+
|
|
4954
|
+
// B19: on a host that streams (a standalone sprout's native server defines sb_stream_*), a
|
|
4955
|
+
// stream body goes out as it is produced: the response is marked with the internal
|
|
4956
|
+
// x-sb-stream header, the native writer sends the status and headers and holds the
|
|
4957
|
+
// connection open, and each chunk is written as it arrives. Hosts that do not stream
|
|
4958
|
+
// (sproutd's workers, for now) link the weak fallbacks, report unsupported and keep buffering.
|
|
4959
|
+
// op: "s" supported, "o" open, "w" write data, "e" end. Returns a short string.
|
|
4960
|
+
// oxlint-disable-next-line no-unused-vars -- read inside the RawC block, not by JS.
|
|
4961
|
+
function __sbStreamOp(op, id, data) {
|
|
4962
|
+
let out = "";
|
|
4963
|
+
// oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
|
|
4964
|
+
Porffor.c`
|
|
4965
|
+
extern int sb_stream_supported(void);
|
|
4966
|
+
extern unsigned int sb_stream_open(void);
|
|
4967
|
+
extern int sb_stream_write(unsigned int, const char*, size_t);
|
|
4968
|
+
extern void sb_stream_end(unsigned int);
|
|
4969
|
+
const char* __o; size_t __ol; char* __oo = 0;
|
|
4970
|
+
porf_native_fetch_read_value(op, &__o, &__ol, &__oo);
|
|
4971
|
+
char __op = __ol ? __o[0] : 0;
|
|
4972
|
+
if (__oo) free(__oo);
|
|
4973
|
+
const char* __i; size_t __il; char* __io = 0;
|
|
4974
|
+
porf_native_fetch_read_value(id, &__i, &__il, &__io);
|
|
4975
|
+
char __ib[16]; size_t __ik = __il < 15 ? __il : 15;
|
|
4976
|
+
memcpy(__ib, __i, __ik); __ib[__ik] = 0;
|
|
4977
|
+
if (__io) free(__io);
|
|
4978
|
+
unsigned int __id = (unsigned int)strtoul(__ib, 0, 10);
|
|
4979
|
+
char __r[24]; __r[0] = 0;
|
|
4980
|
+
if (sb_stream_supported()) {
|
|
4981
|
+
if (__op == 's') snprintf(__r, sizeof(__r), "1");
|
|
4982
|
+
else if (__op == 'o') snprintf(__r, sizeof(__r), "%u", sb_stream_open());
|
|
4983
|
+
else if (__op == 'w') {
|
|
4984
|
+
const char* __d = 0; size_t __dl = 0;
|
|
4985
|
+
int __w = -1;
|
|
4986
|
+
if (porf_native_fetch_read_raw_bytes(data, &__d, &__dl) == 0) __w = sb_stream_write(__id, __d, __dl);
|
|
4987
|
+
snprintf(__r, sizeof(__r), "%d", __w);
|
|
4988
|
+
} else if (__op == 'e') { sb_stream_end(__id); snprintf(__r, sizeof(__r), "0"); }
|
|
4989
|
+
}
|
|
4990
|
+
out = porf_box((f64)porf_native_fetch_alloc_bytestring(__r, strlen(__r)), 195);
|
|
4991
|
+
`;
|
|
4992
|
+
return out;
|
|
4993
|
+
}
|
|
4994
|
+
let __sbStreaming = -1;
|
|
4995
|
+
function __sbStreamingSupported() {
|
|
4996
|
+
if (__sbStreaming < 0) __sbStreaming = __sbStreamOp("s", "0", "") === "1" ? 1 : 0;
|
|
4997
|
+
return __sbStreaming === 1;
|
|
4998
|
+
}
|
|
4999
|
+
function __sbStreamResponse(response) {
|
|
5000
|
+
const stream = response.body;
|
|
5001
|
+
const id = __sbStreamOp("o", "0", "");
|
|
5002
|
+
response.headers.set("x-sb-stream", id);
|
|
5003
|
+
response.body = "";
|
|
5004
|
+
(async () => {
|
|
5005
|
+
const reader = stream.getReader();
|
|
5006
|
+
try {
|
|
5007
|
+
for (;;) {
|
|
5008
|
+
const { value, done } = await reader.read();
|
|
5009
|
+
if (done) break;
|
|
5010
|
+
let bytes;
|
|
5011
|
+
if (value instanceof Uint8Array) bytes = value;
|
|
5012
|
+
else if (value instanceof ArrayBuffer) bytes = new Uint8Array(value);
|
|
5013
|
+
else if (ArrayBuffer.isView(value)) bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
5014
|
+
else throw new TypeError("This ReadableStream did not return bytes.");
|
|
5015
|
+
if (bytes.length > 0 && __sbStreamOp("w", id, __sbToBytes(bytes)) !== "0") {
|
|
5016
|
+
// The client went away: stop the source.
|
|
5017
|
+
reader.cancel();
|
|
5018
|
+
break;
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
} catch (error) {
|
|
5022
|
+
// The status line is already out; log and end what was sent.
|
|
5023
|
+
__sbLogUncaught("fetch", error);
|
|
5024
|
+
}
|
|
5025
|
+
__sbStreamOp("e", id, "");
|
|
5026
|
+
})();
|
|
5027
|
+
return response;
|
|
5028
|
+
}
|
|
5029
|
+
|
|
4376
5030
|
async function __sbFetchWithDrain(res, tasks) {
|
|
4377
5031
|
let r;
|
|
4378
5032
|
try {
|
|
4379
5033
|
r = res && __sbIsFn(res.then) ? await res : res;
|
|
5034
|
+
if (__sbHasStreamBody(r)) {
|
|
5035
|
+
if (__sbStreamingSupported()) __sbStreamResponse(r);
|
|
5036
|
+
else r.body = await __sbCollectBody(r.body);
|
|
5037
|
+
}
|
|
4380
5038
|
} catch (error) {
|
|
4381
5039
|
// #179 — a rejected handler promise must not take the process down with it.
|
|
4382
5040
|
r = __sbErrorResponse(error);
|
|
4383
5041
|
}
|
|
4384
|
-
|
|
5042
|
+
// The response goes out now and ctx.waitUntil work keeps running after it, as in
|
|
5043
|
+
// workerd; it was awaited first, so a 1.5 s background task delayed the response by
|
|
5044
|
+
// 1.5 s. The 25 s cap still bounds it.
|
|
5045
|
+
__sbDrainWaitUntil(tasks);
|
|
4385
5046
|
return r;
|
|
4386
5047
|
}
|
|
4387
5048
|
|
|
@@ -4391,7 +5052,10 @@ async function __sbFetchWithDrain(res, tasks) {
|
|
|
4391
5052
|
// nothing on stderr, which hid #168 entirely.
|
|
4392
5053
|
function __sbErrorResponse(error) {
|
|
4393
5054
|
__sbLogUncaught("fetch", error);
|
|
4394
|
-
|
|
5055
|
+
// Untyped, as workerd sends it: the text/plain default is for handlers' bodies.
|
|
5056
|
+
const response = new Response("Internal Server Error", { status: 500 });
|
|
5057
|
+
response.headers.delete("content-type");
|
|
5058
|
+
return response;
|
|
4395
5059
|
}
|
|
4396
5060
|
|
|
4397
5061
|
function __sbLogUncaught(where, error) {
|
|
@@ -4416,7 +5080,7 @@ async function __sb204WithDrain(res, tasks) {
|
|
|
4416
5080
|
__sbLogUncaught("scheduled/alarm", error);
|
|
4417
5081
|
}
|
|
4418
5082
|
await __sbDrainWaitUntil(tasks);
|
|
4419
|
-
return new Response(
|
|
5083
|
+
return new Response(null, { status: 204 });
|
|
4420
5084
|
}
|
|
4421
5085
|
|
|
4422
5086
|
async function __sbQueueWithDrain(result, tasks) {
|
|
@@ -4489,7 +5153,7 @@ globalThis.__sbEntry = function (handlers, request) {
|
|
|
4489
5153
|
}
|
|
4490
5154
|
// Same promise-identity rule as above applies to `__sbFetchWithDrain`'s
|
|
4491
5155
|
// own return, which is why it's tail-called rather than chained on here.
|
|
4492
|
-
if (__sbTasks.length || (__res && __sbIsFn(__res.then))) return __sbFetchWithDrain(__res, __sbTasks);
|
|
5156
|
+
if (__sbTasks.length || (__res && __sbIsFn(__res.then)) || __sbHasStreamBody(__res)) return __sbFetchWithDrain(__res, __sbTasks);
|
|
4493
5157
|
return __sbTagCpu(__res, __t0);
|
|
4494
5158
|
}
|
|
4495
5159
|
if (!__sbTriggerAuthed(request)) return new Response("forbidden", { status: 403 });
|
|
@@ -4515,10 +5179,10 @@ globalThis.__sbEntry = function (handlers, request) {
|
|
|
4515
5179
|
} catch (error) {
|
|
4516
5180
|
// #179 — same rule as fetch: don't let a throw here crash the process.
|
|
4517
5181
|
__sbLogUncaught("scheduled", error);
|
|
4518
|
-
return new Response(
|
|
5182
|
+
return new Response(null, { status: 204 });
|
|
4519
5183
|
}
|
|
4520
5184
|
if (__sbTasks.length || (__sres && __sbIsFn(__sres.then))) return __sb204WithDrain(__sres, __sbTasks);
|
|
4521
|
-
return new Response(
|
|
5185
|
+
return new Response(null, { status: 204 });
|
|
4522
5186
|
}
|
|
4523
5187
|
|
|
4524
5188
|
if (trigger === "queue") {
|
|
@@ -4550,12 +5214,12 @@ globalThis.__sbEntry = function (handlers, request) {
|
|
|
4550
5214
|
} catch (error) {
|
|
4551
5215
|
// #179 — same rule as fetch: don't let a throw here crash the process.
|
|
4552
5216
|
__sbLogUncaught("alarm", error);
|
|
4553
|
-
return new Response(
|
|
5217
|
+
return new Response(null, { status: 204 });
|
|
4554
5218
|
}
|
|
4555
5219
|
// Was fire-and-forget before: an async `alarm()` and any `state.waitUntil()`
|
|
4556
5220
|
// it queued were both dropped the moment 204 went out. Await both.
|
|
4557
5221
|
if (inst.__sbTasks.length || (__ares && __sbIsFn(__ares.then))) return __sb204WithDrain(__ares, inst.__sbTasks);
|
|
4558
|
-
return new Response(
|
|
5222
|
+
return new Response(null, { status: 204 });
|
|
4559
5223
|
}
|
|
4560
5224
|
|
|
4561
5225
|
return new Response("unknown trigger", { status: 400 });
|