@solidjs/web 2.0.0-rc.4 → 2.0.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev.cjs +163 -36
- package/dist/dev.js +161 -37
- package/dist/server.cjs +178 -37
- package/dist/server.js +177 -38
- package/dist/web.cjs +143 -36
- package/dist/web.js +141 -37
- package/frames/dist/client.cjs +41 -8
- package/frames/dist/client.dev.cjs +41 -8
- package/frames/dist/client.dev.js +41 -8
- package/frames/dist/client.js +41 -8
- package/frames/dist/server.cjs +497 -44
- package/frames/dist/server.js +497 -44
- package/package.json +2 -2
- package/serialization/dist/decode.cjs +4 -2
- package/serialization/dist/decode.js +4 -2
- package/serialization/dist/serialization.cjs +12 -8
- package/serialization/dist/serialization.js +12 -8
- package/serialization/types/index.d.ts +7 -0
- package/serialization/types/serializer-decode.d.ts +14 -1
- package/serialization/types/serializer.d.ts +7 -0
- package/serialization/types-cjs/index.d.cts +7 -0
- package/serialization/types-cjs/serializer-decode.d.cts +14 -1
- package/serialization/types-cjs/serializer.d.cts +7 -0
- package/server-functions/dist/client.cjs +164 -35
- package/server-functions/dist/client.js +161 -36
- package/server-functions/dist/server.cjs +899 -136
- package/server-functions/dist/server.dev.cjs +919 -136
- package/server-functions/dist/server.dev.js +915 -137
- package/server-functions/dist/server.js +895 -137
- package/types/client.d.ts +2 -1
- package/types/constants.d.ts +3 -1
- package/types/cookies.d.ts +6 -14
- package/types/frames/frame-client.d.ts +4 -0
- package/types/frames/serializer-decode.d.ts +14 -1
- package/types/frames/serializer.d.ts +7 -0
- package/types/jsx.d.ts +11 -16
- package/types/response.d.ts +11 -0
- package/types/serializer-decode.d.ts +14 -1
- package/types/serializer.d.ts +7 -0
- package/types/server-functions/client.d.ts +22 -2
- package/types/server-functions/flash.d.ts +9 -0
- package/types/server-functions/server.d.ts +131 -11
- package/types/server-functions/shared.d.ts +77 -14
- package/types/server-mock.d.ts +17 -2
- package/types/server.d.ts +16 -2
- package/types-cjs/client.d.cts +2 -1
- package/types-cjs/constants.d.cts +3 -1
- package/types-cjs/cookies.d.cts +6 -14
- package/types-cjs/frames/frame-client.d.cts +4 -0
- package/types-cjs/frames/serializer-decode.d.cts +14 -1
- package/types-cjs/frames/serializer.d.cts +7 -0
- package/types-cjs/jsx.d.cts +11 -16
- package/types-cjs/response.d.cts +11 -0
- package/types-cjs/serializer-decode.d.cts +14 -1
- package/types-cjs/serializer.d.cts +7 -0
- package/types-cjs/server-functions/client.d.cts +22 -2
- package/types-cjs/server-functions/flash.d.cts +9 -0
- package/types-cjs/server-functions/server.d.cts +131 -11
- package/types-cjs/server-functions/shared.d.cts +77 -14
- package/types-cjs/server-mock.d.cts +17 -2
- package/types-cjs/server.d.cts +16 -2
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
var solidJs = require('solid-js');
|
|
4
4
|
|
|
5
|
+
const COMPOSED_BODY_FRAMING = /*#__PURE__*/new Set(["content-length", "content-encoding", "transfer-encoding"]);
|
|
6
|
+
function isHttpNavigationTarget(target) {
|
|
7
|
+
try {
|
|
8
|
+
const protocol = new URL(target, "http://base.invalid").protocol;
|
|
9
|
+
return protocol === "http:" || protocol === "https:";
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
const ENVELOPE = Symbol.for("solid.ResponseEnvelope");
|
|
6
16
|
function isResponseEnvelope(value) {
|
|
7
17
|
return !!(value && typeof value === "object" && value[ENVELOPE]);
|
|
@@ -11,6 +21,8 @@ function isSafeError(value) {
|
|
|
11
21
|
return !!(value && (typeof value === "object" || typeof value === "function") && value[SAFE_ERROR]);
|
|
12
22
|
}
|
|
13
23
|
const REVALIDATE_HEADER = "X-Revalidate";
|
|
24
|
+
const RESPONSE_HEADER_VALUE_LIMIT = 4096;
|
|
25
|
+
const NULL_BODY_STATUSES = new Set([204, 205, 304]);
|
|
14
26
|
|
|
15
27
|
const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
|
|
16
28
|
function getServerFunctionMetadata(fn) {
|
|
@@ -83,6 +95,7 @@ function decodeSafe(text) {
|
|
|
83
95
|
}
|
|
84
96
|
}
|
|
85
97
|
function serializeCookie(name, value, options = {}) {
|
|
98
|
+
assertServableCookie(name, options);
|
|
86
99
|
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
87
100
|
cookie += `; Path=${options.path === undefined ? "/" : options.path}`;
|
|
88
101
|
if (options.domain) cookie += `; Domain=${options.domain}`;
|
|
@@ -90,12 +103,32 @@ function serializeCookie(name, value, options = {}) {
|
|
|
90
103
|
if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
91
104
|
if (options.httpOnly) cookie += "; HttpOnly";
|
|
92
105
|
if (options.secure) cookie += "; Secure";
|
|
106
|
+
if (options.partitioned) cookie += "; Partitioned";
|
|
93
107
|
if (options.sameSite) {
|
|
94
108
|
const sameSite = options.sameSite.toLowerCase();
|
|
95
109
|
cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
|
|
96
110
|
}
|
|
97
111
|
return cookie;
|
|
98
112
|
}
|
|
113
|
+
function assertServableCookie(name, options) {
|
|
114
|
+
const reject = reason => {
|
|
115
|
+
throw new Error(`serializeCookie: every browser silently rejects this cookie — ${reason}. ` + `It would never come back on a request, with no error anywhere.`);
|
|
116
|
+
};
|
|
117
|
+
const lower = name.toLowerCase();
|
|
118
|
+
if (lower.startsWith("__host-")) {
|
|
119
|
+
if (!options.secure) reject(`the __Host- prefix on \`${name}\` requires \`secure: true\``);
|
|
120
|
+
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`);
|
|
121
|
+
if (options.domain) reject(`the __Host- prefix on \`${name}\` forbids \`Domain\` (got \`${options.domain}\`)`);
|
|
122
|
+
} else if (lower.startsWith("__secure-") && !options.secure) {
|
|
123
|
+
reject(`the __Secure- prefix on \`${name}\` requires \`secure: true\``);
|
|
124
|
+
}
|
|
125
|
+
if (options.sameSite && options.sameSite.toLowerCase() === "none" && !options.secure) {
|
|
126
|
+
reject("`SameSite=None` requires `secure: true`");
|
|
127
|
+
}
|
|
128
|
+
if (options.partitioned && !options.secure) {
|
|
129
|
+
reject("`Partitioned` requires `secure: true`");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
99
132
|
const FLASH_COOKIE = "flash";
|
|
100
133
|
const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
|
|
101
134
|
function hasFlashCookie(cookieHeader) {
|
|
@@ -114,8 +147,23 @@ function configureServerFunctionsCodec(codec) {
|
|
|
114
147
|
function getServerFunctionsCodec() {
|
|
115
148
|
return codecConfig.codec;
|
|
116
149
|
}
|
|
117
|
-
|
|
150
|
+
const UNNAMED_FLIGHT_SOURCE = "true";
|
|
151
|
+
const flightConfig = {
|
|
152
|
+
consumers: new Map()
|
|
153
|
+
};
|
|
154
|
+
function assertFlightSource(source) {
|
|
155
|
+
if (source === UNNAMED_FLIGHT_SOURCE || source === "" || source.includes(",")) {
|
|
156
|
+
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).`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function subscribeFlightData(sourceOrConsumer, maybeConsumer) {
|
|
160
|
+
const named = typeof sourceOrConsumer === "string";
|
|
161
|
+
if (named) assertFlightSource(sourceOrConsumer);
|
|
162
|
+
const source = named ? sourceOrConsumer : UNNAMED_FLIGHT_SOURCE;
|
|
163
|
+
const consumer = named ? maybeConsumer : sourceOrConsumer;
|
|
164
|
+
flightConfig.consumers.set(source, consumer);
|
|
118
165
|
return () => {
|
|
166
|
+
if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
|
|
119
167
|
};
|
|
120
168
|
}
|
|
121
169
|
function serverFunctionAddress(endpoint, id) {
|
|
@@ -127,10 +175,18 @@ function parseServerFunctionAddress(pathname, endpoint) {
|
|
|
127
175
|
if (!pathname.startsWith(mount)) return null;
|
|
128
176
|
const rest = pathname.slice(mount.length);
|
|
129
177
|
if (!rest.startsWith("/")) return null;
|
|
130
|
-
|
|
178
|
+
let segment = rest.slice(1);
|
|
179
|
+
let data = false;
|
|
180
|
+
if (segment.startsWith("data/")) {
|
|
181
|
+
segment = segment.slice(5);
|
|
182
|
+
data = true;
|
|
183
|
+
}
|
|
131
184
|
if (!segment || segment.includes("/")) return null;
|
|
132
185
|
try {
|
|
133
|
-
return
|
|
186
|
+
return {
|
|
187
|
+
id: decodeURIComponent(segment),
|
|
188
|
+
data
|
|
189
|
+
};
|
|
134
190
|
} catch {
|
|
135
191
|
return null;
|
|
136
192
|
}
|
|
@@ -162,6 +218,28 @@ function decodeErrorHeaderValue(value) {
|
|
|
162
218
|
}
|
|
163
219
|
const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
164
220
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
221
|
+
const UNKNOWN_HEADER = "X-Server-Function-Unknown";
|
|
222
|
+
const REDIRECT_HEADER = "X-Server-Function-Redirect";
|
|
223
|
+
function decodeRedirectHeaderValue(value) {
|
|
224
|
+
if (typeof value !== "string") return undefined;
|
|
225
|
+
const at = value.indexOf(" ");
|
|
226
|
+
if (at < 0) return undefined;
|
|
227
|
+
const status = Number(value.slice(0, at));
|
|
228
|
+
const url = value.slice(at + 1);
|
|
229
|
+
if (!Number.isInteger(status) || !url) return undefined;
|
|
230
|
+
if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) return undefined;
|
|
231
|
+
let parsed;
|
|
232
|
+
try {
|
|
233
|
+
parsed = new URL(url);
|
|
234
|
+
} catch {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
|
|
238
|
+
return {
|
|
239
|
+
status,
|
|
240
|
+
url
|
|
241
|
+
};
|
|
242
|
+
}
|
|
165
243
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
166
244
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
167
245
|
const BodyFormat = {
|
|
@@ -204,7 +282,11 @@ function isJSONSafe(value) {
|
|
|
204
282
|
const proto = Object.getPrototypeOf(v);
|
|
205
283
|
if (proto !== Object.prototype && proto !== null) return false;
|
|
206
284
|
if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
|
|
207
|
-
for (const k in v)
|
|
285
|
+
for (const k in v) {
|
|
286
|
+
const descriptor = Object.getOwnPropertyDescriptor(v, k);
|
|
287
|
+
if (descriptor === undefined || !("value" in descriptor)) return false;
|
|
288
|
+
stack.push(descriptor.value);
|
|
289
|
+
}
|
|
208
290
|
}
|
|
209
291
|
}
|
|
210
292
|
return true;
|
|
@@ -313,18 +395,34 @@ function createChunk(data) {
|
|
|
313
395
|
class ChunkReader {
|
|
314
396
|
constructor(stream) {
|
|
315
397
|
this.reader = stream.getReader();
|
|
316
|
-
this.
|
|
398
|
+
this.store = new Uint8Array(0);
|
|
399
|
+
this.buffer = this.store;
|
|
317
400
|
this.done = false;
|
|
318
401
|
}
|
|
319
402
|
async readChunk() {
|
|
320
403
|
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 {
|
|
404
|
+
if (chunk.done) {
|
|
327
405
|
this.done = true;
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const incoming = chunk.value;
|
|
409
|
+
const store = this.store;
|
|
410
|
+
const start = this.buffer.byteOffset;
|
|
411
|
+
const end = start + this.buffer.length;
|
|
412
|
+
const needed = this.buffer.length + incoming.length;
|
|
413
|
+
if (end + incoming.length <= store.length) {
|
|
414
|
+
store.set(incoming, end);
|
|
415
|
+
this.buffer = store.subarray(start, end + incoming.length);
|
|
416
|
+
} else if (needed <= store.length) {
|
|
417
|
+
store.copyWithin(0, start, end);
|
|
418
|
+
store.set(incoming, this.buffer.length);
|
|
419
|
+
this.buffer = store.subarray(0, needed);
|
|
420
|
+
} else {
|
|
421
|
+
const grown = new Uint8Array(Math.max(needed, store.length * 2));
|
|
422
|
+
grown.set(this.buffer);
|
|
423
|
+
grown.set(incoming, this.buffer.length);
|
|
424
|
+
this.store = grown;
|
|
425
|
+
this.buffer = grown.subarray(0, needed);
|
|
328
426
|
}
|
|
329
427
|
}
|
|
330
428
|
async next() {
|
|
@@ -366,6 +464,27 @@ class ChunkReader {
|
|
|
366
464
|
}
|
|
367
465
|
}
|
|
368
466
|
}
|
|
467
|
+
const ERROR_TRAILER_PREFIX = "!";
|
|
468
|
+
function encodeErrorTrailer(error) {
|
|
469
|
+
const shaped = error instanceof Error ? error : new Error(String(error));
|
|
470
|
+
return ERROR_TRAILER_PREFIX + JSON.stringify(shaped.name && shaped.name !== "Error" ? {
|
|
471
|
+
name: shaped.name,
|
|
472
|
+
message: shaped.message
|
|
473
|
+
} : {
|
|
474
|
+
message: shaped.message
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function errorFromTrailer(payload) {
|
|
478
|
+
let shape;
|
|
479
|
+
try {
|
|
480
|
+
shape = JSON.parse(payload.slice(1));
|
|
481
|
+
} catch {
|
|
482
|
+
shape = null;
|
|
483
|
+
}
|
|
484
|
+
const error = new Error(shape && typeof shape.message === "string" ? shape.message : "Server function result could not be delivered.");
|
|
485
|
+
if (shape && typeof shape.name === "string") error.name = shape.name;
|
|
486
|
+
return error;
|
|
487
|
+
}
|
|
369
488
|
async function deserializeStream(source, codecOptions) {
|
|
370
489
|
if (!source.body) {
|
|
371
490
|
throw new Error("missing body");
|
|
@@ -373,11 +492,17 @@ async function deserializeStream(source, codecOptions) {
|
|
|
373
492
|
const reader = new ChunkReader(source.body);
|
|
374
493
|
const result = await reader.next();
|
|
375
494
|
if (!result.done) {
|
|
495
|
+
if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
496
|
+
throw errorFromTrailer(result.value);
|
|
497
|
+
}
|
|
376
498
|
const {
|
|
377
499
|
createJSONDeserializer
|
|
378
500
|
} = await import('@solidjs/web/serialization/decode');
|
|
379
501
|
const deserializeChunk = createJSONDeserializer(codecOptions);
|
|
380
502
|
function interpretChunk(chunk) {
|
|
503
|
+
if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
504
|
+
throw errorFromTrailer(chunk);
|
|
505
|
+
}
|
|
381
506
|
return deserializeChunk(JSON.parse(chunk));
|
|
382
507
|
}
|
|
383
508
|
reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
|
|
@@ -441,7 +566,8 @@ function copyInitHeaders(init) {
|
|
|
441
566
|
for (const cookie of init.getSetCookie()) headers.append("Set-Cookie", cookie);
|
|
442
567
|
return headers;
|
|
443
568
|
}
|
|
444
|
-
const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, "Location"
|
|
569
|
+
const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, REDIRECT_HEADER, "Location",
|
|
570
|
+
...COMPOSED_BODY_FRAMING].map(header => header.toLowerCase()));
|
|
445
571
|
function fillsStubGap(key, headers, response) {
|
|
446
572
|
if (key === "set-cookie" || STUB_GAP_FILL_EXCLUDED.has(key)) return false;
|
|
447
573
|
if (response.body === null && (key === "content-type" || key === "content-length")) return false;
|
|
@@ -457,24 +583,16 @@ function commitEventResponse(response, event = getRequestEvent()) {
|
|
|
457
583
|
if (fillsStubGap(key, response.headers, response)) hasGaps = true;
|
|
458
584
|
});
|
|
459
585
|
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
|
-
}
|
|
586
|
+
const headers = copyInitHeaders(response.headers);
|
|
587
|
+
for (const cookie of cookies) headers.append("Set-Cookie", cookie);
|
|
588
|
+
stub.headers.forEach((value, key) => {
|
|
589
|
+
if (fillsStubGap(key, headers, response)) headers.set(key, value);
|
|
590
|
+
});
|
|
591
|
+
return new Response(response.body, {
|
|
592
|
+
status: response.status,
|
|
593
|
+
statusText: response.statusText,
|
|
594
|
+
headers
|
|
595
|
+
});
|
|
478
596
|
}
|
|
479
597
|
|
|
480
598
|
function encodeInputValue(value) {
|
|
@@ -506,11 +624,35 @@ function encodeFlashCookie(url, result, input, thrown) {
|
|
|
506
624
|
thrown: !!thrown,
|
|
507
625
|
input: input.map(encodeInputValue)
|
|
508
626
|
};
|
|
627
|
+
if (fitsCookie(payload)) return flashCookie(payload);
|
|
628
|
+
payload.truncated = true;
|
|
629
|
+
payload.input = [];
|
|
630
|
+
if (!fitsCookie(payload)) {
|
|
631
|
+
if (typeof payload.result === "string") {
|
|
632
|
+
let prefix = payload.result;
|
|
633
|
+
while (prefix.length > 0 && !fitsCookie({
|
|
634
|
+
...payload,
|
|
635
|
+
result: prefix
|
|
636
|
+
})) {
|
|
637
|
+
prefix = prefix.slice(0, prefix.length >> 1);
|
|
638
|
+
}
|
|
639
|
+
payload.result = prefix.length > 0 ? prefix : true;
|
|
640
|
+
} else {
|
|
641
|
+
payload.result = true;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return flashCookie(payload);
|
|
645
|
+
}
|
|
646
|
+
function flashCookie(payload) {
|
|
509
647
|
return serializeCookie(FLASH_COOKIE, JSON.stringify(payload), {
|
|
510
648
|
secure: true,
|
|
511
649
|
httpOnly: true
|
|
512
650
|
});
|
|
513
651
|
}
|
|
652
|
+
const COOKIE_PAIR_BUDGET = 4000;
|
|
653
|
+
function fitsCookie(payload) {
|
|
654
|
+
return FLASH_COOKIE.length + 1 + encodeURIComponent(JSON.stringify(payload)).length <= COOKIE_PAIR_BUDGET;
|
|
655
|
+
}
|
|
514
656
|
function decodeFlashCookie(cookieHeader) {
|
|
515
657
|
const match = parseCookieHeader(cookieHeader)[FLASH_COOKIE];
|
|
516
658
|
if (!match) return;
|
|
@@ -518,12 +660,14 @@ function decodeFlashCookie(cookieHeader) {
|
|
|
518
660
|
const payload = JSON.parse(match);
|
|
519
661
|
if (!payload || !payload.result) return;
|
|
520
662
|
const result = payload.error ? new Error(payload.result) : payload.result;
|
|
521
|
-
|
|
663
|
+
const submission = {
|
|
522
664
|
input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
|
|
523
665
|
url: payload.url,
|
|
524
666
|
result: payload.thrown ? undefined : result,
|
|
525
667
|
error: payload.thrown ? result : undefined
|
|
526
668
|
};
|
|
669
|
+
if (payload.truncated) submission.truncated = true;
|
|
670
|
+
return submission;
|
|
527
671
|
} catch (error) {
|
|
528
672
|
console.error(error);
|
|
529
673
|
}
|
|
@@ -538,7 +682,9 @@ const config = {
|
|
|
538
682
|
transformDirectResult: undefined,
|
|
539
683
|
handleNoJS: undefined,
|
|
540
684
|
endpoint: "/_server",
|
|
541
|
-
csrf: true
|
|
685
|
+
csrf: true,
|
|
686
|
+
bodySizeLimit: 1_048_576,
|
|
687
|
+
maxArguments: 1000
|
|
542
688
|
};
|
|
543
689
|
function configureServerFunctionsServer({
|
|
544
690
|
provideEvent,
|
|
@@ -550,7 +696,9 @@ function configureServerFunctionsServer({
|
|
|
550
696
|
handleNoJS,
|
|
551
697
|
endpoint,
|
|
552
698
|
csrf,
|
|
553
|
-
codec
|
|
699
|
+
codec,
|
|
700
|
+
bodySizeLimit,
|
|
701
|
+
maxArguments
|
|
554
702
|
} = {}) {
|
|
555
703
|
if (provideEvent !== undefined) config.provideEvent = provideEvent;
|
|
556
704
|
if (wrapInvocation !== undefined) config.wrapInvocation = wrapInvocation;
|
|
@@ -562,6 +710,16 @@ function configureServerFunctionsServer({
|
|
|
562
710
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
563
711
|
if (csrf !== undefined) config.csrf = csrf;
|
|
564
712
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
713
|
+
if (bodySizeLimit !== undefined) config.bodySizeLimit = bodySizeLimit;
|
|
714
|
+
if (maxArguments !== undefined) config.maxArguments = maxArguments;
|
|
715
|
+
}
|
|
716
|
+
const flightSources = new Map();
|
|
717
|
+
function registerFlightDataSource(source, hook) {
|
|
718
|
+
assertFlightSource(source);
|
|
719
|
+
flightSources.set(source, hook);
|
|
720
|
+
return () => {
|
|
721
|
+
if (flightSources.get(source) === hook) flightSources.delete(source);
|
|
722
|
+
};
|
|
565
723
|
}
|
|
566
724
|
function provideEvent(event, fn) {
|
|
567
725
|
if (config.provideEvent) return config.provideEvent(event, fn);
|
|
@@ -569,6 +727,52 @@ function provideEvent(event, fn) {
|
|
|
569
727
|
if (ctx) return ctx.run(event, fn);
|
|
570
728
|
throw new Error("No request event provider. Configure one with configureServerFunctionsServer({ provideEvent }).");
|
|
571
729
|
}
|
|
730
|
+
function scopeDeferredResult(value, scope) {
|
|
731
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") return value;
|
|
732
|
+
const promised = scope(() => nativePromise(value));
|
|
733
|
+
if (promised) {
|
|
734
|
+
return promised.then(result => scope(() => scopeDeferredResult(result, scope)));
|
|
735
|
+
}
|
|
736
|
+
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
737
|
+
let reader;
|
|
738
|
+
return new ReadableStream({
|
|
739
|
+
async pull(controller) {
|
|
740
|
+
try {
|
|
741
|
+
const step = await scope(() => {
|
|
742
|
+
if (!reader) reader = value.getReader();
|
|
743
|
+
return reader.read();
|
|
744
|
+
});
|
|
745
|
+
if (step.done) controller.close();else controller.enqueue(step.value);
|
|
746
|
+
} catch (error) {
|
|
747
|
+
controller.error(error);
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
cancel(reason) {
|
|
751
|
+
return scope(() => reader ? reader.cancel(reason) : value.cancel(reason));
|
|
752
|
+
}
|
|
753
|
+
}, {
|
|
754
|
+
highWaterMark: 0
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
const scopedIterator = symbol => ({
|
|
758
|
+
[symbol]() {
|
|
759
|
+
const iterator = scope(() => value[symbol]());
|
|
760
|
+
return new Proxy(iterator, {
|
|
761
|
+
get(target, property) {
|
|
762
|
+
const member = Reflect.get(target, property, target);
|
|
763
|
+
return typeof member === "function" && (property === "next" || property === "return" || property === "throw") ? (...args) => scope(() => member.apply(target, args)) : member;
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
if (typeof value[Symbol.asyncIterator] === "function") {
|
|
769
|
+
return scopedIterator(Symbol.asyncIterator);
|
|
770
|
+
}
|
|
771
|
+
if (typeof value[Symbol.iterator] === "function" && !Array.isArray(value) && !(value instanceof Map) && !(value instanceof Set) && !ArrayBuffer.isView(value)) {
|
|
772
|
+
return scopedIterator(Symbol.iterator);
|
|
773
|
+
}
|
|
774
|
+
return value;
|
|
775
|
+
}
|
|
572
776
|
const REGISTRATIONS = new Map();
|
|
573
777
|
const METHODS = new Map();
|
|
574
778
|
const INVOCATIONS = new WeakMap();
|
|
@@ -583,6 +787,7 @@ function provideRPC() {
|
|
|
583
787
|
}
|
|
584
788
|
function registerServerFunction(id, callback) {
|
|
585
789
|
provideRPC();
|
|
790
|
+
if (REGISTRATIONS.get(id) !== callback) METHODS.delete(id);
|
|
586
791
|
REGISTRATIONS.set(id, callback);
|
|
587
792
|
return callback;
|
|
588
793
|
}
|
|
@@ -656,13 +861,17 @@ function createServerReference({
|
|
|
656
861
|
const ogEvt = getRequestEvent();
|
|
657
862
|
if (!ogEvt) throw new Error("Cannot call server function outside of a request");
|
|
658
863
|
const evt = {
|
|
659
|
-
...ogEvt
|
|
864
|
+
...ogEvt,
|
|
865
|
+
locals: {
|
|
866
|
+
...ogEvt.locals
|
|
867
|
+
}
|
|
660
868
|
};
|
|
661
869
|
INVOCATIONS.set(evt, {
|
|
662
870
|
id
|
|
663
871
|
});
|
|
664
872
|
evt.serverOnly = true;
|
|
665
|
-
const
|
|
873
|
+
const scope = run => provideEvent(evt, run);
|
|
874
|
+
let result = provideEvent(evt, () => {
|
|
666
875
|
const run = () => fn.apply(thisArg, args);
|
|
667
876
|
return config.wrapInvocation ? config.wrapInvocation(run, {
|
|
668
877
|
id,
|
|
@@ -671,19 +880,20 @@ function createServerReference({
|
|
|
671
880
|
direct: true
|
|
672
881
|
}) : run();
|
|
673
882
|
});
|
|
883
|
+
result = scopeDeferredResult(result, scope);
|
|
674
884
|
const transform = config.transformDirectResult;
|
|
675
885
|
if (transform && result && typeof result.then === "function") {
|
|
676
|
-
return result.then(value => transform(value, {
|
|
886
|
+
return result.then(value => scopeDeferredResult(transform(value, {
|
|
677
887
|
id,
|
|
678
888
|
args,
|
|
679
889
|
event: evt
|
|
680
|
-
}));
|
|
890
|
+
}), scope));
|
|
681
891
|
}
|
|
682
|
-
return transform ? transform(result, {
|
|
892
|
+
return transform ? scopeDeferredResult(transform(result, {
|
|
683
893
|
id,
|
|
684
894
|
args,
|
|
685
895
|
event: evt
|
|
686
|
-
}) : result;
|
|
896
|
+
}), scope) : result;
|
|
687
897
|
}
|
|
688
898
|
});
|
|
689
899
|
return proxy;
|
|
@@ -727,18 +937,107 @@ function getServerFunctionInvocation() {
|
|
|
727
937
|
function getEventServerFunctionInvocation(event) {
|
|
728
938
|
return event && INVOCATIONS.get(event);
|
|
729
939
|
}
|
|
730
|
-
function
|
|
940
|
+
function resolveAddress(url) {
|
|
731
941
|
return parseServerFunctionAddress(url.pathname, config.endpoint);
|
|
732
942
|
}
|
|
733
|
-
|
|
943
|
+
const DECODE_DEPTH_LIMIT = 64;
|
|
944
|
+
function assertDecodeDepth(value) {
|
|
945
|
+
let level = [value];
|
|
946
|
+
for (let depth = 0; level.length > 0; depth++) {
|
|
947
|
+
if (depth > DECODE_DEPTH_LIMIT) {
|
|
948
|
+
throw new TypeError("Server function arguments exceed the decode depth limit");
|
|
949
|
+
}
|
|
950
|
+
const next = [];
|
|
951
|
+
for (const node of level) {
|
|
952
|
+
if (node === null || typeof node !== "object") continue;
|
|
953
|
+
if (Array.isArray(node)) {
|
|
954
|
+
for (const child of node) next.push(child);
|
|
955
|
+
} else {
|
|
956
|
+
for (const key of Object.keys(node)) next.push(node[key]);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
level = next;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
const UNSAFE_ARGUMENT_KEYS = ["__proto__", "constructor", "prototype"];
|
|
963
|
+
function stripUnsafeArgumentKeys(value) {
|
|
964
|
+
const stack = [value];
|
|
965
|
+
const seen = new Set();
|
|
966
|
+
while (stack.length) {
|
|
967
|
+
const v = stack.pop();
|
|
968
|
+
if (v === null || typeof v !== "object" || seen.has(v)) continue;
|
|
969
|
+
seen.add(v);
|
|
970
|
+
for (const key of UNSAFE_ARGUMENT_KEYS) {
|
|
971
|
+
delete v[key];
|
|
972
|
+
}
|
|
973
|
+
for (const key of Object.keys(v)) stack.push(v[key]);
|
|
974
|
+
if (v instanceof Map) {
|
|
975
|
+
for (const [k, entry] of v) stack.push(k, entry);
|
|
976
|
+
} else if (v instanceof Set) {
|
|
977
|
+
for (const member of v) stack.push(member);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return value;
|
|
981
|
+
}
|
|
982
|
+
async function bufferBodyWithin(request, limit) {
|
|
983
|
+
const reader = request.body.getReader();
|
|
984
|
+
const signal = request.signal;
|
|
985
|
+
const chunks = [];
|
|
986
|
+
let total = 0;
|
|
987
|
+
const onAbort = () => {
|
|
988
|
+
reader.cancel(signal.reason).catch(() => {});
|
|
989
|
+
};
|
|
990
|
+
if (signal.aborted) onAbort();else signal.addEventListener("abort", onAbort, {
|
|
991
|
+
once: true
|
|
992
|
+
});
|
|
993
|
+
try {
|
|
994
|
+
for (;;) {
|
|
995
|
+
const {
|
|
996
|
+
done,
|
|
997
|
+
value
|
|
998
|
+
} = await reader.read();
|
|
999
|
+
if (signal.aborted) throw signal.reason;
|
|
1000
|
+
if (done) break;
|
|
1001
|
+
total += value.byteLength;
|
|
1002
|
+
if (total > limit) {
|
|
1003
|
+
reader.cancel().catch(() => {});
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
chunks.push(value);
|
|
1007
|
+
}
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
reader.cancel(error).catch(() => {});
|
|
1010
|
+
throw error;
|
|
1011
|
+
} finally {
|
|
1012
|
+
signal.removeEventListener("abort", onAbort);
|
|
1013
|
+
reader.releaseLock();
|
|
1014
|
+
}
|
|
1015
|
+
const body = new Uint8Array(total);
|
|
1016
|
+
let offset = 0;
|
|
1017
|
+
for (const chunk of chunks) {
|
|
1018
|
+
body.set(chunk, offset);
|
|
1019
|
+
offset += chunk.byteLength;
|
|
1020
|
+
}
|
|
1021
|
+
return new Request(request, {
|
|
1022
|
+
body
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
async function parseArguments(request, url, scripted, codec) {
|
|
734
1026
|
const parsed = [];
|
|
735
1027
|
const bodyFormat = request.method === "POST" ? request.headers.get(BODY_FORMAT_HEADER) : null;
|
|
736
1028
|
const args = url.searchParams.get("args");
|
|
737
|
-
if (args && (!
|
|
738
|
-
|
|
1029
|
+
if (args && (!scripted || request.method === "GET" || bodyFormat !== BodyFormat.Serialized)) {
|
|
1030
|
+
let result;
|
|
1031
|
+
if (args.startsWith(";0x")) {
|
|
1032
|
+
result = await deserializeString(args, codec);
|
|
1033
|
+
} else {
|
|
1034
|
+
result = JSON.parse(args);
|
|
1035
|
+
assertDecodeDepth(result);
|
|
1036
|
+
}
|
|
739
1037
|
if (!Array.isArray(result)) {
|
|
740
1038
|
throw new TypeError("Server function arguments must encode an array");
|
|
741
1039
|
}
|
|
1040
|
+
stripUnsafeArgumentKeys(result);
|
|
742
1041
|
for (const arg of result) {
|
|
743
1042
|
parsed.push(arg);
|
|
744
1043
|
}
|
|
@@ -748,18 +1047,37 @@ async function parseArguments(request, url, instance, codec) {
|
|
|
748
1047
|
if (request.method === "POST" && request.body !== null) {
|
|
749
1048
|
const decoded = await extractBody(request.clone(), codec);
|
|
750
1049
|
if (bodyFormat === BodyFormat.Serialized || bodyFormat === BodyFormat.Json) {
|
|
751
|
-
|
|
1050
|
+
if (bodyFormat === BodyFormat.Json) assertDecodeDepth(decoded);
|
|
1051
|
+
if (!Array.isArray(decoded)) {
|
|
1052
|
+
throw new TypeError("Server function arguments must encode an array");
|
|
1053
|
+
}
|
|
1054
|
+
return stripUnsafeArgumentKeys(decoded);
|
|
1055
|
+
}
|
|
1056
|
+
if (decoded === undefined) {
|
|
1057
|
+
if (bodyFormat === null && (await request.clone().arrayBuffer()).byteLength === 0) {
|
|
1058
|
+
return parsed;
|
|
1059
|
+
}
|
|
1060
|
+
throw new TypeError("Server function body carries no usable encoding");
|
|
752
1061
|
}
|
|
753
1062
|
parsed.push(decoded);
|
|
754
1063
|
}
|
|
755
1064
|
return parsed;
|
|
756
1065
|
}
|
|
757
|
-
async function foldFlightData(
|
|
1066
|
+
async function foldFlightData(hooks, event, headers, outcome, context = {}) {
|
|
758
1067
|
if (outcome.value instanceof Response && outcome.value.body) return outcome.value;
|
|
759
1068
|
digestOutcome(event, outcome);
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
1069
|
+
const folded = [];
|
|
1070
|
+
for (const [source, hook] of hooks) {
|
|
1071
|
+
try {
|
|
1072
|
+
const slice = await hook(event, outcome);
|
|
1073
|
+
if (slice !== undefined) folded.push([source, slice]);
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
console.error(`Error collecting flight data for source "${source}"`, error);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
if (folded.length === 0) return outcome.value;
|
|
1079
|
+
const data = Object.fromEntries(folded);
|
|
1080
|
+
headers.set(SINGLE_FLIGHT_HEADER, folded.map(([source]) => source).join(","));
|
|
763
1081
|
if (context.transformFlightResult) {
|
|
764
1082
|
const transformed = await context.transformFlightResult(event, {
|
|
765
1083
|
value: outcome.value,
|
|
@@ -839,7 +1157,7 @@ function foldSetCookies(headers, setCookies) {
|
|
|
839
1157
|
}
|
|
840
1158
|
function mergeResponseHeaders(target, source) {
|
|
841
1159
|
source.forEach((value, key) => {
|
|
842
|
-
if (key !== "set-cookie") target.append(key, value);
|
|
1160
|
+
if (key !== "set-cookie" && !COMPOSED_BODY_FRAMING.has(key)) target.append(key, value);
|
|
843
1161
|
});
|
|
844
1162
|
if (source.getSetCookie) {
|
|
845
1163
|
for (const cookie of source.getSetCookie()) target.append("Set-Cookie", cookie);
|
|
@@ -848,6 +1166,48 @@ function mergeResponseHeaders(target, source) {
|
|
|
848
1166
|
}
|
|
849
1167
|
}
|
|
850
1168
|
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
1169
|
+
function maskRedirect(headers, response, requestUrl) {
|
|
1170
|
+
const target = response.headers && response.headers.get("Location");
|
|
1171
|
+
if (target) {
|
|
1172
|
+
headers.set(REDIRECT_HEADER, `${response.status} ${new URL(target, requestUrl)}`);
|
|
1173
|
+
}
|
|
1174
|
+
headers.delete("Location");
|
|
1175
|
+
}
|
|
1176
|
+
const BOUNDED_COMPOSED_HEADERS = [REDIRECT_HEADER, "Location", REVALIDATE_HEADER];
|
|
1177
|
+
function enforceComposedHeaderInvariants(response) {
|
|
1178
|
+
for (const name of BOUNDED_COMPOSED_HEADERS) {
|
|
1179
|
+
const value = response.headers.get(name);
|
|
1180
|
+
if (value === null) continue;
|
|
1181
|
+
if (value.length > RESPONSE_HEADER_VALUE_LIMIT) {
|
|
1182
|
+
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);
|
|
1183
|
+
}
|
|
1184
|
+
if (name === REVALIDATE_HEADER) continue;
|
|
1185
|
+
const target = name === REDIRECT_HEADER ? value.slice(value.indexOf(" ") + 1) : value;
|
|
1186
|
+
if (!isHttpNavigationTarget(target)) {
|
|
1187
|
+
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);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return response;
|
|
1191
|
+
}
|
|
1192
|
+
function refuseComposedHeader(response, name, headerMessage, body) {
|
|
1193
|
+
if (response.body) {
|
|
1194
|
+
try {
|
|
1195
|
+
const cancelled = response.body.cancel();
|
|
1196
|
+
if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
|
|
1197
|
+
} catch {}
|
|
1198
|
+
}
|
|
1199
|
+
const headers = new Headers();
|
|
1200
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(DEV ? headerMessage : GENERIC_SERVER_ERROR_MESSAGE));
|
|
1201
|
+
return new Response(body, {
|
|
1202
|
+
status: 500,
|
|
1203
|
+
headers
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
function warnScripted304(functionId) {
|
|
1207
|
+
if (DEV) {
|
|
1208
|
+
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.`);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
851
1211
|
function createNoJSHandler({
|
|
852
1212
|
base = ""
|
|
853
1213
|
} = {}) {
|
|
@@ -891,44 +1251,291 @@ function isFormPost(request) {
|
|
|
891
1251
|
const type = request.headers.get("content-type") || "";
|
|
892
1252
|
return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
|
|
893
1253
|
}
|
|
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();
|
|
1254
|
+
function guardFailures(value, state) {
|
|
1255
|
+
if (!state) state = {
|
|
1256
|
+
seen: new WeakMap(),
|
|
1257
|
+
cyclic: new WeakSet()
|
|
905
1258
|
};
|
|
906
|
-
|
|
1259
|
+
const entered = enterGuard(value, state);
|
|
1260
|
+
if (!(entered instanceof Frame)) return entered;
|
|
1261
|
+
const stack = [entered];
|
|
1262
|
+
let delivered = NOTHING;
|
|
1263
|
+
for (;;) {
|
|
1264
|
+
const top = stack[stack.length - 1];
|
|
1265
|
+
const items = top.items;
|
|
1266
|
+
let pushed = null;
|
|
1267
|
+
while (top.i < items.length) {
|
|
1268
|
+
const i = top.i;
|
|
1269
|
+
let original;
|
|
1270
|
+
if (top.kind === OBJECT) {
|
|
1271
|
+
const descriptor = top.descriptors[items[i]];
|
|
1272
|
+
if ("value" in descriptor) {
|
|
1273
|
+
original = descriptor.value;
|
|
1274
|
+
} else if (typeof descriptor.get === "function") {
|
|
1275
|
+
if (top.accessorRead !== i) {
|
|
1276
|
+
try {
|
|
1277
|
+
top.accessorValue = descriptor.get.call(top.value);
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
throw sanitizeServerError(error);
|
|
1280
|
+
}
|
|
1281
|
+
top.accessorRead = i;
|
|
1282
|
+
}
|
|
1283
|
+
original = top.accessorValue;
|
|
1284
|
+
} else {
|
|
1285
|
+
top.i++;
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
} else {
|
|
1289
|
+
original = items[i];
|
|
1290
|
+
}
|
|
1291
|
+
let guarded;
|
|
1292
|
+
if (delivered !== NOTHING) {
|
|
1293
|
+
guarded = delivered;
|
|
1294
|
+
delivered = NOTHING;
|
|
1295
|
+
} else {
|
|
1296
|
+
guarded = enterGuard(original, state);
|
|
1297
|
+
if (guarded instanceof Frame) {
|
|
1298
|
+
pushed = guarded;
|
|
1299
|
+
break;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
if (top.kind === ARRAY) {
|
|
1303
|
+
if (guarded !== original) {
|
|
1304
|
+
top.next[i] = guarded;
|
|
1305
|
+
top.changed = true;
|
|
1306
|
+
}
|
|
1307
|
+
} else if (top.kind === MAP) {
|
|
1308
|
+
if ((i & 1) === 0) top.pendingKey = guarded;else top.next.set(top.pendingKey, guarded);
|
|
1309
|
+
if (guarded !== original) top.changed = true;
|
|
1310
|
+
} else if (top.kind === SET) {
|
|
1311
|
+
top.next.add(guarded);
|
|
1312
|
+
if (guarded !== original) top.changed = true;
|
|
1313
|
+
} else if (guarded !== original || top.accessorRead === i) {
|
|
1314
|
+
Object.defineProperty(top.next, items[i], {
|
|
1315
|
+
value: guarded,
|
|
1316
|
+
writable: true,
|
|
1317
|
+
configurable: true,
|
|
1318
|
+
enumerable: top.descriptors[items[i]].enumerable
|
|
1319
|
+
});
|
|
1320
|
+
top.changed = true;
|
|
1321
|
+
}
|
|
1322
|
+
top.i++;
|
|
1323
|
+
}
|
|
1324
|
+
if (pushed !== null) {
|
|
1325
|
+
stack.push(pushed);
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
stack.pop();
|
|
1329
|
+
const out = keepGuarded(top.value, top.next, top.changed, state);
|
|
1330
|
+
if (stack.length === 0) return out;
|
|
1331
|
+
delivered = out;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
const NOTHING = Symbol();
|
|
1335
|
+
const ARRAY = 0;
|
|
1336
|
+
const MAP = 1;
|
|
1337
|
+
const SET = 2;
|
|
1338
|
+
const OBJECT = 3;
|
|
1339
|
+
class Frame {
|
|
1340
|
+
constructor(kind, value, next, items, descriptors) {
|
|
1341
|
+
this.kind = kind;
|
|
1342
|
+
this.value = value;
|
|
1343
|
+
this.next = next;
|
|
1344
|
+
this.items = items;
|
|
1345
|
+
this.descriptors = descriptors;
|
|
1346
|
+
this.i = 0;
|
|
1347
|
+
this.changed = false;
|
|
1348
|
+
this.accessorRead = -1;
|
|
1349
|
+
this.accessorValue = undefined;
|
|
1350
|
+
this.pendingKey = undefined;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
function guardOperation(state, run) {
|
|
1354
|
+
return state.scope ? state.scope(run) : run();
|
|
1355
|
+
}
|
|
1356
|
+
function enterGuard(value, state) {
|
|
1357
|
+
if (value === null || typeof value !== "object") return value;
|
|
1358
|
+
if (state.seen.has(value)) {
|
|
1359
|
+
state.cyclic.add(value);
|
|
1360
|
+
return state.seen.get(value);
|
|
1361
|
+
}
|
|
1362
|
+
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
1363
|
+
let reader;
|
|
1364
|
+
const gate = state.gate;
|
|
1365
|
+
let finished = false;
|
|
1366
|
+
const close = () => {
|
|
1367
|
+
if (finished) return;
|
|
1368
|
+
finished = true;
|
|
1369
|
+
try {
|
|
1370
|
+
const cancelled = guardOperation(state, () => reader ? reader.cancel() : value.cancel());
|
|
1371
|
+
if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
|
|
1372
|
+
} catch {}
|
|
1373
|
+
};
|
|
1374
|
+
const guardedStream = new ReadableStream({
|
|
1375
|
+
async pull(controller) {
|
|
1376
|
+
try {
|
|
1377
|
+
if (gate && !finished && !gate.wantsMore()) await gate.awaitDemand();
|
|
1378
|
+
if (finished) {
|
|
1379
|
+
controller.close();
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
if (!reader) reader = guardOperation(state, () => value.getReader());
|
|
1383
|
+
const {
|
|
1384
|
+
done,
|
|
1385
|
+
value: chunk
|
|
1386
|
+
} = await guardOperation(state, () => reader.read());
|
|
1387
|
+
done ? controller.close() : controller.enqueue(guardOperation(state, () => guardFailures(chunk, state)));
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
controller.error(guardOperation(state, () => sanitizeServerError(error)));
|
|
1390
|
+
}
|
|
1391
|
+
},
|
|
1392
|
+
cancel(reason) {
|
|
1393
|
+
finished = true;
|
|
1394
|
+
return guardOperation(state, () => reader ? reader.cancel(reason) : value.cancel(reason));
|
|
1395
|
+
}
|
|
1396
|
+
});
|
|
1397
|
+
if (gate) gate.onOpen(close);
|
|
1398
|
+
state.seen.set(value, guardedStream);
|
|
1399
|
+
return guardedStream;
|
|
1400
|
+
}
|
|
1401
|
+
if (typeof value.then === "function") {
|
|
1402
|
+
const guardedPromise = Promise.resolve(value).then(resolved => guardOperation(state, () => guardFailures(resolved, state)), error => {
|
|
1403
|
+
throw guardOperation(state, () => sanitizeServerError(error));
|
|
1404
|
+
});
|
|
1405
|
+
guardedPromise.catch(() => {});
|
|
1406
|
+
state.seen.set(value, guardedPromise);
|
|
1407
|
+
return guardedPromise;
|
|
1408
|
+
}
|
|
1409
|
+
if (typeof value[Symbol.asyncIterator] === "function") {
|
|
907
1410
|
const source = value;
|
|
908
|
-
|
|
1411
|
+
const gate = state.gate;
|
|
1412
|
+
const guardedIterable = {
|
|
909
1413
|
[Symbol.asyncIterator]() {
|
|
910
|
-
const
|
|
1414
|
+
const iterator = guardOperation(state, () => source[Symbol.asyncIterator]());
|
|
911
1415
|
let finished = false;
|
|
912
|
-
|
|
1416
|
+
const close = () => {
|
|
913
1417
|
if (finished) return;
|
|
914
1418
|
finished = true;
|
|
915
1419
|
try {
|
|
916
|
-
const returned =
|
|
1420
|
+
const returned = iterator.return && guardOperation(state, () => iterator.return());
|
|
917
1421
|
if (returned && typeof returned.then === "function") returned.then(undefined, () => {});
|
|
918
1422
|
} catch {}
|
|
919
1423
|
};
|
|
920
|
-
if (
|
|
1424
|
+
if (gate) gate.onOpen(close);
|
|
1425
|
+
const step = () => finished ? Promise.resolve({
|
|
1426
|
+
done: true,
|
|
1427
|
+
value: undefined
|
|
1428
|
+
}) : guardOperation(state, () => iterator.next()).then(step => {
|
|
1429
|
+
if (step.done) {
|
|
1430
|
+
finished = true;
|
|
1431
|
+
return step;
|
|
1432
|
+
}
|
|
1433
|
+
return {
|
|
1434
|
+
done: false,
|
|
1435
|
+
value: guardOperation(state, () => guardFailures(step.value, state))
|
|
1436
|
+
};
|
|
1437
|
+
}, error => {
|
|
1438
|
+
throw guardOperation(state, () => sanitizeServerError(error));
|
|
1439
|
+
});
|
|
921
1440
|
return {
|
|
922
|
-
next: () => finished ?
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1441
|
+
next: () => finished || !gate || gate.wantsMore() ? step() : gate.awaitDemand().then(step),
|
|
1442
|
+
return: () => {
|
|
1443
|
+
close();
|
|
1444
|
+
return Promise.resolve({
|
|
1445
|
+
done: true,
|
|
1446
|
+
value: undefined
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
926
1449
|
};
|
|
927
1450
|
}
|
|
928
1451
|
};
|
|
1452
|
+
state.seen.set(value, guardedIterable);
|
|
1453
|
+
return guardedIterable;
|
|
1454
|
+
}
|
|
1455
|
+
if (state.scope && typeof value[Symbol.iterator] === "function" && !Array.isArray(value) && !(value instanceof Map) && !(value instanceof Set) && !ArrayBuffer.isView(value)) {
|
|
1456
|
+
const scopedIterable = scopeDeferredResult(value, state.scope);
|
|
1457
|
+
state.seen.set(value, scopedIterable);
|
|
1458
|
+
return scopedIterable;
|
|
1459
|
+
}
|
|
1460
|
+
if (Array.isArray(value)) {
|
|
1461
|
+
const next = value.slice();
|
|
1462
|
+
state.seen.set(value, next);
|
|
1463
|
+
return new Frame(ARRAY, value, next, value, null);
|
|
1464
|
+
}
|
|
1465
|
+
if (value instanceof Map) {
|
|
1466
|
+
const next = new Map();
|
|
1467
|
+
state.seen.set(value, next);
|
|
1468
|
+
const items = [];
|
|
1469
|
+
for (const entry of value) items.push(entry[0], entry[1]);
|
|
1470
|
+
return new Frame(MAP, value, next, items, null);
|
|
1471
|
+
}
|
|
1472
|
+
if (value instanceof Set) {
|
|
1473
|
+
const next = new Set();
|
|
1474
|
+
state.seen.set(value, next);
|
|
1475
|
+
return new Frame(SET, value, next, [...value], null);
|
|
929
1476
|
}
|
|
1477
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1478
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
1479
|
+
state.seen.set(value, value);
|
|
1480
|
+
return value;
|
|
1481
|
+
}
|
|
1482
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1483
|
+
for (const key of Object.keys(descriptors)) {
|
|
1484
|
+
descriptors[key].configurable = true;
|
|
1485
|
+
if ("value" in descriptors[key]) descriptors[key].writable = true;
|
|
1486
|
+
}
|
|
1487
|
+
const next = Object.create(prototype, descriptors);
|
|
1488
|
+
state.seen.set(value, next);
|
|
1489
|
+
return new Frame(OBJECT, value, next, Object.keys(value), descriptors);
|
|
1490
|
+
}
|
|
1491
|
+
function keepGuarded(value, next, changed, state) {
|
|
1492
|
+
if (changed || state.cyclic.has(value)) return next;
|
|
1493
|
+
state.seen.set(value, value);
|
|
1494
|
+
return value;
|
|
1495
|
+
}
|
|
1496
|
+
function serializeResponseStream(value, codecOptions, signal, scope) {
|
|
1497
|
+
let closed = false;
|
|
1498
|
+
let streamController = null;
|
|
1499
|
+
let demandWaiters = null;
|
|
1500
|
+
const wantsMore = () => streamController !== null && streamController.desiredSize > 0;
|
|
1501
|
+
const awaitDemand = () => new Promise(resolve => (demandWaiters ??= []).push(resolve));
|
|
1502
|
+
const supplyDemand = () => {
|
|
1503
|
+
const resolvers = demandWaiters;
|
|
1504
|
+
demandWaiters = null;
|
|
1505
|
+
if (resolvers) for (const resolve of resolvers) resolve();
|
|
1506
|
+
};
|
|
1507
|
+
const sourceClosers = new Set();
|
|
1508
|
+
const gate = {
|
|
1509
|
+
wantsMore,
|
|
1510
|
+
awaitDemand,
|
|
1511
|
+
onOpen(close) {
|
|
1512
|
+
if (closed) close();else sourceClosers.add(close);
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
const guardState = {
|
|
1516
|
+
seen: new WeakMap(),
|
|
1517
|
+
cyclic: new WeakSet(),
|
|
1518
|
+
gate,
|
|
1519
|
+
scope
|
|
1520
|
+
};
|
|
1521
|
+
value = guardOperation(guardState, () => guardFailures(value, guardState));
|
|
1522
|
+
let cancelSerialize = null;
|
|
1523
|
+
let onAbort = null;
|
|
1524
|
+
const finishSource = () => {
|
|
1525
|
+
for (const close of sourceClosers) close();
|
|
1526
|
+
sourceClosers.clear();
|
|
1527
|
+
supplyDemand();
|
|
1528
|
+
};
|
|
1529
|
+
const teardown = () => {
|
|
1530
|
+
if (closed) return;
|
|
1531
|
+
closed = true;
|
|
1532
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1533
|
+
if (cancelSerialize) cancelSerialize();
|
|
1534
|
+
finishSource();
|
|
1535
|
+
};
|
|
930
1536
|
return new ReadableStream({
|
|
931
1537
|
async start(controller) {
|
|
1538
|
+
streamController = controller;
|
|
932
1539
|
if (signal) {
|
|
933
1540
|
if (signal.aborted) {
|
|
934
1541
|
teardown();
|
|
@@ -964,29 +1571,54 @@ function serializeResponseStream(value, codecOptions, signal) {
|
|
|
964
1571
|
if (closed) return;
|
|
965
1572
|
closed = true;
|
|
966
1573
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1574
|
+
finishSource();
|
|
967
1575
|
controller.close();
|
|
968
1576
|
},
|
|
969
1577
|
onError(error) {
|
|
970
1578
|
if (closed) return;
|
|
971
1579
|
closed = true;
|
|
972
1580
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
973
|
-
|
|
1581
|
+
finishSource();
|
|
1582
|
+
try {
|
|
1583
|
+
const delivered = sanitizeServerError(DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error);
|
|
1584
|
+
controller.enqueue(createChunk(encodeErrorTrailer(delivered)));
|
|
1585
|
+
controller.close();
|
|
1586
|
+
} catch {
|
|
1587
|
+
try {
|
|
1588
|
+
controller.error(error);
|
|
1589
|
+
} catch {}
|
|
1590
|
+
}
|
|
974
1591
|
}
|
|
975
1592
|
});
|
|
976
1593
|
},
|
|
1594
|
+
pull() {
|
|
1595
|
+
supplyDemand();
|
|
1596
|
+
},
|
|
977
1597
|
cancel() {
|
|
978
1598
|
teardown();
|
|
979
1599
|
}
|
|
980
1600
|
});
|
|
981
1601
|
}
|
|
982
|
-
function serializedResponse(value, headers, codec, signal) {
|
|
1602
|
+
function serializedResponse(value, headers, codec, signal, scope) {
|
|
983
1603
|
headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
|
|
984
1604
|
headers.set("Content-Type", "text/plain");
|
|
985
|
-
return new Response(serializeResponseStream(value, codec, signal), {
|
|
1605
|
+
return new Response(serializeResponseStream(value, codec, signal, scope), {
|
|
986
1606
|
headers
|
|
987
1607
|
});
|
|
988
1608
|
}
|
|
989
|
-
function encodeResult(value, headers, status, codec, signal) {
|
|
1609
|
+
function encodeResult(value, headers, status, codec, signal, scope) {
|
|
1610
|
+
if (NULL_BODY_STATUSES.has(status)) {
|
|
1611
|
+
if (value === undefined || value === null) {
|
|
1612
|
+
headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
|
|
1613
|
+
return new Response(null, {
|
|
1614
|
+
status,
|
|
1615
|
+
headers
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
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.`);
|
|
1619
|
+
headers.set(ERROR_HEADER, encodeErrorHeaderValue(error.message));
|
|
1620
|
+
return encodeResult(error, headers, 500, codec, signal, scope);
|
|
1621
|
+
}
|
|
990
1622
|
const direct = getHeadersAndBody(value);
|
|
991
1623
|
if (direct) {
|
|
992
1624
|
for (const [key, val] of Object.entries(direct.headers || {})) {
|
|
@@ -1005,21 +1637,37 @@ function encodeResult(value, headers, status, codec, signal) {
|
|
|
1005
1637
|
});
|
|
1006
1638
|
}
|
|
1007
1639
|
try {
|
|
1008
|
-
|
|
1640
|
+
const jsonSafe = scope ? scope(() => isJSONSafe(value)) : isJSONSafe(value);
|
|
1641
|
+
if (jsonSafe) {
|
|
1009
1642
|
headers.set(BODY_FORMAT_HEADER, BodyFormat.Json);
|
|
1010
1643
|
headers.set("Content-Type", "application/json");
|
|
1011
|
-
|
|
1644
|
+
const body = scope ? scope(() => JSON.stringify(value)) : JSON.stringify(value);
|
|
1645
|
+
return new Response(body, {
|
|
1012
1646
|
status,
|
|
1013
1647
|
headers
|
|
1014
1648
|
});
|
|
1015
1649
|
}
|
|
1016
1650
|
} catch {
|
|
1017
1651
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
status,
|
|
1021
|
-
|
|
1022
|
-
|
|
1652
|
+
try {
|
|
1653
|
+
const response = serializedResponse(value, headers, codec, signal, scope);
|
|
1654
|
+
return status === 200 ? response : new Response(response.body, {
|
|
1655
|
+
status,
|
|
1656
|
+
headers
|
|
1657
|
+
});
|
|
1658
|
+
} catch (error) {
|
|
1659
|
+
throw DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
const ERROR_HEADER_VALUE_LIMIT = 1024;
|
|
1663
|
+
function boundedErrorHeaderValue(message) {
|
|
1664
|
+
let label = message.length > 256 ? message.slice(0, 256) : message;
|
|
1665
|
+
let encoded = encodeErrorHeaderValue(label);
|
|
1666
|
+
while (encoded.length > ERROR_HEADER_VALUE_LIMIT && label.length > 1) {
|
|
1667
|
+
label = label.slice(0, Math.ceil(label.length / 2));
|
|
1668
|
+
encoded = encodeErrorHeaderValue(label);
|
|
1669
|
+
}
|
|
1670
|
+
return encoded;
|
|
1023
1671
|
}
|
|
1024
1672
|
const GENERIC_SERVER_ERROR_MESSAGE = "Internal Server Error";
|
|
1025
1673
|
let DEV = true === true;
|
|
@@ -1043,11 +1691,12 @@ function serverFunctionUrl(id, boundArgs) {
|
|
|
1043
1691
|
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
1044
1692
|
}
|
|
1045
1693
|
function parseServerFunctionUrl(url) {
|
|
1046
|
-
|
|
1694
|
+
const parsed = parseServerFunctionAddress(new URL(url, "http://localhost").pathname, config.endpoint);
|
|
1695
|
+
return parsed && parsed.id;
|
|
1047
1696
|
}
|
|
1048
1697
|
async function matchesOrigin(origin, request, matcher) {
|
|
1049
1698
|
if (matcher === undefined) return origin === new URL(request.url).origin;
|
|
1050
|
-
if (typeof matcher === "function") return
|
|
1699
|
+
if (typeof matcher === "function") return (await matcher(origin, request)) === true;
|
|
1051
1700
|
return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
|
|
1052
1701
|
}
|
|
1053
1702
|
async function allowsServerFunctionRequest(request, options) {
|
|
@@ -1099,14 +1748,37 @@ function forbiddenResponse() {
|
|
|
1099
1748
|
}
|
|
1100
1749
|
}));
|
|
1101
1750
|
}
|
|
1751
|
+
function nativePromise(value) {
|
|
1752
|
+
if (value instanceof Promise) return value;
|
|
1753
|
+
try {
|
|
1754
|
+
if (Object.prototype.toString.call(value) === "[object Promise]") return Promise.prototype.then.call(value, value => value);
|
|
1755
|
+
} catch {}
|
|
1756
|
+
}
|
|
1102
1757
|
async function handleServerFunctionRequest(request, options = {}) {
|
|
1103
|
-
const codec =
|
|
1758
|
+
const codec = {
|
|
1759
|
+
...(options.codec !== undefined ? options.codec : getServerFunctionsCodec())
|
|
1760
|
+
};
|
|
1761
|
+
codec.serializeErrorStacks ??= DEV;
|
|
1104
1762
|
const url = new URL(request.url);
|
|
1105
1763
|
const method = request.method;
|
|
1106
|
-
const
|
|
1764
|
+
const address = resolveAddress(url);
|
|
1765
|
+
const functionId = address && address.id;
|
|
1107
1766
|
const declaredRead = (method === "GET" || method === "HEAD") && functionId !== null && METHODS.get(functionId) === "GET";
|
|
1108
1767
|
const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
|
|
1109
|
-
const protectsRequest = csrf !== false && !declaredRead;
|
|
1768
|
+
const protectsRequest = csrf !== false && (!declaredRead || typeof csrf === "object" && csrf.protectDeclaredReads === true);
|
|
1769
|
+
let serverFunction;
|
|
1770
|
+
if (functionId) {
|
|
1771
|
+
try {
|
|
1772
|
+
serverFunction = getServerFunction(functionId);
|
|
1773
|
+
} catch {
|
|
1774
|
+
return finalizeTransportResponse(new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
1775
|
+
status: 404,
|
|
1776
|
+
headers: {
|
|
1777
|
+
[UNKNOWN_HEADER]: "true"
|
|
1778
|
+
}
|
|
1779
|
+
}), method);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1110
1782
|
if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
|
|
1111
1783
|
return finalizeTransportResponse(forbiddenResponse(), method);
|
|
1112
1784
|
}
|
|
@@ -1117,15 +1789,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1117
1789
|
});
|
|
1118
1790
|
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1119
1791
|
}
|
|
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
|
-
}
|
|
1792
|
+
const scripted = address.data;
|
|
1129
1793
|
if (method !== "POST" && !declaredRead) {
|
|
1130
1794
|
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
1131
1795
|
status: 405,
|
|
@@ -1135,25 +1799,103 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1135
1799
|
});
|
|
1136
1800
|
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1137
1801
|
}
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1802
|
+
const bodySizeLimit = options.bodySizeLimit !== undefined ? options.bodySizeLimit : config.bodySizeLimit;
|
|
1803
|
+
const argsEncoding = url.searchParams.get("args");
|
|
1804
|
+
if (argsEncoding !== null && argsEncoding.length > bodySizeLimit) {
|
|
1805
|
+
const response = new Response(DEV ? "Server function arguments exceed the configured bodySizeLimit" : null, {
|
|
1806
|
+
status: 413
|
|
1807
|
+
});
|
|
1808
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1809
|
+
}
|
|
1810
|
+
if (method === "POST" && request.body !== null && bodySizeLimit !== Infinity) {
|
|
1811
|
+
const raw = request.headers.get("content-length");
|
|
1812
|
+
const declared = raw !== null && /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
1813
|
+
if (declared > bodySizeLimit) {
|
|
1814
|
+
const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
|
|
1815
|
+
status: 413
|
|
1816
|
+
});
|
|
1817
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1818
|
+
}
|
|
1819
|
+
if (!(declared > 0)) {
|
|
1820
|
+
let bounded;
|
|
1821
|
+
try {
|
|
1822
|
+
bounded = await bufferBodyWithin(request, bodySizeLimit);
|
|
1823
|
+
} catch {
|
|
1824
|
+
const response = new Response(DEV ? "Malformed server function arguments" : null, {
|
|
1825
|
+
status: 400
|
|
1826
|
+
});
|
|
1827
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1828
|
+
}
|
|
1829
|
+
if (bounded === null) {
|
|
1830
|
+
const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
|
|
1831
|
+
status: 413
|
|
1832
|
+
});
|
|
1833
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1834
|
+
}
|
|
1835
|
+
request = bounded;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
let event;
|
|
1839
|
+
try {
|
|
1840
|
+
event = options.createEvent ? options.createEvent(request) : {
|
|
1841
|
+
request,
|
|
1842
|
+
locals: {}
|
|
1843
|
+
};
|
|
1844
|
+
const promised = nativePromise(event);
|
|
1845
|
+
if (promised) event = await promised;
|
|
1846
|
+
} catch (error) {
|
|
1847
|
+
const safe = sanitizeServerError(error);
|
|
1848
|
+
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1849
|
+
const headers = new Headers();
|
|
1850
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(message));
|
|
1851
|
+
const response = scripted ? encodeResult(safe, headers, 500, codec, request.signal) : new Response(DEV ? message : null, {
|
|
1852
|
+
status: 500
|
|
1853
|
+
});
|
|
1854
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1855
|
+
}
|
|
1856
|
+
const refuseCommitted = raw => {
|
|
1857
|
+
const response = commitEventResponse(raw, event);
|
|
1858
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1141
1859
|
};
|
|
1142
1860
|
const provide = options.provideEvent || provideEvent;
|
|
1861
|
+
const scope = run => provide(event, run);
|
|
1143
1862
|
const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
|
|
1144
1863
|
const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
|
|
1145
1864
|
const wrapInvocation = options.wrapInvocation !== undefined ? options.wrapInvocation : config.wrapInvocation;
|
|
1146
1865
|
const transformFlightResult = options.transformFlightResult !== undefined ? options.transformFlightResult : config.transformFlightResult;
|
|
1147
|
-
|
|
1148
|
-
|
|
1866
|
+
let handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS;
|
|
1867
|
+
if (handleNoJS === undefined && !scripted && isFormPost(request)) {
|
|
1868
|
+
const fetchMode = request.headers.get("Sec-Fetch-Mode");
|
|
1869
|
+
if (fetchMode === null || fetchMode === "navigate") {
|
|
1870
|
+
handleNoJS = defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler());
|
|
1871
|
+
} else {
|
|
1872
|
+
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, {
|
|
1873
|
+
status: 400
|
|
1874
|
+
});
|
|
1875
|
+
return refuseCommitted(response);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
const flightHeader = scripted && method === "POST" ? request.headers.get(SINGLE_FLIGHT_HEADER) : null;
|
|
1879
|
+
const flightHooks = flightHeader ? flightHeader.split(",").flatMap(source => {
|
|
1880
|
+
const hook = source === "true" ? flightHook : flightSources.get(source);
|
|
1881
|
+
return hook ? [[source, hook]] : [];
|
|
1882
|
+
}) : [];
|
|
1883
|
+
const collectsFlight = flightHooks.length > 0;
|
|
1149
1884
|
let parsed;
|
|
1150
1885
|
try {
|
|
1151
|
-
parsed = await parseArguments(request, url,
|
|
1886
|
+
parsed = await parseArguments(request, url, scripted, codec);
|
|
1152
1887
|
} catch {
|
|
1153
1888
|
const response = new Response(DEV ? "Malformed server function arguments" : null, {
|
|
1154
1889
|
status: 400
|
|
1155
1890
|
});
|
|
1156
|
-
return
|
|
1891
|
+
return refuseCommitted(response);
|
|
1892
|
+
}
|
|
1893
|
+
const maxArguments = options.maxArguments !== undefined ? options.maxArguments : config.maxArguments;
|
|
1894
|
+
if (parsed.length > maxArguments) {
|
|
1895
|
+
const response = new Response(DEV ? "Server function call exceeds the configured maxArguments" : null, {
|
|
1896
|
+
status: 400
|
|
1897
|
+
});
|
|
1898
|
+
return refuseCommitted(response);
|
|
1157
1899
|
}
|
|
1158
1900
|
const flightContext = {
|
|
1159
1901
|
id: functionId,
|
|
@@ -1167,7 +1909,8 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1167
1909
|
const headers = new Headers();
|
|
1168
1910
|
const dispatch = async () => {
|
|
1169
1911
|
try {
|
|
1170
|
-
let
|
|
1912
|
+
let invocations = 0;
|
|
1913
|
+
const invokeOnce = async () => {
|
|
1171
1914
|
INVOCATIONS.set(event, {
|
|
1172
1915
|
id: functionId
|
|
1173
1916
|
});
|
|
@@ -1179,7 +1922,16 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1179
1922
|
request,
|
|
1180
1923
|
direct: false
|
|
1181
1924
|
}) : run();
|
|
1925
|
+
};
|
|
1926
|
+
let result = await provide(event, () => {
|
|
1927
|
+
if (++invocations > 1) {
|
|
1928
|
+
throw new Error("provideEvent invoked the server function callback more than once: a second " + "invocation would commit the call's side effects twice. The hook must call " + "fn exactly once and return its result.");
|
|
1929
|
+
}
|
|
1930
|
+
return invokeOnce();
|
|
1182
1931
|
});
|
|
1932
|
+
if (invocations !== 1) {
|
|
1933
|
+
throw new Error(invocations === 0 ? "provideEvent returned without invoking the server function callback: the call " + "would have answered as a void success without running the function. The hook " + "must call fn exactly once and return its result." : "provideEvent invoked the server function callback more than once: a second " + "invocation would commit the call's side effects twice. The hook must call " + "fn exactly once and return its result.");
|
|
1934
|
+
}
|
|
1183
1935
|
if (transformResult) {
|
|
1184
1936
|
result = await transformResult(event, result, flightContext);
|
|
1185
1937
|
}
|
|
@@ -1190,25 +1942,29 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1190
1942
|
response,
|
|
1191
1943
|
value
|
|
1192
1944
|
} = result;
|
|
1193
|
-
if (!
|
|
1945
|
+
if (!scripted && !handleNoJS && response && response.body) {
|
|
1194
1946
|
return response;
|
|
1195
1947
|
}
|
|
1196
1948
|
if (response && response.headers) {
|
|
1197
1949
|
mergeResponseHeaders(headers, response.headers);
|
|
1198
1950
|
}
|
|
1199
|
-
if (response && response.status && (
|
|
1951
|
+
if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
|
|
1200
1952
|
status = response.status;
|
|
1953
|
+
} else if (response && response.status) {
|
|
1954
|
+
maskRedirect(headers, response, request.url);
|
|
1201
1955
|
}
|
|
1202
1956
|
metadata = response;
|
|
1203
1957
|
result = value;
|
|
1204
1958
|
} else if (result instanceof Response) {
|
|
1205
1959
|
if (result.headers && result.headers.has("X-Content-Raw")) return result;
|
|
1206
|
-
if (
|
|
1960
|
+
if (scripted) {
|
|
1207
1961
|
if (result.headers) {
|
|
1208
1962
|
mergeResponseHeaders(headers, result.headers);
|
|
1209
1963
|
}
|
|
1210
|
-
if (result.status && (result.status
|
|
1964
|
+
if (result.status && !validRedirectStatuses.has(result.status)) {
|
|
1211
1965
|
status = result.status;
|
|
1966
|
+
} else if (result.status) {
|
|
1967
|
+
maskRedirect(headers, result, request.url);
|
|
1212
1968
|
}
|
|
1213
1969
|
metadata = result;
|
|
1214
1970
|
if (result.body == null) {
|
|
@@ -1217,7 +1973,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1217
1973
|
}
|
|
1218
1974
|
}
|
|
1219
1975
|
if (collectsFlight) {
|
|
1220
|
-
result = await foldFlightData(
|
|
1976
|
+
result = await foldFlightData(flightHooks, event, headers, {
|
|
1221
1977
|
id: functionId,
|
|
1222
1978
|
value: result,
|
|
1223
1979
|
response: metadata,
|
|
@@ -1226,19 +1982,37 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1226
1982
|
}, flightContext);
|
|
1227
1983
|
if (result instanceof Response && result.headers.has("X-Content-Raw")) return result;
|
|
1228
1984
|
}
|
|
1229
|
-
if (!
|
|
1230
|
-
if (handleNoJS) return handleNoJS(result, request, parsed);
|
|
1985
|
+
if (!scripted) {
|
|
1986
|
+
if (handleNoJS) return handleNoJS(result ?? metadata, request, parsed);
|
|
1231
1987
|
if (result instanceof Response) return result;
|
|
1232
|
-
return encodeResult(result, headers,
|
|
1988
|
+
return encodeResult(result, headers, status, codec, request.signal, scope);
|
|
1233
1989
|
}
|
|
1234
|
-
|
|
1990
|
+
if (status === 304) warnScripted304(functionId);
|
|
1991
|
+
return encodeResult(result, headers, status, codec, request.signal, scope);
|
|
1235
1992
|
} catch (x) {
|
|
1993
|
+
const respondThrown = value => {
|
|
1994
|
+
const safe = sanitizeServerError(value);
|
|
1995
|
+
if (!scripted) {
|
|
1996
|
+
if (handleNoJS) return handleNoJS(safe, request, parsed, true);
|
|
1997
|
+
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1998
|
+
return new Response(DEV ? message : null, {
|
|
1999
|
+
status: 500
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
|
|
2003
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(error));
|
|
2004
|
+
return encodeResult(safe, headers, 500, codec, request.signal, scope);
|
|
2005
|
+
};
|
|
1236
2006
|
if (x instanceof Response || isResponseEnvelope(x)) {
|
|
1237
2007
|
if (transformResult) {
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
2008
|
+
try {
|
|
2009
|
+
x = await transformResult(event, x, {
|
|
2010
|
+
...flightContext,
|
|
2011
|
+
thrown: true
|
|
2012
|
+
});
|
|
2013
|
+
} catch (hookError) {
|
|
2014
|
+
return respondThrown(hookError);
|
|
2015
|
+
}
|
|
1242
2016
|
}
|
|
1243
2017
|
let status = 200;
|
|
1244
2018
|
let metadata;
|
|
@@ -1250,8 +2024,10 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1250
2024
|
if (response && response.headers) {
|
|
1251
2025
|
mergeResponseHeaders(headers, response.headers);
|
|
1252
2026
|
}
|
|
1253
|
-
if (response && response.status && (!
|
|
2027
|
+
if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
|
|
1254
2028
|
status = response.status;
|
|
2029
|
+
} else if (response && response.status) {
|
|
2030
|
+
maskRedirect(headers, response, request.url);
|
|
1255
2031
|
}
|
|
1256
2032
|
metadata = response;
|
|
1257
2033
|
x = value;
|
|
@@ -1259,8 +2035,10 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1259
2035
|
if (x.headers) {
|
|
1260
2036
|
mergeResponseHeaders(headers, x.headers);
|
|
1261
2037
|
}
|
|
1262
|
-
if (x.status && (!
|
|
2038
|
+
if (x.status && (!scripted || !validRedirectStatuses.has(x.status))) {
|
|
1263
2039
|
status = x.status;
|
|
2040
|
+
} else if (x.status) {
|
|
2041
|
+
maskRedirect(headers, x, request.url);
|
|
1264
2042
|
}
|
|
1265
2043
|
metadata = x;
|
|
1266
2044
|
if (x.body == null) {
|
|
@@ -1268,7 +2046,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1268
2046
|
}
|
|
1269
2047
|
}
|
|
1270
2048
|
if (collectsFlight) {
|
|
1271
|
-
x = await foldFlightData(
|
|
2049
|
+
x = await foldFlightData(flightHooks, event, headers, {
|
|
1272
2050
|
id: functionId,
|
|
1273
2051
|
value: x,
|
|
1274
2052
|
response: metadata,
|
|
@@ -1276,38 +2054,38 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1276
2054
|
thrown: true
|
|
1277
2055
|
}, flightContext);
|
|
1278
2056
|
if (x instanceof Response && x.headers.has("X-Content-Raw")) {
|
|
2057
|
+
x = ownResponse(x);
|
|
1279
2058
|
x.headers.set(ERROR_HEADER, "true");
|
|
1280
2059
|
return x;
|
|
1281
2060
|
}
|
|
1282
2061
|
}
|
|
1283
2062
|
headers.set(ERROR_HEADER, "true");
|
|
1284
|
-
if (!
|
|
2063
|
+
if (!scripted) {
|
|
1285
2064
|
if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
|
|
1286
2065
|
if (x instanceof Response) return x;
|
|
1287
2066
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
const safe = sanitizeServerError(x);
|
|
1291
|
-
if (!instance) {
|
|
1292
|
-
if (handleNoJS) return handleNoJS(safe, request, parsed, true);
|
|
1293
|
-
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1294
|
-
return new Response(DEV ? message : null, {
|
|
1295
|
-
status: 500
|
|
1296
|
-
});
|
|
2067
|
+
if (scripted && status === 304) warnScripted304(functionId);
|
|
2068
|
+
return encodeResult(x, headers, status, codec, request.signal, scope);
|
|
1297
2069
|
}
|
|
1298
|
-
|
|
1299
|
-
headers.set(ERROR_HEADER, encodeErrorHeaderValue(error));
|
|
1300
|
-
return encodeResult(safe, headers, 200, codec, request.signal);
|
|
2070
|
+
return respondThrown(x);
|
|
1301
2071
|
}
|
|
1302
2072
|
};
|
|
1303
|
-
const response = commitEventResponse(await dispatch(), event);
|
|
2073
|
+
const response = commitEventResponse(enforceComposedHeaderInvariants(ownResponse(await dispatch())), event);
|
|
1304
2074
|
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1305
2075
|
}
|
|
2076
|
+
function ownResponse(response) {
|
|
2077
|
+
try {
|
|
2078
|
+
return new Response(response.body, response);
|
|
2079
|
+
} catch {
|
|
2080
|
+
return response;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
1306
2083
|
function finalizeTransportResponse(response, method) {
|
|
1307
2084
|
const stripBody = method === "HEAD" && response.body !== null;
|
|
1308
|
-
|
|
2085
|
+
const defaultsCache = !response.headers.has("Cache-Control") && response.status !== 304;
|
|
2086
|
+
if (stripBody || defaultsCache) {
|
|
1309
2087
|
try {
|
|
1310
|
-
if (
|
|
2088
|
+
if (defaultsCache) {
|
|
1311
2089
|
response.headers.set("Cache-Control", "no-store");
|
|
1312
2090
|
}
|
|
1313
2091
|
if (!stripBody) return response;
|
|
@@ -1319,7 +2097,7 @@ function finalizeTransportResponse(response, method) {
|
|
|
1319
2097
|
});
|
|
1320
2098
|
} catch {
|
|
1321
2099
|
const headers = new Headers(response.headers);
|
|
1322
|
-
if (!headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
|
|
2100
|
+
if (defaultsCache && !headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
|
|
1323
2101
|
if (stripBody) response.body.cancel().catch(() => {});
|
|
1324
2102
|
return new Response(stripBody ? null : response.body, {
|
|
1325
2103
|
status: response.status,
|
|
@@ -1336,14 +2114,17 @@ exports.FLASH_COOKIE = FLASH_COOKIE;
|
|
|
1336
2114
|
exports.GENERIC_SERVER_ERROR_MESSAGE = GENERIC_SERVER_ERROR_MESSAGE;
|
|
1337
2115
|
exports.GET = GET;
|
|
1338
2116
|
exports.INSTANCE_HEADER = INSTANCE_HEADER;
|
|
2117
|
+
exports.REDIRECT_HEADER = REDIRECT_HEADER;
|
|
1339
2118
|
exports.SERVER_FUNCTION_INVOKE = SERVER_FUNCTION_INVOKE;
|
|
1340
2119
|
exports.SINGLE_FLIGHT_HEADER = SINGLE_FLIGHT_HEADER;
|
|
2120
|
+
exports.UNKNOWN_HEADER = UNKNOWN_HEADER;
|
|
1341
2121
|
exports.clearFlashCookie = clearFlashCookie;
|
|
1342
2122
|
exports.configureServerFunctionsServer = configureServerFunctionsServer;
|
|
1343
2123
|
exports.createNoJSHandler = createNoJSHandler;
|
|
1344
2124
|
exports.createServerReference = createServerReference;
|
|
1345
2125
|
exports.decodeErrorHeaderValue = decodeErrorHeaderValue;
|
|
1346
2126
|
exports.decodeFlashCookie = decodeFlashCookie;
|
|
2127
|
+
exports.decodeRedirectHeaderValue = decodeRedirectHeaderValue;
|
|
1347
2128
|
exports.decodeResponse = decodeResponse;
|
|
1348
2129
|
exports.decodeResponsePayload = decodeResponsePayload;
|
|
1349
2130
|
exports.encodeErrorHeaderValue = encodeErrorHeaderValue;
|
|
@@ -1353,6 +2134,7 @@ exports.getEventServerFunctionInvocation = getEventServerFunctionInvocation;
|
|
|
1353
2134
|
exports.getServerFunction = getServerFunction;
|
|
1354
2135
|
exports.getServerFunctionInvocation = getServerFunctionInvocation;
|
|
1355
2136
|
exports.getServerFunctionMetadata = getServerFunctionMetadata;
|
|
2137
|
+
exports.guardFailures = guardFailures;
|
|
1356
2138
|
exports.handleServerFunctionRequest = handleServerFunctionRequest;
|
|
1357
2139
|
exports.hasFlashCookie = hasFlashCookie;
|
|
1358
2140
|
exports.invoke = invoke;
|
|
@@ -1360,6 +2142,7 @@ exports.isServerFunction = isServerFunction;
|
|
|
1360
2142
|
exports.live = live;
|
|
1361
2143
|
exports.observeServerFunctionCalls = observeServerFunctionCalls;
|
|
1362
2144
|
exports.parseServerFunctionUrl = parseServerFunctionUrl;
|
|
2145
|
+
exports.registerFlightDataSource = registerFlightDataSource;
|
|
1363
2146
|
exports.registerServerFunction = registerServerFunction;
|
|
1364
2147
|
exports.registerServerReference = registerServerReference;
|
|
1365
2148
|
exports.sanitizeServerError = sanitizeServerError;
|