@solidjs/web 2.0.0-rc.4 → 2.0.0-rc.5
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 +52 -7
- package/dist/dev.js +51 -8
- package/dist/server.cjs +162 -35
- package/dist/server.js +161 -36
- package/dist/web.cjs +32 -7
- package/dist/web.js +31 -8
- 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 +432 -44
- package/frames/dist/server.js +432 -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 +150 -33
- package/server-functions/dist/client.js +147 -34
- package/server-functions/dist/server.cjs +731 -118
- package/server-functions/dist/server.dev.cjs +751 -118
- package/server-functions/dist/server.dev.js +747 -119
- package/server-functions/dist/server.js +727 -119
- 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 +58 -9
- 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/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 +58 -9
- 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
|
@@ -11,6 +11,8 @@ function isSafeError(value) {
|
|
|
11
11
|
return !!(value && (typeof value === "object" || typeof value === "function") && value[SAFE_ERROR]);
|
|
12
12
|
}
|
|
13
13
|
const REVALIDATE_HEADER = "X-Revalidate";
|
|
14
|
+
const RESPONSE_HEADER_VALUE_LIMIT = 4096;
|
|
15
|
+
const NULL_BODY_STATUSES = new Set([204, 205, 304]);
|
|
14
16
|
|
|
15
17
|
const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
|
|
16
18
|
function getServerFunctionMetadata(fn) {
|
|
@@ -83,6 +85,7 @@ function decodeSafe(text) {
|
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
function serializeCookie(name, value, options = {}) {
|
|
88
|
+
assertServableCookie(name, options);
|
|
86
89
|
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
87
90
|
cookie += `; Path=${options.path === undefined ? "/" : options.path}`;
|
|
88
91
|
if (options.domain) cookie += `; Domain=${options.domain}`;
|
|
@@ -90,12 +93,32 @@ function serializeCookie(name, value, options = {}) {
|
|
|
90
93
|
if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
91
94
|
if (options.httpOnly) cookie += "; HttpOnly";
|
|
92
95
|
if (options.secure) cookie += "; Secure";
|
|
96
|
+
if (options.partitioned) cookie += "; Partitioned";
|
|
93
97
|
if (options.sameSite) {
|
|
94
98
|
const sameSite = options.sameSite.toLowerCase();
|
|
95
99
|
cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
|
|
96
100
|
}
|
|
97
101
|
return cookie;
|
|
98
102
|
}
|
|
103
|
+
function assertServableCookie(name, options) {
|
|
104
|
+
const reject = reason => {
|
|
105
|
+
throw new Error(`serializeCookie: every browser silently rejects this cookie — ${reason}. ` + `It would never come back on a request, with no error anywhere.`);
|
|
106
|
+
};
|
|
107
|
+
const lower = name.toLowerCase();
|
|
108
|
+
if (lower.startsWith("__host-")) {
|
|
109
|
+
if (!options.secure) reject(`the __Host- prefix on \`${name}\` requires \`secure: true\``);
|
|
110
|
+
if (options.path !== undefined && options.path !== "/") reject(`the __Host- prefix on \`${name}\` requires \`Path=/\` (got \`${options.path}\`) — ` + `host-locking is the prefix's whole contract, so it cannot be path-scoped`);
|
|
111
|
+
if (options.domain) reject(`the __Host- prefix on \`${name}\` forbids \`Domain\` (got \`${options.domain}\`)`);
|
|
112
|
+
} else if (lower.startsWith("__secure-") && !options.secure) {
|
|
113
|
+
reject(`the __Secure- prefix on \`${name}\` requires \`secure: true\``);
|
|
114
|
+
}
|
|
115
|
+
if (options.sameSite && options.sameSite.toLowerCase() === "none" && !options.secure) {
|
|
116
|
+
reject("`SameSite=None` requires `secure: true`");
|
|
117
|
+
}
|
|
118
|
+
if (options.partitioned && !options.secure) {
|
|
119
|
+
reject("`Partitioned` requires `secure: true`");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
99
122
|
const FLASH_COOKIE = "flash";
|
|
100
123
|
const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
|
|
101
124
|
function hasFlashCookie(cookieHeader) {
|
|
@@ -114,8 +137,23 @@ function configureServerFunctionsCodec(codec) {
|
|
|
114
137
|
function getServerFunctionsCodec() {
|
|
115
138
|
return codecConfig.codec;
|
|
116
139
|
}
|
|
117
|
-
|
|
140
|
+
const UNNAMED_FLIGHT_SOURCE = "true";
|
|
141
|
+
const flightConfig = {
|
|
142
|
+
consumers: new Map()
|
|
143
|
+
};
|
|
144
|
+
function assertFlightSource(source) {
|
|
145
|
+
if (source === UNNAMED_FLIGHT_SOURCE || source === "" || source.includes(",")) {
|
|
146
|
+
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).`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function subscribeFlightData(sourceOrConsumer, maybeConsumer) {
|
|
150
|
+
const named = typeof sourceOrConsumer === "string";
|
|
151
|
+
if (named) assertFlightSource(sourceOrConsumer);
|
|
152
|
+
const source = named ? sourceOrConsumer : UNNAMED_FLIGHT_SOURCE;
|
|
153
|
+
const consumer = named ? maybeConsumer : sourceOrConsumer;
|
|
154
|
+
flightConfig.consumers.set(source, consumer);
|
|
118
155
|
return () => {
|
|
156
|
+
if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
|
|
119
157
|
};
|
|
120
158
|
}
|
|
121
159
|
function serverFunctionAddress(endpoint, id) {
|
|
@@ -127,10 +165,18 @@ function parseServerFunctionAddress(pathname, endpoint) {
|
|
|
127
165
|
if (!pathname.startsWith(mount)) return null;
|
|
128
166
|
const rest = pathname.slice(mount.length);
|
|
129
167
|
if (!rest.startsWith("/")) return null;
|
|
130
|
-
|
|
168
|
+
let segment = rest.slice(1);
|
|
169
|
+
let data = false;
|
|
170
|
+
if (segment.startsWith("data/")) {
|
|
171
|
+
segment = segment.slice(5);
|
|
172
|
+
data = true;
|
|
173
|
+
}
|
|
131
174
|
if (!segment || segment.includes("/")) return null;
|
|
132
175
|
try {
|
|
133
|
-
return
|
|
176
|
+
return {
|
|
177
|
+
id: decodeURIComponent(segment),
|
|
178
|
+
data
|
|
179
|
+
};
|
|
134
180
|
} catch {
|
|
135
181
|
return null;
|
|
136
182
|
}
|
|
@@ -162,6 +208,28 @@ function decodeErrorHeaderValue(value) {
|
|
|
162
208
|
}
|
|
163
209
|
const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
164
210
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
211
|
+
const UNKNOWN_HEADER = "X-Server-Function-Unknown";
|
|
212
|
+
const REDIRECT_HEADER = "X-Server-Function-Redirect";
|
|
213
|
+
function decodeRedirectHeaderValue(value) {
|
|
214
|
+
if (typeof value !== "string") return undefined;
|
|
215
|
+
const at = value.indexOf(" ");
|
|
216
|
+
if (at < 0) return undefined;
|
|
217
|
+
const status = Number(value.slice(0, at));
|
|
218
|
+
const url = value.slice(at + 1);
|
|
219
|
+
if (!Number.isInteger(status) || !url) return undefined;
|
|
220
|
+
if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) return undefined;
|
|
221
|
+
let parsed;
|
|
222
|
+
try {
|
|
223
|
+
parsed = new URL(url);
|
|
224
|
+
} catch {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
|
|
228
|
+
return {
|
|
229
|
+
status,
|
|
230
|
+
url
|
|
231
|
+
};
|
|
232
|
+
}
|
|
165
233
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
166
234
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
167
235
|
const BodyFormat = {
|
|
@@ -204,7 +272,11 @@ function isJSONSafe(value) {
|
|
|
204
272
|
const proto = Object.getPrototypeOf(v);
|
|
205
273
|
if (proto !== Object.prototype && proto !== null) return false;
|
|
206
274
|
if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
|
|
207
|
-
for (const k in v)
|
|
275
|
+
for (const k in v) {
|
|
276
|
+
const descriptor = Object.getOwnPropertyDescriptor(v, k);
|
|
277
|
+
if (descriptor === undefined || !("value" in descriptor)) return false;
|
|
278
|
+
stack.push(descriptor.value);
|
|
279
|
+
}
|
|
208
280
|
}
|
|
209
281
|
}
|
|
210
282
|
return true;
|
|
@@ -313,18 +385,34 @@ function createChunk(data) {
|
|
|
313
385
|
class ChunkReader {
|
|
314
386
|
constructor(stream) {
|
|
315
387
|
this.reader = stream.getReader();
|
|
316
|
-
this.
|
|
388
|
+
this.store = new Uint8Array(0);
|
|
389
|
+
this.buffer = this.store;
|
|
317
390
|
this.done = false;
|
|
318
391
|
}
|
|
319
392
|
async readChunk() {
|
|
320
393
|
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 {
|
|
394
|
+
if (chunk.done) {
|
|
327
395
|
this.done = true;
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const incoming = chunk.value;
|
|
399
|
+
const store = this.store;
|
|
400
|
+
const start = this.buffer.byteOffset;
|
|
401
|
+
const end = start + this.buffer.length;
|
|
402
|
+
const needed = this.buffer.length + incoming.length;
|
|
403
|
+
if (end + incoming.length <= store.length) {
|
|
404
|
+
store.set(incoming, end);
|
|
405
|
+
this.buffer = store.subarray(start, end + incoming.length);
|
|
406
|
+
} else if (needed <= store.length) {
|
|
407
|
+
store.copyWithin(0, start, end);
|
|
408
|
+
store.set(incoming, this.buffer.length);
|
|
409
|
+
this.buffer = store.subarray(0, needed);
|
|
410
|
+
} else {
|
|
411
|
+
const grown = new Uint8Array(Math.max(needed, store.length * 2));
|
|
412
|
+
grown.set(this.buffer);
|
|
413
|
+
grown.set(incoming, this.buffer.length);
|
|
414
|
+
this.store = grown;
|
|
415
|
+
this.buffer = grown.subarray(0, needed);
|
|
328
416
|
}
|
|
329
417
|
}
|
|
330
418
|
async next() {
|
|
@@ -366,6 +454,27 @@ class ChunkReader {
|
|
|
366
454
|
}
|
|
367
455
|
}
|
|
368
456
|
}
|
|
457
|
+
const ERROR_TRAILER_PREFIX = "!";
|
|
458
|
+
function encodeErrorTrailer(error) {
|
|
459
|
+
const shaped = error instanceof Error ? error : new Error(String(error));
|
|
460
|
+
return ERROR_TRAILER_PREFIX + JSON.stringify(shaped.name && shaped.name !== "Error" ? {
|
|
461
|
+
name: shaped.name,
|
|
462
|
+
message: shaped.message
|
|
463
|
+
} : {
|
|
464
|
+
message: shaped.message
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
function errorFromTrailer(payload) {
|
|
468
|
+
let shape;
|
|
469
|
+
try {
|
|
470
|
+
shape = JSON.parse(payload.slice(1));
|
|
471
|
+
} catch {
|
|
472
|
+
shape = null;
|
|
473
|
+
}
|
|
474
|
+
const error = new Error(shape && typeof shape.message === "string" ? shape.message : "Server function result could not be delivered.");
|
|
475
|
+
if (shape && typeof shape.name === "string") error.name = shape.name;
|
|
476
|
+
return error;
|
|
477
|
+
}
|
|
369
478
|
async function deserializeStream(source, codecOptions) {
|
|
370
479
|
if (!source.body) {
|
|
371
480
|
throw new Error("missing body");
|
|
@@ -373,11 +482,17 @@ async function deserializeStream(source, codecOptions) {
|
|
|
373
482
|
const reader = new ChunkReader(source.body);
|
|
374
483
|
const result = await reader.next();
|
|
375
484
|
if (!result.done) {
|
|
485
|
+
if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
486
|
+
throw errorFromTrailer(result.value);
|
|
487
|
+
}
|
|
376
488
|
const {
|
|
377
489
|
createJSONDeserializer
|
|
378
490
|
} = await import('@solidjs/web/serialization/decode');
|
|
379
491
|
const deserializeChunk = createJSONDeserializer(codecOptions);
|
|
380
492
|
function interpretChunk(chunk) {
|
|
493
|
+
if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
494
|
+
throw errorFromTrailer(chunk);
|
|
495
|
+
}
|
|
381
496
|
return deserializeChunk(JSON.parse(chunk));
|
|
382
497
|
}
|
|
383
498
|
reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
|
|
@@ -441,7 +556,7 @@ function copyInitHeaders(init) {
|
|
|
441
556
|
for (const cookie of init.getSetCookie()) headers.append("Set-Cookie", cookie);
|
|
442
557
|
return headers;
|
|
443
558
|
}
|
|
444
|
-
const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, "Location"].map(header => header.toLowerCase()));
|
|
559
|
+
const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, REDIRECT_HEADER, "Location"].map(header => header.toLowerCase()));
|
|
445
560
|
function fillsStubGap(key, headers, response) {
|
|
446
561
|
if (key === "set-cookie" || STUB_GAP_FILL_EXCLUDED.has(key)) return false;
|
|
447
562
|
if (response.body === null && (key === "content-type" || key === "content-length")) return false;
|
|
@@ -457,24 +572,16 @@ function commitEventResponse(response, event = getRequestEvent()) {
|
|
|
457
572
|
if (fillsStubGap(key, response.headers, response)) hasGaps = true;
|
|
458
573
|
});
|
|
459
574
|
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
|
-
}
|
|
575
|
+
const headers = copyInitHeaders(response.headers);
|
|
576
|
+
for (const cookie of cookies) headers.append("Set-Cookie", cookie);
|
|
577
|
+
stub.headers.forEach((value, key) => {
|
|
578
|
+
if (fillsStubGap(key, headers, response)) headers.set(key, value);
|
|
579
|
+
});
|
|
580
|
+
return new Response(response.body, {
|
|
581
|
+
status: response.status,
|
|
582
|
+
statusText: response.statusText,
|
|
583
|
+
headers
|
|
584
|
+
});
|
|
478
585
|
}
|
|
479
586
|
|
|
480
587
|
function encodeInputValue(value) {
|
|
@@ -506,11 +613,35 @@ function encodeFlashCookie(url, result, input, thrown) {
|
|
|
506
613
|
thrown: !!thrown,
|
|
507
614
|
input: input.map(encodeInputValue)
|
|
508
615
|
};
|
|
616
|
+
if (fitsCookie(payload)) return flashCookie(payload);
|
|
617
|
+
payload.truncated = true;
|
|
618
|
+
payload.input = [];
|
|
619
|
+
if (!fitsCookie(payload)) {
|
|
620
|
+
if (typeof payload.result === "string") {
|
|
621
|
+
let prefix = payload.result;
|
|
622
|
+
while (prefix.length > 0 && !fitsCookie({
|
|
623
|
+
...payload,
|
|
624
|
+
result: prefix
|
|
625
|
+
})) {
|
|
626
|
+
prefix = prefix.slice(0, prefix.length >> 1);
|
|
627
|
+
}
|
|
628
|
+
payload.result = prefix.length > 0 ? prefix : true;
|
|
629
|
+
} else {
|
|
630
|
+
payload.result = true;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return flashCookie(payload);
|
|
634
|
+
}
|
|
635
|
+
function flashCookie(payload) {
|
|
509
636
|
return serializeCookie(FLASH_COOKIE, JSON.stringify(payload), {
|
|
510
637
|
secure: true,
|
|
511
638
|
httpOnly: true
|
|
512
639
|
});
|
|
513
640
|
}
|
|
641
|
+
const COOKIE_PAIR_BUDGET = 4000;
|
|
642
|
+
function fitsCookie(payload) {
|
|
643
|
+
return FLASH_COOKIE.length + 1 + encodeURIComponent(JSON.stringify(payload)).length <= COOKIE_PAIR_BUDGET;
|
|
644
|
+
}
|
|
514
645
|
function decodeFlashCookie(cookieHeader) {
|
|
515
646
|
const match = parseCookieHeader(cookieHeader)[FLASH_COOKIE];
|
|
516
647
|
if (!match) return;
|
|
@@ -518,12 +649,14 @@ function decodeFlashCookie(cookieHeader) {
|
|
|
518
649
|
const payload = JSON.parse(match);
|
|
519
650
|
if (!payload || !payload.result) return;
|
|
520
651
|
const result = payload.error ? new Error(payload.result) : payload.result;
|
|
521
|
-
|
|
652
|
+
const submission = {
|
|
522
653
|
input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
|
|
523
654
|
url: payload.url,
|
|
524
655
|
result: payload.thrown ? undefined : result,
|
|
525
656
|
error: payload.thrown ? result : undefined
|
|
526
657
|
};
|
|
658
|
+
if (payload.truncated) submission.truncated = true;
|
|
659
|
+
return submission;
|
|
527
660
|
} catch (error) {
|
|
528
661
|
console.error(error);
|
|
529
662
|
}
|
|
@@ -538,7 +671,9 @@ const config = {
|
|
|
538
671
|
transformDirectResult: undefined,
|
|
539
672
|
handleNoJS: undefined,
|
|
540
673
|
endpoint: "/_server",
|
|
541
|
-
csrf: true
|
|
674
|
+
csrf: true,
|
|
675
|
+
bodySizeLimit: 1_048_576,
|
|
676
|
+
maxArguments: 1000
|
|
542
677
|
};
|
|
543
678
|
function configureServerFunctionsServer({
|
|
544
679
|
provideEvent,
|
|
@@ -550,7 +685,9 @@ function configureServerFunctionsServer({
|
|
|
550
685
|
handleNoJS,
|
|
551
686
|
endpoint,
|
|
552
687
|
csrf,
|
|
553
|
-
codec
|
|
688
|
+
codec,
|
|
689
|
+
bodySizeLimit,
|
|
690
|
+
maxArguments
|
|
554
691
|
} = {}) {
|
|
555
692
|
if (provideEvent !== undefined) config.provideEvent = provideEvent;
|
|
556
693
|
if (wrapInvocation !== undefined) config.wrapInvocation = wrapInvocation;
|
|
@@ -562,6 +699,16 @@ function configureServerFunctionsServer({
|
|
|
562
699
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
563
700
|
if (csrf !== undefined) config.csrf = csrf;
|
|
564
701
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
702
|
+
if (bodySizeLimit !== undefined) config.bodySizeLimit = bodySizeLimit;
|
|
703
|
+
if (maxArguments !== undefined) config.maxArguments = maxArguments;
|
|
704
|
+
}
|
|
705
|
+
const flightSources = new Map();
|
|
706
|
+
function registerFlightDataSource(source, hook) {
|
|
707
|
+
assertFlightSource(source);
|
|
708
|
+
flightSources.set(source, hook);
|
|
709
|
+
return () => {
|
|
710
|
+
if (flightSources.get(source) === hook) flightSources.delete(source);
|
|
711
|
+
};
|
|
565
712
|
}
|
|
566
713
|
function provideEvent(event, fn) {
|
|
567
714
|
if (config.provideEvent) return config.provideEvent(event, fn);
|
|
@@ -583,6 +730,7 @@ function provideRPC() {
|
|
|
583
730
|
}
|
|
584
731
|
function registerServerFunction(id, callback) {
|
|
585
732
|
provideRPC();
|
|
733
|
+
if (REGISTRATIONS.get(id) !== callback) METHODS.delete(id);
|
|
586
734
|
REGISTRATIONS.set(id, callback);
|
|
587
735
|
return callback;
|
|
588
736
|
}
|
|
@@ -656,7 +804,10 @@ function createServerReference({
|
|
|
656
804
|
const ogEvt = getRequestEvent();
|
|
657
805
|
if (!ogEvt) throw new Error("Cannot call server function outside of a request");
|
|
658
806
|
const evt = {
|
|
659
|
-
...ogEvt
|
|
807
|
+
...ogEvt,
|
|
808
|
+
locals: {
|
|
809
|
+
...ogEvt.locals
|
|
810
|
+
}
|
|
660
811
|
};
|
|
661
812
|
INVOCATIONS.set(evt, {
|
|
662
813
|
id
|
|
@@ -727,15 +878,67 @@ function getServerFunctionInvocation() {
|
|
|
727
878
|
function getEventServerFunctionInvocation(event) {
|
|
728
879
|
return event && INVOCATIONS.get(event);
|
|
729
880
|
}
|
|
730
|
-
function
|
|
881
|
+
function resolveAddress(url) {
|
|
731
882
|
return parseServerFunctionAddress(url.pathname, config.endpoint);
|
|
732
883
|
}
|
|
733
|
-
|
|
884
|
+
const DECODE_DEPTH_LIMIT = 64;
|
|
885
|
+
function assertDecodeDepth(value) {
|
|
886
|
+
let level = [value];
|
|
887
|
+
for (let depth = 0; level.length > 0; depth++) {
|
|
888
|
+
if (depth > DECODE_DEPTH_LIMIT) {
|
|
889
|
+
throw new TypeError("Server function arguments exceed the decode depth limit");
|
|
890
|
+
}
|
|
891
|
+
const next = [];
|
|
892
|
+
for (const node of level) {
|
|
893
|
+
if (node === null || typeof node !== "object") continue;
|
|
894
|
+
if (Array.isArray(node)) {
|
|
895
|
+
for (const child of node) next.push(child);
|
|
896
|
+
} else {
|
|
897
|
+
for (const key of Object.keys(node)) next.push(node[key]);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
level = next;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
async function bufferBodyWithin(request, limit) {
|
|
904
|
+
const reader = request.clone().body.getReader();
|
|
905
|
+
const chunks = [];
|
|
906
|
+
let total = 0;
|
|
907
|
+
for (;;) {
|
|
908
|
+
const {
|
|
909
|
+
done,
|
|
910
|
+
value
|
|
911
|
+
} = await reader.read();
|
|
912
|
+
if (done) break;
|
|
913
|
+
total += value.byteLength;
|
|
914
|
+
if (total > limit) {
|
|
915
|
+
reader.cancel().catch(() => {});
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
chunks.push(value);
|
|
919
|
+
}
|
|
920
|
+
const body = new Uint8Array(total);
|
|
921
|
+
let offset = 0;
|
|
922
|
+
for (const chunk of chunks) {
|
|
923
|
+
body.set(chunk, offset);
|
|
924
|
+
offset += chunk.byteLength;
|
|
925
|
+
}
|
|
926
|
+
return new Request(request, {
|
|
927
|
+
body
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
async function parseArguments(request, url, scripted, codec) {
|
|
734
931
|
const parsed = [];
|
|
735
932
|
const bodyFormat = request.method === "POST" ? request.headers.get(BODY_FORMAT_HEADER) : null;
|
|
736
933
|
const args = url.searchParams.get("args");
|
|
737
|
-
if (args && (!
|
|
738
|
-
|
|
934
|
+
if (args && (!scripted || request.method === "GET" || bodyFormat !== BodyFormat.Serialized)) {
|
|
935
|
+
let result;
|
|
936
|
+
if (args.startsWith(";0x")) {
|
|
937
|
+
result = await deserializeString(args, codec);
|
|
938
|
+
} else {
|
|
939
|
+
result = JSON.parse(args);
|
|
940
|
+
assertDecodeDepth(result);
|
|
941
|
+
}
|
|
739
942
|
if (!Array.isArray(result)) {
|
|
740
943
|
throw new TypeError("Server function arguments must encode an array");
|
|
741
944
|
}
|
|
@@ -748,18 +951,34 @@ async function parseArguments(request, url, instance, codec) {
|
|
|
748
951
|
if (request.method === "POST" && request.body !== null) {
|
|
749
952
|
const decoded = await extractBody(request.clone(), codec);
|
|
750
953
|
if (bodyFormat === BodyFormat.Serialized || bodyFormat === BodyFormat.Json) {
|
|
954
|
+
if (bodyFormat === BodyFormat.Json) assertDecodeDepth(decoded);
|
|
955
|
+
if (!Array.isArray(decoded)) {
|
|
956
|
+
throw new TypeError("Server function arguments must encode an array");
|
|
957
|
+
}
|
|
751
958
|
return decoded;
|
|
752
959
|
}
|
|
960
|
+
if (decoded === undefined) {
|
|
961
|
+
throw new TypeError("Server function body carries no usable encoding");
|
|
962
|
+
}
|
|
753
963
|
parsed.push(decoded);
|
|
754
964
|
}
|
|
755
965
|
return parsed;
|
|
756
966
|
}
|
|
757
|
-
async function foldFlightData(
|
|
967
|
+
async function foldFlightData(hooks, event, headers, outcome, context = {}) {
|
|
758
968
|
if (outcome.value instanceof Response && outcome.value.body) return outcome.value;
|
|
759
969
|
digestOutcome(event, outcome);
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
970
|
+
const folded = [];
|
|
971
|
+
for (const [source, hook] of hooks) {
|
|
972
|
+
try {
|
|
973
|
+
const slice = await hook(event, outcome);
|
|
974
|
+
if (slice !== undefined) folded.push([source, slice]);
|
|
975
|
+
} catch (error) {
|
|
976
|
+
console.error(`Error collecting flight data for source "${source}"`, error);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (folded.length === 0) return outcome.value;
|
|
980
|
+
const data = Object.fromEntries(folded);
|
|
981
|
+
headers.set(SINGLE_FLIGHT_HEADER, folded.map(([source]) => source).join(","));
|
|
763
982
|
if (context.transformFlightResult) {
|
|
764
983
|
const transformed = await context.transformFlightResult(event, {
|
|
765
984
|
value: outcome.value,
|
|
@@ -848,6 +1067,52 @@ function mergeResponseHeaders(target, source) {
|
|
|
848
1067
|
}
|
|
849
1068
|
}
|
|
850
1069
|
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
1070
|
+
function maskRedirect(headers, response, requestUrl) {
|
|
1071
|
+
const target = response.headers && response.headers.get("Location");
|
|
1072
|
+
if (target) {
|
|
1073
|
+
headers.set(REDIRECT_HEADER, `${response.status} ${new URL(target, requestUrl)}`);
|
|
1074
|
+
}
|
|
1075
|
+
headers.delete("Location");
|
|
1076
|
+
}
|
|
1077
|
+
const BOUNDED_COMPOSED_HEADERS = [REDIRECT_HEADER, "Location", REVALIDATE_HEADER];
|
|
1078
|
+
function refusedTargetScheme(target) {
|
|
1079
|
+
const match = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.exec(target);
|
|
1080
|
+
return match !== null && !/^https?:$/i.test(match[0]);
|
|
1081
|
+
}
|
|
1082
|
+
function enforceComposedHeaderInvariants(response) {
|
|
1083
|
+
for (const name of BOUNDED_COMPOSED_HEADERS) {
|
|
1084
|
+
const value = response.headers.get(name);
|
|
1085
|
+
if (value === null) continue;
|
|
1086
|
+
if (value.length > RESPONSE_HEADER_VALUE_LIMIT) {
|
|
1087
|
+
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);
|
|
1088
|
+
}
|
|
1089
|
+
if (name === REVALIDATE_HEADER) continue;
|
|
1090
|
+
const target = name === REDIRECT_HEADER ? value.slice(value.indexOf(" ") + 1) : value;
|
|
1091
|
+
if (refusedTargetScheme(target)) {
|
|
1092
|
+
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);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return response;
|
|
1096
|
+
}
|
|
1097
|
+
function refuseComposedHeader(response, name, headerMessage, body) {
|
|
1098
|
+
if (response.body) {
|
|
1099
|
+
try {
|
|
1100
|
+
const cancelled = response.body.cancel();
|
|
1101
|
+
if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
|
|
1102
|
+
} catch {}
|
|
1103
|
+
}
|
|
1104
|
+
const headers = new Headers();
|
|
1105
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(DEV ? headerMessage : GENERIC_SERVER_ERROR_MESSAGE));
|
|
1106
|
+
return new Response(body, {
|
|
1107
|
+
status: 500,
|
|
1108
|
+
headers
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
function warnScripted304(functionId) {
|
|
1112
|
+
if (DEV) {
|
|
1113
|
+
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.`);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
851
1116
|
function createNoJSHandler({
|
|
852
1117
|
base = ""
|
|
853
1118
|
} = {}) {
|
|
@@ -891,44 +1156,279 @@ function isFormPost(request) {
|
|
|
891
1156
|
const type = request.headers.get("content-type") || "";
|
|
892
1157
|
return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
|
|
893
1158
|
}
|
|
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();
|
|
1159
|
+
function guardFailures(value, state) {
|
|
1160
|
+
if (!state) state = {
|
|
1161
|
+
seen: new WeakMap(),
|
|
1162
|
+
cyclic: new WeakSet()
|
|
905
1163
|
};
|
|
906
|
-
|
|
1164
|
+
const entered = enterGuard(value, state);
|
|
1165
|
+
if (!(entered instanceof Frame)) return entered;
|
|
1166
|
+
const stack = [entered];
|
|
1167
|
+
let delivered = NOTHING;
|
|
1168
|
+
for (;;) {
|
|
1169
|
+
const top = stack[stack.length - 1];
|
|
1170
|
+
const items = top.items;
|
|
1171
|
+
let pushed = null;
|
|
1172
|
+
while (top.i < items.length) {
|
|
1173
|
+
const i = top.i;
|
|
1174
|
+
let original;
|
|
1175
|
+
if (top.kind === OBJECT) {
|
|
1176
|
+
const descriptor = top.descriptors[items[i]];
|
|
1177
|
+
if ("value" in descriptor) {
|
|
1178
|
+
original = descriptor.value;
|
|
1179
|
+
} else if (typeof descriptor.get === "function") {
|
|
1180
|
+
if (top.accessorRead !== i) {
|
|
1181
|
+
try {
|
|
1182
|
+
top.accessorValue = descriptor.get.call(top.value);
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
throw sanitizeServerError(error);
|
|
1185
|
+
}
|
|
1186
|
+
top.accessorRead = i;
|
|
1187
|
+
}
|
|
1188
|
+
original = top.accessorValue;
|
|
1189
|
+
} else {
|
|
1190
|
+
top.i++;
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1193
|
+
} else {
|
|
1194
|
+
original = items[i];
|
|
1195
|
+
}
|
|
1196
|
+
let guarded;
|
|
1197
|
+
if (delivered !== NOTHING) {
|
|
1198
|
+
guarded = delivered;
|
|
1199
|
+
delivered = NOTHING;
|
|
1200
|
+
} else {
|
|
1201
|
+
guarded = enterGuard(original, state);
|
|
1202
|
+
if (guarded instanceof Frame) {
|
|
1203
|
+
pushed = guarded;
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (top.kind === ARRAY) {
|
|
1208
|
+
if (guarded !== original) {
|
|
1209
|
+
top.next[i] = guarded;
|
|
1210
|
+
top.changed = true;
|
|
1211
|
+
}
|
|
1212
|
+
} else if (top.kind === MAP) {
|
|
1213
|
+
if ((i & 1) === 0) top.pendingKey = guarded;else top.next.set(top.pendingKey, guarded);
|
|
1214
|
+
if (guarded !== original) top.changed = true;
|
|
1215
|
+
} else if (top.kind === SET) {
|
|
1216
|
+
top.next.add(guarded);
|
|
1217
|
+
if (guarded !== original) top.changed = true;
|
|
1218
|
+
} else if (guarded !== original || top.accessorRead === i) {
|
|
1219
|
+
Object.defineProperty(top.next, items[i], top.accessorRead === i ? {
|
|
1220
|
+
enumerable: true,
|
|
1221
|
+
configurable: true,
|
|
1222
|
+
writable: true,
|
|
1223
|
+
value: guarded
|
|
1224
|
+
} : {
|
|
1225
|
+
...top.descriptors[items[i]],
|
|
1226
|
+
value: guarded
|
|
1227
|
+
});
|
|
1228
|
+
top.changed = true;
|
|
1229
|
+
}
|
|
1230
|
+
top.i++;
|
|
1231
|
+
}
|
|
1232
|
+
if (pushed !== null) {
|
|
1233
|
+
stack.push(pushed);
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
stack.pop();
|
|
1237
|
+
const out = keepGuarded(top.value, top.next, top.changed, state);
|
|
1238
|
+
if (stack.length === 0) return out;
|
|
1239
|
+
delivered = out;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
const NOTHING = Symbol();
|
|
1243
|
+
const ARRAY = 0;
|
|
1244
|
+
const MAP = 1;
|
|
1245
|
+
const SET = 2;
|
|
1246
|
+
const OBJECT = 3;
|
|
1247
|
+
class Frame {
|
|
1248
|
+
constructor(kind, value, next, items, descriptors) {
|
|
1249
|
+
this.kind = kind;
|
|
1250
|
+
this.value = value;
|
|
1251
|
+
this.next = next;
|
|
1252
|
+
this.items = items;
|
|
1253
|
+
this.descriptors = descriptors;
|
|
1254
|
+
this.i = 0;
|
|
1255
|
+
this.changed = false;
|
|
1256
|
+
this.accessorRead = -1;
|
|
1257
|
+
this.accessorValue = undefined;
|
|
1258
|
+
this.pendingKey = undefined;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
function enterGuard(value, state) {
|
|
1262
|
+
if (value === null || typeof value !== "object") return value;
|
|
1263
|
+
if (state.seen.has(value)) {
|
|
1264
|
+
state.cyclic.add(value);
|
|
1265
|
+
return state.seen.get(value);
|
|
1266
|
+
}
|
|
1267
|
+
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
1268
|
+
let reader;
|
|
1269
|
+
const gate = state.gate;
|
|
1270
|
+
let finished = false;
|
|
1271
|
+
const close = () => {
|
|
1272
|
+
if (finished) return;
|
|
1273
|
+
finished = true;
|
|
1274
|
+
try {
|
|
1275
|
+
const cancelled = reader ? reader.cancel() : value.cancel();
|
|
1276
|
+
if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
|
|
1277
|
+
} catch {}
|
|
1278
|
+
};
|
|
1279
|
+
const guardedStream = new ReadableStream({
|
|
1280
|
+
async pull(controller) {
|
|
1281
|
+
try {
|
|
1282
|
+
if (gate && !finished && !gate.wantsMore()) await gate.awaitDemand();
|
|
1283
|
+
if (finished) {
|
|
1284
|
+
controller.close();
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
if (!reader) reader = value.getReader();
|
|
1288
|
+
const {
|
|
1289
|
+
done,
|
|
1290
|
+
value: chunk
|
|
1291
|
+
} = await reader.read();
|
|
1292
|
+
done ? controller.close() : controller.enqueue(guardFailures(chunk, state));
|
|
1293
|
+
} catch (error) {
|
|
1294
|
+
controller.error(sanitizeServerError(error));
|
|
1295
|
+
}
|
|
1296
|
+
},
|
|
1297
|
+
cancel(reason) {
|
|
1298
|
+
finished = true;
|
|
1299
|
+
return reader ? reader.cancel(reason) : value.cancel(reason);
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
if (gate) gate.onOpen(close);
|
|
1303
|
+
state.seen.set(value, guardedStream);
|
|
1304
|
+
return guardedStream;
|
|
1305
|
+
}
|
|
1306
|
+
if (typeof value.then === "function") {
|
|
1307
|
+
const guardedPromise = Promise.resolve(value).then(resolved => guardFailures(resolved, state), error => {
|
|
1308
|
+
throw sanitizeServerError(error);
|
|
1309
|
+
});
|
|
1310
|
+
state.seen.set(value, guardedPromise);
|
|
1311
|
+
return guardedPromise;
|
|
1312
|
+
}
|
|
1313
|
+
if (typeof value[Symbol.asyncIterator] === "function") {
|
|
907
1314
|
const source = value;
|
|
908
|
-
|
|
1315
|
+
const gate = state.gate;
|
|
1316
|
+
const guardedIterable = {
|
|
909
1317
|
[Symbol.asyncIterator]() {
|
|
910
|
-
const
|
|
1318
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
911
1319
|
let finished = false;
|
|
912
|
-
|
|
1320
|
+
const close = () => {
|
|
913
1321
|
if (finished) return;
|
|
914
1322
|
finished = true;
|
|
915
1323
|
try {
|
|
916
|
-
const returned =
|
|
1324
|
+
const returned = iterator.return && iterator.return();
|
|
917
1325
|
if (returned && typeof returned.then === "function") returned.then(undefined, () => {});
|
|
918
1326
|
} catch {}
|
|
919
1327
|
};
|
|
920
|
-
if (
|
|
1328
|
+
if (gate) gate.onOpen(close);
|
|
1329
|
+
const step = () => finished ? Promise.resolve({
|
|
1330
|
+
done: true,
|
|
1331
|
+
value: undefined
|
|
1332
|
+
}) : iterator.next().then(step => {
|
|
1333
|
+
if (step.done) {
|
|
1334
|
+
finished = true;
|
|
1335
|
+
return step;
|
|
1336
|
+
}
|
|
1337
|
+
return {
|
|
1338
|
+
done: false,
|
|
1339
|
+
value: guardFailures(step.value, state)
|
|
1340
|
+
};
|
|
1341
|
+
}, error => {
|
|
1342
|
+
throw sanitizeServerError(error);
|
|
1343
|
+
});
|
|
921
1344
|
return {
|
|
922
|
-
next: () => finished ?
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1345
|
+
next: () => finished || !gate || gate.wantsMore() ? step() : gate.awaitDemand().then(step),
|
|
1346
|
+
return: () => {
|
|
1347
|
+
close();
|
|
1348
|
+
return Promise.resolve({
|
|
1349
|
+
done: true,
|
|
1350
|
+
value: undefined
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
926
1353
|
};
|
|
927
1354
|
}
|
|
928
1355
|
};
|
|
1356
|
+
state.seen.set(value, guardedIterable);
|
|
1357
|
+
return guardedIterable;
|
|
1358
|
+
}
|
|
1359
|
+
if (Array.isArray(value)) {
|
|
1360
|
+
const next = value.slice();
|
|
1361
|
+
state.seen.set(value, next);
|
|
1362
|
+
return new Frame(ARRAY, value, next, value, null);
|
|
1363
|
+
}
|
|
1364
|
+
if (value instanceof Map) {
|
|
1365
|
+
const next = new Map();
|
|
1366
|
+
state.seen.set(value, next);
|
|
1367
|
+
const items = [];
|
|
1368
|
+
for (const entry of value) items.push(entry[0], entry[1]);
|
|
1369
|
+
return new Frame(MAP, value, next, items, null);
|
|
1370
|
+
}
|
|
1371
|
+
if (value instanceof Set) {
|
|
1372
|
+
const next = new Set();
|
|
1373
|
+
state.seen.set(value, next);
|
|
1374
|
+
return new Frame(SET, value, next, [...value], null);
|
|
929
1375
|
}
|
|
1376
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1377
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
1378
|
+
state.seen.set(value, value);
|
|
1379
|
+
return value;
|
|
1380
|
+
}
|
|
1381
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1382
|
+
const next = Object.create(prototype, descriptors);
|
|
1383
|
+
state.seen.set(value, next);
|
|
1384
|
+
return new Frame(OBJECT, value, next, Object.keys(descriptors), descriptors);
|
|
1385
|
+
}
|
|
1386
|
+
function keepGuarded(value, next, changed, state) {
|
|
1387
|
+
if (changed || state.cyclic.has(value)) return next;
|
|
1388
|
+
state.seen.set(value, value);
|
|
1389
|
+
return value;
|
|
1390
|
+
}
|
|
1391
|
+
function serializeResponseStream(value, codecOptions, signal) {
|
|
1392
|
+
let closed = false;
|
|
1393
|
+
let streamController = null;
|
|
1394
|
+
let demandWaiters = null;
|
|
1395
|
+
const wantsMore = () => streamController !== null && streamController.desiredSize > 0;
|
|
1396
|
+
const awaitDemand = () => new Promise(resolve => (demandWaiters ??= []).push(resolve));
|
|
1397
|
+
const supplyDemand = () => {
|
|
1398
|
+
const resolvers = demandWaiters;
|
|
1399
|
+
demandWaiters = null;
|
|
1400
|
+
if (resolvers) for (const resolve of resolvers) resolve();
|
|
1401
|
+
};
|
|
1402
|
+
const sourceClosers = new Set();
|
|
1403
|
+
const gate = {
|
|
1404
|
+
wantsMore,
|
|
1405
|
+
awaitDemand,
|
|
1406
|
+
onOpen(close) {
|
|
1407
|
+
if (closed) close();else sourceClosers.add(close);
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
value = guardFailures(value, {
|
|
1411
|
+
seen: new WeakMap(),
|
|
1412
|
+
cyclic: new WeakSet(),
|
|
1413
|
+
gate
|
|
1414
|
+
});
|
|
1415
|
+
let cancelSerialize = null;
|
|
1416
|
+
let onAbort = null;
|
|
1417
|
+
const finishSource = () => {
|
|
1418
|
+
for (const close of sourceClosers) close();
|
|
1419
|
+
sourceClosers.clear();
|
|
1420
|
+
supplyDemand();
|
|
1421
|
+
};
|
|
1422
|
+
const teardown = () => {
|
|
1423
|
+
if (closed) return;
|
|
1424
|
+
closed = true;
|
|
1425
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1426
|
+
if (cancelSerialize) cancelSerialize();
|
|
1427
|
+
finishSource();
|
|
1428
|
+
};
|
|
930
1429
|
return new ReadableStream({
|
|
931
1430
|
async start(controller) {
|
|
1431
|
+
streamController = controller;
|
|
932
1432
|
if (signal) {
|
|
933
1433
|
if (signal.aborted) {
|
|
934
1434
|
teardown();
|
|
@@ -964,16 +1464,29 @@ function serializeResponseStream(value, codecOptions, signal) {
|
|
|
964
1464
|
if (closed) return;
|
|
965
1465
|
closed = true;
|
|
966
1466
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1467
|
+
finishSource();
|
|
967
1468
|
controller.close();
|
|
968
1469
|
},
|
|
969
1470
|
onError(error) {
|
|
970
1471
|
if (closed) return;
|
|
971
1472
|
closed = true;
|
|
972
1473
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
973
|
-
|
|
1474
|
+
finishSource();
|
|
1475
|
+
try {
|
|
1476
|
+
const delivered = sanitizeServerError(DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error);
|
|
1477
|
+
controller.enqueue(createChunk(encodeErrorTrailer(delivered)));
|
|
1478
|
+
controller.close();
|
|
1479
|
+
} catch {
|
|
1480
|
+
try {
|
|
1481
|
+
controller.error(error);
|
|
1482
|
+
} catch {}
|
|
1483
|
+
}
|
|
974
1484
|
}
|
|
975
1485
|
});
|
|
976
1486
|
},
|
|
1487
|
+
pull() {
|
|
1488
|
+
supplyDemand();
|
|
1489
|
+
},
|
|
977
1490
|
cancel() {
|
|
978
1491
|
teardown();
|
|
979
1492
|
}
|
|
@@ -987,6 +1500,18 @@ function serializedResponse(value, headers, codec, signal) {
|
|
|
987
1500
|
});
|
|
988
1501
|
}
|
|
989
1502
|
function encodeResult(value, headers, status, codec, signal) {
|
|
1503
|
+
if (NULL_BODY_STATUSES.has(status)) {
|
|
1504
|
+
if (value === undefined || value === null) {
|
|
1505
|
+
headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
|
|
1506
|
+
return new Response(null, {
|
|
1507
|
+
status,
|
|
1508
|
+
headers
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
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.`);
|
|
1512
|
+
headers.set(ERROR_HEADER, encodeErrorHeaderValue(error.message));
|
|
1513
|
+
return encodeResult(error, headers, 500, codec, signal);
|
|
1514
|
+
}
|
|
990
1515
|
const direct = getHeadersAndBody(value);
|
|
991
1516
|
if (direct) {
|
|
992
1517
|
for (const [key, val] of Object.entries(direct.headers || {})) {
|
|
@@ -1015,11 +1540,25 @@ function encodeResult(value, headers, status, codec, signal) {
|
|
|
1015
1540
|
}
|
|
1016
1541
|
} catch {
|
|
1017
1542
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
status,
|
|
1021
|
-
|
|
1022
|
-
|
|
1543
|
+
try {
|
|
1544
|
+
const response = serializedResponse(value, headers, codec, signal);
|
|
1545
|
+
return status === 200 ? response : new Response(response.body, {
|
|
1546
|
+
status,
|
|
1547
|
+
headers
|
|
1548
|
+
});
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
throw DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
const ERROR_HEADER_VALUE_LIMIT = 1024;
|
|
1554
|
+
function boundedErrorHeaderValue(message) {
|
|
1555
|
+
let label = message.length > 256 ? message.slice(0, 256) : message;
|
|
1556
|
+
let encoded = encodeErrorHeaderValue(label);
|
|
1557
|
+
while (encoded.length > ERROR_HEADER_VALUE_LIMIT && label.length > 1) {
|
|
1558
|
+
label = label.slice(0, Math.ceil(label.length / 2));
|
|
1559
|
+
encoded = encodeErrorHeaderValue(label);
|
|
1560
|
+
}
|
|
1561
|
+
return encoded;
|
|
1023
1562
|
}
|
|
1024
1563
|
const GENERIC_SERVER_ERROR_MESSAGE = "Internal Server Error";
|
|
1025
1564
|
let DEV = true === true;
|
|
@@ -1043,11 +1582,12 @@ function serverFunctionUrl(id, boundArgs) {
|
|
|
1043
1582
|
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
1044
1583
|
}
|
|
1045
1584
|
function parseServerFunctionUrl(url) {
|
|
1046
|
-
|
|
1585
|
+
const parsed = parseServerFunctionAddress(new URL(url, "http://localhost").pathname, config.endpoint);
|
|
1586
|
+
return parsed && parsed.id;
|
|
1047
1587
|
}
|
|
1048
1588
|
async function matchesOrigin(origin, request, matcher) {
|
|
1049
1589
|
if (matcher === undefined) return origin === new URL(request.url).origin;
|
|
1050
|
-
if (typeof matcher === "function") return
|
|
1590
|
+
if (typeof matcher === "function") return (await matcher(origin, request)) === true;
|
|
1051
1591
|
return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
|
|
1052
1592
|
}
|
|
1053
1593
|
async function allowsServerFunctionRequest(request, options) {
|
|
@@ -1103,10 +1643,24 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1103
1643
|
const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
|
|
1104
1644
|
const url = new URL(request.url);
|
|
1105
1645
|
const method = request.method;
|
|
1106
|
-
const
|
|
1646
|
+
const address = resolveAddress(url);
|
|
1647
|
+
const functionId = address && address.id;
|
|
1107
1648
|
const declaredRead = (method === "GET" || method === "HEAD") && functionId !== null && METHODS.get(functionId) === "GET";
|
|
1108
1649
|
const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
|
|
1109
|
-
const protectsRequest = csrf !== false && !declaredRead;
|
|
1650
|
+
const protectsRequest = csrf !== false && (!declaredRead || typeof csrf === "object" && csrf.protectDeclaredReads === true);
|
|
1651
|
+
let serverFunction;
|
|
1652
|
+
if (functionId) {
|
|
1653
|
+
try {
|
|
1654
|
+
serverFunction = getServerFunction(functionId);
|
|
1655
|
+
} catch {
|
|
1656
|
+
return finalizeTransportResponse(new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
1657
|
+
status: 404,
|
|
1658
|
+
headers: {
|
|
1659
|
+
[UNKNOWN_HEADER]: "true"
|
|
1660
|
+
}
|
|
1661
|
+
}), method);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1110
1664
|
if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
|
|
1111
1665
|
return finalizeTransportResponse(forbiddenResponse(), method);
|
|
1112
1666
|
}
|
|
@@ -1117,15 +1671,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1117
1671
|
});
|
|
1118
1672
|
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1119
1673
|
}
|
|
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
|
-
}
|
|
1674
|
+
const scripted = address.data;
|
|
1129
1675
|
if (method !== "POST" && !declaredRead) {
|
|
1130
1676
|
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
1131
1677
|
status: 405,
|
|
@@ -1135,25 +1681,81 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1135
1681
|
});
|
|
1136
1682
|
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1137
1683
|
}
|
|
1138
|
-
const
|
|
1684
|
+
const bodySizeLimit = options.bodySizeLimit !== undefined ? options.bodySizeLimit : config.bodySizeLimit;
|
|
1685
|
+
const argsEncoding = url.searchParams.get("args");
|
|
1686
|
+
if (argsEncoding !== null && argsEncoding.length > bodySizeLimit) {
|
|
1687
|
+
const response = new Response(DEV ? "Server function arguments exceed the configured bodySizeLimit" : null, {
|
|
1688
|
+
status: 413
|
|
1689
|
+
});
|
|
1690
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1691
|
+
}
|
|
1692
|
+
if (method === "POST" && request.body !== null && bodySizeLimit !== Infinity) {
|
|
1693
|
+
const raw = request.headers.get("content-length");
|
|
1694
|
+
const declared = raw !== null && /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
1695
|
+
if (declared > bodySizeLimit) {
|
|
1696
|
+
const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
|
|
1697
|
+
status: 413
|
|
1698
|
+
});
|
|
1699
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1700
|
+
}
|
|
1701
|
+
if (!(declared > 0)) {
|
|
1702
|
+
const bounded = await bufferBodyWithin(request, bodySizeLimit);
|
|
1703
|
+
if (bounded === null) {
|
|
1704
|
+
const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
|
|
1705
|
+
status: 413
|
|
1706
|
+
});
|
|
1707
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1708
|
+
}
|
|
1709
|
+
request = bounded;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
let event = options.createEvent ? options.createEvent(request) : {
|
|
1139
1713
|
request,
|
|
1140
1714
|
locals: {}
|
|
1141
1715
|
};
|
|
1716
|
+
if (typeof event?.then === "function") event = await event;
|
|
1717
|
+
const refuseCommitted = raw => {
|
|
1718
|
+
const response = commitEventResponse(raw, event);
|
|
1719
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1720
|
+
};
|
|
1142
1721
|
const provide = options.provideEvent || provideEvent;
|
|
1143
1722
|
const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
|
|
1144
1723
|
const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
|
|
1145
1724
|
const wrapInvocation = options.wrapInvocation !== undefined ? options.wrapInvocation : config.wrapInvocation;
|
|
1146
1725
|
const transformFlightResult = options.transformFlightResult !== undefined ? options.transformFlightResult : config.transformFlightResult;
|
|
1147
|
-
|
|
1148
|
-
|
|
1726
|
+
let handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS;
|
|
1727
|
+
if (handleNoJS === undefined && !scripted && isFormPost(request)) {
|
|
1728
|
+
const fetchMode = request.headers.get("Sec-Fetch-Mode");
|
|
1729
|
+
if (fetchMode === null || fetchMode === "navigate") {
|
|
1730
|
+
handleNoJS = defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler());
|
|
1731
|
+
} else {
|
|
1732
|
+
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, {
|
|
1733
|
+
status: 400
|
|
1734
|
+
});
|
|
1735
|
+
return refuseCommitted(response);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
const flightHeader = scripted && method === "POST" ? request.headers.get(SINGLE_FLIGHT_HEADER) : null;
|
|
1739
|
+
const flightHooks = flightHeader ? flightHeader.split(",").flatMap(source => {
|
|
1740
|
+
const hook = source === "true" ? flightHook : flightSources.get(source);
|
|
1741
|
+
return hook ? [[source, hook]] : [];
|
|
1742
|
+
}) : [];
|
|
1743
|
+
const collectsFlight = flightHooks.length > 0;
|
|
1149
1744
|
let parsed;
|
|
1150
1745
|
try {
|
|
1151
|
-
parsed = await parseArguments(request, url,
|
|
1746
|
+
parsed = await parseArguments(request, url, scripted, codec);
|
|
1152
1747
|
} catch {
|
|
1153
1748
|
const response = new Response(DEV ? "Malformed server function arguments" : null, {
|
|
1154
1749
|
status: 400
|
|
1155
1750
|
});
|
|
1156
|
-
return
|
|
1751
|
+
return refuseCommitted(response);
|
|
1752
|
+
}
|
|
1753
|
+
const maxArguments = options.maxArguments !== undefined ? options.maxArguments : config.maxArguments;
|
|
1754
|
+
if (parsed.length > maxArguments) {
|
|
1755
|
+
const response = new Response(DEV ? "Server function call exceeds the configured maxArguments" : null, {
|
|
1756
|
+
status: 400
|
|
1757
|
+
});
|
|
1758
|
+
return refuseCommitted(response);
|
|
1157
1759
|
}
|
|
1158
1760
|
const flightContext = {
|
|
1159
1761
|
id: functionId,
|
|
@@ -1190,25 +1792,29 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1190
1792
|
response,
|
|
1191
1793
|
value
|
|
1192
1794
|
} = result;
|
|
1193
|
-
if (!
|
|
1795
|
+
if (!scripted && !handleNoJS && response && response.body) {
|
|
1194
1796
|
return response;
|
|
1195
1797
|
}
|
|
1196
1798
|
if (response && response.headers) {
|
|
1197
1799
|
mergeResponseHeaders(headers, response.headers);
|
|
1198
1800
|
}
|
|
1199
|
-
if (response && response.status && (
|
|
1801
|
+
if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
|
|
1200
1802
|
status = response.status;
|
|
1803
|
+
} else if (response && response.status) {
|
|
1804
|
+
maskRedirect(headers, response, request.url);
|
|
1201
1805
|
}
|
|
1202
1806
|
metadata = response;
|
|
1203
1807
|
result = value;
|
|
1204
1808
|
} else if (result instanceof Response) {
|
|
1205
1809
|
if (result.headers && result.headers.has("X-Content-Raw")) return result;
|
|
1206
|
-
if (
|
|
1810
|
+
if (scripted) {
|
|
1207
1811
|
if (result.headers) {
|
|
1208
1812
|
mergeResponseHeaders(headers, result.headers);
|
|
1209
1813
|
}
|
|
1210
|
-
if (result.status && (result.status
|
|
1814
|
+
if (result.status && !validRedirectStatuses.has(result.status)) {
|
|
1211
1815
|
status = result.status;
|
|
1816
|
+
} else if (result.status) {
|
|
1817
|
+
maskRedirect(headers, result, request.url);
|
|
1212
1818
|
}
|
|
1213
1819
|
metadata = result;
|
|
1214
1820
|
if (result.body == null) {
|
|
@@ -1217,7 +1823,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1217
1823
|
}
|
|
1218
1824
|
}
|
|
1219
1825
|
if (collectsFlight) {
|
|
1220
|
-
result = await foldFlightData(
|
|
1826
|
+
result = await foldFlightData(flightHooks, event, headers, {
|
|
1221
1827
|
id: functionId,
|
|
1222
1828
|
value: result,
|
|
1223
1829
|
response: metadata,
|
|
@@ -1226,19 +1832,37 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1226
1832
|
}, flightContext);
|
|
1227
1833
|
if (result instanceof Response && result.headers.has("X-Content-Raw")) return result;
|
|
1228
1834
|
}
|
|
1229
|
-
if (!
|
|
1230
|
-
if (handleNoJS) return handleNoJS(result, request, parsed);
|
|
1835
|
+
if (!scripted) {
|
|
1836
|
+
if (handleNoJS) return handleNoJS(result ?? metadata, request, parsed);
|
|
1231
1837
|
if (result instanceof Response) return result;
|
|
1232
|
-
return encodeResult(result, headers,
|
|
1838
|
+
return encodeResult(result, headers, status, codec, request.signal);
|
|
1233
1839
|
}
|
|
1840
|
+
if (status === 304) warnScripted304(functionId);
|
|
1234
1841
|
return encodeResult(result, headers, status, codec, request.signal);
|
|
1235
1842
|
} catch (x) {
|
|
1843
|
+
const respondThrown = value => {
|
|
1844
|
+
const safe = sanitizeServerError(value);
|
|
1845
|
+
if (!scripted) {
|
|
1846
|
+
if (handleNoJS) return handleNoJS(safe, request, parsed, true);
|
|
1847
|
+
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1848
|
+
return new Response(DEV ? message : null, {
|
|
1849
|
+
status: 500
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
|
|
1853
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(error));
|
|
1854
|
+
return encodeResult(safe, headers, 500, codec, request.signal);
|
|
1855
|
+
};
|
|
1236
1856
|
if (x instanceof Response || isResponseEnvelope(x)) {
|
|
1237
1857
|
if (transformResult) {
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1858
|
+
try {
|
|
1859
|
+
x = await transformResult(event, x, {
|
|
1860
|
+
...flightContext,
|
|
1861
|
+
thrown: true
|
|
1862
|
+
});
|
|
1863
|
+
} catch (hookError) {
|
|
1864
|
+
return respondThrown(hookError);
|
|
1865
|
+
}
|
|
1242
1866
|
}
|
|
1243
1867
|
let status = 200;
|
|
1244
1868
|
let metadata;
|
|
@@ -1250,8 +1874,10 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1250
1874
|
if (response && response.headers) {
|
|
1251
1875
|
mergeResponseHeaders(headers, response.headers);
|
|
1252
1876
|
}
|
|
1253
|
-
if (response && response.status && (!
|
|
1877
|
+
if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
|
|
1254
1878
|
status = response.status;
|
|
1879
|
+
} else if (response && response.status) {
|
|
1880
|
+
maskRedirect(headers, response, request.url);
|
|
1255
1881
|
}
|
|
1256
1882
|
metadata = response;
|
|
1257
1883
|
x = value;
|
|
@@ -1259,8 +1885,10 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1259
1885
|
if (x.headers) {
|
|
1260
1886
|
mergeResponseHeaders(headers, x.headers);
|
|
1261
1887
|
}
|
|
1262
|
-
if (x.status && (!
|
|
1888
|
+
if (x.status && (!scripted || !validRedirectStatuses.has(x.status))) {
|
|
1263
1889
|
status = x.status;
|
|
1890
|
+
} else if (x.status) {
|
|
1891
|
+
maskRedirect(headers, x, request.url);
|
|
1264
1892
|
}
|
|
1265
1893
|
metadata = x;
|
|
1266
1894
|
if (x.body == null) {
|
|
@@ -1268,7 +1896,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1268
1896
|
}
|
|
1269
1897
|
}
|
|
1270
1898
|
if (collectsFlight) {
|
|
1271
|
-
x = await foldFlightData(
|
|
1899
|
+
x = await foldFlightData(flightHooks, event, headers, {
|
|
1272
1900
|
id: functionId,
|
|
1273
1901
|
value: x,
|
|
1274
1902
|
response: metadata,
|
|
@@ -1276,38 +1904,38 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1276
1904
|
thrown: true
|
|
1277
1905
|
}, flightContext);
|
|
1278
1906
|
if (x instanceof Response && x.headers.has("X-Content-Raw")) {
|
|
1907
|
+
x = ownResponse(x);
|
|
1279
1908
|
x.headers.set(ERROR_HEADER, "true");
|
|
1280
1909
|
return x;
|
|
1281
1910
|
}
|
|
1282
1911
|
}
|
|
1283
1912
|
headers.set(ERROR_HEADER, "true");
|
|
1284
|
-
if (!
|
|
1913
|
+
if (!scripted) {
|
|
1285
1914
|
if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
|
|
1286
1915
|
if (x instanceof Response) return x;
|
|
1287
1916
|
}
|
|
1917
|
+
if (scripted && status === 304) warnScripted304(functionId);
|
|
1288
1918
|
return encodeResult(x, headers, status, codec, request.signal);
|
|
1289
1919
|
}
|
|
1290
|
-
|
|
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
|
-
});
|
|
1297
|
-
}
|
|
1298
|
-
const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
|
|
1299
|
-
headers.set(ERROR_HEADER, encodeErrorHeaderValue(error));
|
|
1300
|
-
return encodeResult(safe, headers, 200, codec, request.signal);
|
|
1920
|
+
return respondThrown(x);
|
|
1301
1921
|
}
|
|
1302
1922
|
};
|
|
1303
|
-
const response = commitEventResponse(await dispatch(), event);
|
|
1923
|
+
const response = commitEventResponse(enforceComposedHeaderInvariants(ownResponse(await dispatch())), event);
|
|
1304
1924
|
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1305
1925
|
}
|
|
1926
|
+
function ownResponse(response) {
|
|
1927
|
+
try {
|
|
1928
|
+
return new Response(response.body, response);
|
|
1929
|
+
} catch {
|
|
1930
|
+
return response;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1306
1933
|
function finalizeTransportResponse(response, method) {
|
|
1307
1934
|
const stripBody = method === "HEAD" && response.body !== null;
|
|
1308
|
-
|
|
1935
|
+
const defaultsCache = !response.headers.has("Cache-Control") && response.status !== 304;
|
|
1936
|
+
if (stripBody || defaultsCache) {
|
|
1309
1937
|
try {
|
|
1310
|
-
if (
|
|
1938
|
+
if (defaultsCache) {
|
|
1311
1939
|
response.headers.set("Cache-Control", "no-store");
|
|
1312
1940
|
}
|
|
1313
1941
|
if (!stripBody) return response;
|
|
@@ -1319,7 +1947,7 @@ function finalizeTransportResponse(response, method) {
|
|
|
1319
1947
|
});
|
|
1320
1948
|
} catch {
|
|
1321
1949
|
const headers = new Headers(response.headers);
|
|
1322
|
-
if (!headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
|
|
1950
|
+
if (defaultsCache && !headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
|
|
1323
1951
|
if (stripBody) response.body.cancel().catch(() => {});
|
|
1324
1952
|
return new Response(stripBody ? null : response.body, {
|
|
1325
1953
|
status: response.status,
|
|
@@ -1336,14 +1964,17 @@ exports.FLASH_COOKIE = FLASH_COOKIE;
|
|
|
1336
1964
|
exports.GENERIC_SERVER_ERROR_MESSAGE = GENERIC_SERVER_ERROR_MESSAGE;
|
|
1337
1965
|
exports.GET = GET;
|
|
1338
1966
|
exports.INSTANCE_HEADER = INSTANCE_HEADER;
|
|
1967
|
+
exports.REDIRECT_HEADER = REDIRECT_HEADER;
|
|
1339
1968
|
exports.SERVER_FUNCTION_INVOKE = SERVER_FUNCTION_INVOKE;
|
|
1340
1969
|
exports.SINGLE_FLIGHT_HEADER = SINGLE_FLIGHT_HEADER;
|
|
1970
|
+
exports.UNKNOWN_HEADER = UNKNOWN_HEADER;
|
|
1341
1971
|
exports.clearFlashCookie = clearFlashCookie;
|
|
1342
1972
|
exports.configureServerFunctionsServer = configureServerFunctionsServer;
|
|
1343
1973
|
exports.createNoJSHandler = createNoJSHandler;
|
|
1344
1974
|
exports.createServerReference = createServerReference;
|
|
1345
1975
|
exports.decodeErrorHeaderValue = decodeErrorHeaderValue;
|
|
1346
1976
|
exports.decodeFlashCookie = decodeFlashCookie;
|
|
1977
|
+
exports.decodeRedirectHeaderValue = decodeRedirectHeaderValue;
|
|
1347
1978
|
exports.decodeResponse = decodeResponse;
|
|
1348
1979
|
exports.decodeResponsePayload = decodeResponsePayload;
|
|
1349
1980
|
exports.encodeErrorHeaderValue = encodeErrorHeaderValue;
|
|
@@ -1353,6 +1984,7 @@ exports.getEventServerFunctionInvocation = getEventServerFunctionInvocation;
|
|
|
1353
1984
|
exports.getServerFunction = getServerFunction;
|
|
1354
1985
|
exports.getServerFunctionInvocation = getServerFunctionInvocation;
|
|
1355
1986
|
exports.getServerFunctionMetadata = getServerFunctionMetadata;
|
|
1987
|
+
exports.guardFailures = guardFailures;
|
|
1356
1988
|
exports.handleServerFunctionRequest = handleServerFunctionRequest;
|
|
1357
1989
|
exports.hasFlashCookie = hasFlashCookie;
|
|
1358
1990
|
exports.invoke = invoke;
|
|
@@ -1360,6 +1992,7 @@ exports.isServerFunction = isServerFunction;
|
|
|
1360
1992
|
exports.live = live;
|
|
1361
1993
|
exports.observeServerFunctionCalls = observeServerFunctionCalls;
|
|
1362
1994
|
exports.parseServerFunctionUrl = parseServerFunctionUrl;
|
|
1995
|
+
exports.registerFlightDataSource = registerFlightDataSource;
|
|
1363
1996
|
exports.registerServerFunction = registerServerFunction;
|
|
1364
1997
|
exports.registerServerReference = registerServerReference;
|
|
1365
1998
|
exports.sanitizeServerError = sanitizeServerError;
|