@solidjs/web 2.0.0-rc.3 → 2.0.0-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev.cjs +398 -17
- package/dist/dev.js +396 -19
- package/dist/server.cjs +166 -38
- package/dist/server.js +165 -39
- package/dist/web.cjs +369 -17
- package/dist/web.js +367 -19
- package/frames/dist/client.cjs +41 -8
- package/frames/dist/client.dev.cjs +41 -8
- package/frames/dist/client.dev.js +41 -8
- package/frames/dist/client.js +41 -8
- package/frames/dist/server.cjs +432 -44
- package/frames/dist/server.js +432 -44
- package/package.json +2 -2
- package/serialization/dist/decode.cjs +4 -2
- package/serialization/dist/decode.js +4 -2
- package/serialization/dist/serialization.cjs +12 -8
- package/serialization/dist/serialization.js +12 -8
- package/serialization/types/index.d.ts +7 -0
- package/serialization/types/serializer-decode.d.ts +14 -1
- package/serialization/types/serializer.d.ts +7 -0
- package/serialization/types-cjs/index.d.cts +7 -0
- package/serialization/types-cjs/serializer-decode.d.cts +14 -1
- package/serialization/types-cjs/serializer.d.cts +7 -0
- package/server-functions/dist/client.cjs +271 -47
- package/server-functions/dist/client.js +264 -47
- package/server-functions/dist/server.cjs +872 -131
- package/server-functions/dist/server.dev.cjs +892 -131
- package/server-functions/dist/server.dev.js +884 -131
- package/server-functions/dist/server.js +864 -131
- package/types/client.d.ts +4 -2
- 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/index.d.ts +1 -0
- package/types/jsx.d.ts +11 -16
- package/types/patch-driver.d.ts +3 -0
- package/types/response.d.ts +20 -1
- package/types/serializer-decode.d.ts +14 -1
- package/types/serializer.d.ts +7 -0
- package/types/server-functions/client.d.ts +58 -7
- package/types/server-functions/flash.d.ts +9 -0
- package/types/server-functions/registry.d.ts +38 -1
- package/types/server-functions/server.d.ts +66 -14
- package/types/server-functions/shared.d.ts +122 -16
- package/types/server-mock.d.ts +17 -2
- package/types/server.d.ts +16 -2
- package/types-cjs/client.d.cts +4 -2
- package/types-cjs/cookies.d.cts +6 -14
- package/types-cjs/frames/frame-client.d.cts +4 -0
- package/types-cjs/frames/serializer-decode.d.cts +14 -1
- package/types-cjs/frames/serializer.d.cts +7 -0
- package/types-cjs/index.d.cts +1 -0
- package/types-cjs/jsx.d.cts +11 -16
- package/types-cjs/patch-driver.d.cts +3 -0
- package/types-cjs/response.d.cts +20 -1
- 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 +58 -7
- package/types-cjs/server-functions/flash.d.cts +9 -0
- package/types-cjs/server-functions/registry.d.cts +38 -1
- package/types-cjs/server-functions/server.d.cts +66 -14
- package/types-cjs/server-functions/shared.d.cts +122 -16
- package/types-cjs/server-mock.d.cts +17 -2
- package/types-cjs/server.d.cts +16 -2
|
@@ -9,6 +9,8 @@ function isSafeError(value) {
|
|
|
9
9
|
return !!(value && (typeof value === "object" || typeof value === "function") && value[SAFE_ERROR]);
|
|
10
10
|
}
|
|
11
11
|
const REVALIDATE_HEADER = "X-Revalidate";
|
|
12
|
+
const RESPONSE_HEADER_VALUE_LIMIT = 4096;
|
|
13
|
+
const NULL_BODY_STATUSES = new Set([204, 205, 304]);
|
|
12
14
|
|
|
13
15
|
const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
|
|
14
16
|
function getServerFunctionMetadata(fn) {
|
|
@@ -26,6 +28,32 @@ function withMeta(fn, meta) {
|
|
|
26
28
|
Object.assign(metadata, meta);
|
|
27
29
|
return fn;
|
|
28
30
|
}
|
|
31
|
+
const SERVER_FUNCTION_INVOKE = Symbol.for("solid.ServerFunctionInvoke");
|
|
32
|
+
const INVOKE_OPTION_REDIRECTS = {
|
|
33
|
+
headers: "Session-dynamic headers belong to the prepareRequest hook, declaration metadata to " + "withMeta(fn, meta), and data belongs in the arguments, where it is serialized, typed, " + "and part of the cache key.",
|
|
34
|
+
method: "The method is declaration-scoped: declare the function with GET(fn).",
|
|
35
|
+
body: "The arguments are the body: pass them in the args array.",
|
|
36
|
+
timeout: "Compose timeouts through `signal` with AbortSignal.timeout(ms) (and AbortSignal.any to " + "combine it with a caller signal)."
|
|
37
|
+
};
|
|
38
|
+
function invoke(fn, options, ...args) {
|
|
39
|
+
const channel = typeof fn === "function" && fn[SERVER_FUNCTION_INVOKE];
|
|
40
|
+
if (!channel) {
|
|
41
|
+
throw new Error(isServerFunction(fn) ? "invoke: this wrapper does not forward the invocation channel " + "(SERVER_FUNCTION_INVOKE). Wrappers that share calls (caches, channels) opt in " + "deliberately — a caller's signal cannot own a wire other callers share. Invoke " + "the underlying reference directly, or use the wrapper's own per-call idioms." : "invoke expects a server function reference (or a wrapper that forwards its " + "invocation channel). Per-call options apply at the transport; for a data " + "layer's calls, use its per-call options instead.");
|
|
42
|
+
}
|
|
43
|
+
if (options === null || typeof options !== "object") {
|
|
44
|
+
throw new Error("invoke's second argument is the invocation options bag: invoke(fn, { signal }, ...args)");
|
|
45
|
+
}
|
|
46
|
+
const picked = {};
|
|
47
|
+
for (const key in options) {
|
|
48
|
+
if (key !== "signal" && key !== "keepalive" && key !== "priority") {
|
|
49
|
+
throw new Error(`\`${key}\` is not an invocation option. ` + (INVOKE_OPTION_REDIRECTS[key] || "Options here are strictly invocation-scoped (they vary between calls of the " + "same function): signal, keepalive, priority."));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (options.signal !== undefined) picked.signal = options.signal;
|
|
53
|
+
if (options.keepalive !== undefined) picked.keepalive = options.keepalive;
|
|
54
|
+
if (options.priority !== undefined) picked.priority = options.priority;
|
|
55
|
+
return channel(args, picked);
|
|
56
|
+
}
|
|
29
57
|
const LIVE_SOURCE = Symbol.for("solid.LiveSource");
|
|
30
58
|
const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
|
|
31
59
|
function provideServerFunctionRPC(rpc) {
|
|
@@ -62,6 +90,7 @@ function serializeCookie(name, value, options = {}) {
|
|
|
62
90
|
if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
63
91
|
if (options.httpOnly) cookie += "; HttpOnly";
|
|
64
92
|
if (options.secure) cookie += "; Secure";
|
|
93
|
+
if (options.partitioned) cookie += "; Partitioned";
|
|
65
94
|
if (options.sameSite) {
|
|
66
95
|
const sameSite = options.sameSite.toLowerCase();
|
|
67
96
|
cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
|
|
@@ -86,11 +115,50 @@ function configureServerFunctionsCodec(codec) {
|
|
|
86
115
|
function getServerFunctionsCodec() {
|
|
87
116
|
return codecConfig.codec;
|
|
88
117
|
}
|
|
89
|
-
|
|
118
|
+
const UNNAMED_FLIGHT_SOURCE = "true";
|
|
119
|
+
const flightConfig = {
|
|
120
|
+
consumers: new Map()
|
|
121
|
+
};
|
|
122
|
+
function assertFlightSource(source) {
|
|
123
|
+
if (source === UNNAMED_FLIGHT_SOURCE || source === "" || source.includes(",")) {
|
|
124
|
+
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).`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function subscribeFlightData(sourceOrConsumer, maybeConsumer) {
|
|
128
|
+
const named = typeof sourceOrConsumer === "string";
|
|
129
|
+
if (named) assertFlightSource(sourceOrConsumer);
|
|
130
|
+
const source = named ? sourceOrConsumer : UNNAMED_FLIGHT_SOURCE;
|
|
131
|
+
const consumer = named ? maybeConsumer : sourceOrConsumer;
|
|
132
|
+
flightConfig.consumers.set(source, consumer);
|
|
90
133
|
return () => {
|
|
134
|
+
if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
|
|
91
135
|
};
|
|
92
136
|
}
|
|
93
|
-
|
|
137
|
+
function serverFunctionAddress(endpoint, id) {
|
|
138
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
139
|
+
return `${mount}/${encodeURIComponent(id)}`;
|
|
140
|
+
}
|
|
141
|
+
function parseServerFunctionAddress(pathname, endpoint) {
|
|
142
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
143
|
+
if (!pathname.startsWith(mount)) return null;
|
|
144
|
+
const rest = pathname.slice(mount.length);
|
|
145
|
+
if (!rest.startsWith("/")) return null;
|
|
146
|
+
let segment = rest.slice(1);
|
|
147
|
+
let data = false;
|
|
148
|
+
if (segment.startsWith("data/")) {
|
|
149
|
+
segment = segment.slice(5);
|
|
150
|
+
data = true;
|
|
151
|
+
}
|
|
152
|
+
if (!segment || segment.includes("/")) return null;
|
|
153
|
+
try {
|
|
154
|
+
return {
|
|
155
|
+
id: decodeURIComponent(segment),
|
|
156
|
+
data
|
|
157
|
+
};
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
94
162
|
const ERROR_HEADER = "X-Server-Function-Error";
|
|
95
163
|
const ERROR_HEADER_MARKER = "=?1?";
|
|
96
164
|
const NEEDS_ENCODING = /[^\x20-\x7e\xa0-\xff]/;
|
|
@@ -118,6 +186,28 @@ function decodeErrorHeaderValue(value) {
|
|
|
118
186
|
}
|
|
119
187
|
const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
120
188
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
189
|
+
const UNKNOWN_HEADER = "X-Server-Function-Unknown";
|
|
190
|
+
const REDIRECT_HEADER = "X-Server-Function-Redirect";
|
|
191
|
+
function decodeRedirectHeaderValue(value) {
|
|
192
|
+
if (typeof value !== "string") return undefined;
|
|
193
|
+
const at = value.indexOf(" ");
|
|
194
|
+
if (at < 0) return undefined;
|
|
195
|
+
const status = Number(value.slice(0, at));
|
|
196
|
+
const url = value.slice(at + 1);
|
|
197
|
+
if (!Number.isInteger(status) || !url) return undefined;
|
|
198
|
+
if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) return undefined;
|
|
199
|
+
let parsed;
|
|
200
|
+
try {
|
|
201
|
+
parsed = new URL(url);
|
|
202
|
+
} catch {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
|
|
206
|
+
return {
|
|
207
|
+
status,
|
|
208
|
+
url
|
|
209
|
+
};
|
|
210
|
+
}
|
|
121
211
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
122
212
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
123
213
|
const BodyFormat = {
|
|
@@ -129,7 +219,8 @@ const BodyFormat = {
|
|
|
129
219
|
File: "5",
|
|
130
220
|
ArrayBuffer: "6",
|
|
131
221
|
Uint8Array: "7",
|
|
132
|
-
Json: "8"
|
|
222
|
+
Json: "8",
|
|
223
|
+
Void: "9"
|
|
133
224
|
};
|
|
134
225
|
const JSON_SAFE_DEPTH_LIMIT = 4096;
|
|
135
226
|
const EXIT = {};
|
|
@@ -159,7 +250,11 @@ function isJSONSafe(value) {
|
|
|
159
250
|
const proto = Object.getPrototypeOf(v);
|
|
160
251
|
if (proto !== Object.prototype && proto !== null) return false;
|
|
161
252
|
if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
|
|
162
|
-
for (const k in v)
|
|
253
|
+
for (const k in v) {
|
|
254
|
+
const descriptor = Object.getOwnPropertyDescriptor(v, k);
|
|
255
|
+
if (descriptor === undefined || !("value" in descriptor)) return false;
|
|
256
|
+
stack.push(descriptor.value);
|
|
257
|
+
}
|
|
163
258
|
}
|
|
164
259
|
}
|
|
165
260
|
return true;
|
|
@@ -268,18 +363,34 @@ function createChunk(data) {
|
|
|
268
363
|
class ChunkReader {
|
|
269
364
|
constructor(stream) {
|
|
270
365
|
this.reader = stream.getReader();
|
|
271
|
-
this.
|
|
366
|
+
this.store = new Uint8Array(0);
|
|
367
|
+
this.buffer = this.store;
|
|
272
368
|
this.done = false;
|
|
273
369
|
}
|
|
274
370
|
async readChunk() {
|
|
275
371
|
const chunk = await this.reader.read();
|
|
276
|
-
if (
|
|
277
|
-
const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
|
|
278
|
-
newBuffer.set(this.buffer);
|
|
279
|
-
newBuffer.set(chunk.value, this.buffer.length);
|
|
280
|
-
this.buffer = newBuffer;
|
|
281
|
-
} else {
|
|
372
|
+
if (chunk.done) {
|
|
282
373
|
this.done = true;
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const incoming = chunk.value;
|
|
377
|
+
const store = this.store;
|
|
378
|
+
const start = this.buffer.byteOffset;
|
|
379
|
+
const end = start + this.buffer.length;
|
|
380
|
+
const needed = this.buffer.length + incoming.length;
|
|
381
|
+
if (end + incoming.length <= store.length) {
|
|
382
|
+
store.set(incoming, end);
|
|
383
|
+
this.buffer = store.subarray(start, end + incoming.length);
|
|
384
|
+
} else if (needed <= store.length) {
|
|
385
|
+
store.copyWithin(0, start, end);
|
|
386
|
+
store.set(incoming, this.buffer.length);
|
|
387
|
+
this.buffer = store.subarray(0, needed);
|
|
388
|
+
} else {
|
|
389
|
+
const grown = new Uint8Array(Math.max(needed, store.length * 2));
|
|
390
|
+
grown.set(this.buffer);
|
|
391
|
+
grown.set(incoming, this.buffer.length);
|
|
392
|
+
this.store = grown;
|
|
393
|
+
this.buffer = grown.subarray(0, needed);
|
|
283
394
|
}
|
|
284
395
|
}
|
|
285
396
|
async next() {
|
|
@@ -321,6 +432,27 @@ class ChunkReader {
|
|
|
321
432
|
}
|
|
322
433
|
}
|
|
323
434
|
}
|
|
435
|
+
const ERROR_TRAILER_PREFIX = "!";
|
|
436
|
+
function encodeErrorTrailer(error) {
|
|
437
|
+
const shaped = error instanceof Error ? error : new Error(String(error));
|
|
438
|
+
return ERROR_TRAILER_PREFIX + JSON.stringify(shaped.name && shaped.name !== "Error" ? {
|
|
439
|
+
name: shaped.name,
|
|
440
|
+
message: shaped.message
|
|
441
|
+
} : {
|
|
442
|
+
message: shaped.message
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
function errorFromTrailer(payload) {
|
|
446
|
+
let shape;
|
|
447
|
+
try {
|
|
448
|
+
shape = JSON.parse(payload.slice(1));
|
|
449
|
+
} catch {
|
|
450
|
+
shape = null;
|
|
451
|
+
}
|
|
452
|
+
const error = new Error(shape && typeof shape.message === "string" ? shape.message : "Server function result could not be delivered.");
|
|
453
|
+
if (shape && typeof shape.name === "string") error.name = shape.name;
|
|
454
|
+
return error;
|
|
455
|
+
}
|
|
324
456
|
async function deserializeStream(source, codecOptions) {
|
|
325
457
|
if (!source.body) {
|
|
326
458
|
throw new Error("missing body");
|
|
@@ -328,11 +460,17 @@ async function deserializeStream(source, codecOptions) {
|
|
|
328
460
|
const reader = new ChunkReader(source.body);
|
|
329
461
|
const result = await reader.next();
|
|
330
462
|
if (!result.done) {
|
|
463
|
+
if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
464
|
+
throw errorFromTrailer(result.value);
|
|
465
|
+
}
|
|
331
466
|
const {
|
|
332
467
|
createJSONDeserializer
|
|
333
468
|
} = await import('@solidjs/web/serialization/decode');
|
|
334
469
|
const deserializeChunk = createJSONDeserializer(codecOptions);
|
|
335
470
|
function interpretChunk(chunk) {
|
|
471
|
+
if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
472
|
+
throw errorFromTrailer(chunk);
|
|
473
|
+
}
|
|
336
474
|
return deserializeChunk(JSON.parse(chunk));
|
|
337
475
|
}
|
|
338
476
|
reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
|
|
@@ -396,7 +534,7 @@ function copyInitHeaders(init) {
|
|
|
396
534
|
for (const cookie of init.getSetCookie()) headers.append("Set-Cookie", cookie);
|
|
397
535
|
return headers;
|
|
398
536
|
}
|
|
399
|
-
const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, "Location"].map(header => header.toLowerCase()));
|
|
537
|
+
const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, REDIRECT_HEADER, "Location"].map(header => header.toLowerCase()));
|
|
400
538
|
function fillsStubGap(key, headers, response) {
|
|
401
539
|
if (key === "set-cookie" || STUB_GAP_FILL_EXCLUDED.has(key)) return false;
|
|
402
540
|
if (response.body === null && (key === "content-type" || key === "content-length")) return false;
|
|
@@ -412,24 +550,16 @@ function commitEventResponse(response, event = getRequestEvent()) {
|
|
|
412
550
|
if (fillsStubGap(key, response.headers, response)) hasGaps = true;
|
|
413
551
|
});
|
|
414
552
|
if (!cookies.length && !hasGaps) return response;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (fillsStubGap(key, headers, response)) headers.set(key, value);
|
|
426
|
-
});
|
|
427
|
-
return new Response(response.body, {
|
|
428
|
-
status: response.status,
|
|
429
|
-
statusText: response.statusText,
|
|
430
|
-
headers
|
|
431
|
-
});
|
|
432
|
-
}
|
|
553
|
+
const headers = copyInitHeaders(response.headers);
|
|
554
|
+
for (const cookie of cookies) headers.append("Set-Cookie", cookie);
|
|
555
|
+
stub.headers.forEach((value, key) => {
|
|
556
|
+
if (fillsStubGap(key, headers, response)) headers.set(key, value);
|
|
557
|
+
});
|
|
558
|
+
return new Response(response.body, {
|
|
559
|
+
status: response.status,
|
|
560
|
+
statusText: response.statusText,
|
|
561
|
+
headers
|
|
562
|
+
});
|
|
433
563
|
}
|
|
434
564
|
|
|
435
565
|
function encodeInputValue(value) {
|
|
@@ -461,11 +591,35 @@ function encodeFlashCookie(url, result, input, thrown) {
|
|
|
461
591
|
thrown: !!thrown,
|
|
462
592
|
input: input.map(encodeInputValue)
|
|
463
593
|
};
|
|
594
|
+
if (fitsCookie(payload)) return flashCookie(payload);
|
|
595
|
+
payload.truncated = true;
|
|
596
|
+
payload.input = [];
|
|
597
|
+
if (!fitsCookie(payload)) {
|
|
598
|
+
if (typeof payload.result === "string") {
|
|
599
|
+
let prefix = payload.result;
|
|
600
|
+
while (prefix.length > 0 && !fitsCookie({
|
|
601
|
+
...payload,
|
|
602
|
+
result: prefix
|
|
603
|
+
})) {
|
|
604
|
+
prefix = prefix.slice(0, prefix.length >> 1);
|
|
605
|
+
}
|
|
606
|
+
payload.result = prefix.length > 0 ? prefix : true;
|
|
607
|
+
} else {
|
|
608
|
+
payload.result = true;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return flashCookie(payload);
|
|
612
|
+
}
|
|
613
|
+
function flashCookie(payload) {
|
|
464
614
|
return serializeCookie(FLASH_COOKIE, JSON.stringify(payload), {
|
|
465
615
|
secure: true,
|
|
466
616
|
httpOnly: true
|
|
467
617
|
});
|
|
468
618
|
}
|
|
619
|
+
const COOKIE_PAIR_BUDGET = 4000;
|
|
620
|
+
function fitsCookie(payload) {
|
|
621
|
+
return FLASH_COOKIE.length + 1 + encodeURIComponent(JSON.stringify(payload)).length <= COOKIE_PAIR_BUDGET;
|
|
622
|
+
}
|
|
469
623
|
function decodeFlashCookie(cookieHeader) {
|
|
470
624
|
const match = parseCookieHeader(cookieHeader)[FLASH_COOKIE];
|
|
471
625
|
if (!match) return;
|
|
@@ -473,12 +627,14 @@ function decodeFlashCookie(cookieHeader) {
|
|
|
473
627
|
const payload = JSON.parse(match);
|
|
474
628
|
if (!payload || !payload.result) return;
|
|
475
629
|
const result = payload.error ? new Error(payload.result) : payload.result;
|
|
476
|
-
|
|
630
|
+
const submission = {
|
|
477
631
|
input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
|
|
478
632
|
url: payload.url,
|
|
479
633
|
result: payload.thrown ? undefined : result,
|
|
480
634
|
error: payload.thrown ? result : undefined
|
|
481
635
|
};
|
|
636
|
+
if (payload.truncated) submission.truncated = true;
|
|
637
|
+
return submission;
|
|
482
638
|
} catch (error) {
|
|
483
639
|
console.error(error);
|
|
484
640
|
}
|
|
@@ -493,7 +649,9 @@ const config = {
|
|
|
493
649
|
transformDirectResult: undefined,
|
|
494
650
|
handleNoJS: undefined,
|
|
495
651
|
endpoint: "/_server",
|
|
496
|
-
csrf: true
|
|
652
|
+
csrf: true,
|
|
653
|
+
bodySizeLimit: 1_048_576,
|
|
654
|
+
maxArguments: 1000
|
|
497
655
|
};
|
|
498
656
|
function configureServerFunctionsServer({
|
|
499
657
|
provideEvent,
|
|
@@ -505,7 +663,9 @@ function configureServerFunctionsServer({
|
|
|
505
663
|
handleNoJS,
|
|
506
664
|
endpoint,
|
|
507
665
|
csrf,
|
|
508
|
-
codec
|
|
666
|
+
codec,
|
|
667
|
+
bodySizeLimit,
|
|
668
|
+
maxArguments
|
|
509
669
|
} = {}) {
|
|
510
670
|
if (provideEvent !== undefined) config.provideEvent = provideEvent;
|
|
511
671
|
if (wrapInvocation !== undefined) config.wrapInvocation = wrapInvocation;
|
|
@@ -517,6 +677,16 @@ function configureServerFunctionsServer({
|
|
|
517
677
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
518
678
|
if (csrf !== undefined) config.csrf = csrf;
|
|
519
679
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
680
|
+
if (bodySizeLimit !== undefined) config.bodySizeLimit = bodySizeLimit;
|
|
681
|
+
if (maxArguments !== undefined) config.maxArguments = maxArguments;
|
|
682
|
+
}
|
|
683
|
+
const flightSources = new Map();
|
|
684
|
+
function registerFlightDataSource(source, hook) {
|
|
685
|
+
assertFlightSource(source);
|
|
686
|
+
flightSources.set(source, hook);
|
|
687
|
+
return () => {
|
|
688
|
+
if (flightSources.get(source) === hook) flightSources.delete(source);
|
|
689
|
+
};
|
|
520
690
|
}
|
|
521
691
|
function provideEvent(event, fn) {
|
|
522
692
|
if (config.provideEvent) return config.provideEvent(event, fn);
|
|
@@ -538,6 +708,7 @@ function provideRPC() {
|
|
|
538
708
|
}
|
|
539
709
|
function registerServerFunction(id, callback) {
|
|
540
710
|
provideRPC();
|
|
711
|
+
if (REGISTRATIONS.get(id) !== callback) METHODS.delete(id);
|
|
541
712
|
REGISTRATIONS.set(id, callback);
|
|
542
713
|
return callback;
|
|
543
714
|
}
|
|
@@ -549,6 +720,9 @@ function getServerFunction(id) {
|
|
|
549
720
|
throw new Error("invalid server function: " + id);
|
|
550
721
|
}
|
|
551
722
|
function registerServerReference(id, fn, name) {
|
|
723
|
+
if (typeof fn !== "function") {
|
|
724
|
+
throw new Error(`Server function${name ? ` \`${name}\`` : ""} (${id}) is not a function: a module-level ` + `"use server" export must evaluate to a server function (got ${fn === null ? "null" : typeof fn}). Move non-function exports out of the directive module.`);
|
|
725
|
+
}
|
|
552
726
|
registerServerFunction(id, fn);
|
|
553
727
|
return {
|
|
554
728
|
id,
|
|
@@ -556,6 +730,33 @@ function registerServerReference(id, fn, name) {
|
|
|
556
730
|
name
|
|
557
731
|
};
|
|
558
732
|
}
|
|
733
|
+
function inProcessInvoker(call) {
|
|
734
|
+
return (args, options) => {
|
|
735
|
+
const signal = options && options.signal;
|
|
736
|
+
if (!signal) return call(...args);
|
|
737
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
738
|
+
return new Promise((resolve, reject) => {
|
|
739
|
+
const onAbort = () => reject(signal.reason);
|
|
740
|
+
signal.addEventListener("abort", onAbort, {
|
|
741
|
+
once: true
|
|
742
|
+
});
|
|
743
|
+
let result;
|
|
744
|
+
try {
|
|
745
|
+
result = call(...args);
|
|
746
|
+
} catch (error) {
|
|
747
|
+
signal.removeEventListener("abort", onAbort);
|
|
748
|
+
return reject(error);
|
|
749
|
+
}
|
|
750
|
+
Promise.resolve(result).then(value => {
|
|
751
|
+
signal.removeEventListener("abort", onAbort);
|
|
752
|
+
resolve(value);
|
|
753
|
+
}, error => {
|
|
754
|
+
signal.removeEventListener("abort", onAbort);
|
|
755
|
+
reject(error);
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
};
|
|
759
|
+
}
|
|
559
760
|
function createServerReference({
|
|
560
761
|
id,
|
|
561
762
|
fn,
|
|
@@ -566,20 +767,25 @@ function createServerReference({
|
|
|
566
767
|
const metadata = name === undefined ? {} : {
|
|
567
768
|
name
|
|
568
769
|
};
|
|
569
|
-
|
|
770
|
+
const invokeChannel = inProcessInvoker((...args) => proxy(...args));
|
|
771
|
+
const proxy = new Proxy(fn, {
|
|
570
772
|
get(target, prop) {
|
|
571
773
|
if (prop === "id") return id;
|
|
572
774
|
if (prop === "url") {
|
|
573
|
-
return
|
|
775
|
+
return serverFunctionAddress(config.endpoint, id);
|
|
574
776
|
}
|
|
575
777
|
if (prop === SERVER_FUNCTION_METADATA) return metadata;
|
|
778
|
+
if (prop === SERVER_FUNCTION_INVOKE) return invokeChannel;
|
|
576
779
|
return target[prop];
|
|
577
780
|
},
|
|
578
781
|
apply(target, thisArg, args) {
|
|
579
782
|
const ogEvt = getRequestEvent();
|
|
580
783
|
if (!ogEvt) throw new Error("Cannot call server function outside of a request");
|
|
581
784
|
const evt = {
|
|
582
|
-
...ogEvt
|
|
785
|
+
...ogEvt,
|
|
786
|
+
locals: {
|
|
787
|
+
...ogEvt.locals
|
|
788
|
+
}
|
|
583
789
|
};
|
|
584
790
|
INVOCATIONS.set(evt, {
|
|
585
791
|
id
|
|
@@ -609,6 +815,7 @@ function createServerReference({
|
|
|
609
815
|
}) : result;
|
|
610
816
|
}
|
|
611
817
|
});
|
|
818
|
+
return proxy;
|
|
612
819
|
}
|
|
613
820
|
function GET(fn) {
|
|
614
821
|
if (!isServerFunction(fn) || typeof fn.id !== "string") {
|
|
@@ -635,6 +842,7 @@ function live(fn) {
|
|
|
635
842
|
return result;
|
|
636
843
|
};
|
|
637
844
|
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
845
|
+
wrapped[SERVER_FUNCTION_INVOKE] = inProcessInvoker(wrapped);
|
|
638
846
|
wrapped.id = fn.id;
|
|
639
847
|
Object.defineProperty(wrapped, "url", {
|
|
640
848
|
get: () => fn.url,
|
|
@@ -648,40 +856,107 @@ function getServerFunctionInvocation() {
|
|
|
648
856
|
function getEventServerFunctionInvocation(event) {
|
|
649
857
|
return event && INVOCATIONS.get(event);
|
|
650
858
|
}
|
|
651
|
-
function
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
859
|
+
function resolveAddress(url) {
|
|
860
|
+
return parseServerFunctionAddress(url.pathname, config.endpoint);
|
|
861
|
+
}
|
|
862
|
+
const DECODE_DEPTH_LIMIT = 64;
|
|
863
|
+
function assertDecodeDepth(value) {
|
|
864
|
+
let level = [value];
|
|
865
|
+
for (let depth = 0; level.length > 0; depth++) {
|
|
866
|
+
if (depth > DECODE_DEPTH_LIMIT) {
|
|
867
|
+
throw new TypeError("Server function arguments exceed the decode depth limit");
|
|
868
|
+
}
|
|
869
|
+
const next = [];
|
|
870
|
+
for (const node of level) {
|
|
871
|
+
if (node === null || typeof node !== "object") continue;
|
|
872
|
+
if (Array.isArray(node)) {
|
|
873
|
+
for (const child of node) next.push(child);
|
|
874
|
+
} else {
|
|
875
|
+
for (const key of Object.keys(node)) next.push(node[key]);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
level = next;
|
|
655
879
|
}
|
|
656
|
-
return url.searchParams.get("id");
|
|
657
880
|
}
|
|
658
|
-
async function
|
|
881
|
+
async function bufferBodyWithin(request, limit) {
|
|
882
|
+
const reader = request.clone().body.getReader();
|
|
883
|
+
const chunks = [];
|
|
884
|
+
let total = 0;
|
|
885
|
+
for (;;) {
|
|
886
|
+
const {
|
|
887
|
+
done,
|
|
888
|
+
value
|
|
889
|
+
} = await reader.read();
|
|
890
|
+
if (done) break;
|
|
891
|
+
total += value.byteLength;
|
|
892
|
+
if (total > limit) {
|
|
893
|
+
reader.cancel().catch(() => {});
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
chunks.push(value);
|
|
897
|
+
}
|
|
898
|
+
const body = new Uint8Array(total);
|
|
899
|
+
let offset = 0;
|
|
900
|
+
for (const chunk of chunks) {
|
|
901
|
+
body.set(chunk, offset);
|
|
902
|
+
offset += chunk.byteLength;
|
|
903
|
+
}
|
|
904
|
+
return new Request(request, {
|
|
905
|
+
body
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
async function parseArguments(request, url, scripted, codec) {
|
|
659
909
|
const parsed = [];
|
|
660
910
|
const bodyFormat = request.method === "POST" ? request.headers.get(BODY_FORMAT_HEADER) : null;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
911
|
+
const args = url.searchParams.get("args");
|
|
912
|
+
if (args && (!scripted || request.method === "GET" || bodyFormat !== BodyFormat.Serialized)) {
|
|
913
|
+
let result;
|
|
914
|
+
if (args.startsWith(";0x")) {
|
|
915
|
+
result = await deserializeString(args, codec);
|
|
916
|
+
} else {
|
|
917
|
+
result = JSON.parse(args);
|
|
918
|
+
assertDecodeDepth(result);
|
|
919
|
+
}
|
|
920
|
+
if (!Array.isArray(result)) {
|
|
921
|
+
throw new TypeError("Server function arguments must encode an array");
|
|
668
922
|
}
|
|
923
|
+
for (const arg of result) {
|
|
924
|
+
parsed.push(arg);
|
|
925
|
+
}
|
|
926
|
+
} else if (!args && url.search && (request.method === "GET" || request.method === "HEAD")) {
|
|
927
|
+
parsed.push(url.searchParams);
|
|
669
928
|
}
|
|
670
929
|
if (request.method === "POST" && request.body !== null) {
|
|
671
930
|
const decoded = await extractBody(request.clone(), codec);
|
|
672
931
|
if (bodyFormat === BodyFormat.Serialized || bodyFormat === BodyFormat.Json) {
|
|
932
|
+
if (bodyFormat === BodyFormat.Json) assertDecodeDepth(decoded);
|
|
933
|
+
if (!Array.isArray(decoded)) {
|
|
934
|
+
throw new TypeError("Server function arguments must encode an array");
|
|
935
|
+
}
|
|
673
936
|
return decoded;
|
|
674
937
|
}
|
|
938
|
+
if (decoded === undefined) {
|
|
939
|
+
throw new TypeError("Server function body carries no usable encoding");
|
|
940
|
+
}
|
|
675
941
|
parsed.push(decoded);
|
|
676
942
|
}
|
|
677
943
|
return parsed;
|
|
678
944
|
}
|
|
679
|
-
async function foldFlightData(
|
|
945
|
+
async function foldFlightData(hooks, event, headers, outcome, context = {}) {
|
|
680
946
|
if (outcome.value instanceof Response && outcome.value.body) return outcome.value;
|
|
681
947
|
digestOutcome(event, outcome);
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
948
|
+
const folded = [];
|
|
949
|
+
for (const [source, hook] of hooks) {
|
|
950
|
+
try {
|
|
951
|
+
const slice = await hook(event, outcome);
|
|
952
|
+
if (slice !== undefined) folded.push([source, slice]);
|
|
953
|
+
} catch (error) {
|
|
954
|
+
console.error(`Error collecting flight data for source "${source}"`, error);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (folded.length === 0) return outcome.value;
|
|
958
|
+
const data = Object.fromEntries(folded);
|
|
959
|
+
headers.set(SINGLE_FLIGHT_HEADER, folded.map(([source]) => source).join(","));
|
|
685
960
|
if (context.transformFlightResult) {
|
|
686
961
|
const transformed = await context.transformFlightResult(event, {
|
|
687
962
|
value: outcome.value,
|
|
@@ -770,6 +1045,52 @@ function mergeResponseHeaders(target, source) {
|
|
|
770
1045
|
}
|
|
771
1046
|
}
|
|
772
1047
|
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
1048
|
+
function maskRedirect(headers, response, requestUrl) {
|
|
1049
|
+
const target = response.headers && response.headers.get("Location");
|
|
1050
|
+
if (target) {
|
|
1051
|
+
headers.set(REDIRECT_HEADER, `${response.status} ${new URL(target, requestUrl)}`);
|
|
1052
|
+
}
|
|
1053
|
+
headers.delete("Location");
|
|
1054
|
+
}
|
|
1055
|
+
const BOUNDED_COMPOSED_HEADERS = [REDIRECT_HEADER, "Location", REVALIDATE_HEADER];
|
|
1056
|
+
function refusedTargetScheme(target) {
|
|
1057
|
+
const match = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.exec(target);
|
|
1058
|
+
return match !== null && !/^https?:$/i.test(match[0]);
|
|
1059
|
+
}
|
|
1060
|
+
function enforceComposedHeaderInvariants(response) {
|
|
1061
|
+
for (const name of BOUNDED_COMPOSED_HEADERS) {
|
|
1062
|
+
const value = response.headers.get(name);
|
|
1063
|
+
if (value === null) continue;
|
|
1064
|
+
if (value.length > RESPONSE_HEADER_VALUE_LIMIT) {
|
|
1065
|
+
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);
|
|
1066
|
+
}
|
|
1067
|
+
if (name === REVALIDATE_HEADER) continue;
|
|
1068
|
+
const target = name === REDIRECT_HEADER ? value.slice(value.indexOf(" ") + 1) : value;
|
|
1069
|
+
if (refusedTargetScheme(target)) {
|
|
1070
|
+
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);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return response;
|
|
1074
|
+
}
|
|
1075
|
+
function refuseComposedHeader(response, name, headerMessage, body) {
|
|
1076
|
+
if (response.body) {
|
|
1077
|
+
try {
|
|
1078
|
+
const cancelled = response.body.cancel();
|
|
1079
|
+
if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
|
|
1080
|
+
} catch {}
|
|
1081
|
+
}
|
|
1082
|
+
const headers = new Headers();
|
|
1083
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(DEV ? headerMessage : GENERIC_SERVER_ERROR_MESSAGE));
|
|
1084
|
+
return new Response(body, {
|
|
1085
|
+
status: 500,
|
|
1086
|
+
headers
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
function warnScripted304(functionId) {
|
|
1090
|
+
if (DEV) {
|
|
1091
|
+
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.`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
773
1094
|
function createNoJSHandler({
|
|
774
1095
|
base = ""
|
|
775
1096
|
} = {}) {
|
|
@@ -813,44 +1134,279 @@ function isFormPost(request) {
|
|
|
813
1134
|
const type = request.headers.get("content-type") || "";
|
|
814
1135
|
return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
|
|
815
1136
|
}
|
|
816
|
-
function
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
let onAbort = null;
|
|
821
|
-
const teardown = () => {
|
|
822
|
-
if (closed) return;
|
|
823
|
-
closed = true;
|
|
824
|
-
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
825
|
-
if (cancelSerialize) cancelSerialize();
|
|
826
|
-
if (closeIterator) closeIterator();
|
|
1137
|
+
function guardFailures(value, state) {
|
|
1138
|
+
if (!state) state = {
|
|
1139
|
+
seen: new WeakMap(),
|
|
1140
|
+
cyclic: new WeakSet()
|
|
827
1141
|
};
|
|
828
|
-
|
|
1142
|
+
const entered = enterGuard(value, state);
|
|
1143
|
+
if (!(entered instanceof Frame)) return entered;
|
|
1144
|
+
const stack = [entered];
|
|
1145
|
+
let delivered = NOTHING;
|
|
1146
|
+
for (;;) {
|
|
1147
|
+
const top = stack[stack.length - 1];
|
|
1148
|
+
const items = top.items;
|
|
1149
|
+
let pushed = null;
|
|
1150
|
+
while (top.i < items.length) {
|
|
1151
|
+
const i = top.i;
|
|
1152
|
+
let original;
|
|
1153
|
+
if (top.kind === OBJECT) {
|
|
1154
|
+
const descriptor = top.descriptors[items[i]];
|
|
1155
|
+
if ("value" in descriptor) {
|
|
1156
|
+
original = descriptor.value;
|
|
1157
|
+
} else if (typeof descriptor.get === "function") {
|
|
1158
|
+
if (top.accessorRead !== i) {
|
|
1159
|
+
try {
|
|
1160
|
+
top.accessorValue = descriptor.get.call(top.value);
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
throw sanitizeServerError(error);
|
|
1163
|
+
}
|
|
1164
|
+
top.accessorRead = i;
|
|
1165
|
+
}
|
|
1166
|
+
original = top.accessorValue;
|
|
1167
|
+
} else {
|
|
1168
|
+
top.i++;
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
} else {
|
|
1172
|
+
original = items[i];
|
|
1173
|
+
}
|
|
1174
|
+
let guarded;
|
|
1175
|
+
if (delivered !== NOTHING) {
|
|
1176
|
+
guarded = delivered;
|
|
1177
|
+
delivered = NOTHING;
|
|
1178
|
+
} else {
|
|
1179
|
+
guarded = enterGuard(original, state);
|
|
1180
|
+
if (guarded instanceof Frame) {
|
|
1181
|
+
pushed = guarded;
|
|
1182
|
+
break;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
if (top.kind === ARRAY) {
|
|
1186
|
+
if (guarded !== original) {
|
|
1187
|
+
top.next[i] = guarded;
|
|
1188
|
+
top.changed = true;
|
|
1189
|
+
}
|
|
1190
|
+
} else if (top.kind === MAP) {
|
|
1191
|
+
if ((i & 1) === 0) top.pendingKey = guarded;else top.next.set(top.pendingKey, guarded);
|
|
1192
|
+
if (guarded !== original) top.changed = true;
|
|
1193
|
+
} else if (top.kind === SET) {
|
|
1194
|
+
top.next.add(guarded);
|
|
1195
|
+
if (guarded !== original) top.changed = true;
|
|
1196
|
+
} else if (guarded !== original || top.accessorRead === i) {
|
|
1197
|
+
Object.defineProperty(top.next, items[i], top.accessorRead === i ? {
|
|
1198
|
+
enumerable: true,
|
|
1199
|
+
configurable: true,
|
|
1200
|
+
writable: true,
|
|
1201
|
+
value: guarded
|
|
1202
|
+
} : {
|
|
1203
|
+
...top.descriptors[items[i]],
|
|
1204
|
+
value: guarded
|
|
1205
|
+
});
|
|
1206
|
+
top.changed = true;
|
|
1207
|
+
}
|
|
1208
|
+
top.i++;
|
|
1209
|
+
}
|
|
1210
|
+
if (pushed !== null) {
|
|
1211
|
+
stack.push(pushed);
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
stack.pop();
|
|
1215
|
+
const out = keepGuarded(top.value, top.next, top.changed, state);
|
|
1216
|
+
if (stack.length === 0) return out;
|
|
1217
|
+
delivered = out;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
const NOTHING = Symbol();
|
|
1221
|
+
const ARRAY = 0;
|
|
1222
|
+
const MAP = 1;
|
|
1223
|
+
const SET = 2;
|
|
1224
|
+
const OBJECT = 3;
|
|
1225
|
+
class Frame {
|
|
1226
|
+
constructor(kind, value, next, items, descriptors) {
|
|
1227
|
+
this.kind = kind;
|
|
1228
|
+
this.value = value;
|
|
1229
|
+
this.next = next;
|
|
1230
|
+
this.items = items;
|
|
1231
|
+
this.descriptors = descriptors;
|
|
1232
|
+
this.i = 0;
|
|
1233
|
+
this.changed = false;
|
|
1234
|
+
this.accessorRead = -1;
|
|
1235
|
+
this.accessorValue = undefined;
|
|
1236
|
+
this.pendingKey = undefined;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
function enterGuard(value, state) {
|
|
1240
|
+
if (value === null || typeof value !== "object") return value;
|
|
1241
|
+
if (state.seen.has(value)) {
|
|
1242
|
+
state.cyclic.add(value);
|
|
1243
|
+
return state.seen.get(value);
|
|
1244
|
+
}
|
|
1245
|
+
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
1246
|
+
let reader;
|
|
1247
|
+
const gate = state.gate;
|
|
1248
|
+
let finished = false;
|
|
1249
|
+
const close = () => {
|
|
1250
|
+
if (finished) return;
|
|
1251
|
+
finished = true;
|
|
1252
|
+
try {
|
|
1253
|
+
const cancelled = reader ? reader.cancel() : value.cancel();
|
|
1254
|
+
if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
|
|
1255
|
+
} catch {}
|
|
1256
|
+
};
|
|
1257
|
+
const guardedStream = new ReadableStream({
|
|
1258
|
+
async pull(controller) {
|
|
1259
|
+
try {
|
|
1260
|
+
if (gate && !finished && !gate.wantsMore()) await gate.awaitDemand();
|
|
1261
|
+
if (finished) {
|
|
1262
|
+
controller.close();
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
if (!reader) reader = value.getReader();
|
|
1266
|
+
const {
|
|
1267
|
+
done,
|
|
1268
|
+
value: chunk
|
|
1269
|
+
} = await reader.read();
|
|
1270
|
+
done ? controller.close() : controller.enqueue(guardFailures(chunk, state));
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
controller.error(sanitizeServerError(error));
|
|
1273
|
+
}
|
|
1274
|
+
},
|
|
1275
|
+
cancel(reason) {
|
|
1276
|
+
finished = true;
|
|
1277
|
+
return reader ? reader.cancel(reason) : value.cancel(reason);
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
if (gate) gate.onOpen(close);
|
|
1281
|
+
state.seen.set(value, guardedStream);
|
|
1282
|
+
return guardedStream;
|
|
1283
|
+
}
|
|
1284
|
+
if (typeof value.then === "function") {
|
|
1285
|
+
const guardedPromise = Promise.resolve(value).then(resolved => guardFailures(resolved, state), error => {
|
|
1286
|
+
throw sanitizeServerError(error);
|
|
1287
|
+
});
|
|
1288
|
+
state.seen.set(value, guardedPromise);
|
|
1289
|
+
return guardedPromise;
|
|
1290
|
+
}
|
|
1291
|
+
if (typeof value[Symbol.asyncIterator] === "function") {
|
|
829
1292
|
const source = value;
|
|
830
|
-
|
|
1293
|
+
const gate = state.gate;
|
|
1294
|
+
const guardedIterable = {
|
|
831
1295
|
[Symbol.asyncIterator]() {
|
|
832
|
-
const
|
|
1296
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
833
1297
|
let finished = false;
|
|
834
|
-
|
|
1298
|
+
const close = () => {
|
|
835
1299
|
if (finished) return;
|
|
836
1300
|
finished = true;
|
|
837
1301
|
try {
|
|
838
|
-
const returned =
|
|
1302
|
+
const returned = iterator.return && iterator.return();
|
|
839
1303
|
if (returned && typeof returned.then === "function") returned.then(undefined, () => {});
|
|
840
1304
|
} catch {}
|
|
841
1305
|
};
|
|
842
|
-
if (
|
|
1306
|
+
if (gate) gate.onOpen(close);
|
|
1307
|
+
const step = () => finished ? Promise.resolve({
|
|
1308
|
+
done: true,
|
|
1309
|
+
value: undefined
|
|
1310
|
+
}) : iterator.next().then(step => {
|
|
1311
|
+
if (step.done) {
|
|
1312
|
+
finished = true;
|
|
1313
|
+
return step;
|
|
1314
|
+
}
|
|
1315
|
+
return {
|
|
1316
|
+
done: false,
|
|
1317
|
+
value: guardFailures(step.value, state)
|
|
1318
|
+
};
|
|
1319
|
+
}, error => {
|
|
1320
|
+
throw sanitizeServerError(error);
|
|
1321
|
+
});
|
|
843
1322
|
return {
|
|
844
|
-
next: () => finished ?
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1323
|
+
next: () => finished || !gate || gate.wantsMore() ? step() : gate.awaitDemand().then(step),
|
|
1324
|
+
return: () => {
|
|
1325
|
+
close();
|
|
1326
|
+
return Promise.resolve({
|
|
1327
|
+
done: true,
|
|
1328
|
+
value: undefined
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
848
1331
|
};
|
|
849
1332
|
}
|
|
850
1333
|
};
|
|
1334
|
+
state.seen.set(value, guardedIterable);
|
|
1335
|
+
return guardedIterable;
|
|
1336
|
+
}
|
|
1337
|
+
if (Array.isArray(value)) {
|
|
1338
|
+
const next = value.slice();
|
|
1339
|
+
state.seen.set(value, next);
|
|
1340
|
+
return new Frame(ARRAY, value, next, value, null);
|
|
851
1341
|
}
|
|
1342
|
+
if (value instanceof Map) {
|
|
1343
|
+
const next = new Map();
|
|
1344
|
+
state.seen.set(value, next);
|
|
1345
|
+
const items = [];
|
|
1346
|
+
for (const entry of value) items.push(entry[0], entry[1]);
|
|
1347
|
+
return new Frame(MAP, value, next, items, null);
|
|
1348
|
+
}
|
|
1349
|
+
if (value instanceof Set) {
|
|
1350
|
+
const next = new Set();
|
|
1351
|
+
state.seen.set(value, next);
|
|
1352
|
+
return new Frame(SET, value, next, [...value], null);
|
|
1353
|
+
}
|
|
1354
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1355
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
1356
|
+
state.seen.set(value, value);
|
|
1357
|
+
return value;
|
|
1358
|
+
}
|
|
1359
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1360
|
+
const next = Object.create(prototype, descriptors);
|
|
1361
|
+
state.seen.set(value, next);
|
|
1362
|
+
return new Frame(OBJECT, value, next, Object.keys(descriptors), descriptors);
|
|
1363
|
+
}
|
|
1364
|
+
function keepGuarded(value, next, changed, state) {
|
|
1365
|
+
if (changed || state.cyclic.has(value)) return next;
|
|
1366
|
+
state.seen.set(value, value);
|
|
1367
|
+
return value;
|
|
1368
|
+
}
|
|
1369
|
+
function serializeResponseStream(value, codecOptions, signal) {
|
|
1370
|
+
let closed = false;
|
|
1371
|
+
let streamController = null;
|
|
1372
|
+
let demandWaiters = null;
|
|
1373
|
+
const wantsMore = () => streamController !== null && streamController.desiredSize > 0;
|
|
1374
|
+
const awaitDemand = () => new Promise(resolve => (demandWaiters ??= []).push(resolve));
|
|
1375
|
+
const supplyDemand = () => {
|
|
1376
|
+
const resolvers = demandWaiters;
|
|
1377
|
+
demandWaiters = null;
|
|
1378
|
+
if (resolvers) for (const resolve of resolvers) resolve();
|
|
1379
|
+
};
|
|
1380
|
+
const sourceClosers = new Set();
|
|
1381
|
+
const gate = {
|
|
1382
|
+
wantsMore,
|
|
1383
|
+
awaitDemand,
|
|
1384
|
+
onOpen(close) {
|
|
1385
|
+
if (closed) close();else sourceClosers.add(close);
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
value = guardFailures(value, {
|
|
1389
|
+
seen: new WeakMap(),
|
|
1390
|
+
cyclic: new WeakSet(),
|
|
1391
|
+
gate
|
|
1392
|
+
});
|
|
1393
|
+
let cancelSerialize = null;
|
|
1394
|
+
let onAbort = null;
|
|
1395
|
+
const finishSource = () => {
|
|
1396
|
+
for (const close of sourceClosers) close();
|
|
1397
|
+
sourceClosers.clear();
|
|
1398
|
+
supplyDemand();
|
|
1399
|
+
};
|
|
1400
|
+
const teardown = () => {
|
|
1401
|
+
if (closed) return;
|
|
1402
|
+
closed = true;
|
|
1403
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1404
|
+
if (cancelSerialize) cancelSerialize();
|
|
1405
|
+
finishSource();
|
|
1406
|
+
};
|
|
852
1407
|
return new ReadableStream({
|
|
853
1408
|
async start(controller) {
|
|
1409
|
+
streamController = controller;
|
|
854
1410
|
if (signal) {
|
|
855
1411
|
if (signal.aborted) {
|
|
856
1412
|
teardown();
|
|
@@ -886,16 +1442,29 @@ function serializeResponseStream(value, codecOptions, signal) {
|
|
|
886
1442
|
if (closed) return;
|
|
887
1443
|
closed = true;
|
|
888
1444
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1445
|
+
finishSource();
|
|
889
1446
|
controller.close();
|
|
890
1447
|
},
|
|
891
1448
|
onError(error) {
|
|
892
1449
|
if (closed) return;
|
|
893
1450
|
closed = true;
|
|
894
1451
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
895
|
-
|
|
1452
|
+
finishSource();
|
|
1453
|
+
try {
|
|
1454
|
+
const delivered = sanitizeServerError(DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error);
|
|
1455
|
+
controller.enqueue(createChunk(encodeErrorTrailer(delivered)));
|
|
1456
|
+
controller.close();
|
|
1457
|
+
} catch {
|
|
1458
|
+
try {
|
|
1459
|
+
controller.error(error);
|
|
1460
|
+
} catch {}
|
|
1461
|
+
}
|
|
896
1462
|
}
|
|
897
1463
|
});
|
|
898
1464
|
},
|
|
1465
|
+
pull() {
|
|
1466
|
+
supplyDemand();
|
|
1467
|
+
},
|
|
899
1468
|
cancel() {
|
|
900
1469
|
teardown();
|
|
901
1470
|
}
|
|
@@ -909,6 +1478,18 @@ function serializedResponse(value, headers, codec, signal) {
|
|
|
909
1478
|
});
|
|
910
1479
|
}
|
|
911
1480
|
function encodeResult(value, headers, status, codec, signal) {
|
|
1481
|
+
if (NULL_BODY_STATUSES.has(status)) {
|
|
1482
|
+
if (value === undefined || value === null) {
|
|
1483
|
+
headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
|
|
1484
|
+
return new Response(null, {
|
|
1485
|
+
status,
|
|
1486
|
+
headers
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
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.`);
|
|
1490
|
+
headers.set(ERROR_HEADER, encodeErrorHeaderValue(error.message));
|
|
1491
|
+
return encodeResult(error, headers, 500, codec, signal);
|
|
1492
|
+
}
|
|
912
1493
|
const direct = getHeadersAndBody(value);
|
|
913
1494
|
if (direct) {
|
|
914
1495
|
for (const [key, val] of Object.entries(direct.headers || {})) {
|
|
@@ -920,6 +1501,7 @@ function encodeResult(value, headers, status, codec, signal) {
|
|
|
920
1501
|
});
|
|
921
1502
|
}
|
|
922
1503
|
if (value === undefined) {
|
|
1504
|
+
headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
|
|
923
1505
|
return new Response(null, {
|
|
924
1506
|
status,
|
|
925
1507
|
headers
|
|
@@ -936,11 +1518,25 @@ function encodeResult(value, headers, status, codec, signal) {
|
|
|
936
1518
|
}
|
|
937
1519
|
} catch {
|
|
938
1520
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
status,
|
|
942
|
-
|
|
943
|
-
|
|
1521
|
+
try {
|
|
1522
|
+
const response = serializedResponse(value, headers, codec, signal);
|
|
1523
|
+
return status === 200 ? response : new Response(response.body, {
|
|
1524
|
+
status,
|
|
1525
|
+
headers
|
|
1526
|
+
});
|
|
1527
|
+
} catch (error) {
|
|
1528
|
+
throw DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
const ERROR_HEADER_VALUE_LIMIT = 1024;
|
|
1532
|
+
function boundedErrorHeaderValue(message) {
|
|
1533
|
+
let label = message.length > 256 ? message.slice(0, 256) : message;
|
|
1534
|
+
let encoded = encodeErrorHeaderValue(label);
|
|
1535
|
+
while (encoded.length > ERROR_HEADER_VALUE_LIMIT && label.length > 1) {
|
|
1536
|
+
label = label.slice(0, Math.ceil(label.length / 2));
|
|
1537
|
+
encoded = encodeErrorHeaderValue(label);
|
|
1538
|
+
}
|
|
1539
|
+
return encoded;
|
|
944
1540
|
}
|
|
945
1541
|
const GENERIC_SERVER_ERROR_MESSAGE = "Internal Server Error";
|
|
946
1542
|
let DEV = false === true;
|
|
@@ -955,9 +1551,21 @@ function sanitizeServerError(value) {
|
|
|
955
1551
|
function observeServerFunctionCalls() {
|
|
956
1552
|
return () => {};
|
|
957
1553
|
}
|
|
1554
|
+
function serverFunctionUrl(id, boundArgs) {
|
|
1555
|
+
const address = serverFunctionAddress(config.endpoint, id);
|
|
1556
|
+
if (!boundArgs || !boundArgs.length) return address;
|
|
1557
|
+
if (!isJSONSafe(boundArgs)) {
|
|
1558
|
+
throw new Error("Bound arguments in an action url must be JSON-safe: the server reads them the way it " + "reads a form post's, and that convention has no codec. Pass the value through the " + "function's body, or call the reference instead of rendering a url for it.");
|
|
1559
|
+
}
|
|
1560
|
+
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
1561
|
+
}
|
|
1562
|
+
function parseServerFunctionUrl(url) {
|
|
1563
|
+
const parsed = parseServerFunctionAddress(new URL(url, "http://localhost").pathname, config.endpoint);
|
|
1564
|
+
return parsed && parsed.id;
|
|
1565
|
+
}
|
|
958
1566
|
async function matchesOrigin(origin, request, matcher) {
|
|
959
1567
|
if (matcher === undefined) return origin === new URL(request.url).origin;
|
|
960
|
-
if (typeof matcher === "function") return
|
|
1568
|
+
if (typeof matcher === "function") return (await matcher(origin, request)) === true;
|
|
961
1569
|
return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
|
|
962
1570
|
}
|
|
963
1571
|
async function allowsServerFunctionRequest(request, options) {
|
|
@@ -1012,49 +1620,121 @@ function forbiddenResponse() {
|
|
|
1012
1620
|
async function handleServerFunctionRequest(request, options = {}) {
|
|
1013
1621
|
const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
|
|
1014
1622
|
const url = new URL(request.url);
|
|
1623
|
+
const method = request.method;
|
|
1624
|
+
const address = resolveAddress(url);
|
|
1625
|
+
const functionId = address && address.id;
|
|
1626
|
+
const declaredRead = (method === "GET" || method === "HEAD") && functionId !== null && METHODS.get(functionId) === "GET";
|
|
1015
1627
|
const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
|
|
1016
|
-
const protectsRequest = csrf !== false;
|
|
1628
|
+
const protectsRequest = csrf !== false && (!declaredRead || typeof csrf === "object" && csrf.protectDeclaredReads === true);
|
|
1629
|
+
let serverFunction;
|
|
1630
|
+
if (functionId) {
|
|
1631
|
+
try {
|
|
1632
|
+
serverFunction = getServerFunction(functionId);
|
|
1633
|
+
} catch {
|
|
1634
|
+
return finalizeTransportResponse(new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
1635
|
+
status: 404,
|
|
1636
|
+
headers: {
|
|
1637
|
+
[UNKNOWN_HEADER]: "true"
|
|
1638
|
+
}
|
|
1639
|
+
}), method);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1017
1642
|
if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
|
|
1018
|
-
return forbiddenResponse();
|
|
1643
|
+
return finalizeTransportResponse(forbiddenResponse(), method);
|
|
1019
1644
|
}
|
|
1020
1645
|
const instance = request.headers.get(INSTANCE_HEADER);
|
|
1021
|
-
const functionId = resolveFunctionId(request, url);
|
|
1022
1646
|
if (!functionId) {
|
|
1023
1647
|
const response = new Response(DEV ? "Server function not found" : null, {
|
|
1024
1648
|
status: 404
|
|
1025
1649
|
});
|
|
1026
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1650
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1027
1651
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
serverFunction = getServerFunction(functionId);
|
|
1031
|
-
} catch {
|
|
1032
|
-
const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
1033
|
-
status: 404
|
|
1034
|
-
});
|
|
1035
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1036
|
-
}
|
|
1037
|
-
if (request.method === "GET" && METHODS.get(functionId) !== "GET") {
|
|
1652
|
+
const scripted = address.data;
|
|
1653
|
+
if (method !== "POST" && !declaredRead) {
|
|
1038
1654
|
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
1039
1655
|
status: 405,
|
|
1040
1656
|
headers: {
|
|
1041
|
-
Allow: "POST"
|
|
1657
|
+
Allow: METHODS.get(functionId) === "GET" ? "POST, GET, HEAD" : "POST"
|
|
1042
1658
|
}
|
|
1043
1659
|
});
|
|
1044
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1660
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1045
1661
|
}
|
|
1046
|
-
const
|
|
1662
|
+
const bodySizeLimit = options.bodySizeLimit !== undefined ? options.bodySizeLimit : config.bodySizeLimit;
|
|
1663
|
+
const argsEncoding = url.searchParams.get("args");
|
|
1664
|
+
if (argsEncoding !== null && argsEncoding.length > bodySizeLimit) {
|
|
1665
|
+
const response = new Response(DEV ? "Server function arguments exceed the configured bodySizeLimit" : null, {
|
|
1666
|
+
status: 413
|
|
1667
|
+
});
|
|
1668
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1669
|
+
}
|
|
1670
|
+
if (method === "POST" && request.body !== null && bodySizeLimit !== Infinity) {
|
|
1671
|
+
const raw = request.headers.get("content-length");
|
|
1672
|
+
const declared = raw !== null && /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
1673
|
+
if (declared > bodySizeLimit) {
|
|
1674
|
+
const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
|
|
1675
|
+
status: 413
|
|
1676
|
+
});
|
|
1677
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1678
|
+
}
|
|
1679
|
+
if (!(declared > 0)) {
|
|
1680
|
+
const bounded = await bufferBodyWithin(request, bodySizeLimit);
|
|
1681
|
+
if (bounded === null) {
|
|
1682
|
+
const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
|
|
1683
|
+
status: 413
|
|
1684
|
+
});
|
|
1685
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1686
|
+
}
|
|
1687
|
+
request = bounded;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
let event = options.createEvent ? options.createEvent(request) : {
|
|
1047
1691
|
request,
|
|
1048
1692
|
locals: {}
|
|
1049
1693
|
};
|
|
1694
|
+
if (typeof event?.then === "function") event = await event;
|
|
1695
|
+
const refuseCommitted = raw => {
|
|
1696
|
+
const response = commitEventResponse(raw, event);
|
|
1697
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1698
|
+
};
|
|
1050
1699
|
const provide = options.provideEvent || provideEvent;
|
|
1051
1700
|
const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
|
|
1052
1701
|
const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
|
|
1053
1702
|
const wrapInvocation = options.wrapInvocation !== undefined ? options.wrapInvocation : config.wrapInvocation;
|
|
1054
1703
|
const transformFlightResult = options.transformFlightResult !== undefined ? options.transformFlightResult : config.transformFlightResult;
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1704
|
+
let handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS;
|
|
1705
|
+
if (handleNoJS === undefined && !scripted && isFormPost(request)) {
|
|
1706
|
+
const fetchMode = request.headers.get("Sec-Fetch-Mode");
|
|
1707
|
+
if (fetchMode === null || fetchMode === "navigate") {
|
|
1708
|
+
handleNoJS = defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler());
|
|
1709
|
+
} else {
|
|
1710
|
+
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, {
|
|
1711
|
+
status: 400
|
|
1712
|
+
});
|
|
1713
|
+
return refuseCommitted(response);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
const flightHeader = scripted && method === "POST" ? request.headers.get(SINGLE_FLIGHT_HEADER) : null;
|
|
1717
|
+
const flightHooks = flightHeader ? flightHeader.split(",").flatMap(source => {
|
|
1718
|
+
const hook = source === "true" ? flightHook : flightSources.get(source);
|
|
1719
|
+
return hook ? [[source, hook]] : [];
|
|
1720
|
+
}) : [];
|
|
1721
|
+
const collectsFlight = flightHooks.length > 0;
|
|
1722
|
+
let parsed;
|
|
1723
|
+
try {
|
|
1724
|
+
parsed = await parseArguments(request, url, scripted, codec);
|
|
1725
|
+
} catch {
|
|
1726
|
+
const response = new Response(DEV ? "Malformed server function arguments" : null, {
|
|
1727
|
+
status: 400
|
|
1728
|
+
});
|
|
1729
|
+
return refuseCommitted(response);
|
|
1730
|
+
}
|
|
1731
|
+
const maxArguments = options.maxArguments !== undefined ? options.maxArguments : config.maxArguments;
|
|
1732
|
+
if (parsed.length > maxArguments) {
|
|
1733
|
+
const response = new Response(DEV ? "Server function call exceeds the configured maxArguments" : null, {
|
|
1734
|
+
status: 400
|
|
1735
|
+
});
|
|
1736
|
+
return refuseCommitted(response);
|
|
1737
|
+
}
|
|
1058
1738
|
const flightContext = {
|
|
1059
1739
|
id: functionId,
|
|
1060
1740
|
args: parsed,
|
|
@@ -1090,25 +1770,29 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1090
1770
|
response,
|
|
1091
1771
|
value
|
|
1092
1772
|
} = result;
|
|
1093
|
-
if (!
|
|
1773
|
+
if (!scripted && !handleNoJS && response && response.body) {
|
|
1094
1774
|
return response;
|
|
1095
1775
|
}
|
|
1096
1776
|
if (response && response.headers) {
|
|
1097
1777
|
mergeResponseHeaders(headers, response.headers);
|
|
1098
1778
|
}
|
|
1099
|
-
if (response && response.status && (
|
|
1779
|
+
if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
|
|
1100
1780
|
status = response.status;
|
|
1781
|
+
} else if (response && response.status) {
|
|
1782
|
+
maskRedirect(headers, response, request.url);
|
|
1101
1783
|
}
|
|
1102
1784
|
metadata = response;
|
|
1103
1785
|
result = value;
|
|
1104
1786
|
} else if (result instanceof Response) {
|
|
1105
1787
|
if (result.headers && result.headers.has("X-Content-Raw")) return result;
|
|
1106
|
-
if (
|
|
1788
|
+
if (scripted) {
|
|
1107
1789
|
if (result.headers) {
|
|
1108
1790
|
mergeResponseHeaders(headers, result.headers);
|
|
1109
1791
|
}
|
|
1110
|
-
if (result.status && (result.status
|
|
1792
|
+
if (result.status && !validRedirectStatuses.has(result.status)) {
|
|
1111
1793
|
status = result.status;
|
|
1794
|
+
} else if (result.status) {
|
|
1795
|
+
maskRedirect(headers, result, request.url);
|
|
1112
1796
|
}
|
|
1113
1797
|
metadata = result;
|
|
1114
1798
|
if (result.body == null) {
|
|
@@ -1117,7 +1801,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1117
1801
|
}
|
|
1118
1802
|
}
|
|
1119
1803
|
if (collectsFlight) {
|
|
1120
|
-
result = await foldFlightData(
|
|
1804
|
+
result = await foldFlightData(flightHooks, event, headers, {
|
|
1121
1805
|
id: functionId,
|
|
1122
1806
|
value: result,
|
|
1123
1807
|
response: metadata,
|
|
@@ -1126,19 +1810,37 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1126
1810
|
}, flightContext);
|
|
1127
1811
|
if (result instanceof Response && result.headers.has("X-Content-Raw")) return result;
|
|
1128
1812
|
}
|
|
1129
|
-
if (!
|
|
1130
|
-
if (handleNoJS) return handleNoJS(result, request, parsed);
|
|
1813
|
+
if (!scripted) {
|
|
1814
|
+
if (handleNoJS) return handleNoJS(result ?? metadata, request, parsed);
|
|
1131
1815
|
if (result instanceof Response) return result;
|
|
1132
|
-
return encodeResult(result, headers,
|
|
1816
|
+
return encodeResult(result, headers, status, codec, request.signal);
|
|
1133
1817
|
}
|
|
1818
|
+
if (status === 304) warnScripted304(functionId);
|
|
1134
1819
|
return encodeResult(result, headers, status, codec, request.signal);
|
|
1135
1820
|
} catch (x) {
|
|
1821
|
+
const respondThrown = value => {
|
|
1822
|
+
const safe = sanitizeServerError(value);
|
|
1823
|
+
if (!scripted) {
|
|
1824
|
+
if (handleNoJS) return handleNoJS(safe, request, parsed, true);
|
|
1825
|
+
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1826
|
+
return new Response(DEV ? message : null, {
|
|
1827
|
+
status: 500
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
|
|
1831
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(error));
|
|
1832
|
+
return encodeResult(safe, headers, 500, codec, request.signal);
|
|
1833
|
+
};
|
|
1136
1834
|
if (x instanceof Response || isResponseEnvelope(x)) {
|
|
1137
1835
|
if (transformResult) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1836
|
+
try {
|
|
1837
|
+
x = await transformResult(event, x, {
|
|
1838
|
+
...flightContext,
|
|
1839
|
+
thrown: true
|
|
1840
|
+
});
|
|
1841
|
+
} catch (hookError) {
|
|
1842
|
+
return respondThrown(hookError);
|
|
1843
|
+
}
|
|
1142
1844
|
}
|
|
1143
1845
|
let status = 200;
|
|
1144
1846
|
let metadata;
|
|
@@ -1150,8 +1852,10 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1150
1852
|
if (response && response.headers) {
|
|
1151
1853
|
mergeResponseHeaders(headers, response.headers);
|
|
1152
1854
|
}
|
|
1153
|
-
if (response && response.status && (!
|
|
1855
|
+
if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
|
|
1154
1856
|
status = response.status;
|
|
1857
|
+
} else if (response && response.status) {
|
|
1858
|
+
maskRedirect(headers, response, request.url);
|
|
1155
1859
|
}
|
|
1156
1860
|
metadata = response;
|
|
1157
1861
|
x = value;
|
|
@@ -1159,8 +1863,10 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1159
1863
|
if (x.headers) {
|
|
1160
1864
|
mergeResponseHeaders(headers, x.headers);
|
|
1161
1865
|
}
|
|
1162
|
-
if (x.status && (!
|
|
1866
|
+
if (x.status && (!scripted || !validRedirectStatuses.has(x.status))) {
|
|
1163
1867
|
status = x.status;
|
|
1868
|
+
} else if (x.status) {
|
|
1869
|
+
maskRedirect(headers, x, request.url);
|
|
1164
1870
|
}
|
|
1165
1871
|
metadata = x;
|
|
1166
1872
|
if (x.body == null) {
|
|
@@ -1168,7 +1874,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1168
1874
|
}
|
|
1169
1875
|
}
|
|
1170
1876
|
if (collectsFlight) {
|
|
1171
|
-
x = await foldFlightData(
|
|
1877
|
+
x = await foldFlightData(flightHooks, event, headers, {
|
|
1172
1878
|
id: functionId,
|
|
1173
1879
|
value: x,
|
|
1174
1880
|
response: metadata,
|
|
@@ -1176,32 +1882,59 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1176
1882
|
thrown: true
|
|
1177
1883
|
}, flightContext);
|
|
1178
1884
|
if (x instanceof Response && x.headers.has("X-Content-Raw")) {
|
|
1885
|
+
x = ownResponse(x);
|
|
1179
1886
|
x.headers.set(ERROR_HEADER, "true");
|
|
1180
1887
|
return x;
|
|
1181
1888
|
}
|
|
1182
1889
|
}
|
|
1183
1890
|
headers.set(ERROR_HEADER, "true");
|
|
1184
|
-
if (!
|
|
1891
|
+
if (!scripted) {
|
|
1185
1892
|
if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
|
|
1186
1893
|
if (x instanceof Response) return x;
|
|
1187
1894
|
}
|
|
1895
|
+
if (scripted && status === 304) warnScripted304(functionId);
|
|
1188
1896
|
return encodeResult(x, headers, status, codec, request.signal);
|
|
1189
1897
|
}
|
|
1190
|
-
|
|
1191
|
-
if (!instance) {
|
|
1192
|
-
if (handleNoJS) return handleNoJS(safe, request, parsed, true);
|
|
1193
|
-
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1194
|
-
return new Response(DEV ? message : null, {
|
|
1195
|
-
status: 500
|
|
1196
|
-
});
|
|
1197
|
-
}
|
|
1198
|
-
const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
|
|
1199
|
-
headers.set(ERROR_HEADER, encodeErrorHeaderValue(error));
|
|
1200
|
-
return encodeResult(safe, headers, 200, codec, request.signal);
|
|
1898
|
+
return respondThrown(x);
|
|
1201
1899
|
}
|
|
1202
1900
|
};
|
|
1203
|
-
const response = commitEventResponse(await dispatch(), event);
|
|
1204
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1901
|
+
const response = commitEventResponse(enforceComposedHeaderInvariants(ownResponse(await dispatch())), event);
|
|
1902
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1903
|
+
}
|
|
1904
|
+
function ownResponse(response) {
|
|
1905
|
+
try {
|
|
1906
|
+
return new Response(response.body, response);
|
|
1907
|
+
} catch {
|
|
1908
|
+
return response;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
function finalizeTransportResponse(response, method) {
|
|
1912
|
+
const stripBody = method === "HEAD" && response.body !== null;
|
|
1913
|
+
const defaultsCache = !response.headers.has("Cache-Control") && response.status !== 304;
|
|
1914
|
+
if (stripBody || defaultsCache) {
|
|
1915
|
+
try {
|
|
1916
|
+
if (defaultsCache) {
|
|
1917
|
+
response.headers.set("Cache-Control", "no-store");
|
|
1918
|
+
}
|
|
1919
|
+
if (!stripBody) return response;
|
|
1920
|
+
response.body.cancel().catch(() => {});
|
|
1921
|
+
return new Response(null, {
|
|
1922
|
+
status: response.status,
|
|
1923
|
+
statusText: response.statusText,
|
|
1924
|
+
headers: response.headers
|
|
1925
|
+
});
|
|
1926
|
+
} catch {
|
|
1927
|
+
const headers = new Headers(response.headers);
|
|
1928
|
+
if (defaultsCache && !headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
|
|
1929
|
+
if (stripBody) response.body.cancel().catch(() => {});
|
|
1930
|
+
return new Response(stripBody ? null : response.body, {
|
|
1931
|
+
status: response.status,
|
|
1932
|
+
statusText: response.statusText,
|
|
1933
|
+
headers
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
return response;
|
|
1205
1938
|
}
|
|
1206
1939
|
|
|
1207
|
-
export { ERROR_HEADER, FLASH_COOKIE,
|
|
1940
|
+
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 };
|