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