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