@solidjs/web 2.0.0-rc.4 → 2.0.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev.cjs +163 -36
- package/dist/dev.js +161 -37
- package/dist/server.cjs +178 -37
- package/dist/server.js +177 -38
- package/dist/web.cjs +143 -36
- package/dist/web.js +141 -37
- package/frames/dist/client.cjs +41 -8
- package/frames/dist/client.dev.cjs +41 -8
- package/frames/dist/client.dev.js +41 -8
- package/frames/dist/client.js +41 -8
- package/frames/dist/server.cjs +497 -44
- package/frames/dist/server.js +497 -44
- package/package.json +2 -2
- package/serialization/dist/decode.cjs +4 -2
- package/serialization/dist/decode.js +4 -2
- package/serialization/dist/serialization.cjs +12 -8
- package/serialization/dist/serialization.js +12 -8
- package/serialization/types/index.d.ts +7 -0
- package/serialization/types/serializer-decode.d.ts +14 -1
- package/serialization/types/serializer.d.ts +7 -0
- package/serialization/types-cjs/index.d.cts +7 -0
- package/serialization/types-cjs/serializer-decode.d.cts +14 -1
- package/serialization/types-cjs/serializer.d.cts +7 -0
- package/server-functions/dist/client.cjs +164 -35
- package/server-functions/dist/client.js +161 -36
- package/server-functions/dist/server.cjs +899 -136
- package/server-functions/dist/server.dev.cjs +919 -136
- package/server-functions/dist/server.dev.js +915 -137
- package/server-functions/dist/server.js +895 -137
- package/types/client.d.ts +2 -1
- package/types/constants.d.ts +3 -1
- package/types/cookies.d.ts +6 -14
- package/types/frames/frame-client.d.ts +4 -0
- package/types/frames/serializer-decode.d.ts +14 -1
- package/types/frames/serializer.d.ts +7 -0
- package/types/jsx.d.ts +11 -16
- package/types/response.d.ts +11 -0
- package/types/serializer-decode.d.ts +14 -1
- package/types/serializer.d.ts +7 -0
- package/types/server-functions/client.d.ts +22 -2
- package/types/server-functions/flash.d.ts +9 -0
- package/types/server-functions/server.d.ts +131 -11
- package/types/server-functions/shared.d.ts +77 -14
- package/types/server-mock.d.ts +17 -2
- package/types/server.d.ts +16 -2
- package/types-cjs/client.d.cts +2 -1
- package/types-cjs/constants.d.cts +3 -1
- package/types-cjs/cookies.d.cts +6 -14
- package/types-cjs/frames/frame-client.d.cts +4 -0
- package/types-cjs/frames/serializer-decode.d.cts +14 -1
- package/types-cjs/frames/serializer.d.cts +7 -0
- package/types-cjs/jsx.d.cts +11 -16
- package/types-cjs/response.d.cts +11 -0
- package/types-cjs/serializer-decode.d.cts +14 -1
- package/types-cjs/serializer.d.cts +7 -0
- package/types-cjs/server-functions/client.d.cts +22 -2
- package/types-cjs/server-functions/flash.d.cts +9 -0
- package/types-cjs/server-functions/server.d.cts +131 -11
- package/types-cjs/server-functions/shared.d.cts +77 -14
- package/types-cjs/server-mock.d.cts +17 -2
- package/types-cjs/server.d.cts +16 -2
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
var solidJs = require('solid-js');
|
|
4
4
|
|
|
5
|
+
const COMPOSED_BODY_FRAMING = /*#__PURE__*/new Set(["content-length", "content-encoding", "transfer-encoding"]);
|
|
6
|
+
function isHttpNavigationTarget(target) {
|
|
7
|
+
try {
|
|
8
|
+
const protocol = new URL(target, "http://base.invalid").protocol;
|
|
9
|
+
return protocol === "http:" || protocol === "https:";
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
const ENVELOPE = Symbol.for("solid.ResponseEnvelope");
|
|
6
16
|
function isResponseEnvelope(value) {
|
|
7
17
|
return !!(value && typeof value === "object" && value[ENVELOPE]);
|
|
@@ -11,6 +21,8 @@ function isSafeError(value) {
|
|
|
11
21
|
return !!(value && (typeof value === "object" || typeof value === "function") && value[SAFE_ERROR]);
|
|
12
22
|
}
|
|
13
23
|
const REVALIDATE_HEADER = "X-Revalidate";
|
|
24
|
+
const RESPONSE_HEADER_VALUE_LIMIT = 4096;
|
|
25
|
+
const NULL_BODY_STATUSES = new Set([204, 205, 304]);
|
|
14
26
|
|
|
15
27
|
const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
|
|
16
28
|
function getServerFunctionMetadata(fn) {
|
|
@@ -90,6 +102,7 @@ function serializeCookie(name, value, options = {}) {
|
|
|
90
102
|
if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
91
103
|
if (options.httpOnly) cookie += "; HttpOnly";
|
|
92
104
|
if (options.secure) cookie += "; Secure";
|
|
105
|
+
if (options.partitioned) cookie += "; Partitioned";
|
|
93
106
|
if (options.sameSite) {
|
|
94
107
|
const sameSite = options.sameSite.toLowerCase();
|
|
95
108
|
cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
|
|
@@ -114,8 +127,23 @@ function configureServerFunctionsCodec(codec) {
|
|
|
114
127
|
function getServerFunctionsCodec() {
|
|
115
128
|
return codecConfig.codec;
|
|
116
129
|
}
|
|
117
|
-
|
|
130
|
+
const UNNAMED_FLIGHT_SOURCE = "true";
|
|
131
|
+
const flightConfig = {
|
|
132
|
+
consumers: new Map()
|
|
133
|
+
};
|
|
134
|
+
function assertFlightSource(source) {
|
|
135
|
+
if (source === UNNAMED_FLIGHT_SOURCE || source === "" || source.includes(",")) {
|
|
136
|
+
throw new TypeError(`Invalid flight data source id "${source}": ids ride the ` + `${SINGLE_FLIGHT_HEADER} header as a comma-separated list, and "true" is ` + `reserved for the unnamed registration (the bare consumer/hook signatures).`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function subscribeFlightData(sourceOrConsumer, maybeConsumer) {
|
|
140
|
+
const named = typeof sourceOrConsumer === "string";
|
|
141
|
+
if (named) assertFlightSource(sourceOrConsumer);
|
|
142
|
+
const source = named ? sourceOrConsumer : UNNAMED_FLIGHT_SOURCE;
|
|
143
|
+
const consumer = named ? maybeConsumer : sourceOrConsumer;
|
|
144
|
+
flightConfig.consumers.set(source, consumer);
|
|
118
145
|
return () => {
|
|
146
|
+
if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
|
|
119
147
|
};
|
|
120
148
|
}
|
|
121
149
|
function serverFunctionAddress(endpoint, id) {
|
|
@@ -127,10 +155,18 @@ function parseServerFunctionAddress(pathname, endpoint) {
|
|
|
127
155
|
if (!pathname.startsWith(mount)) return null;
|
|
128
156
|
const rest = pathname.slice(mount.length);
|
|
129
157
|
if (!rest.startsWith("/")) return null;
|
|
130
|
-
|
|
158
|
+
let segment = rest.slice(1);
|
|
159
|
+
let data = false;
|
|
160
|
+
if (segment.startsWith("data/")) {
|
|
161
|
+
segment = segment.slice(5);
|
|
162
|
+
data = true;
|
|
163
|
+
}
|
|
131
164
|
if (!segment || segment.includes("/")) return null;
|
|
132
165
|
try {
|
|
133
|
-
return
|
|
166
|
+
return {
|
|
167
|
+
id: decodeURIComponent(segment),
|
|
168
|
+
data
|
|
169
|
+
};
|
|
134
170
|
} catch {
|
|
135
171
|
return null;
|
|
136
172
|
}
|
|
@@ -162,6 +198,28 @@ function decodeErrorHeaderValue(value) {
|
|
|
162
198
|
}
|
|
163
199
|
const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
164
200
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
201
|
+
const UNKNOWN_HEADER = "X-Server-Function-Unknown";
|
|
202
|
+
const REDIRECT_HEADER = "X-Server-Function-Redirect";
|
|
203
|
+
function decodeRedirectHeaderValue(value) {
|
|
204
|
+
if (typeof value !== "string") return undefined;
|
|
205
|
+
const at = value.indexOf(" ");
|
|
206
|
+
if (at < 0) return undefined;
|
|
207
|
+
const status = Number(value.slice(0, at));
|
|
208
|
+
const url = value.slice(at + 1);
|
|
209
|
+
if (!Number.isInteger(status) || !url) return undefined;
|
|
210
|
+
if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) return undefined;
|
|
211
|
+
let parsed;
|
|
212
|
+
try {
|
|
213
|
+
parsed = new URL(url);
|
|
214
|
+
} catch {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
|
|
218
|
+
return {
|
|
219
|
+
status,
|
|
220
|
+
url
|
|
221
|
+
};
|
|
222
|
+
}
|
|
165
223
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
166
224
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
167
225
|
const BodyFormat = {
|
|
@@ -204,7 +262,11 @@ function isJSONSafe(value) {
|
|
|
204
262
|
const proto = Object.getPrototypeOf(v);
|
|
205
263
|
if (proto !== Object.prototype && proto !== null) return false;
|
|
206
264
|
if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
|
|
207
|
-
for (const k in v)
|
|
265
|
+
for (const k in v) {
|
|
266
|
+
const descriptor = Object.getOwnPropertyDescriptor(v, k);
|
|
267
|
+
if (descriptor === undefined || !("value" in descriptor)) return false;
|
|
268
|
+
stack.push(descriptor.value);
|
|
269
|
+
}
|
|
208
270
|
}
|
|
209
271
|
}
|
|
210
272
|
return true;
|
|
@@ -313,18 +375,34 @@ function createChunk(data) {
|
|
|
313
375
|
class ChunkReader {
|
|
314
376
|
constructor(stream) {
|
|
315
377
|
this.reader = stream.getReader();
|
|
316
|
-
this.
|
|
378
|
+
this.store = new Uint8Array(0);
|
|
379
|
+
this.buffer = this.store;
|
|
317
380
|
this.done = false;
|
|
318
381
|
}
|
|
319
382
|
async readChunk() {
|
|
320
383
|
const chunk = await this.reader.read();
|
|
321
|
-
if (
|
|
322
|
-
const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
|
|
323
|
-
newBuffer.set(this.buffer);
|
|
324
|
-
newBuffer.set(chunk.value, this.buffer.length);
|
|
325
|
-
this.buffer = newBuffer;
|
|
326
|
-
} else {
|
|
384
|
+
if (chunk.done) {
|
|
327
385
|
this.done = true;
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const incoming = chunk.value;
|
|
389
|
+
const store = this.store;
|
|
390
|
+
const start = this.buffer.byteOffset;
|
|
391
|
+
const end = start + this.buffer.length;
|
|
392
|
+
const needed = this.buffer.length + incoming.length;
|
|
393
|
+
if (end + incoming.length <= store.length) {
|
|
394
|
+
store.set(incoming, end);
|
|
395
|
+
this.buffer = store.subarray(start, end + incoming.length);
|
|
396
|
+
} else if (needed <= store.length) {
|
|
397
|
+
store.copyWithin(0, start, end);
|
|
398
|
+
store.set(incoming, this.buffer.length);
|
|
399
|
+
this.buffer = store.subarray(0, needed);
|
|
400
|
+
} else {
|
|
401
|
+
const grown = new Uint8Array(Math.max(needed, store.length * 2));
|
|
402
|
+
grown.set(this.buffer);
|
|
403
|
+
grown.set(incoming, this.buffer.length);
|
|
404
|
+
this.store = grown;
|
|
405
|
+
this.buffer = grown.subarray(0, needed);
|
|
328
406
|
}
|
|
329
407
|
}
|
|
330
408
|
async next() {
|
|
@@ -366,6 +444,27 @@ class ChunkReader {
|
|
|
366
444
|
}
|
|
367
445
|
}
|
|
368
446
|
}
|
|
447
|
+
const ERROR_TRAILER_PREFIX = "!";
|
|
448
|
+
function encodeErrorTrailer(error) {
|
|
449
|
+
const shaped = error instanceof Error ? error : new Error(String(error));
|
|
450
|
+
return ERROR_TRAILER_PREFIX + JSON.stringify(shaped.name && shaped.name !== "Error" ? {
|
|
451
|
+
name: shaped.name,
|
|
452
|
+
message: shaped.message
|
|
453
|
+
} : {
|
|
454
|
+
message: shaped.message
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
function errorFromTrailer(payload) {
|
|
458
|
+
let shape;
|
|
459
|
+
try {
|
|
460
|
+
shape = JSON.parse(payload.slice(1));
|
|
461
|
+
} catch {
|
|
462
|
+
shape = null;
|
|
463
|
+
}
|
|
464
|
+
const error = new Error(shape && typeof shape.message === "string" ? shape.message : "Server function result could not be delivered.");
|
|
465
|
+
if (shape && typeof shape.name === "string") error.name = shape.name;
|
|
466
|
+
return error;
|
|
467
|
+
}
|
|
369
468
|
async function deserializeStream(source, codecOptions) {
|
|
370
469
|
if (!source.body) {
|
|
371
470
|
throw new Error("missing body");
|
|
@@ -373,11 +472,17 @@ async function deserializeStream(source, codecOptions) {
|
|
|
373
472
|
const reader = new ChunkReader(source.body);
|
|
374
473
|
const result = await reader.next();
|
|
375
474
|
if (!result.done) {
|
|
475
|
+
if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
476
|
+
throw errorFromTrailer(result.value);
|
|
477
|
+
}
|
|
376
478
|
const {
|
|
377
479
|
createJSONDeserializer
|
|
378
480
|
} = await import('@solidjs/web/serialization/decode');
|
|
379
481
|
const deserializeChunk = createJSONDeserializer(codecOptions);
|
|
380
482
|
function interpretChunk(chunk) {
|
|
483
|
+
if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
484
|
+
throw errorFromTrailer(chunk);
|
|
485
|
+
}
|
|
381
486
|
return deserializeChunk(JSON.parse(chunk));
|
|
382
487
|
}
|
|
383
488
|
reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
|
|
@@ -441,7 +546,8 @@ function copyInitHeaders(init) {
|
|
|
441
546
|
for (const cookie of init.getSetCookie()) headers.append("Set-Cookie", cookie);
|
|
442
547
|
return headers;
|
|
443
548
|
}
|
|
444
|
-
const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, "Location"
|
|
549
|
+
const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, REDIRECT_HEADER, "Location",
|
|
550
|
+
...COMPOSED_BODY_FRAMING].map(header => header.toLowerCase()));
|
|
445
551
|
function fillsStubGap(key, headers, response) {
|
|
446
552
|
if (key === "set-cookie" || STUB_GAP_FILL_EXCLUDED.has(key)) return false;
|
|
447
553
|
if (response.body === null && (key === "content-type" || key === "content-length")) return false;
|
|
@@ -457,24 +563,16 @@ function commitEventResponse(response, event = getRequestEvent()) {
|
|
|
457
563
|
if (fillsStubGap(key, response.headers, response)) hasGaps = true;
|
|
458
564
|
});
|
|
459
565
|
if (!cookies.length && !hasGaps) return response;
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (fillsStubGap(key, headers, response)) headers.set(key, value);
|
|
471
|
-
});
|
|
472
|
-
return new Response(response.body, {
|
|
473
|
-
status: response.status,
|
|
474
|
-
statusText: response.statusText,
|
|
475
|
-
headers
|
|
476
|
-
});
|
|
477
|
-
}
|
|
566
|
+
const headers = copyInitHeaders(response.headers);
|
|
567
|
+
for (const cookie of cookies) headers.append("Set-Cookie", cookie);
|
|
568
|
+
stub.headers.forEach((value, key) => {
|
|
569
|
+
if (fillsStubGap(key, headers, response)) headers.set(key, value);
|
|
570
|
+
});
|
|
571
|
+
return new Response(response.body, {
|
|
572
|
+
status: response.status,
|
|
573
|
+
statusText: response.statusText,
|
|
574
|
+
headers
|
|
575
|
+
});
|
|
478
576
|
}
|
|
479
577
|
|
|
480
578
|
function encodeInputValue(value) {
|
|
@@ -506,11 +604,35 @@ function encodeFlashCookie(url, result, input, thrown) {
|
|
|
506
604
|
thrown: !!thrown,
|
|
507
605
|
input: input.map(encodeInputValue)
|
|
508
606
|
};
|
|
607
|
+
if (fitsCookie(payload)) return flashCookie(payload);
|
|
608
|
+
payload.truncated = true;
|
|
609
|
+
payload.input = [];
|
|
610
|
+
if (!fitsCookie(payload)) {
|
|
611
|
+
if (typeof payload.result === "string") {
|
|
612
|
+
let prefix = payload.result;
|
|
613
|
+
while (prefix.length > 0 && !fitsCookie({
|
|
614
|
+
...payload,
|
|
615
|
+
result: prefix
|
|
616
|
+
})) {
|
|
617
|
+
prefix = prefix.slice(0, prefix.length >> 1);
|
|
618
|
+
}
|
|
619
|
+
payload.result = prefix.length > 0 ? prefix : true;
|
|
620
|
+
} else {
|
|
621
|
+
payload.result = true;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return flashCookie(payload);
|
|
625
|
+
}
|
|
626
|
+
function flashCookie(payload) {
|
|
509
627
|
return serializeCookie(FLASH_COOKIE, JSON.stringify(payload), {
|
|
510
628
|
secure: true,
|
|
511
629
|
httpOnly: true
|
|
512
630
|
});
|
|
513
631
|
}
|
|
632
|
+
const COOKIE_PAIR_BUDGET = 4000;
|
|
633
|
+
function fitsCookie(payload) {
|
|
634
|
+
return FLASH_COOKIE.length + 1 + encodeURIComponent(JSON.stringify(payload)).length <= COOKIE_PAIR_BUDGET;
|
|
635
|
+
}
|
|
514
636
|
function decodeFlashCookie(cookieHeader) {
|
|
515
637
|
const match = parseCookieHeader(cookieHeader)[FLASH_COOKIE];
|
|
516
638
|
if (!match) return;
|
|
@@ -518,12 +640,14 @@ function decodeFlashCookie(cookieHeader) {
|
|
|
518
640
|
const payload = JSON.parse(match);
|
|
519
641
|
if (!payload || !payload.result) return;
|
|
520
642
|
const result = payload.error ? new Error(payload.result) : payload.result;
|
|
521
|
-
|
|
643
|
+
const submission = {
|
|
522
644
|
input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
|
|
523
645
|
url: payload.url,
|
|
524
646
|
result: payload.thrown ? undefined : result,
|
|
525
647
|
error: payload.thrown ? result : undefined
|
|
526
648
|
};
|
|
649
|
+
if (payload.truncated) submission.truncated = true;
|
|
650
|
+
return submission;
|
|
527
651
|
} catch (error) {
|
|
528
652
|
console.error(error);
|
|
529
653
|
}
|
|
@@ -538,7 +662,9 @@ const config = {
|
|
|
538
662
|
transformDirectResult: undefined,
|
|
539
663
|
handleNoJS: undefined,
|
|
540
664
|
endpoint: "/_server",
|
|
541
|
-
csrf: true
|
|
665
|
+
csrf: true,
|
|
666
|
+
bodySizeLimit: 1_048_576,
|
|
667
|
+
maxArguments: 1000
|
|
542
668
|
};
|
|
543
669
|
function configureServerFunctionsServer({
|
|
544
670
|
provideEvent,
|
|
@@ -550,7 +676,9 @@ function configureServerFunctionsServer({
|
|
|
550
676
|
handleNoJS,
|
|
551
677
|
endpoint,
|
|
552
678
|
csrf,
|
|
553
|
-
codec
|
|
679
|
+
codec,
|
|
680
|
+
bodySizeLimit,
|
|
681
|
+
maxArguments
|
|
554
682
|
} = {}) {
|
|
555
683
|
if (provideEvent !== undefined) config.provideEvent = provideEvent;
|
|
556
684
|
if (wrapInvocation !== undefined) config.wrapInvocation = wrapInvocation;
|
|
@@ -562,6 +690,16 @@ function configureServerFunctionsServer({
|
|
|
562
690
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
563
691
|
if (csrf !== undefined) config.csrf = csrf;
|
|
564
692
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
693
|
+
if (bodySizeLimit !== undefined) config.bodySizeLimit = bodySizeLimit;
|
|
694
|
+
if (maxArguments !== undefined) config.maxArguments = maxArguments;
|
|
695
|
+
}
|
|
696
|
+
const flightSources = new Map();
|
|
697
|
+
function registerFlightDataSource(source, hook) {
|
|
698
|
+
assertFlightSource(source);
|
|
699
|
+
flightSources.set(source, hook);
|
|
700
|
+
return () => {
|
|
701
|
+
if (flightSources.get(source) === hook) flightSources.delete(source);
|
|
702
|
+
};
|
|
565
703
|
}
|
|
566
704
|
function provideEvent(event, fn) {
|
|
567
705
|
if (config.provideEvent) return config.provideEvent(event, fn);
|
|
@@ -569,6 +707,52 @@ function provideEvent(event, fn) {
|
|
|
569
707
|
if (ctx) return ctx.run(event, fn);
|
|
570
708
|
throw new Error("No request event provider. Configure one with configureServerFunctionsServer({ provideEvent }).");
|
|
571
709
|
}
|
|
710
|
+
function scopeDeferredResult(value, scope) {
|
|
711
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") return value;
|
|
712
|
+
const promised = scope(() => nativePromise(value));
|
|
713
|
+
if (promised) {
|
|
714
|
+
return promised.then(result => scope(() => scopeDeferredResult(result, scope)));
|
|
715
|
+
}
|
|
716
|
+
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
717
|
+
let reader;
|
|
718
|
+
return new ReadableStream({
|
|
719
|
+
async pull(controller) {
|
|
720
|
+
try {
|
|
721
|
+
const step = await scope(() => {
|
|
722
|
+
if (!reader) reader = value.getReader();
|
|
723
|
+
return reader.read();
|
|
724
|
+
});
|
|
725
|
+
if (step.done) controller.close();else controller.enqueue(step.value);
|
|
726
|
+
} catch (error) {
|
|
727
|
+
controller.error(error);
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
cancel(reason) {
|
|
731
|
+
return scope(() => reader ? reader.cancel(reason) : value.cancel(reason));
|
|
732
|
+
}
|
|
733
|
+
}, {
|
|
734
|
+
highWaterMark: 0
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
const scopedIterator = symbol => ({
|
|
738
|
+
[symbol]() {
|
|
739
|
+
const iterator = scope(() => value[symbol]());
|
|
740
|
+
return new Proxy(iterator, {
|
|
741
|
+
get(target, property) {
|
|
742
|
+
const member = Reflect.get(target, property, target);
|
|
743
|
+
return typeof member === "function" && (property === "next" || property === "return" || property === "throw") ? (...args) => scope(() => member.apply(target, args)) : member;
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
if (typeof value[Symbol.asyncIterator] === "function") {
|
|
749
|
+
return scopedIterator(Symbol.asyncIterator);
|
|
750
|
+
}
|
|
751
|
+
if (typeof value[Symbol.iterator] === "function" && !Array.isArray(value) && !(value instanceof Map) && !(value instanceof Set) && !ArrayBuffer.isView(value)) {
|
|
752
|
+
return scopedIterator(Symbol.iterator);
|
|
753
|
+
}
|
|
754
|
+
return value;
|
|
755
|
+
}
|
|
572
756
|
const REGISTRATIONS = new Map();
|
|
573
757
|
const METHODS = new Map();
|
|
574
758
|
const INVOCATIONS = new WeakMap();
|
|
@@ -583,6 +767,7 @@ function provideRPC() {
|
|
|
583
767
|
}
|
|
584
768
|
function registerServerFunction(id, callback) {
|
|
585
769
|
provideRPC();
|
|
770
|
+
if (REGISTRATIONS.get(id) !== callback) METHODS.delete(id);
|
|
586
771
|
REGISTRATIONS.set(id, callback);
|
|
587
772
|
return callback;
|
|
588
773
|
}
|
|
@@ -656,13 +841,17 @@ function createServerReference({
|
|
|
656
841
|
const ogEvt = getRequestEvent();
|
|
657
842
|
if (!ogEvt) throw new Error("Cannot call server function outside of a request");
|
|
658
843
|
const evt = {
|
|
659
|
-
...ogEvt
|
|
844
|
+
...ogEvt,
|
|
845
|
+
locals: {
|
|
846
|
+
...ogEvt.locals
|
|
847
|
+
}
|
|
660
848
|
};
|
|
661
849
|
INVOCATIONS.set(evt, {
|
|
662
850
|
id
|
|
663
851
|
});
|
|
664
852
|
evt.serverOnly = true;
|
|
665
|
-
const
|
|
853
|
+
const scope = run => provideEvent(evt, run);
|
|
854
|
+
let result = provideEvent(evt, () => {
|
|
666
855
|
const run = () => fn.apply(thisArg, args);
|
|
667
856
|
return config.wrapInvocation ? config.wrapInvocation(run, {
|
|
668
857
|
id,
|
|
@@ -671,19 +860,20 @@ function createServerReference({
|
|
|
671
860
|
direct: true
|
|
672
861
|
}) : run();
|
|
673
862
|
});
|
|
863
|
+
result = scopeDeferredResult(result, scope);
|
|
674
864
|
const transform = config.transformDirectResult;
|
|
675
865
|
if (transform && result && typeof result.then === "function") {
|
|
676
|
-
return result.then(value => transform(value, {
|
|
866
|
+
return result.then(value => scopeDeferredResult(transform(value, {
|
|
677
867
|
id,
|
|
678
868
|
args,
|
|
679
869
|
event: evt
|
|
680
|
-
}));
|
|
870
|
+
}), scope));
|
|
681
871
|
}
|
|
682
|
-
return transform ? transform(result, {
|
|
872
|
+
return transform ? scopeDeferredResult(transform(result, {
|
|
683
873
|
id,
|
|
684
874
|
args,
|
|
685
875
|
event: evt
|
|
686
|
-
}) : result;
|
|
876
|
+
}), scope) : result;
|
|
687
877
|
}
|
|
688
878
|
});
|
|
689
879
|
return proxy;
|
|
@@ -727,18 +917,107 @@ function getServerFunctionInvocation() {
|
|
|
727
917
|
function getEventServerFunctionInvocation(event) {
|
|
728
918
|
return event && INVOCATIONS.get(event);
|
|
729
919
|
}
|
|
730
|
-
function
|
|
920
|
+
function resolveAddress(url) {
|
|
731
921
|
return parseServerFunctionAddress(url.pathname, config.endpoint);
|
|
732
922
|
}
|
|
733
|
-
|
|
923
|
+
const DECODE_DEPTH_LIMIT = 64;
|
|
924
|
+
function assertDecodeDepth(value) {
|
|
925
|
+
let level = [value];
|
|
926
|
+
for (let depth = 0; level.length > 0; depth++) {
|
|
927
|
+
if (depth > DECODE_DEPTH_LIMIT) {
|
|
928
|
+
throw new TypeError("Server function arguments exceed the decode depth limit");
|
|
929
|
+
}
|
|
930
|
+
const next = [];
|
|
931
|
+
for (const node of level) {
|
|
932
|
+
if (node === null || typeof node !== "object") continue;
|
|
933
|
+
if (Array.isArray(node)) {
|
|
934
|
+
for (const child of node) next.push(child);
|
|
935
|
+
} else {
|
|
936
|
+
for (const key of Object.keys(node)) next.push(node[key]);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
level = next;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
const UNSAFE_ARGUMENT_KEYS = ["__proto__", "constructor", "prototype"];
|
|
943
|
+
function stripUnsafeArgumentKeys(value) {
|
|
944
|
+
const stack = [value];
|
|
945
|
+
const seen = new Set();
|
|
946
|
+
while (stack.length) {
|
|
947
|
+
const v = stack.pop();
|
|
948
|
+
if (v === null || typeof v !== "object" || seen.has(v)) continue;
|
|
949
|
+
seen.add(v);
|
|
950
|
+
for (const key of UNSAFE_ARGUMENT_KEYS) {
|
|
951
|
+
delete v[key];
|
|
952
|
+
}
|
|
953
|
+
for (const key of Object.keys(v)) stack.push(v[key]);
|
|
954
|
+
if (v instanceof Map) {
|
|
955
|
+
for (const [k, entry] of v) stack.push(k, entry);
|
|
956
|
+
} else if (v instanceof Set) {
|
|
957
|
+
for (const member of v) stack.push(member);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return value;
|
|
961
|
+
}
|
|
962
|
+
async function bufferBodyWithin(request, limit) {
|
|
963
|
+
const reader = request.body.getReader();
|
|
964
|
+
const signal = request.signal;
|
|
965
|
+
const chunks = [];
|
|
966
|
+
let total = 0;
|
|
967
|
+
const onAbort = () => {
|
|
968
|
+
reader.cancel(signal.reason).catch(() => {});
|
|
969
|
+
};
|
|
970
|
+
if (signal.aborted) onAbort();else signal.addEventListener("abort", onAbort, {
|
|
971
|
+
once: true
|
|
972
|
+
});
|
|
973
|
+
try {
|
|
974
|
+
for (;;) {
|
|
975
|
+
const {
|
|
976
|
+
done,
|
|
977
|
+
value
|
|
978
|
+
} = await reader.read();
|
|
979
|
+
if (signal.aborted) throw signal.reason;
|
|
980
|
+
if (done) break;
|
|
981
|
+
total += value.byteLength;
|
|
982
|
+
if (total > limit) {
|
|
983
|
+
reader.cancel().catch(() => {});
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
chunks.push(value);
|
|
987
|
+
}
|
|
988
|
+
} catch (error) {
|
|
989
|
+
reader.cancel(error).catch(() => {});
|
|
990
|
+
throw error;
|
|
991
|
+
} finally {
|
|
992
|
+
signal.removeEventListener("abort", onAbort);
|
|
993
|
+
reader.releaseLock();
|
|
994
|
+
}
|
|
995
|
+
const body = new Uint8Array(total);
|
|
996
|
+
let offset = 0;
|
|
997
|
+
for (const chunk of chunks) {
|
|
998
|
+
body.set(chunk, offset);
|
|
999
|
+
offset += chunk.byteLength;
|
|
1000
|
+
}
|
|
1001
|
+
return new Request(request, {
|
|
1002
|
+
body
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
async function parseArguments(request, url, scripted, codec) {
|
|
734
1006
|
const parsed = [];
|
|
735
1007
|
const bodyFormat = request.method === "POST" ? request.headers.get(BODY_FORMAT_HEADER) : null;
|
|
736
1008
|
const args = url.searchParams.get("args");
|
|
737
|
-
if (args && (!
|
|
738
|
-
|
|
1009
|
+
if (args && (!scripted || request.method === "GET" || bodyFormat !== BodyFormat.Serialized)) {
|
|
1010
|
+
let result;
|
|
1011
|
+
if (args.startsWith(";0x")) {
|
|
1012
|
+
result = await deserializeString(args, codec);
|
|
1013
|
+
} else {
|
|
1014
|
+
result = JSON.parse(args);
|
|
1015
|
+
assertDecodeDepth(result);
|
|
1016
|
+
}
|
|
739
1017
|
if (!Array.isArray(result)) {
|
|
740
1018
|
throw new TypeError("Server function arguments must encode an array");
|
|
741
1019
|
}
|
|
1020
|
+
stripUnsafeArgumentKeys(result);
|
|
742
1021
|
for (const arg of result) {
|
|
743
1022
|
parsed.push(arg);
|
|
744
1023
|
}
|
|
@@ -748,18 +1027,37 @@ async function parseArguments(request, url, instance, codec) {
|
|
|
748
1027
|
if (request.method === "POST" && request.body !== null) {
|
|
749
1028
|
const decoded = await extractBody(request.clone(), codec);
|
|
750
1029
|
if (bodyFormat === BodyFormat.Serialized || bodyFormat === BodyFormat.Json) {
|
|
751
|
-
|
|
1030
|
+
if (bodyFormat === BodyFormat.Json) assertDecodeDepth(decoded);
|
|
1031
|
+
if (!Array.isArray(decoded)) {
|
|
1032
|
+
throw new TypeError("Server function arguments must encode an array");
|
|
1033
|
+
}
|
|
1034
|
+
return stripUnsafeArgumentKeys(decoded);
|
|
1035
|
+
}
|
|
1036
|
+
if (decoded === undefined) {
|
|
1037
|
+
if (bodyFormat === null && (await request.clone().arrayBuffer()).byteLength === 0) {
|
|
1038
|
+
return parsed;
|
|
1039
|
+
}
|
|
1040
|
+
throw new TypeError("Server function body carries no usable encoding");
|
|
752
1041
|
}
|
|
753
1042
|
parsed.push(decoded);
|
|
754
1043
|
}
|
|
755
1044
|
return parsed;
|
|
756
1045
|
}
|
|
757
|
-
async function foldFlightData(
|
|
1046
|
+
async function foldFlightData(hooks, event, headers, outcome, context = {}) {
|
|
758
1047
|
if (outcome.value instanceof Response && outcome.value.body) return outcome.value;
|
|
759
1048
|
digestOutcome(event, outcome);
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
1049
|
+
const folded = [];
|
|
1050
|
+
for (const [source, hook] of hooks) {
|
|
1051
|
+
try {
|
|
1052
|
+
const slice = await hook(event, outcome);
|
|
1053
|
+
if (slice !== undefined) folded.push([source, slice]);
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
console.error(`Error collecting flight data for source "${source}"`, error);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (folded.length === 0) return outcome.value;
|
|
1059
|
+
const data = Object.fromEntries(folded);
|
|
1060
|
+
headers.set(SINGLE_FLIGHT_HEADER, folded.map(([source]) => source).join(","));
|
|
763
1061
|
if (context.transformFlightResult) {
|
|
764
1062
|
const transformed = await context.transformFlightResult(event, {
|
|
765
1063
|
value: outcome.value,
|
|
@@ -839,7 +1137,7 @@ function foldSetCookies(headers, setCookies) {
|
|
|
839
1137
|
}
|
|
840
1138
|
function mergeResponseHeaders(target, source) {
|
|
841
1139
|
source.forEach((value, key) => {
|
|
842
|
-
if (key !== "set-cookie") target.append(key, value);
|
|
1140
|
+
if (key !== "set-cookie" && !COMPOSED_BODY_FRAMING.has(key)) target.append(key, value);
|
|
843
1141
|
});
|
|
844
1142
|
if (source.getSetCookie) {
|
|
845
1143
|
for (const cookie of source.getSetCookie()) target.append("Set-Cookie", cookie);
|
|
@@ -848,6 +1146,48 @@ function mergeResponseHeaders(target, source) {
|
|
|
848
1146
|
}
|
|
849
1147
|
}
|
|
850
1148
|
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
1149
|
+
function maskRedirect(headers, response, requestUrl) {
|
|
1150
|
+
const target = response.headers && response.headers.get("Location");
|
|
1151
|
+
if (target) {
|
|
1152
|
+
headers.set(REDIRECT_HEADER, `${response.status} ${new URL(target, requestUrl)}`);
|
|
1153
|
+
}
|
|
1154
|
+
headers.delete("Location");
|
|
1155
|
+
}
|
|
1156
|
+
const BOUNDED_COMPOSED_HEADERS = [REDIRECT_HEADER, "Location", REVALIDATE_HEADER];
|
|
1157
|
+
function enforceComposedHeaderInvariants(response) {
|
|
1158
|
+
for (const name of BOUNDED_COMPOSED_HEADERS) {
|
|
1159
|
+
const value = response.headers.get(name);
|
|
1160
|
+
if (value === null) continue;
|
|
1161
|
+
if (value.length > RESPONSE_HEADER_VALUE_LIMIT) {
|
|
1162
|
+
return refuseComposedHeader(response, name, `${name} response header refused at ${value.length} characters`, DEV ? `The ${name} response header is ${value.length} characters; past ` + `${RESPONSE_HEADER_VALUE_LIMIT} it would overflow receivers (an 8 KiB proxy ` + `buffer holds the whole header block) and the response dies at the socket after ` + `the mutation committed. Refused rather than trimmed: a cut redirect target is a ` + `different address, a dropped revalidate key is a silently stale cache. The ` + `redirect()/reload() helpers enforce this bound with the full reasoning at the ` + `call site.` : null);
|
|
1163
|
+
}
|
|
1164
|
+
if (name === REVALIDATE_HEADER) continue;
|
|
1165
|
+
const target = name === REDIRECT_HEADER ? value.slice(value.indexOf(" ") + 1) : value;
|
|
1166
|
+
if (!isHttpNavigationTarget(target)) {
|
|
1167
|
+
return refuseComposedHeader(response, name, `${name} response header refused: non-http(s) navigation target`, DEV ? `The ${name} response header carries a navigation target with a non-http(s) ` + `scheme ("${target.slice(0, 64)}"). A javascript: target is same-origin script ` + `execution in any integration that navigates to it, so only http(s) and ` + `relative targets leave this transport. If the target came from request data ` + `(?next= and friends), validate it against your own origin before redirecting.` : null);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
return response;
|
|
1171
|
+
}
|
|
1172
|
+
function refuseComposedHeader(response, name, headerMessage, body) {
|
|
1173
|
+
if (response.body) {
|
|
1174
|
+
try {
|
|
1175
|
+
const cancelled = response.body.cancel();
|
|
1176
|
+
if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
|
|
1177
|
+
} catch {}
|
|
1178
|
+
}
|
|
1179
|
+
const headers = new Headers();
|
|
1180
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(DEV ? headerMessage : GENERIC_SERVER_ERROR_MESSAGE));
|
|
1181
|
+
return new Response(body, {
|
|
1182
|
+
status: 500,
|
|
1183
|
+
headers
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
function warnScripted304(functionId) {
|
|
1187
|
+
if (DEV) {
|
|
1188
|
+
console.warn(`Server function "${functionId}" answered a scripted call with 304 Not Modified. ` + `The client transport sends no conditional headers, so nothing was asked to be ` + `revalidated: the call resolves to undefined, not "unchanged". For conditional ` + `reads, declare the function GET and set ETag/Cache-Control response headers - ` + `the browser owns that exchange and replays its cached answer on a 304.`);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
851
1191
|
function createNoJSHandler({
|
|
852
1192
|
base = ""
|
|
853
1193
|
} = {}) {
|
|
@@ -891,44 +1231,291 @@ function isFormPost(request) {
|
|
|
891
1231
|
const type = request.headers.get("content-type") || "";
|
|
892
1232
|
return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
|
|
893
1233
|
}
|
|
894
|
-
function
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
let onAbort = null;
|
|
899
|
-
const teardown = () => {
|
|
900
|
-
if (closed) return;
|
|
901
|
-
closed = true;
|
|
902
|
-
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
903
|
-
if (cancelSerialize) cancelSerialize();
|
|
904
|
-
if (closeIterator) closeIterator();
|
|
1234
|
+
function guardFailures(value, state) {
|
|
1235
|
+
if (!state) state = {
|
|
1236
|
+
seen: new WeakMap(),
|
|
1237
|
+
cyclic: new WeakSet()
|
|
905
1238
|
};
|
|
906
|
-
|
|
1239
|
+
const entered = enterGuard(value, state);
|
|
1240
|
+
if (!(entered instanceof Frame)) return entered;
|
|
1241
|
+
const stack = [entered];
|
|
1242
|
+
let delivered = NOTHING;
|
|
1243
|
+
for (;;) {
|
|
1244
|
+
const top = stack[stack.length - 1];
|
|
1245
|
+
const items = top.items;
|
|
1246
|
+
let pushed = null;
|
|
1247
|
+
while (top.i < items.length) {
|
|
1248
|
+
const i = top.i;
|
|
1249
|
+
let original;
|
|
1250
|
+
if (top.kind === OBJECT) {
|
|
1251
|
+
const descriptor = top.descriptors[items[i]];
|
|
1252
|
+
if ("value" in descriptor) {
|
|
1253
|
+
original = descriptor.value;
|
|
1254
|
+
} else if (typeof descriptor.get === "function") {
|
|
1255
|
+
if (top.accessorRead !== i) {
|
|
1256
|
+
try {
|
|
1257
|
+
top.accessorValue = descriptor.get.call(top.value);
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
throw sanitizeServerError(error);
|
|
1260
|
+
}
|
|
1261
|
+
top.accessorRead = i;
|
|
1262
|
+
}
|
|
1263
|
+
original = top.accessorValue;
|
|
1264
|
+
} else {
|
|
1265
|
+
top.i++;
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
} else {
|
|
1269
|
+
original = items[i];
|
|
1270
|
+
}
|
|
1271
|
+
let guarded;
|
|
1272
|
+
if (delivered !== NOTHING) {
|
|
1273
|
+
guarded = delivered;
|
|
1274
|
+
delivered = NOTHING;
|
|
1275
|
+
} else {
|
|
1276
|
+
guarded = enterGuard(original, state);
|
|
1277
|
+
if (guarded instanceof Frame) {
|
|
1278
|
+
pushed = guarded;
|
|
1279
|
+
break;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
if (top.kind === ARRAY) {
|
|
1283
|
+
if (guarded !== original) {
|
|
1284
|
+
top.next[i] = guarded;
|
|
1285
|
+
top.changed = true;
|
|
1286
|
+
}
|
|
1287
|
+
} else if (top.kind === MAP) {
|
|
1288
|
+
if ((i & 1) === 0) top.pendingKey = guarded;else top.next.set(top.pendingKey, guarded);
|
|
1289
|
+
if (guarded !== original) top.changed = true;
|
|
1290
|
+
} else if (top.kind === SET) {
|
|
1291
|
+
top.next.add(guarded);
|
|
1292
|
+
if (guarded !== original) top.changed = true;
|
|
1293
|
+
} else if (guarded !== original || top.accessorRead === i) {
|
|
1294
|
+
Object.defineProperty(top.next, items[i], {
|
|
1295
|
+
value: guarded,
|
|
1296
|
+
writable: true,
|
|
1297
|
+
configurable: true,
|
|
1298
|
+
enumerable: top.descriptors[items[i]].enumerable
|
|
1299
|
+
});
|
|
1300
|
+
top.changed = true;
|
|
1301
|
+
}
|
|
1302
|
+
top.i++;
|
|
1303
|
+
}
|
|
1304
|
+
if (pushed !== null) {
|
|
1305
|
+
stack.push(pushed);
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
stack.pop();
|
|
1309
|
+
const out = keepGuarded(top.value, top.next, top.changed, state);
|
|
1310
|
+
if (stack.length === 0) return out;
|
|
1311
|
+
delivered = out;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
const NOTHING = Symbol();
|
|
1315
|
+
const ARRAY = 0;
|
|
1316
|
+
const MAP = 1;
|
|
1317
|
+
const SET = 2;
|
|
1318
|
+
const OBJECT = 3;
|
|
1319
|
+
class Frame {
|
|
1320
|
+
constructor(kind, value, next, items, descriptors) {
|
|
1321
|
+
this.kind = kind;
|
|
1322
|
+
this.value = value;
|
|
1323
|
+
this.next = next;
|
|
1324
|
+
this.items = items;
|
|
1325
|
+
this.descriptors = descriptors;
|
|
1326
|
+
this.i = 0;
|
|
1327
|
+
this.changed = false;
|
|
1328
|
+
this.accessorRead = -1;
|
|
1329
|
+
this.accessorValue = undefined;
|
|
1330
|
+
this.pendingKey = undefined;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
function guardOperation(state, run) {
|
|
1334
|
+
return state.scope ? state.scope(run) : run();
|
|
1335
|
+
}
|
|
1336
|
+
function enterGuard(value, state) {
|
|
1337
|
+
if (value === null || typeof value !== "object") return value;
|
|
1338
|
+
if (state.seen.has(value)) {
|
|
1339
|
+
state.cyclic.add(value);
|
|
1340
|
+
return state.seen.get(value);
|
|
1341
|
+
}
|
|
1342
|
+
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
1343
|
+
let reader;
|
|
1344
|
+
const gate = state.gate;
|
|
1345
|
+
let finished = false;
|
|
1346
|
+
const close = () => {
|
|
1347
|
+
if (finished) return;
|
|
1348
|
+
finished = true;
|
|
1349
|
+
try {
|
|
1350
|
+
const cancelled = guardOperation(state, () => reader ? reader.cancel() : value.cancel());
|
|
1351
|
+
if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
|
|
1352
|
+
} catch {}
|
|
1353
|
+
};
|
|
1354
|
+
const guardedStream = new ReadableStream({
|
|
1355
|
+
async pull(controller) {
|
|
1356
|
+
try {
|
|
1357
|
+
if (gate && !finished && !gate.wantsMore()) await gate.awaitDemand();
|
|
1358
|
+
if (finished) {
|
|
1359
|
+
controller.close();
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
if (!reader) reader = guardOperation(state, () => value.getReader());
|
|
1363
|
+
const {
|
|
1364
|
+
done,
|
|
1365
|
+
value: chunk
|
|
1366
|
+
} = await guardOperation(state, () => reader.read());
|
|
1367
|
+
done ? controller.close() : controller.enqueue(guardOperation(state, () => guardFailures(chunk, state)));
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
controller.error(guardOperation(state, () => sanitizeServerError(error)));
|
|
1370
|
+
}
|
|
1371
|
+
},
|
|
1372
|
+
cancel(reason) {
|
|
1373
|
+
finished = true;
|
|
1374
|
+
return guardOperation(state, () => reader ? reader.cancel(reason) : value.cancel(reason));
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
if (gate) gate.onOpen(close);
|
|
1378
|
+
state.seen.set(value, guardedStream);
|
|
1379
|
+
return guardedStream;
|
|
1380
|
+
}
|
|
1381
|
+
if (typeof value.then === "function") {
|
|
1382
|
+
const guardedPromise = Promise.resolve(value).then(resolved => guardOperation(state, () => guardFailures(resolved, state)), error => {
|
|
1383
|
+
throw guardOperation(state, () => sanitizeServerError(error));
|
|
1384
|
+
});
|
|
1385
|
+
guardedPromise.catch(() => {});
|
|
1386
|
+
state.seen.set(value, guardedPromise);
|
|
1387
|
+
return guardedPromise;
|
|
1388
|
+
}
|
|
1389
|
+
if (typeof value[Symbol.asyncIterator] === "function") {
|
|
907
1390
|
const source = value;
|
|
908
|
-
|
|
1391
|
+
const gate = state.gate;
|
|
1392
|
+
const guardedIterable = {
|
|
909
1393
|
[Symbol.asyncIterator]() {
|
|
910
|
-
const
|
|
1394
|
+
const iterator = guardOperation(state, () => source[Symbol.asyncIterator]());
|
|
911
1395
|
let finished = false;
|
|
912
|
-
|
|
1396
|
+
const close = () => {
|
|
913
1397
|
if (finished) return;
|
|
914
1398
|
finished = true;
|
|
915
1399
|
try {
|
|
916
|
-
const returned =
|
|
1400
|
+
const returned = iterator.return && guardOperation(state, () => iterator.return());
|
|
917
1401
|
if (returned && typeof returned.then === "function") returned.then(undefined, () => {});
|
|
918
1402
|
} catch {}
|
|
919
1403
|
};
|
|
920
|
-
if (
|
|
1404
|
+
if (gate) gate.onOpen(close);
|
|
1405
|
+
const step = () => finished ? Promise.resolve({
|
|
1406
|
+
done: true,
|
|
1407
|
+
value: undefined
|
|
1408
|
+
}) : guardOperation(state, () => iterator.next()).then(step => {
|
|
1409
|
+
if (step.done) {
|
|
1410
|
+
finished = true;
|
|
1411
|
+
return step;
|
|
1412
|
+
}
|
|
1413
|
+
return {
|
|
1414
|
+
done: false,
|
|
1415
|
+
value: guardOperation(state, () => guardFailures(step.value, state))
|
|
1416
|
+
};
|
|
1417
|
+
}, error => {
|
|
1418
|
+
throw guardOperation(state, () => sanitizeServerError(error));
|
|
1419
|
+
});
|
|
921
1420
|
return {
|
|
922
|
-
next: () => finished ?
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1421
|
+
next: () => finished || !gate || gate.wantsMore() ? step() : gate.awaitDemand().then(step),
|
|
1422
|
+
return: () => {
|
|
1423
|
+
close();
|
|
1424
|
+
return Promise.resolve({
|
|
1425
|
+
done: true,
|
|
1426
|
+
value: undefined
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
926
1429
|
};
|
|
927
1430
|
}
|
|
928
1431
|
};
|
|
1432
|
+
state.seen.set(value, guardedIterable);
|
|
1433
|
+
return guardedIterable;
|
|
1434
|
+
}
|
|
1435
|
+
if (state.scope && typeof value[Symbol.iterator] === "function" && !Array.isArray(value) && !(value instanceof Map) && !(value instanceof Set) && !ArrayBuffer.isView(value)) {
|
|
1436
|
+
const scopedIterable = scopeDeferredResult(value, state.scope);
|
|
1437
|
+
state.seen.set(value, scopedIterable);
|
|
1438
|
+
return scopedIterable;
|
|
1439
|
+
}
|
|
1440
|
+
if (Array.isArray(value)) {
|
|
1441
|
+
const next = value.slice();
|
|
1442
|
+
state.seen.set(value, next);
|
|
1443
|
+
return new Frame(ARRAY, value, next, value, null);
|
|
1444
|
+
}
|
|
1445
|
+
if (value instanceof Map) {
|
|
1446
|
+
const next = new Map();
|
|
1447
|
+
state.seen.set(value, next);
|
|
1448
|
+
const items = [];
|
|
1449
|
+
for (const entry of value) items.push(entry[0], entry[1]);
|
|
1450
|
+
return new Frame(MAP, value, next, items, null);
|
|
1451
|
+
}
|
|
1452
|
+
if (value instanceof Set) {
|
|
1453
|
+
const next = new Set();
|
|
1454
|
+
state.seen.set(value, next);
|
|
1455
|
+
return new Frame(SET, value, next, [...value], null);
|
|
1456
|
+
}
|
|
1457
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1458
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
1459
|
+
state.seen.set(value, value);
|
|
1460
|
+
return value;
|
|
929
1461
|
}
|
|
1462
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1463
|
+
for (const key of Object.keys(descriptors)) {
|
|
1464
|
+
descriptors[key].configurable = true;
|
|
1465
|
+
if ("value" in descriptors[key]) descriptors[key].writable = true;
|
|
1466
|
+
}
|
|
1467
|
+
const next = Object.create(prototype, descriptors);
|
|
1468
|
+
state.seen.set(value, next);
|
|
1469
|
+
return new Frame(OBJECT, value, next, Object.keys(value), descriptors);
|
|
1470
|
+
}
|
|
1471
|
+
function keepGuarded(value, next, changed, state) {
|
|
1472
|
+
if (changed || state.cyclic.has(value)) return next;
|
|
1473
|
+
state.seen.set(value, value);
|
|
1474
|
+
return value;
|
|
1475
|
+
}
|
|
1476
|
+
function serializeResponseStream(value, codecOptions, signal, scope) {
|
|
1477
|
+
let closed = false;
|
|
1478
|
+
let streamController = null;
|
|
1479
|
+
let demandWaiters = null;
|
|
1480
|
+
const wantsMore = () => streamController !== null && streamController.desiredSize > 0;
|
|
1481
|
+
const awaitDemand = () => new Promise(resolve => (demandWaiters ??= []).push(resolve));
|
|
1482
|
+
const supplyDemand = () => {
|
|
1483
|
+
const resolvers = demandWaiters;
|
|
1484
|
+
demandWaiters = null;
|
|
1485
|
+
if (resolvers) for (const resolve of resolvers) resolve();
|
|
1486
|
+
};
|
|
1487
|
+
const sourceClosers = new Set();
|
|
1488
|
+
const gate = {
|
|
1489
|
+
wantsMore,
|
|
1490
|
+
awaitDemand,
|
|
1491
|
+
onOpen(close) {
|
|
1492
|
+
if (closed) close();else sourceClosers.add(close);
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
const guardState = {
|
|
1496
|
+
seen: new WeakMap(),
|
|
1497
|
+
cyclic: new WeakSet(),
|
|
1498
|
+
gate,
|
|
1499
|
+
scope
|
|
1500
|
+
};
|
|
1501
|
+
value = guardOperation(guardState, () => guardFailures(value, guardState));
|
|
1502
|
+
let cancelSerialize = null;
|
|
1503
|
+
let onAbort = null;
|
|
1504
|
+
const finishSource = () => {
|
|
1505
|
+
for (const close of sourceClosers) close();
|
|
1506
|
+
sourceClosers.clear();
|
|
1507
|
+
supplyDemand();
|
|
1508
|
+
};
|
|
1509
|
+
const teardown = () => {
|
|
1510
|
+
if (closed) return;
|
|
1511
|
+
closed = true;
|
|
1512
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1513
|
+
if (cancelSerialize) cancelSerialize();
|
|
1514
|
+
finishSource();
|
|
1515
|
+
};
|
|
930
1516
|
return new ReadableStream({
|
|
931
1517
|
async start(controller) {
|
|
1518
|
+
streamController = controller;
|
|
932
1519
|
if (signal) {
|
|
933
1520
|
if (signal.aborted) {
|
|
934
1521
|
teardown();
|
|
@@ -964,29 +1551,54 @@ function serializeResponseStream(value, codecOptions, signal) {
|
|
|
964
1551
|
if (closed) return;
|
|
965
1552
|
closed = true;
|
|
966
1553
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1554
|
+
finishSource();
|
|
967
1555
|
controller.close();
|
|
968
1556
|
},
|
|
969
1557
|
onError(error) {
|
|
970
1558
|
if (closed) return;
|
|
971
1559
|
closed = true;
|
|
972
1560
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
973
|
-
|
|
1561
|
+
finishSource();
|
|
1562
|
+
try {
|
|
1563
|
+
const delivered = sanitizeServerError(DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error);
|
|
1564
|
+
controller.enqueue(createChunk(encodeErrorTrailer(delivered)));
|
|
1565
|
+
controller.close();
|
|
1566
|
+
} catch {
|
|
1567
|
+
try {
|
|
1568
|
+
controller.error(error);
|
|
1569
|
+
} catch {}
|
|
1570
|
+
}
|
|
974
1571
|
}
|
|
975
1572
|
});
|
|
976
1573
|
},
|
|
1574
|
+
pull() {
|
|
1575
|
+
supplyDemand();
|
|
1576
|
+
},
|
|
977
1577
|
cancel() {
|
|
978
1578
|
teardown();
|
|
979
1579
|
}
|
|
980
1580
|
});
|
|
981
1581
|
}
|
|
982
|
-
function serializedResponse(value, headers, codec, signal) {
|
|
1582
|
+
function serializedResponse(value, headers, codec, signal, scope) {
|
|
983
1583
|
headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
|
|
984
1584
|
headers.set("Content-Type", "text/plain");
|
|
985
|
-
return new Response(serializeResponseStream(value, codec, signal), {
|
|
1585
|
+
return new Response(serializeResponseStream(value, codec, signal, scope), {
|
|
986
1586
|
headers
|
|
987
1587
|
});
|
|
988
1588
|
}
|
|
989
|
-
function encodeResult(value, headers, status, codec, signal) {
|
|
1589
|
+
function encodeResult(value, headers, status, codec, signal, scope) {
|
|
1590
|
+
if (NULL_BODY_STATUSES.has(status)) {
|
|
1591
|
+
if (value === undefined || value === null) {
|
|
1592
|
+
headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
|
|
1593
|
+
return new Response(null, {
|
|
1594
|
+
status,
|
|
1595
|
+
headers
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
const error = new Error(`Server function answered status ${status}, which forbids a response body, with a value. ` + `Return respond(undefined, { status: ${status} }) for a bodiless answer, or drop the ` + `status to send the value.`);
|
|
1599
|
+
headers.set(ERROR_HEADER, encodeErrorHeaderValue(error.message));
|
|
1600
|
+
return encodeResult(error, headers, 500, codec, signal, scope);
|
|
1601
|
+
}
|
|
990
1602
|
const direct = getHeadersAndBody(value);
|
|
991
1603
|
if (direct) {
|
|
992
1604
|
for (const [key, val] of Object.entries(direct.headers || {})) {
|
|
@@ -1005,21 +1617,37 @@ function encodeResult(value, headers, status, codec, signal) {
|
|
|
1005
1617
|
});
|
|
1006
1618
|
}
|
|
1007
1619
|
try {
|
|
1008
|
-
|
|
1620
|
+
const jsonSafe = scope ? scope(() => isJSONSafe(value)) : isJSONSafe(value);
|
|
1621
|
+
if (jsonSafe) {
|
|
1009
1622
|
headers.set(BODY_FORMAT_HEADER, BodyFormat.Json);
|
|
1010
1623
|
headers.set("Content-Type", "application/json");
|
|
1011
|
-
|
|
1624
|
+
const body = scope ? scope(() => JSON.stringify(value)) : JSON.stringify(value);
|
|
1625
|
+
return new Response(body, {
|
|
1012
1626
|
status,
|
|
1013
1627
|
headers
|
|
1014
1628
|
});
|
|
1015
1629
|
}
|
|
1016
1630
|
} catch {
|
|
1017
1631
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
status,
|
|
1021
|
-
|
|
1022
|
-
|
|
1632
|
+
try {
|
|
1633
|
+
const response = serializedResponse(value, headers, codec, signal, scope);
|
|
1634
|
+
return status === 200 ? response : new Response(response.body, {
|
|
1635
|
+
status,
|
|
1636
|
+
headers
|
|
1637
|
+
});
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
throw DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
const ERROR_HEADER_VALUE_LIMIT = 1024;
|
|
1643
|
+
function boundedErrorHeaderValue(message) {
|
|
1644
|
+
let label = message.length > 256 ? message.slice(0, 256) : message;
|
|
1645
|
+
let encoded = encodeErrorHeaderValue(label);
|
|
1646
|
+
while (encoded.length > ERROR_HEADER_VALUE_LIMIT && label.length > 1) {
|
|
1647
|
+
label = label.slice(0, Math.ceil(label.length / 2));
|
|
1648
|
+
encoded = encodeErrorHeaderValue(label);
|
|
1649
|
+
}
|
|
1650
|
+
return encoded;
|
|
1023
1651
|
}
|
|
1024
1652
|
const GENERIC_SERVER_ERROR_MESSAGE = "Internal Server Error";
|
|
1025
1653
|
let DEV = false === true;
|
|
@@ -1043,11 +1671,12 @@ function serverFunctionUrl(id, boundArgs) {
|
|
|
1043
1671
|
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
1044
1672
|
}
|
|
1045
1673
|
function parseServerFunctionUrl(url) {
|
|
1046
|
-
|
|
1674
|
+
const parsed = parseServerFunctionAddress(new URL(url, "http://localhost").pathname, config.endpoint);
|
|
1675
|
+
return parsed && parsed.id;
|
|
1047
1676
|
}
|
|
1048
1677
|
async function matchesOrigin(origin, request, matcher) {
|
|
1049
1678
|
if (matcher === undefined) return origin === new URL(request.url).origin;
|
|
1050
|
-
if (typeof matcher === "function") return
|
|
1679
|
+
if (typeof matcher === "function") return (await matcher(origin, request)) === true;
|
|
1051
1680
|
return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
|
|
1052
1681
|
}
|
|
1053
1682
|
async function allowsServerFunctionRequest(request, options) {
|
|
@@ -1099,14 +1728,37 @@ function forbiddenResponse() {
|
|
|
1099
1728
|
}
|
|
1100
1729
|
}));
|
|
1101
1730
|
}
|
|
1731
|
+
function nativePromise(value) {
|
|
1732
|
+
if (value instanceof Promise) return value;
|
|
1733
|
+
try {
|
|
1734
|
+
if (Object.prototype.toString.call(value) === "[object Promise]") return Promise.prototype.then.call(value, value => value);
|
|
1735
|
+
} catch {}
|
|
1736
|
+
}
|
|
1102
1737
|
async function handleServerFunctionRequest(request, options = {}) {
|
|
1103
|
-
const codec =
|
|
1738
|
+
const codec = {
|
|
1739
|
+
...(options.codec !== undefined ? options.codec : getServerFunctionsCodec())
|
|
1740
|
+
};
|
|
1741
|
+
codec.serializeErrorStacks ??= DEV;
|
|
1104
1742
|
const url = new URL(request.url);
|
|
1105
1743
|
const method = request.method;
|
|
1106
|
-
const
|
|
1744
|
+
const address = resolveAddress(url);
|
|
1745
|
+
const functionId = address && address.id;
|
|
1107
1746
|
const declaredRead = (method === "GET" || method === "HEAD") && functionId !== null && METHODS.get(functionId) === "GET";
|
|
1108
1747
|
const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
|
|
1109
|
-
const protectsRequest = csrf !== false && !declaredRead;
|
|
1748
|
+
const protectsRequest = csrf !== false && (!declaredRead || typeof csrf === "object" && csrf.protectDeclaredReads === true);
|
|
1749
|
+
let serverFunction;
|
|
1750
|
+
if (functionId) {
|
|
1751
|
+
try {
|
|
1752
|
+
serverFunction = getServerFunction(functionId);
|
|
1753
|
+
} catch {
|
|
1754
|
+
return finalizeTransportResponse(new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
1755
|
+
status: 404,
|
|
1756
|
+
headers: {
|
|
1757
|
+
[UNKNOWN_HEADER]: "true"
|
|
1758
|
+
}
|
|
1759
|
+
}), method);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1110
1762
|
if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
|
|
1111
1763
|
return finalizeTransportResponse(forbiddenResponse(), method);
|
|
1112
1764
|
}
|
|
@@ -1117,15 +1769,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1117
1769
|
});
|
|
1118
1770
|
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1119
1771
|
}
|
|
1120
|
-
|
|
1121
|
-
try {
|
|
1122
|
-
serverFunction = getServerFunction(functionId);
|
|
1123
|
-
} catch {
|
|
1124
|
-
const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
1125
|
-
status: 404
|
|
1126
|
-
});
|
|
1127
|
-
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1128
|
-
}
|
|
1772
|
+
const scripted = address.data;
|
|
1129
1773
|
if (method !== "POST" && !declaredRead) {
|
|
1130
1774
|
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
1131
1775
|
status: 405,
|
|
@@ -1135,25 +1779,103 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1135
1779
|
});
|
|
1136
1780
|
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1137
1781
|
}
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1782
|
+
const bodySizeLimit = options.bodySizeLimit !== undefined ? options.bodySizeLimit : config.bodySizeLimit;
|
|
1783
|
+
const argsEncoding = url.searchParams.get("args");
|
|
1784
|
+
if (argsEncoding !== null && argsEncoding.length > bodySizeLimit) {
|
|
1785
|
+
const response = new Response(DEV ? "Server function arguments exceed the configured bodySizeLimit" : null, {
|
|
1786
|
+
status: 413
|
|
1787
|
+
});
|
|
1788
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1789
|
+
}
|
|
1790
|
+
if (method === "POST" && request.body !== null && bodySizeLimit !== Infinity) {
|
|
1791
|
+
const raw = request.headers.get("content-length");
|
|
1792
|
+
const declared = raw !== null && /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
1793
|
+
if (declared > bodySizeLimit) {
|
|
1794
|
+
const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
|
|
1795
|
+
status: 413
|
|
1796
|
+
});
|
|
1797
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1798
|
+
}
|
|
1799
|
+
if (!(declared > 0)) {
|
|
1800
|
+
let bounded;
|
|
1801
|
+
try {
|
|
1802
|
+
bounded = await bufferBodyWithin(request, bodySizeLimit);
|
|
1803
|
+
} catch {
|
|
1804
|
+
const response = new Response(DEV ? "Malformed server function arguments" : null, {
|
|
1805
|
+
status: 400
|
|
1806
|
+
});
|
|
1807
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1808
|
+
}
|
|
1809
|
+
if (bounded === null) {
|
|
1810
|
+
const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
|
|
1811
|
+
status: 413
|
|
1812
|
+
});
|
|
1813
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1814
|
+
}
|
|
1815
|
+
request = bounded;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
let event;
|
|
1819
|
+
try {
|
|
1820
|
+
event = options.createEvent ? options.createEvent(request) : {
|
|
1821
|
+
request,
|
|
1822
|
+
locals: {}
|
|
1823
|
+
};
|
|
1824
|
+
const promised = nativePromise(event);
|
|
1825
|
+
if (promised) event = await promised;
|
|
1826
|
+
} catch (error) {
|
|
1827
|
+
const safe = sanitizeServerError(error);
|
|
1828
|
+
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1829
|
+
const headers = new Headers();
|
|
1830
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(message));
|
|
1831
|
+
const response = scripted ? encodeResult(safe, headers, 500, codec, request.signal) : new Response(DEV ? message : null, {
|
|
1832
|
+
status: 500
|
|
1833
|
+
});
|
|
1834
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1835
|
+
}
|
|
1836
|
+
const refuseCommitted = raw => {
|
|
1837
|
+
const response = commitEventResponse(raw, event);
|
|
1838
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1141
1839
|
};
|
|
1142
1840
|
const provide = options.provideEvent || provideEvent;
|
|
1841
|
+
const scope = run => provide(event, run);
|
|
1143
1842
|
const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
|
|
1144
1843
|
const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
|
|
1145
1844
|
const wrapInvocation = options.wrapInvocation !== undefined ? options.wrapInvocation : config.wrapInvocation;
|
|
1146
1845
|
const transformFlightResult = options.transformFlightResult !== undefined ? options.transformFlightResult : config.transformFlightResult;
|
|
1147
|
-
|
|
1148
|
-
|
|
1846
|
+
let handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS;
|
|
1847
|
+
if (handleNoJS === undefined && !scripted && isFormPost(request)) {
|
|
1848
|
+
const fetchMode = request.headers.get("Sec-Fetch-Mode");
|
|
1849
|
+
if (fetchMode === null || fetchMode === "navigate") {
|
|
1850
|
+
handleNoJS = defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler());
|
|
1851
|
+
} else {
|
|
1852
|
+
const response = new Response(DEV ? "The bare server-function address answers form navigations with the " + "no-JS redirect convention. Scripted callers use the data address " + `(…/data/${functionId}) or send the ${BODY_FORMAT_HEADER} tag.` : null, {
|
|
1853
|
+
status: 400
|
|
1854
|
+
});
|
|
1855
|
+
return refuseCommitted(response);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
const flightHeader = scripted && method === "POST" ? request.headers.get(SINGLE_FLIGHT_HEADER) : null;
|
|
1859
|
+
const flightHooks = flightHeader ? flightHeader.split(",").flatMap(source => {
|
|
1860
|
+
const hook = source === "true" ? flightHook : flightSources.get(source);
|
|
1861
|
+
return hook ? [[source, hook]] : [];
|
|
1862
|
+
}) : [];
|
|
1863
|
+
const collectsFlight = flightHooks.length > 0;
|
|
1149
1864
|
let parsed;
|
|
1150
1865
|
try {
|
|
1151
|
-
parsed = await parseArguments(request, url,
|
|
1866
|
+
parsed = await parseArguments(request, url, scripted, codec);
|
|
1152
1867
|
} catch {
|
|
1153
1868
|
const response = new Response(DEV ? "Malformed server function arguments" : null, {
|
|
1154
1869
|
status: 400
|
|
1155
1870
|
});
|
|
1156
|
-
return
|
|
1871
|
+
return refuseCommitted(response);
|
|
1872
|
+
}
|
|
1873
|
+
const maxArguments = options.maxArguments !== undefined ? options.maxArguments : config.maxArguments;
|
|
1874
|
+
if (parsed.length > maxArguments) {
|
|
1875
|
+
const response = new Response(DEV ? "Server function call exceeds the configured maxArguments" : null, {
|
|
1876
|
+
status: 400
|
|
1877
|
+
});
|
|
1878
|
+
return refuseCommitted(response);
|
|
1157
1879
|
}
|
|
1158
1880
|
const flightContext = {
|
|
1159
1881
|
id: functionId,
|
|
@@ -1167,7 +1889,8 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1167
1889
|
const headers = new Headers();
|
|
1168
1890
|
const dispatch = async () => {
|
|
1169
1891
|
try {
|
|
1170
|
-
let
|
|
1892
|
+
let invocations = 0;
|
|
1893
|
+
const invokeOnce = async () => {
|
|
1171
1894
|
INVOCATIONS.set(event, {
|
|
1172
1895
|
id: functionId
|
|
1173
1896
|
});
|
|
@@ -1179,7 +1902,16 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1179
1902
|
request,
|
|
1180
1903
|
direct: false
|
|
1181
1904
|
}) : run();
|
|
1905
|
+
};
|
|
1906
|
+
let result = await provide(event, () => {
|
|
1907
|
+
if (++invocations > 1) {
|
|
1908
|
+
throw new Error("provideEvent invoked the server function callback more than once: a second " + "invocation would commit the call's side effects twice. The hook must call " + "fn exactly once and return its result.");
|
|
1909
|
+
}
|
|
1910
|
+
return invokeOnce();
|
|
1182
1911
|
});
|
|
1912
|
+
if (invocations !== 1) {
|
|
1913
|
+
throw new Error(invocations === 0 ? "provideEvent returned without invoking the server function callback: the call " + "would have answered as a void success without running the function. The hook " + "must call fn exactly once and return its result." : "provideEvent invoked the server function callback more than once: a second " + "invocation would commit the call's side effects twice. The hook must call " + "fn exactly once and return its result.");
|
|
1914
|
+
}
|
|
1183
1915
|
if (transformResult) {
|
|
1184
1916
|
result = await transformResult(event, result, flightContext);
|
|
1185
1917
|
}
|
|
@@ -1190,25 +1922,29 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1190
1922
|
response,
|
|
1191
1923
|
value
|
|
1192
1924
|
} = result;
|
|
1193
|
-
if (!
|
|
1925
|
+
if (!scripted && !handleNoJS && response && response.body) {
|
|
1194
1926
|
return response;
|
|
1195
1927
|
}
|
|
1196
1928
|
if (response && response.headers) {
|
|
1197
1929
|
mergeResponseHeaders(headers, response.headers);
|
|
1198
1930
|
}
|
|
1199
|
-
if (response && response.status && (
|
|
1931
|
+
if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
|
|
1200
1932
|
status = response.status;
|
|
1933
|
+
} else if (response && response.status) {
|
|
1934
|
+
maskRedirect(headers, response, request.url);
|
|
1201
1935
|
}
|
|
1202
1936
|
metadata = response;
|
|
1203
1937
|
result = value;
|
|
1204
1938
|
} else if (result instanceof Response) {
|
|
1205
1939
|
if (result.headers && result.headers.has("X-Content-Raw")) return result;
|
|
1206
|
-
if (
|
|
1940
|
+
if (scripted) {
|
|
1207
1941
|
if (result.headers) {
|
|
1208
1942
|
mergeResponseHeaders(headers, result.headers);
|
|
1209
1943
|
}
|
|
1210
|
-
if (result.status && (result.status
|
|
1944
|
+
if (result.status && !validRedirectStatuses.has(result.status)) {
|
|
1211
1945
|
status = result.status;
|
|
1946
|
+
} else if (result.status) {
|
|
1947
|
+
maskRedirect(headers, result, request.url);
|
|
1212
1948
|
}
|
|
1213
1949
|
metadata = result;
|
|
1214
1950
|
if (result.body == null) {
|
|
@@ -1217,7 +1953,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1217
1953
|
}
|
|
1218
1954
|
}
|
|
1219
1955
|
if (collectsFlight) {
|
|
1220
|
-
result = await foldFlightData(
|
|
1956
|
+
result = await foldFlightData(flightHooks, event, headers, {
|
|
1221
1957
|
id: functionId,
|
|
1222
1958
|
value: result,
|
|
1223
1959
|
response: metadata,
|
|
@@ -1226,19 +1962,37 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1226
1962
|
}, flightContext);
|
|
1227
1963
|
if (result instanceof Response && result.headers.has("X-Content-Raw")) return result;
|
|
1228
1964
|
}
|
|
1229
|
-
if (!
|
|
1230
|
-
if (handleNoJS) return handleNoJS(result, request, parsed);
|
|
1965
|
+
if (!scripted) {
|
|
1966
|
+
if (handleNoJS) return handleNoJS(result ?? metadata, request, parsed);
|
|
1231
1967
|
if (result instanceof Response) return result;
|
|
1232
|
-
return encodeResult(result, headers,
|
|
1968
|
+
return encodeResult(result, headers, status, codec, request.signal, scope);
|
|
1233
1969
|
}
|
|
1234
|
-
|
|
1970
|
+
if (status === 304) warnScripted304(functionId);
|
|
1971
|
+
return encodeResult(result, headers, status, codec, request.signal, scope);
|
|
1235
1972
|
} catch (x) {
|
|
1973
|
+
const respondThrown = value => {
|
|
1974
|
+
const safe = sanitizeServerError(value);
|
|
1975
|
+
if (!scripted) {
|
|
1976
|
+
if (handleNoJS) return handleNoJS(safe, request, parsed, true);
|
|
1977
|
+
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1978
|
+
return new Response(DEV ? message : null, {
|
|
1979
|
+
status: 500
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
|
|
1983
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(error));
|
|
1984
|
+
return encodeResult(safe, headers, 500, codec, request.signal, scope);
|
|
1985
|
+
};
|
|
1236
1986
|
if (x instanceof Response || isResponseEnvelope(x)) {
|
|
1237
1987
|
if (transformResult) {
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1988
|
+
try {
|
|
1989
|
+
x = await transformResult(event, x, {
|
|
1990
|
+
...flightContext,
|
|
1991
|
+
thrown: true
|
|
1992
|
+
});
|
|
1993
|
+
} catch (hookError) {
|
|
1994
|
+
return respondThrown(hookError);
|
|
1995
|
+
}
|
|
1242
1996
|
}
|
|
1243
1997
|
let status = 200;
|
|
1244
1998
|
let metadata;
|
|
@@ -1250,8 +2004,10 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1250
2004
|
if (response && response.headers) {
|
|
1251
2005
|
mergeResponseHeaders(headers, response.headers);
|
|
1252
2006
|
}
|
|
1253
|
-
if (response && response.status && (!
|
|
2007
|
+
if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
|
|
1254
2008
|
status = response.status;
|
|
2009
|
+
} else if (response && response.status) {
|
|
2010
|
+
maskRedirect(headers, response, request.url);
|
|
1255
2011
|
}
|
|
1256
2012
|
metadata = response;
|
|
1257
2013
|
x = value;
|
|
@@ -1259,8 +2015,10 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1259
2015
|
if (x.headers) {
|
|
1260
2016
|
mergeResponseHeaders(headers, x.headers);
|
|
1261
2017
|
}
|
|
1262
|
-
if (x.status && (!
|
|
2018
|
+
if (x.status && (!scripted || !validRedirectStatuses.has(x.status))) {
|
|
1263
2019
|
status = x.status;
|
|
2020
|
+
} else if (x.status) {
|
|
2021
|
+
maskRedirect(headers, x, request.url);
|
|
1264
2022
|
}
|
|
1265
2023
|
metadata = x;
|
|
1266
2024
|
if (x.body == null) {
|
|
@@ -1268,7 +2026,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1268
2026
|
}
|
|
1269
2027
|
}
|
|
1270
2028
|
if (collectsFlight) {
|
|
1271
|
-
x = await foldFlightData(
|
|
2029
|
+
x = await foldFlightData(flightHooks, event, headers, {
|
|
1272
2030
|
id: functionId,
|
|
1273
2031
|
value: x,
|
|
1274
2032
|
response: metadata,
|
|
@@ -1276,38 +2034,38 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1276
2034
|
thrown: true
|
|
1277
2035
|
}, flightContext);
|
|
1278
2036
|
if (x instanceof Response && x.headers.has("X-Content-Raw")) {
|
|
2037
|
+
x = ownResponse(x);
|
|
1279
2038
|
x.headers.set(ERROR_HEADER, "true");
|
|
1280
2039
|
return x;
|
|
1281
2040
|
}
|
|
1282
2041
|
}
|
|
1283
2042
|
headers.set(ERROR_HEADER, "true");
|
|
1284
|
-
if (!
|
|
2043
|
+
if (!scripted) {
|
|
1285
2044
|
if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
|
|
1286
2045
|
if (x instanceof Response) return x;
|
|
1287
2046
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
const safe = sanitizeServerError(x);
|
|
1291
|
-
if (!instance) {
|
|
1292
|
-
if (handleNoJS) return handleNoJS(safe, request, parsed, true);
|
|
1293
|
-
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1294
|
-
return new Response(DEV ? message : null, {
|
|
1295
|
-
status: 500
|
|
1296
|
-
});
|
|
2047
|
+
if (scripted && status === 304) warnScripted304(functionId);
|
|
2048
|
+
return encodeResult(x, headers, status, codec, request.signal, scope);
|
|
1297
2049
|
}
|
|
1298
|
-
|
|
1299
|
-
headers.set(ERROR_HEADER, encodeErrorHeaderValue(error));
|
|
1300
|
-
return encodeResult(safe, headers, 200, codec, request.signal);
|
|
2050
|
+
return respondThrown(x);
|
|
1301
2051
|
}
|
|
1302
2052
|
};
|
|
1303
|
-
const response = commitEventResponse(await dispatch(), event);
|
|
2053
|
+
const response = commitEventResponse(enforceComposedHeaderInvariants(ownResponse(await dispatch())), event);
|
|
1304
2054
|
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1305
2055
|
}
|
|
2056
|
+
function ownResponse(response) {
|
|
2057
|
+
try {
|
|
2058
|
+
return new Response(response.body, response);
|
|
2059
|
+
} catch {
|
|
2060
|
+
return response;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
1306
2063
|
function finalizeTransportResponse(response, method) {
|
|
1307
2064
|
const stripBody = method === "HEAD" && response.body !== null;
|
|
1308
|
-
|
|
2065
|
+
const defaultsCache = !response.headers.has("Cache-Control") && response.status !== 304;
|
|
2066
|
+
if (stripBody || defaultsCache) {
|
|
1309
2067
|
try {
|
|
1310
|
-
if (
|
|
2068
|
+
if (defaultsCache) {
|
|
1311
2069
|
response.headers.set("Cache-Control", "no-store");
|
|
1312
2070
|
}
|
|
1313
2071
|
if (!stripBody) return response;
|
|
@@ -1319,7 +2077,7 @@ function finalizeTransportResponse(response, method) {
|
|
|
1319
2077
|
});
|
|
1320
2078
|
} catch {
|
|
1321
2079
|
const headers = new Headers(response.headers);
|
|
1322
|
-
if (!headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
|
|
2080
|
+
if (defaultsCache && !headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
|
|
1323
2081
|
if (stripBody) response.body.cancel().catch(() => {});
|
|
1324
2082
|
return new Response(stripBody ? null : response.body, {
|
|
1325
2083
|
status: response.status,
|
|
@@ -1336,14 +2094,17 @@ exports.FLASH_COOKIE = FLASH_COOKIE;
|
|
|
1336
2094
|
exports.GENERIC_SERVER_ERROR_MESSAGE = GENERIC_SERVER_ERROR_MESSAGE;
|
|
1337
2095
|
exports.GET = GET;
|
|
1338
2096
|
exports.INSTANCE_HEADER = INSTANCE_HEADER;
|
|
2097
|
+
exports.REDIRECT_HEADER = REDIRECT_HEADER;
|
|
1339
2098
|
exports.SERVER_FUNCTION_INVOKE = SERVER_FUNCTION_INVOKE;
|
|
1340
2099
|
exports.SINGLE_FLIGHT_HEADER = SINGLE_FLIGHT_HEADER;
|
|
2100
|
+
exports.UNKNOWN_HEADER = UNKNOWN_HEADER;
|
|
1341
2101
|
exports.clearFlashCookie = clearFlashCookie;
|
|
1342
2102
|
exports.configureServerFunctionsServer = configureServerFunctionsServer;
|
|
1343
2103
|
exports.createNoJSHandler = createNoJSHandler;
|
|
1344
2104
|
exports.createServerReference = createServerReference;
|
|
1345
2105
|
exports.decodeErrorHeaderValue = decodeErrorHeaderValue;
|
|
1346
2106
|
exports.decodeFlashCookie = decodeFlashCookie;
|
|
2107
|
+
exports.decodeRedirectHeaderValue = decodeRedirectHeaderValue;
|
|
1347
2108
|
exports.decodeResponse = decodeResponse;
|
|
1348
2109
|
exports.decodeResponsePayload = decodeResponsePayload;
|
|
1349
2110
|
exports.encodeErrorHeaderValue = encodeErrorHeaderValue;
|
|
@@ -1353,6 +2114,7 @@ exports.getEventServerFunctionInvocation = getEventServerFunctionInvocation;
|
|
|
1353
2114
|
exports.getServerFunction = getServerFunction;
|
|
1354
2115
|
exports.getServerFunctionInvocation = getServerFunctionInvocation;
|
|
1355
2116
|
exports.getServerFunctionMetadata = getServerFunctionMetadata;
|
|
2117
|
+
exports.guardFailures = guardFailures;
|
|
1356
2118
|
exports.handleServerFunctionRequest = handleServerFunctionRequest;
|
|
1357
2119
|
exports.hasFlashCookie = hasFlashCookie;
|
|
1358
2120
|
exports.invoke = invoke;
|
|
@@ -1360,6 +2122,7 @@ exports.isServerFunction = isServerFunction;
|
|
|
1360
2122
|
exports.live = live;
|
|
1361
2123
|
exports.observeServerFunctionCalls = observeServerFunctionCalls;
|
|
1362
2124
|
exports.parseServerFunctionUrl = parseServerFunctionUrl;
|
|
2125
|
+
exports.registerFlightDataSource = registerFlightDataSource;
|
|
1363
2126
|
exports.registerServerFunction = registerServerFunction;
|
|
1364
2127
|
exports.registerServerReference = registerServerReference;
|
|
1365
2128
|
exports.sanitizeServerError = sanitizeServerError;
|