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