@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) {
|
|
@@ -55,6 +83,7 @@ function decodeSafe(text) {
|
|
|
55
83
|
}
|
|
56
84
|
}
|
|
57
85
|
function serializeCookie(name, value, options = {}) {
|
|
86
|
+
assertServableCookie(name, options);
|
|
58
87
|
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
59
88
|
cookie += `; Path=${options.path === undefined ? "/" : options.path}`;
|
|
60
89
|
if (options.domain) cookie += `; Domain=${options.domain}`;
|
|
@@ -62,12 +91,32 @@ function serializeCookie(name, value, options = {}) {
|
|
|
62
91
|
if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
63
92
|
if (options.httpOnly) cookie += "; HttpOnly";
|
|
64
93
|
if (options.secure) cookie += "; Secure";
|
|
94
|
+
if (options.partitioned) cookie += "; Partitioned";
|
|
65
95
|
if (options.sameSite) {
|
|
66
96
|
const sameSite = options.sameSite.toLowerCase();
|
|
67
97
|
cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
|
|
68
98
|
}
|
|
69
99
|
return cookie;
|
|
70
100
|
}
|
|
101
|
+
function assertServableCookie(name, options) {
|
|
102
|
+
const reject = reason => {
|
|
103
|
+
throw new Error(`serializeCookie: every browser silently rejects this cookie — ${reason}. ` + `It would never come back on a request, with no error anywhere.`);
|
|
104
|
+
};
|
|
105
|
+
const lower = name.toLowerCase();
|
|
106
|
+
if (lower.startsWith("__host-")) {
|
|
107
|
+
if (!options.secure) reject(`the __Host- prefix on \`${name}\` requires \`secure: true\``);
|
|
108
|
+
if (options.path !== undefined && options.path !== "/") reject(`the __Host- prefix on \`${name}\` requires \`Path=/\` (got \`${options.path}\`) — ` + `host-locking is the prefix's whole contract, so it cannot be path-scoped`);
|
|
109
|
+
if (options.domain) reject(`the __Host- prefix on \`${name}\` forbids \`Domain\` (got \`${options.domain}\`)`);
|
|
110
|
+
} else if (lower.startsWith("__secure-") && !options.secure) {
|
|
111
|
+
reject(`the __Secure- prefix on \`${name}\` requires \`secure: true\``);
|
|
112
|
+
}
|
|
113
|
+
if (options.sameSite && options.sameSite.toLowerCase() === "none" && !options.secure) {
|
|
114
|
+
reject("`SameSite=None` requires `secure: true`");
|
|
115
|
+
}
|
|
116
|
+
if (options.partitioned && !options.secure) {
|
|
117
|
+
reject("`Partitioned` requires `secure: true`");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
71
120
|
const FLASH_COOKIE = "flash";
|
|
72
121
|
const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
|
|
73
122
|
function hasFlashCookie(cookieHeader) {
|
|
@@ -86,11 +135,50 @@ function configureServerFunctionsCodec(codec) {
|
|
|
86
135
|
function getServerFunctionsCodec() {
|
|
87
136
|
return codecConfig.codec;
|
|
88
137
|
}
|
|
89
|
-
|
|
138
|
+
const UNNAMED_FLIGHT_SOURCE = "true";
|
|
139
|
+
const flightConfig = {
|
|
140
|
+
consumers: new Map()
|
|
141
|
+
};
|
|
142
|
+
function assertFlightSource(source) {
|
|
143
|
+
if (source === UNNAMED_FLIGHT_SOURCE || source === "" || source.includes(",")) {
|
|
144
|
+
throw new TypeError(`Invalid flight data source id "${source}": ids ride the ` + `${SINGLE_FLIGHT_HEADER} header as a comma-separated list, and "true" is ` + `reserved for the unnamed registration (the bare consumer/hook signatures).`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function subscribeFlightData(sourceOrConsumer, maybeConsumer) {
|
|
148
|
+
const named = typeof sourceOrConsumer === "string";
|
|
149
|
+
if (named) assertFlightSource(sourceOrConsumer);
|
|
150
|
+
const source = named ? sourceOrConsumer : UNNAMED_FLIGHT_SOURCE;
|
|
151
|
+
const consumer = named ? maybeConsumer : sourceOrConsumer;
|
|
152
|
+
flightConfig.consumers.set(source, consumer);
|
|
90
153
|
return () => {
|
|
154
|
+
if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
|
|
91
155
|
};
|
|
92
156
|
}
|
|
93
|
-
|
|
157
|
+
function serverFunctionAddress(endpoint, id) {
|
|
158
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
159
|
+
return `${mount}/${encodeURIComponent(id)}`;
|
|
160
|
+
}
|
|
161
|
+
function parseServerFunctionAddress(pathname, endpoint) {
|
|
162
|
+
const mount = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
163
|
+
if (!pathname.startsWith(mount)) return null;
|
|
164
|
+
const rest = pathname.slice(mount.length);
|
|
165
|
+
if (!rest.startsWith("/")) return null;
|
|
166
|
+
let segment = rest.slice(1);
|
|
167
|
+
let data = false;
|
|
168
|
+
if (segment.startsWith("data/")) {
|
|
169
|
+
segment = segment.slice(5);
|
|
170
|
+
data = true;
|
|
171
|
+
}
|
|
172
|
+
if (!segment || segment.includes("/")) return null;
|
|
173
|
+
try {
|
|
174
|
+
return {
|
|
175
|
+
id: decodeURIComponent(segment),
|
|
176
|
+
data
|
|
177
|
+
};
|
|
178
|
+
} catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
94
182
|
const ERROR_HEADER = "X-Server-Function-Error";
|
|
95
183
|
const ERROR_HEADER_MARKER = "=?1?";
|
|
96
184
|
const NEEDS_ENCODING = /[^\x20-\x7e\xa0-\xff]/;
|
|
@@ -118,6 +206,28 @@ function decodeErrorHeaderValue(value) {
|
|
|
118
206
|
}
|
|
119
207
|
const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
120
208
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
209
|
+
const UNKNOWN_HEADER = "X-Server-Function-Unknown";
|
|
210
|
+
const REDIRECT_HEADER = "X-Server-Function-Redirect";
|
|
211
|
+
function decodeRedirectHeaderValue(value) {
|
|
212
|
+
if (typeof value !== "string") return undefined;
|
|
213
|
+
const at = value.indexOf(" ");
|
|
214
|
+
if (at < 0) return undefined;
|
|
215
|
+
const status = Number(value.slice(0, at));
|
|
216
|
+
const url = value.slice(at + 1);
|
|
217
|
+
if (!Number.isInteger(status) || !url) return undefined;
|
|
218
|
+
if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) return undefined;
|
|
219
|
+
let parsed;
|
|
220
|
+
try {
|
|
221
|
+
parsed = new URL(url);
|
|
222
|
+
} catch {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
|
|
226
|
+
return {
|
|
227
|
+
status,
|
|
228
|
+
url
|
|
229
|
+
};
|
|
230
|
+
}
|
|
121
231
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
122
232
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
123
233
|
const BodyFormat = {
|
|
@@ -129,7 +239,8 @@ const BodyFormat = {
|
|
|
129
239
|
File: "5",
|
|
130
240
|
ArrayBuffer: "6",
|
|
131
241
|
Uint8Array: "7",
|
|
132
|
-
Json: "8"
|
|
242
|
+
Json: "8",
|
|
243
|
+
Void: "9"
|
|
133
244
|
};
|
|
134
245
|
const JSON_SAFE_DEPTH_LIMIT = 4096;
|
|
135
246
|
const EXIT = {};
|
|
@@ -159,7 +270,11 @@ function isJSONSafe(value) {
|
|
|
159
270
|
const proto = Object.getPrototypeOf(v);
|
|
160
271
|
if (proto !== Object.prototype && proto !== null) return false;
|
|
161
272
|
if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
|
|
162
|
-
for (const k in v)
|
|
273
|
+
for (const k in v) {
|
|
274
|
+
const descriptor = Object.getOwnPropertyDescriptor(v, k);
|
|
275
|
+
if (descriptor === undefined || !("value" in descriptor)) return false;
|
|
276
|
+
stack.push(descriptor.value);
|
|
277
|
+
}
|
|
163
278
|
}
|
|
164
279
|
}
|
|
165
280
|
return true;
|
|
@@ -268,18 +383,34 @@ function createChunk(data) {
|
|
|
268
383
|
class ChunkReader {
|
|
269
384
|
constructor(stream) {
|
|
270
385
|
this.reader = stream.getReader();
|
|
271
|
-
this.
|
|
386
|
+
this.store = new Uint8Array(0);
|
|
387
|
+
this.buffer = this.store;
|
|
272
388
|
this.done = false;
|
|
273
389
|
}
|
|
274
390
|
async readChunk() {
|
|
275
391
|
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 {
|
|
392
|
+
if (chunk.done) {
|
|
282
393
|
this.done = true;
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const incoming = chunk.value;
|
|
397
|
+
const store = this.store;
|
|
398
|
+
const start = this.buffer.byteOffset;
|
|
399
|
+
const end = start + this.buffer.length;
|
|
400
|
+
const needed = this.buffer.length + incoming.length;
|
|
401
|
+
if (end + incoming.length <= store.length) {
|
|
402
|
+
store.set(incoming, end);
|
|
403
|
+
this.buffer = store.subarray(start, end + incoming.length);
|
|
404
|
+
} else if (needed <= store.length) {
|
|
405
|
+
store.copyWithin(0, start, end);
|
|
406
|
+
store.set(incoming, this.buffer.length);
|
|
407
|
+
this.buffer = store.subarray(0, needed);
|
|
408
|
+
} else {
|
|
409
|
+
const grown = new Uint8Array(Math.max(needed, store.length * 2));
|
|
410
|
+
grown.set(this.buffer);
|
|
411
|
+
grown.set(incoming, this.buffer.length);
|
|
412
|
+
this.store = grown;
|
|
413
|
+
this.buffer = grown.subarray(0, needed);
|
|
283
414
|
}
|
|
284
415
|
}
|
|
285
416
|
async next() {
|
|
@@ -321,6 +452,27 @@ class ChunkReader {
|
|
|
321
452
|
}
|
|
322
453
|
}
|
|
323
454
|
}
|
|
455
|
+
const ERROR_TRAILER_PREFIX = "!";
|
|
456
|
+
function encodeErrorTrailer(error) {
|
|
457
|
+
const shaped = error instanceof Error ? error : new Error(String(error));
|
|
458
|
+
return ERROR_TRAILER_PREFIX + JSON.stringify(shaped.name && shaped.name !== "Error" ? {
|
|
459
|
+
name: shaped.name,
|
|
460
|
+
message: shaped.message
|
|
461
|
+
} : {
|
|
462
|
+
message: shaped.message
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
function errorFromTrailer(payload) {
|
|
466
|
+
let shape;
|
|
467
|
+
try {
|
|
468
|
+
shape = JSON.parse(payload.slice(1));
|
|
469
|
+
} catch {
|
|
470
|
+
shape = null;
|
|
471
|
+
}
|
|
472
|
+
const error = new Error(shape && typeof shape.message === "string" ? shape.message : "Server function result could not be delivered.");
|
|
473
|
+
if (shape && typeof shape.name === "string") error.name = shape.name;
|
|
474
|
+
return error;
|
|
475
|
+
}
|
|
324
476
|
async function deserializeStream(source, codecOptions) {
|
|
325
477
|
if (!source.body) {
|
|
326
478
|
throw new Error("missing body");
|
|
@@ -328,11 +480,17 @@ async function deserializeStream(source, codecOptions) {
|
|
|
328
480
|
const reader = new ChunkReader(source.body);
|
|
329
481
|
const result = await reader.next();
|
|
330
482
|
if (!result.done) {
|
|
483
|
+
if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
484
|
+
throw errorFromTrailer(result.value);
|
|
485
|
+
}
|
|
331
486
|
const {
|
|
332
487
|
createJSONDeserializer
|
|
333
488
|
} = await import('@solidjs/web/serialization/decode');
|
|
334
489
|
const deserializeChunk = createJSONDeserializer(codecOptions);
|
|
335
490
|
function interpretChunk(chunk) {
|
|
491
|
+
if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
492
|
+
throw errorFromTrailer(chunk);
|
|
493
|
+
}
|
|
336
494
|
return deserializeChunk(JSON.parse(chunk));
|
|
337
495
|
}
|
|
338
496
|
reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
|
|
@@ -396,7 +554,7 @@ function copyInitHeaders(init) {
|
|
|
396
554
|
for (const cookie of init.getSetCookie()) headers.append("Set-Cookie", cookie);
|
|
397
555
|
return headers;
|
|
398
556
|
}
|
|
399
|
-
const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, "Location"].map(header => header.toLowerCase()));
|
|
557
|
+
const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, REDIRECT_HEADER, "Location"].map(header => header.toLowerCase()));
|
|
400
558
|
function fillsStubGap(key, headers, response) {
|
|
401
559
|
if (key === "set-cookie" || STUB_GAP_FILL_EXCLUDED.has(key)) return false;
|
|
402
560
|
if (response.body === null && (key === "content-type" || key === "content-length")) return false;
|
|
@@ -412,24 +570,16 @@ function commitEventResponse(response, event = getRequestEvent()) {
|
|
|
412
570
|
if (fillsStubGap(key, response.headers, response)) hasGaps = true;
|
|
413
571
|
});
|
|
414
572
|
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
|
-
}
|
|
573
|
+
const headers = copyInitHeaders(response.headers);
|
|
574
|
+
for (const cookie of cookies) headers.append("Set-Cookie", cookie);
|
|
575
|
+
stub.headers.forEach((value, key) => {
|
|
576
|
+
if (fillsStubGap(key, headers, response)) headers.set(key, value);
|
|
577
|
+
});
|
|
578
|
+
return new Response(response.body, {
|
|
579
|
+
status: response.status,
|
|
580
|
+
statusText: response.statusText,
|
|
581
|
+
headers
|
|
582
|
+
});
|
|
433
583
|
}
|
|
434
584
|
|
|
435
585
|
function encodeInputValue(value) {
|
|
@@ -461,11 +611,35 @@ function encodeFlashCookie(url, result, input, thrown) {
|
|
|
461
611
|
thrown: !!thrown,
|
|
462
612
|
input: input.map(encodeInputValue)
|
|
463
613
|
};
|
|
614
|
+
if (fitsCookie(payload)) return flashCookie(payload);
|
|
615
|
+
payload.truncated = true;
|
|
616
|
+
payload.input = [];
|
|
617
|
+
if (!fitsCookie(payload)) {
|
|
618
|
+
if (typeof payload.result === "string") {
|
|
619
|
+
let prefix = payload.result;
|
|
620
|
+
while (prefix.length > 0 && !fitsCookie({
|
|
621
|
+
...payload,
|
|
622
|
+
result: prefix
|
|
623
|
+
})) {
|
|
624
|
+
prefix = prefix.slice(0, prefix.length >> 1);
|
|
625
|
+
}
|
|
626
|
+
payload.result = prefix.length > 0 ? prefix : true;
|
|
627
|
+
} else {
|
|
628
|
+
payload.result = true;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return flashCookie(payload);
|
|
632
|
+
}
|
|
633
|
+
function flashCookie(payload) {
|
|
464
634
|
return serializeCookie(FLASH_COOKIE, JSON.stringify(payload), {
|
|
465
635
|
secure: true,
|
|
466
636
|
httpOnly: true
|
|
467
637
|
});
|
|
468
638
|
}
|
|
639
|
+
const COOKIE_PAIR_BUDGET = 4000;
|
|
640
|
+
function fitsCookie(payload) {
|
|
641
|
+
return FLASH_COOKIE.length + 1 + encodeURIComponent(JSON.stringify(payload)).length <= COOKIE_PAIR_BUDGET;
|
|
642
|
+
}
|
|
469
643
|
function decodeFlashCookie(cookieHeader) {
|
|
470
644
|
const match = parseCookieHeader(cookieHeader)[FLASH_COOKIE];
|
|
471
645
|
if (!match) return;
|
|
@@ -473,12 +647,14 @@ function decodeFlashCookie(cookieHeader) {
|
|
|
473
647
|
const payload = JSON.parse(match);
|
|
474
648
|
if (!payload || !payload.result) return;
|
|
475
649
|
const result = payload.error ? new Error(payload.result) : payload.result;
|
|
476
|
-
|
|
650
|
+
const submission = {
|
|
477
651
|
input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
|
|
478
652
|
url: payload.url,
|
|
479
653
|
result: payload.thrown ? undefined : result,
|
|
480
654
|
error: payload.thrown ? result : undefined
|
|
481
655
|
};
|
|
656
|
+
if (payload.truncated) submission.truncated = true;
|
|
657
|
+
return submission;
|
|
482
658
|
} catch (error) {
|
|
483
659
|
console.error(error);
|
|
484
660
|
}
|
|
@@ -493,7 +669,9 @@ const config = {
|
|
|
493
669
|
transformDirectResult: undefined,
|
|
494
670
|
handleNoJS: undefined,
|
|
495
671
|
endpoint: "/_server",
|
|
496
|
-
csrf: true
|
|
672
|
+
csrf: true,
|
|
673
|
+
bodySizeLimit: 1_048_576,
|
|
674
|
+
maxArguments: 1000
|
|
497
675
|
};
|
|
498
676
|
function configureServerFunctionsServer({
|
|
499
677
|
provideEvent,
|
|
@@ -505,7 +683,9 @@ function configureServerFunctionsServer({
|
|
|
505
683
|
handleNoJS,
|
|
506
684
|
endpoint,
|
|
507
685
|
csrf,
|
|
508
|
-
codec
|
|
686
|
+
codec,
|
|
687
|
+
bodySizeLimit,
|
|
688
|
+
maxArguments
|
|
509
689
|
} = {}) {
|
|
510
690
|
if (provideEvent !== undefined) config.provideEvent = provideEvent;
|
|
511
691
|
if (wrapInvocation !== undefined) config.wrapInvocation = wrapInvocation;
|
|
@@ -517,6 +697,16 @@ function configureServerFunctionsServer({
|
|
|
517
697
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
518
698
|
if (csrf !== undefined) config.csrf = csrf;
|
|
519
699
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
700
|
+
if (bodySizeLimit !== undefined) config.bodySizeLimit = bodySizeLimit;
|
|
701
|
+
if (maxArguments !== undefined) config.maxArguments = maxArguments;
|
|
702
|
+
}
|
|
703
|
+
const flightSources = new Map();
|
|
704
|
+
function registerFlightDataSource(source, hook) {
|
|
705
|
+
assertFlightSource(source);
|
|
706
|
+
flightSources.set(source, hook);
|
|
707
|
+
return () => {
|
|
708
|
+
if (flightSources.get(source) === hook) flightSources.delete(source);
|
|
709
|
+
};
|
|
520
710
|
}
|
|
521
711
|
function provideEvent(event, fn) {
|
|
522
712
|
if (config.provideEvent) return config.provideEvent(event, fn);
|
|
@@ -538,6 +728,7 @@ function provideRPC() {
|
|
|
538
728
|
}
|
|
539
729
|
function registerServerFunction(id, callback) {
|
|
540
730
|
provideRPC();
|
|
731
|
+
if (REGISTRATIONS.get(id) !== callback) METHODS.delete(id);
|
|
541
732
|
REGISTRATIONS.set(id, callback);
|
|
542
733
|
return callback;
|
|
543
734
|
}
|
|
@@ -549,6 +740,9 @@ function getServerFunction(id) {
|
|
|
549
740
|
throw new Error("invalid server function: " + id);
|
|
550
741
|
}
|
|
551
742
|
function registerServerReference(id, fn, name) {
|
|
743
|
+
if (typeof fn !== "function") {
|
|
744
|
+
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.`);
|
|
745
|
+
}
|
|
552
746
|
registerServerFunction(id, fn);
|
|
553
747
|
return {
|
|
554
748
|
id,
|
|
@@ -556,6 +750,33 @@ function registerServerReference(id, fn, name) {
|
|
|
556
750
|
name
|
|
557
751
|
};
|
|
558
752
|
}
|
|
753
|
+
function inProcessInvoker(call) {
|
|
754
|
+
return (args, options) => {
|
|
755
|
+
const signal = options && options.signal;
|
|
756
|
+
if (!signal) return call(...args);
|
|
757
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
758
|
+
return new Promise((resolve, reject) => {
|
|
759
|
+
const onAbort = () => reject(signal.reason);
|
|
760
|
+
signal.addEventListener("abort", onAbort, {
|
|
761
|
+
once: true
|
|
762
|
+
});
|
|
763
|
+
let result;
|
|
764
|
+
try {
|
|
765
|
+
result = call(...args);
|
|
766
|
+
} catch (error) {
|
|
767
|
+
signal.removeEventListener("abort", onAbort);
|
|
768
|
+
return reject(error);
|
|
769
|
+
}
|
|
770
|
+
Promise.resolve(result).then(value => {
|
|
771
|
+
signal.removeEventListener("abort", onAbort);
|
|
772
|
+
resolve(value);
|
|
773
|
+
}, error => {
|
|
774
|
+
signal.removeEventListener("abort", onAbort);
|
|
775
|
+
reject(error);
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
};
|
|
779
|
+
}
|
|
559
780
|
function createServerReference({
|
|
560
781
|
id,
|
|
561
782
|
fn,
|
|
@@ -566,20 +787,25 @@ function createServerReference({
|
|
|
566
787
|
const metadata = name === undefined ? {} : {
|
|
567
788
|
name
|
|
568
789
|
};
|
|
569
|
-
|
|
790
|
+
const invokeChannel = inProcessInvoker((...args) => proxy(...args));
|
|
791
|
+
const proxy = new Proxy(fn, {
|
|
570
792
|
get(target, prop) {
|
|
571
793
|
if (prop === "id") return id;
|
|
572
794
|
if (prop === "url") {
|
|
573
|
-
return
|
|
795
|
+
return serverFunctionAddress(config.endpoint, id);
|
|
574
796
|
}
|
|
575
797
|
if (prop === SERVER_FUNCTION_METADATA) return metadata;
|
|
798
|
+
if (prop === SERVER_FUNCTION_INVOKE) return invokeChannel;
|
|
576
799
|
return target[prop];
|
|
577
800
|
},
|
|
578
801
|
apply(target, thisArg, args) {
|
|
579
802
|
const ogEvt = getRequestEvent();
|
|
580
803
|
if (!ogEvt) throw new Error("Cannot call server function outside of a request");
|
|
581
804
|
const evt = {
|
|
582
|
-
...ogEvt
|
|
805
|
+
...ogEvt,
|
|
806
|
+
locals: {
|
|
807
|
+
...ogEvt.locals
|
|
808
|
+
}
|
|
583
809
|
};
|
|
584
810
|
INVOCATIONS.set(evt, {
|
|
585
811
|
id
|
|
@@ -609,6 +835,7 @@ function createServerReference({
|
|
|
609
835
|
}) : result;
|
|
610
836
|
}
|
|
611
837
|
});
|
|
838
|
+
return proxy;
|
|
612
839
|
}
|
|
613
840
|
function GET(fn) {
|
|
614
841
|
if (!isServerFunction(fn) || typeof fn.id !== "string") {
|
|
@@ -635,6 +862,7 @@ function live(fn) {
|
|
|
635
862
|
return result;
|
|
636
863
|
};
|
|
637
864
|
wrapped[SERVER_FUNCTION_METADATA] = metadata;
|
|
865
|
+
wrapped[SERVER_FUNCTION_INVOKE] = inProcessInvoker(wrapped);
|
|
638
866
|
wrapped.id = fn.id;
|
|
639
867
|
Object.defineProperty(wrapped, "url", {
|
|
640
868
|
get: () => fn.url,
|
|
@@ -648,40 +876,107 @@ function getServerFunctionInvocation() {
|
|
|
648
876
|
function getEventServerFunctionInvocation(event) {
|
|
649
877
|
return event && INVOCATIONS.get(event);
|
|
650
878
|
}
|
|
651
|
-
function
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
879
|
+
function resolveAddress(url) {
|
|
880
|
+
return parseServerFunctionAddress(url.pathname, config.endpoint);
|
|
881
|
+
}
|
|
882
|
+
const DECODE_DEPTH_LIMIT = 64;
|
|
883
|
+
function assertDecodeDepth(value) {
|
|
884
|
+
let level = [value];
|
|
885
|
+
for (let depth = 0; level.length > 0; depth++) {
|
|
886
|
+
if (depth > DECODE_DEPTH_LIMIT) {
|
|
887
|
+
throw new TypeError("Server function arguments exceed the decode depth limit");
|
|
888
|
+
}
|
|
889
|
+
const next = [];
|
|
890
|
+
for (const node of level) {
|
|
891
|
+
if (node === null || typeof node !== "object") continue;
|
|
892
|
+
if (Array.isArray(node)) {
|
|
893
|
+
for (const child of node) next.push(child);
|
|
894
|
+
} else {
|
|
895
|
+
for (const key of Object.keys(node)) next.push(node[key]);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
level = next;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
async function bufferBodyWithin(request, limit) {
|
|
902
|
+
const reader = request.clone().body.getReader();
|
|
903
|
+
const chunks = [];
|
|
904
|
+
let total = 0;
|
|
905
|
+
for (;;) {
|
|
906
|
+
const {
|
|
907
|
+
done,
|
|
908
|
+
value
|
|
909
|
+
} = await reader.read();
|
|
910
|
+
if (done) break;
|
|
911
|
+
total += value.byteLength;
|
|
912
|
+
if (total > limit) {
|
|
913
|
+
reader.cancel().catch(() => {});
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
chunks.push(value);
|
|
655
917
|
}
|
|
656
|
-
|
|
918
|
+
const body = new Uint8Array(total);
|
|
919
|
+
let offset = 0;
|
|
920
|
+
for (const chunk of chunks) {
|
|
921
|
+
body.set(chunk, offset);
|
|
922
|
+
offset += chunk.byteLength;
|
|
923
|
+
}
|
|
924
|
+
return new Request(request, {
|
|
925
|
+
body
|
|
926
|
+
});
|
|
657
927
|
}
|
|
658
|
-
async function parseArguments(request, url,
|
|
928
|
+
async function parseArguments(request, url, scripted, codec) {
|
|
659
929
|
const parsed = [];
|
|
660
930
|
const bodyFormat = request.method === "POST" ? request.headers.get(BODY_FORMAT_HEADER) : null;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
931
|
+
const args = url.searchParams.get("args");
|
|
932
|
+
if (args && (!scripted || request.method === "GET" || bodyFormat !== BodyFormat.Serialized)) {
|
|
933
|
+
let result;
|
|
934
|
+
if (args.startsWith(";0x")) {
|
|
935
|
+
result = await deserializeString(args, codec);
|
|
936
|
+
} else {
|
|
937
|
+
result = JSON.parse(args);
|
|
938
|
+
assertDecodeDepth(result);
|
|
668
939
|
}
|
|
940
|
+
if (!Array.isArray(result)) {
|
|
941
|
+
throw new TypeError("Server function arguments must encode an array");
|
|
942
|
+
}
|
|
943
|
+
for (const arg of result) {
|
|
944
|
+
parsed.push(arg);
|
|
945
|
+
}
|
|
946
|
+
} else if (!args && url.search && (request.method === "GET" || request.method === "HEAD")) {
|
|
947
|
+
parsed.push(url.searchParams);
|
|
669
948
|
}
|
|
670
949
|
if (request.method === "POST" && request.body !== null) {
|
|
671
950
|
const decoded = await extractBody(request.clone(), codec);
|
|
672
951
|
if (bodyFormat === BodyFormat.Serialized || bodyFormat === BodyFormat.Json) {
|
|
952
|
+
if (bodyFormat === BodyFormat.Json) assertDecodeDepth(decoded);
|
|
953
|
+
if (!Array.isArray(decoded)) {
|
|
954
|
+
throw new TypeError("Server function arguments must encode an array");
|
|
955
|
+
}
|
|
673
956
|
return decoded;
|
|
674
957
|
}
|
|
958
|
+
if (decoded === undefined) {
|
|
959
|
+
throw new TypeError("Server function body carries no usable encoding");
|
|
960
|
+
}
|
|
675
961
|
parsed.push(decoded);
|
|
676
962
|
}
|
|
677
963
|
return parsed;
|
|
678
964
|
}
|
|
679
|
-
async function foldFlightData(
|
|
965
|
+
async function foldFlightData(hooks, event, headers, outcome, context = {}) {
|
|
680
966
|
if (outcome.value instanceof Response && outcome.value.body) return outcome.value;
|
|
681
967
|
digestOutcome(event, outcome);
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
968
|
+
const folded = [];
|
|
969
|
+
for (const [source, hook] of hooks) {
|
|
970
|
+
try {
|
|
971
|
+
const slice = await hook(event, outcome);
|
|
972
|
+
if (slice !== undefined) folded.push([source, slice]);
|
|
973
|
+
} catch (error) {
|
|
974
|
+
console.error(`Error collecting flight data for source "${source}"`, error);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (folded.length === 0) return outcome.value;
|
|
978
|
+
const data = Object.fromEntries(folded);
|
|
979
|
+
headers.set(SINGLE_FLIGHT_HEADER, folded.map(([source]) => source).join(","));
|
|
685
980
|
if (context.transformFlightResult) {
|
|
686
981
|
const transformed = await context.transformFlightResult(event, {
|
|
687
982
|
value: outcome.value,
|
|
@@ -770,6 +1065,52 @@ function mergeResponseHeaders(target, source) {
|
|
|
770
1065
|
}
|
|
771
1066
|
}
|
|
772
1067
|
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
1068
|
+
function maskRedirect(headers, response, requestUrl) {
|
|
1069
|
+
const target = response.headers && response.headers.get("Location");
|
|
1070
|
+
if (target) {
|
|
1071
|
+
headers.set(REDIRECT_HEADER, `${response.status} ${new URL(target, requestUrl)}`);
|
|
1072
|
+
}
|
|
1073
|
+
headers.delete("Location");
|
|
1074
|
+
}
|
|
1075
|
+
const BOUNDED_COMPOSED_HEADERS = [REDIRECT_HEADER, "Location", REVALIDATE_HEADER];
|
|
1076
|
+
function refusedTargetScheme(target) {
|
|
1077
|
+
const match = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.exec(target);
|
|
1078
|
+
return match !== null && !/^https?:$/i.test(match[0]);
|
|
1079
|
+
}
|
|
1080
|
+
function enforceComposedHeaderInvariants(response) {
|
|
1081
|
+
for (const name of BOUNDED_COMPOSED_HEADERS) {
|
|
1082
|
+
const value = response.headers.get(name);
|
|
1083
|
+
if (value === null) continue;
|
|
1084
|
+
if (value.length > RESPONSE_HEADER_VALUE_LIMIT) {
|
|
1085
|
+
return refuseComposedHeader(response, name, `${name} response header refused at ${value.length} characters`, DEV ? `The ${name} response header is ${value.length} characters; past ` + `${RESPONSE_HEADER_VALUE_LIMIT} it would overflow receivers (an 8 KiB proxy ` + `buffer holds the whole header block) and the response dies at the socket after ` + `the mutation committed. Refused rather than trimmed: a cut redirect target is a ` + `different address, a dropped revalidate key is a silently stale cache. The ` + `redirect()/reload() helpers enforce this bound with the full reasoning at the ` + `call site.` : null);
|
|
1086
|
+
}
|
|
1087
|
+
if (name === REVALIDATE_HEADER) continue;
|
|
1088
|
+
const target = name === REDIRECT_HEADER ? value.slice(value.indexOf(" ") + 1) : value;
|
|
1089
|
+
if (refusedTargetScheme(target)) {
|
|
1090
|
+
return refuseComposedHeader(response, name, `${name} response header refused: non-http(s) navigation target`, DEV ? `The ${name} response header carries a navigation target with a non-http(s) ` + `scheme ("${target.slice(0, 64)}"). A javascript: target is same-origin script ` + `execution in any integration that navigates to it, so only http(s) and ` + `relative targets leave this transport. If the target came from request data ` + `(?next= and friends), validate it against your own origin before redirecting.` : null);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
return response;
|
|
1094
|
+
}
|
|
1095
|
+
function refuseComposedHeader(response, name, headerMessage, body) {
|
|
1096
|
+
if (response.body) {
|
|
1097
|
+
try {
|
|
1098
|
+
const cancelled = response.body.cancel();
|
|
1099
|
+
if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
|
|
1100
|
+
} catch {}
|
|
1101
|
+
}
|
|
1102
|
+
const headers = new Headers();
|
|
1103
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(DEV ? headerMessage : GENERIC_SERVER_ERROR_MESSAGE));
|
|
1104
|
+
return new Response(body, {
|
|
1105
|
+
status: 500,
|
|
1106
|
+
headers
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
function warnScripted304(functionId) {
|
|
1110
|
+
if (DEV) {
|
|
1111
|
+
console.warn(`Server function "${functionId}" answered a scripted call with 304 Not Modified. ` + `The client transport sends no conditional headers, so nothing was asked to be ` + `revalidated: the call resolves to undefined, not "unchanged". For conditional ` + `reads, declare the function GET and set ETag/Cache-Control response headers - ` + `the browser owns that exchange and replays its cached answer on a 304.`);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
773
1114
|
function createNoJSHandler({
|
|
774
1115
|
base = ""
|
|
775
1116
|
} = {}) {
|
|
@@ -813,44 +1154,279 @@ function isFormPost(request) {
|
|
|
813
1154
|
const type = request.headers.get("content-type") || "";
|
|
814
1155
|
return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
|
|
815
1156
|
}
|
|
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();
|
|
1157
|
+
function guardFailures(value, state) {
|
|
1158
|
+
if (!state) state = {
|
|
1159
|
+
seen: new WeakMap(),
|
|
1160
|
+
cyclic: new WeakSet()
|
|
827
1161
|
};
|
|
828
|
-
|
|
1162
|
+
const entered = enterGuard(value, state);
|
|
1163
|
+
if (!(entered instanceof Frame)) return entered;
|
|
1164
|
+
const stack = [entered];
|
|
1165
|
+
let delivered = NOTHING;
|
|
1166
|
+
for (;;) {
|
|
1167
|
+
const top = stack[stack.length - 1];
|
|
1168
|
+
const items = top.items;
|
|
1169
|
+
let pushed = null;
|
|
1170
|
+
while (top.i < items.length) {
|
|
1171
|
+
const i = top.i;
|
|
1172
|
+
let original;
|
|
1173
|
+
if (top.kind === OBJECT) {
|
|
1174
|
+
const descriptor = top.descriptors[items[i]];
|
|
1175
|
+
if ("value" in descriptor) {
|
|
1176
|
+
original = descriptor.value;
|
|
1177
|
+
} else if (typeof descriptor.get === "function") {
|
|
1178
|
+
if (top.accessorRead !== i) {
|
|
1179
|
+
try {
|
|
1180
|
+
top.accessorValue = descriptor.get.call(top.value);
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
throw sanitizeServerError(error);
|
|
1183
|
+
}
|
|
1184
|
+
top.accessorRead = i;
|
|
1185
|
+
}
|
|
1186
|
+
original = top.accessorValue;
|
|
1187
|
+
} else {
|
|
1188
|
+
top.i++;
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
} else {
|
|
1192
|
+
original = items[i];
|
|
1193
|
+
}
|
|
1194
|
+
let guarded;
|
|
1195
|
+
if (delivered !== NOTHING) {
|
|
1196
|
+
guarded = delivered;
|
|
1197
|
+
delivered = NOTHING;
|
|
1198
|
+
} else {
|
|
1199
|
+
guarded = enterGuard(original, state);
|
|
1200
|
+
if (guarded instanceof Frame) {
|
|
1201
|
+
pushed = guarded;
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
if (top.kind === ARRAY) {
|
|
1206
|
+
if (guarded !== original) {
|
|
1207
|
+
top.next[i] = guarded;
|
|
1208
|
+
top.changed = true;
|
|
1209
|
+
}
|
|
1210
|
+
} else if (top.kind === MAP) {
|
|
1211
|
+
if ((i & 1) === 0) top.pendingKey = guarded;else top.next.set(top.pendingKey, guarded);
|
|
1212
|
+
if (guarded !== original) top.changed = true;
|
|
1213
|
+
} else if (top.kind === SET) {
|
|
1214
|
+
top.next.add(guarded);
|
|
1215
|
+
if (guarded !== original) top.changed = true;
|
|
1216
|
+
} else if (guarded !== original || top.accessorRead === i) {
|
|
1217
|
+
Object.defineProperty(top.next, items[i], top.accessorRead === i ? {
|
|
1218
|
+
enumerable: true,
|
|
1219
|
+
configurable: true,
|
|
1220
|
+
writable: true,
|
|
1221
|
+
value: guarded
|
|
1222
|
+
} : {
|
|
1223
|
+
...top.descriptors[items[i]],
|
|
1224
|
+
value: guarded
|
|
1225
|
+
});
|
|
1226
|
+
top.changed = true;
|
|
1227
|
+
}
|
|
1228
|
+
top.i++;
|
|
1229
|
+
}
|
|
1230
|
+
if (pushed !== null) {
|
|
1231
|
+
stack.push(pushed);
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
stack.pop();
|
|
1235
|
+
const out = keepGuarded(top.value, top.next, top.changed, state);
|
|
1236
|
+
if (stack.length === 0) return out;
|
|
1237
|
+
delivered = out;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
const NOTHING = Symbol();
|
|
1241
|
+
const ARRAY = 0;
|
|
1242
|
+
const MAP = 1;
|
|
1243
|
+
const SET = 2;
|
|
1244
|
+
const OBJECT = 3;
|
|
1245
|
+
class Frame {
|
|
1246
|
+
constructor(kind, value, next, items, descriptors) {
|
|
1247
|
+
this.kind = kind;
|
|
1248
|
+
this.value = value;
|
|
1249
|
+
this.next = next;
|
|
1250
|
+
this.items = items;
|
|
1251
|
+
this.descriptors = descriptors;
|
|
1252
|
+
this.i = 0;
|
|
1253
|
+
this.changed = false;
|
|
1254
|
+
this.accessorRead = -1;
|
|
1255
|
+
this.accessorValue = undefined;
|
|
1256
|
+
this.pendingKey = undefined;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
function enterGuard(value, state) {
|
|
1260
|
+
if (value === null || typeof value !== "object") return value;
|
|
1261
|
+
if (state.seen.has(value)) {
|
|
1262
|
+
state.cyclic.add(value);
|
|
1263
|
+
return state.seen.get(value);
|
|
1264
|
+
}
|
|
1265
|
+
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
1266
|
+
let reader;
|
|
1267
|
+
const gate = state.gate;
|
|
1268
|
+
let finished = false;
|
|
1269
|
+
const close = () => {
|
|
1270
|
+
if (finished) return;
|
|
1271
|
+
finished = true;
|
|
1272
|
+
try {
|
|
1273
|
+
const cancelled = reader ? reader.cancel() : value.cancel();
|
|
1274
|
+
if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
|
|
1275
|
+
} catch {}
|
|
1276
|
+
};
|
|
1277
|
+
const guardedStream = new ReadableStream({
|
|
1278
|
+
async pull(controller) {
|
|
1279
|
+
try {
|
|
1280
|
+
if (gate && !finished && !gate.wantsMore()) await gate.awaitDemand();
|
|
1281
|
+
if (finished) {
|
|
1282
|
+
controller.close();
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
if (!reader) reader = value.getReader();
|
|
1286
|
+
const {
|
|
1287
|
+
done,
|
|
1288
|
+
value: chunk
|
|
1289
|
+
} = await reader.read();
|
|
1290
|
+
done ? controller.close() : controller.enqueue(guardFailures(chunk, state));
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
controller.error(sanitizeServerError(error));
|
|
1293
|
+
}
|
|
1294
|
+
},
|
|
1295
|
+
cancel(reason) {
|
|
1296
|
+
finished = true;
|
|
1297
|
+
return reader ? reader.cancel(reason) : value.cancel(reason);
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
if (gate) gate.onOpen(close);
|
|
1301
|
+
state.seen.set(value, guardedStream);
|
|
1302
|
+
return guardedStream;
|
|
1303
|
+
}
|
|
1304
|
+
if (typeof value.then === "function") {
|
|
1305
|
+
const guardedPromise = Promise.resolve(value).then(resolved => guardFailures(resolved, state), error => {
|
|
1306
|
+
throw sanitizeServerError(error);
|
|
1307
|
+
});
|
|
1308
|
+
state.seen.set(value, guardedPromise);
|
|
1309
|
+
return guardedPromise;
|
|
1310
|
+
}
|
|
1311
|
+
if (typeof value[Symbol.asyncIterator] === "function") {
|
|
829
1312
|
const source = value;
|
|
830
|
-
|
|
1313
|
+
const gate = state.gate;
|
|
1314
|
+
const guardedIterable = {
|
|
831
1315
|
[Symbol.asyncIterator]() {
|
|
832
|
-
const
|
|
1316
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
833
1317
|
let finished = false;
|
|
834
|
-
|
|
1318
|
+
const close = () => {
|
|
835
1319
|
if (finished) return;
|
|
836
1320
|
finished = true;
|
|
837
1321
|
try {
|
|
838
|
-
const returned =
|
|
1322
|
+
const returned = iterator.return && iterator.return();
|
|
839
1323
|
if (returned && typeof returned.then === "function") returned.then(undefined, () => {});
|
|
840
1324
|
} catch {}
|
|
841
1325
|
};
|
|
842
|
-
if (
|
|
1326
|
+
if (gate) gate.onOpen(close);
|
|
1327
|
+
const step = () => finished ? Promise.resolve({
|
|
1328
|
+
done: true,
|
|
1329
|
+
value: undefined
|
|
1330
|
+
}) : iterator.next().then(step => {
|
|
1331
|
+
if (step.done) {
|
|
1332
|
+
finished = true;
|
|
1333
|
+
return step;
|
|
1334
|
+
}
|
|
1335
|
+
return {
|
|
1336
|
+
done: false,
|
|
1337
|
+
value: guardFailures(step.value, state)
|
|
1338
|
+
};
|
|
1339
|
+
}, error => {
|
|
1340
|
+
throw sanitizeServerError(error);
|
|
1341
|
+
});
|
|
843
1342
|
return {
|
|
844
|
-
next: () => finished ?
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1343
|
+
next: () => finished || !gate || gate.wantsMore() ? step() : gate.awaitDemand().then(step),
|
|
1344
|
+
return: () => {
|
|
1345
|
+
close();
|
|
1346
|
+
return Promise.resolve({
|
|
1347
|
+
done: true,
|
|
1348
|
+
value: undefined
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
848
1351
|
};
|
|
849
1352
|
}
|
|
850
1353
|
};
|
|
1354
|
+
state.seen.set(value, guardedIterable);
|
|
1355
|
+
return guardedIterable;
|
|
1356
|
+
}
|
|
1357
|
+
if (Array.isArray(value)) {
|
|
1358
|
+
const next = value.slice();
|
|
1359
|
+
state.seen.set(value, next);
|
|
1360
|
+
return new Frame(ARRAY, value, next, value, null);
|
|
1361
|
+
}
|
|
1362
|
+
if (value instanceof Map) {
|
|
1363
|
+
const next = new Map();
|
|
1364
|
+
state.seen.set(value, next);
|
|
1365
|
+
const items = [];
|
|
1366
|
+
for (const entry of value) items.push(entry[0], entry[1]);
|
|
1367
|
+
return new Frame(MAP, value, next, items, null);
|
|
1368
|
+
}
|
|
1369
|
+
if (value instanceof Set) {
|
|
1370
|
+
const next = new Set();
|
|
1371
|
+
state.seen.set(value, next);
|
|
1372
|
+
return new Frame(SET, value, next, [...value], null);
|
|
851
1373
|
}
|
|
1374
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1375
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
1376
|
+
state.seen.set(value, value);
|
|
1377
|
+
return value;
|
|
1378
|
+
}
|
|
1379
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1380
|
+
const next = Object.create(prototype, descriptors);
|
|
1381
|
+
state.seen.set(value, next);
|
|
1382
|
+
return new Frame(OBJECT, value, next, Object.keys(descriptors), descriptors);
|
|
1383
|
+
}
|
|
1384
|
+
function keepGuarded(value, next, changed, state) {
|
|
1385
|
+
if (changed || state.cyclic.has(value)) return next;
|
|
1386
|
+
state.seen.set(value, value);
|
|
1387
|
+
return value;
|
|
1388
|
+
}
|
|
1389
|
+
function serializeResponseStream(value, codecOptions, signal) {
|
|
1390
|
+
let closed = false;
|
|
1391
|
+
let streamController = null;
|
|
1392
|
+
let demandWaiters = null;
|
|
1393
|
+
const wantsMore = () => streamController !== null && streamController.desiredSize > 0;
|
|
1394
|
+
const awaitDemand = () => new Promise(resolve => (demandWaiters ??= []).push(resolve));
|
|
1395
|
+
const supplyDemand = () => {
|
|
1396
|
+
const resolvers = demandWaiters;
|
|
1397
|
+
demandWaiters = null;
|
|
1398
|
+
if (resolvers) for (const resolve of resolvers) resolve();
|
|
1399
|
+
};
|
|
1400
|
+
const sourceClosers = new Set();
|
|
1401
|
+
const gate = {
|
|
1402
|
+
wantsMore,
|
|
1403
|
+
awaitDemand,
|
|
1404
|
+
onOpen(close) {
|
|
1405
|
+
if (closed) close();else sourceClosers.add(close);
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
value = guardFailures(value, {
|
|
1409
|
+
seen: new WeakMap(),
|
|
1410
|
+
cyclic: new WeakSet(),
|
|
1411
|
+
gate
|
|
1412
|
+
});
|
|
1413
|
+
let cancelSerialize = null;
|
|
1414
|
+
let onAbort = null;
|
|
1415
|
+
const finishSource = () => {
|
|
1416
|
+
for (const close of sourceClosers) close();
|
|
1417
|
+
sourceClosers.clear();
|
|
1418
|
+
supplyDemand();
|
|
1419
|
+
};
|
|
1420
|
+
const teardown = () => {
|
|
1421
|
+
if (closed) return;
|
|
1422
|
+
closed = true;
|
|
1423
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1424
|
+
if (cancelSerialize) cancelSerialize();
|
|
1425
|
+
finishSource();
|
|
1426
|
+
};
|
|
852
1427
|
return new ReadableStream({
|
|
853
1428
|
async start(controller) {
|
|
1429
|
+
streamController = controller;
|
|
854
1430
|
if (signal) {
|
|
855
1431
|
if (signal.aborted) {
|
|
856
1432
|
teardown();
|
|
@@ -886,16 +1462,29 @@ function serializeResponseStream(value, codecOptions, signal) {
|
|
|
886
1462
|
if (closed) return;
|
|
887
1463
|
closed = true;
|
|
888
1464
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1465
|
+
finishSource();
|
|
889
1466
|
controller.close();
|
|
890
1467
|
},
|
|
891
1468
|
onError(error) {
|
|
892
1469
|
if (closed) return;
|
|
893
1470
|
closed = true;
|
|
894
1471
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
895
|
-
|
|
1472
|
+
finishSource();
|
|
1473
|
+
try {
|
|
1474
|
+
const delivered = sanitizeServerError(DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error);
|
|
1475
|
+
controller.enqueue(createChunk(encodeErrorTrailer(delivered)));
|
|
1476
|
+
controller.close();
|
|
1477
|
+
} catch {
|
|
1478
|
+
try {
|
|
1479
|
+
controller.error(error);
|
|
1480
|
+
} catch {}
|
|
1481
|
+
}
|
|
896
1482
|
}
|
|
897
1483
|
});
|
|
898
1484
|
},
|
|
1485
|
+
pull() {
|
|
1486
|
+
supplyDemand();
|
|
1487
|
+
},
|
|
899
1488
|
cancel() {
|
|
900
1489
|
teardown();
|
|
901
1490
|
}
|
|
@@ -909,6 +1498,18 @@ function serializedResponse(value, headers, codec, signal) {
|
|
|
909
1498
|
});
|
|
910
1499
|
}
|
|
911
1500
|
function encodeResult(value, headers, status, codec, signal) {
|
|
1501
|
+
if (NULL_BODY_STATUSES.has(status)) {
|
|
1502
|
+
if (value === undefined || value === null) {
|
|
1503
|
+
headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
|
|
1504
|
+
return new Response(null, {
|
|
1505
|
+
status,
|
|
1506
|
+
headers
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
const error = new Error(`Server function answered status ${status}, which forbids a response body, with a value. ` + `Return respond(undefined, { status: ${status} }) for a bodiless answer, or drop the ` + `status to send the value.`);
|
|
1510
|
+
headers.set(ERROR_HEADER, encodeErrorHeaderValue(error.message));
|
|
1511
|
+
return encodeResult(error, headers, 500, codec, signal);
|
|
1512
|
+
}
|
|
912
1513
|
const direct = getHeadersAndBody(value);
|
|
913
1514
|
if (direct) {
|
|
914
1515
|
for (const [key, val] of Object.entries(direct.headers || {})) {
|
|
@@ -920,6 +1521,7 @@ function encodeResult(value, headers, status, codec, signal) {
|
|
|
920
1521
|
});
|
|
921
1522
|
}
|
|
922
1523
|
if (value === undefined) {
|
|
1524
|
+
headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
|
|
923
1525
|
return new Response(null, {
|
|
924
1526
|
status,
|
|
925
1527
|
headers
|
|
@@ -936,11 +1538,25 @@ function encodeResult(value, headers, status, codec, signal) {
|
|
|
936
1538
|
}
|
|
937
1539
|
} catch {
|
|
938
1540
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
status,
|
|
942
|
-
|
|
943
|
-
|
|
1541
|
+
try {
|
|
1542
|
+
const response = serializedResponse(value, headers, codec, signal);
|
|
1543
|
+
return status === 200 ? response : new Response(response.body, {
|
|
1544
|
+
status,
|
|
1545
|
+
headers
|
|
1546
|
+
});
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
throw DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
const ERROR_HEADER_VALUE_LIMIT = 1024;
|
|
1552
|
+
function boundedErrorHeaderValue(message) {
|
|
1553
|
+
let label = message.length > 256 ? message.slice(0, 256) : message;
|
|
1554
|
+
let encoded = encodeErrorHeaderValue(label);
|
|
1555
|
+
while (encoded.length > ERROR_HEADER_VALUE_LIMIT && label.length > 1) {
|
|
1556
|
+
label = label.slice(0, Math.ceil(label.length / 2));
|
|
1557
|
+
encoded = encodeErrorHeaderValue(label);
|
|
1558
|
+
}
|
|
1559
|
+
return encoded;
|
|
944
1560
|
}
|
|
945
1561
|
const GENERIC_SERVER_ERROR_MESSAGE = "Internal Server Error";
|
|
946
1562
|
let DEV = true === true;
|
|
@@ -955,9 +1571,21 @@ function sanitizeServerError(value) {
|
|
|
955
1571
|
function observeServerFunctionCalls() {
|
|
956
1572
|
return () => {};
|
|
957
1573
|
}
|
|
1574
|
+
function serverFunctionUrl(id, boundArgs) {
|
|
1575
|
+
const address = serverFunctionAddress(config.endpoint, id);
|
|
1576
|
+
if (!boundArgs || !boundArgs.length) return address;
|
|
1577
|
+
if (!isJSONSafe(boundArgs)) {
|
|
1578
|
+
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.");
|
|
1579
|
+
}
|
|
1580
|
+
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
1581
|
+
}
|
|
1582
|
+
function parseServerFunctionUrl(url) {
|
|
1583
|
+
const parsed = parseServerFunctionAddress(new URL(url, "http://localhost").pathname, config.endpoint);
|
|
1584
|
+
return parsed && parsed.id;
|
|
1585
|
+
}
|
|
958
1586
|
async function matchesOrigin(origin, request, matcher) {
|
|
959
1587
|
if (matcher === undefined) return origin === new URL(request.url).origin;
|
|
960
|
-
if (typeof matcher === "function") return
|
|
1588
|
+
if (typeof matcher === "function") return (await matcher(origin, request)) === true;
|
|
961
1589
|
return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
|
|
962
1590
|
}
|
|
963
1591
|
async function allowsServerFunctionRequest(request, options) {
|
|
@@ -1012,49 +1640,121 @@ function forbiddenResponse() {
|
|
|
1012
1640
|
async function handleServerFunctionRequest(request, options = {}) {
|
|
1013
1641
|
const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
|
|
1014
1642
|
const url = new URL(request.url);
|
|
1643
|
+
const method = request.method;
|
|
1644
|
+
const address = resolveAddress(url);
|
|
1645
|
+
const functionId = address && address.id;
|
|
1646
|
+
const declaredRead = (method === "GET" || method === "HEAD") && functionId !== null && METHODS.get(functionId) === "GET";
|
|
1015
1647
|
const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
|
|
1016
|
-
const protectsRequest = csrf !== false;
|
|
1648
|
+
const protectsRequest = csrf !== false && (!declaredRead || typeof csrf === "object" && csrf.protectDeclaredReads === true);
|
|
1649
|
+
let serverFunction;
|
|
1650
|
+
if (functionId) {
|
|
1651
|
+
try {
|
|
1652
|
+
serverFunction = getServerFunction(functionId);
|
|
1653
|
+
} catch {
|
|
1654
|
+
return finalizeTransportResponse(new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
1655
|
+
status: 404,
|
|
1656
|
+
headers: {
|
|
1657
|
+
[UNKNOWN_HEADER]: "true"
|
|
1658
|
+
}
|
|
1659
|
+
}), method);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1017
1662
|
if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
|
|
1018
|
-
return forbiddenResponse();
|
|
1663
|
+
return finalizeTransportResponse(forbiddenResponse(), method);
|
|
1019
1664
|
}
|
|
1020
1665
|
const instance = request.headers.get(INSTANCE_HEADER);
|
|
1021
|
-
const functionId = resolveFunctionId(request, url);
|
|
1022
1666
|
if (!functionId) {
|
|
1023
1667
|
const response = new Response(DEV ? "Server function not found" : null, {
|
|
1024
1668
|
status: 404
|
|
1025
1669
|
});
|
|
1026
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1027
|
-
}
|
|
1028
|
-
let serverFunction;
|
|
1029
|
-
try {
|
|
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;
|
|
1670
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1036
1671
|
}
|
|
1037
|
-
|
|
1672
|
+
const scripted = address.data;
|
|
1673
|
+
if (method !== "POST" && !declaredRead) {
|
|
1038
1674
|
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
1039
1675
|
status: 405,
|
|
1040
1676
|
headers: {
|
|
1041
|
-
Allow: "POST"
|
|
1677
|
+
Allow: METHODS.get(functionId) === "GET" ? "POST, GET, HEAD" : "POST"
|
|
1042
1678
|
}
|
|
1043
1679
|
});
|
|
1044
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1680
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1681
|
+
}
|
|
1682
|
+
const bodySizeLimit = options.bodySizeLimit !== undefined ? options.bodySizeLimit : config.bodySizeLimit;
|
|
1683
|
+
const argsEncoding = url.searchParams.get("args");
|
|
1684
|
+
if (argsEncoding !== null && argsEncoding.length > bodySizeLimit) {
|
|
1685
|
+
const response = new Response(DEV ? "Server function arguments exceed the configured bodySizeLimit" : null, {
|
|
1686
|
+
status: 413
|
|
1687
|
+
});
|
|
1688
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1689
|
+
}
|
|
1690
|
+
if (method === "POST" && request.body !== null && bodySizeLimit !== Infinity) {
|
|
1691
|
+
const raw = request.headers.get("content-length");
|
|
1692
|
+
const declared = raw !== null && /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
1693
|
+
if (declared > bodySizeLimit) {
|
|
1694
|
+
const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
|
|
1695
|
+
status: 413
|
|
1696
|
+
});
|
|
1697
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1698
|
+
}
|
|
1699
|
+
if (!(declared > 0)) {
|
|
1700
|
+
const bounded = await bufferBodyWithin(request, bodySizeLimit);
|
|
1701
|
+
if (bounded === null) {
|
|
1702
|
+
const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
|
|
1703
|
+
status: 413
|
|
1704
|
+
});
|
|
1705
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1706
|
+
}
|
|
1707
|
+
request = bounded;
|
|
1708
|
+
}
|
|
1045
1709
|
}
|
|
1046
|
-
|
|
1710
|
+
let event = options.createEvent ? options.createEvent(request) : {
|
|
1047
1711
|
request,
|
|
1048
1712
|
locals: {}
|
|
1049
1713
|
};
|
|
1714
|
+
if (typeof event?.then === "function") event = await event;
|
|
1715
|
+
const refuseCommitted = raw => {
|
|
1716
|
+
const response = commitEventResponse(raw, event);
|
|
1717
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1718
|
+
};
|
|
1050
1719
|
const provide = options.provideEvent || provideEvent;
|
|
1051
1720
|
const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
|
|
1052
1721
|
const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
|
|
1053
1722
|
const wrapInvocation = options.wrapInvocation !== undefined ? options.wrapInvocation : config.wrapInvocation;
|
|
1054
1723
|
const transformFlightResult = options.transformFlightResult !== undefined ? options.transformFlightResult : config.transformFlightResult;
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1724
|
+
let handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS;
|
|
1725
|
+
if (handleNoJS === undefined && !scripted && isFormPost(request)) {
|
|
1726
|
+
const fetchMode = request.headers.get("Sec-Fetch-Mode");
|
|
1727
|
+
if (fetchMode === null || fetchMode === "navigate") {
|
|
1728
|
+
handleNoJS = defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler());
|
|
1729
|
+
} else {
|
|
1730
|
+
const response = new Response(DEV ? "The bare server-function address answers form navigations with the " + "no-JS redirect convention. Scripted callers use the data address " + `(…/data/${functionId}) or send the ${BODY_FORMAT_HEADER} tag.` : null, {
|
|
1731
|
+
status: 400
|
|
1732
|
+
});
|
|
1733
|
+
return refuseCommitted(response);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
const flightHeader = scripted && method === "POST" ? request.headers.get(SINGLE_FLIGHT_HEADER) : null;
|
|
1737
|
+
const flightHooks = flightHeader ? flightHeader.split(",").flatMap(source => {
|
|
1738
|
+
const hook = source === "true" ? flightHook : flightSources.get(source);
|
|
1739
|
+
return hook ? [[source, hook]] : [];
|
|
1740
|
+
}) : [];
|
|
1741
|
+
const collectsFlight = flightHooks.length > 0;
|
|
1742
|
+
let parsed;
|
|
1743
|
+
try {
|
|
1744
|
+
parsed = await parseArguments(request, url, scripted, codec);
|
|
1745
|
+
} catch {
|
|
1746
|
+
const response = new Response(DEV ? "Malformed server function arguments" : null, {
|
|
1747
|
+
status: 400
|
|
1748
|
+
});
|
|
1749
|
+
return refuseCommitted(response);
|
|
1750
|
+
}
|
|
1751
|
+
const maxArguments = options.maxArguments !== undefined ? options.maxArguments : config.maxArguments;
|
|
1752
|
+
if (parsed.length > maxArguments) {
|
|
1753
|
+
const response = new Response(DEV ? "Server function call exceeds the configured maxArguments" : null, {
|
|
1754
|
+
status: 400
|
|
1755
|
+
});
|
|
1756
|
+
return refuseCommitted(response);
|
|
1757
|
+
}
|
|
1058
1758
|
const flightContext = {
|
|
1059
1759
|
id: functionId,
|
|
1060
1760
|
args: parsed,
|
|
@@ -1090,25 +1790,29 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1090
1790
|
response,
|
|
1091
1791
|
value
|
|
1092
1792
|
} = result;
|
|
1093
|
-
if (!
|
|
1793
|
+
if (!scripted && !handleNoJS && response && response.body) {
|
|
1094
1794
|
return response;
|
|
1095
1795
|
}
|
|
1096
1796
|
if (response && response.headers) {
|
|
1097
1797
|
mergeResponseHeaders(headers, response.headers);
|
|
1098
1798
|
}
|
|
1099
|
-
if (response && response.status && (
|
|
1799
|
+
if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
|
|
1100
1800
|
status = response.status;
|
|
1801
|
+
} else if (response && response.status) {
|
|
1802
|
+
maskRedirect(headers, response, request.url);
|
|
1101
1803
|
}
|
|
1102
1804
|
metadata = response;
|
|
1103
1805
|
result = value;
|
|
1104
1806
|
} else if (result instanceof Response) {
|
|
1105
1807
|
if (result.headers && result.headers.has("X-Content-Raw")) return result;
|
|
1106
|
-
if (
|
|
1808
|
+
if (scripted) {
|
|
1107
1809
|
if (result.headers) {
|
|
1108
1810
|
mergeResponseHeaders(headers, result.headers);
|
|
1109
1811
|
}
|
|
1110
|
-
if (result.status && (result.status
|
|
1812
|
+
if (result.status && !validRedirectStatuses.has(result.status)) {
|
|
1111
1813
|
status = result.status;
|
|
1814
|
+
} else if (result.status) {
|
|
1815
|
+
maskRedirect(headers, result, request.url);
|
|
1112
1816
|
}
|
|
1113
1817
|
metadata = result;
|
|
1114
1818
|
if (result.body == null) {
|
|
@@ -1117,7 +1821,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1117
1821
|
}
|
|
1118
1822
|
}
|
|
1119
1823
|
if (collectsFlight) {
|
|
1120
|
-
result = await foldFlightData(
|
|
1824
|
+
result = await foldFlightData(flightHooks, event, headers, {
|
|
1121
1825
|
id: functionId,
|
|
1122
1826
|
value: result,
|
|
1123
1827
|
response: metadata,
|
|
@@ -1126,19 +1830,37 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1126
1830
|
}, flightContext);
|
|
1127
1831
|
if (result instanceof Response && result.headers.has("X-Content-Raw")) return result;
|
|
1128
1832
|
}
|
|
1129
|
-
if (!
|
|
1130
|
-
if (handleNoJS) return handleNoJS(result, request, parsed);
|
|
1833
|
+
if (!scripted) {
|
|
1834
|
+
if (handleNoJS) return handleNoJS(result ?? metadata, request, parsed);
|
|
1131
1835
|
if (result instanceof Response) return result;
|
|
1132
|
-
return encodeResult(result, headers,
|
|
1836
|
+
return encodeResult(result, headers, status, codec, request.signal);
|
|
1133
1837
|
}
|
|
1838
|
+
if (status === 304) warnScripted304(functionId);
|
|
1134
1839
|
return encodeResult(result, headers, status, codec, request.signal);
|
|
1135
1840
|
} catch (x) {
|
|
1841
|
+
const respondThrown = value => {
|
|
1842
|
+
const safe = sanitizeServerError(value);
|
|
1843
|
+
if (!scripted) {
|
|
1844
|
+
if (handleNoJS) return handleNoJS(safe, request, parsed, true);
|
|
1845
|
+
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1846
|
+
return new Response(DEV ? message : null, {
|
|
1847
|
+
status: 500
|
|
1848
|
+
});
|
|
1849
|
+
}
|
|
1850
|
+
const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
|
|
1851
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(error));
|
|
1852
|
+
return encodeResult(safe, headers, 500, codec, request.signal);
|
|
1853
|
+
};
|
|
1136
1854
|
if (x instanceof Response || isResponseEnvelope(x)) {
|
|
1137
1855
|
if (transformResult) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1856
|
+
try {
|
|
1857
|
+
x = await transformResult(event, x, {
|
|
1858
|
+
...flightContext,
|
|
1859
|
+
thrown: true
|
|
1860
|
+
});
|
|
1861
|
+
} catch (hookError) {
|
|
1862
|
+
return respondThrown(hookError);
|
|
1863
|
+
}
|
|
1142
1864
|
}
|
|
1143
1865
|
let status = 200;
|
|
1144
1866
|
let metadata;
|
|
@@ -1150,8 +1872,10 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1150
1872
|
if (response && response.headers) {
|
|
1151
1873
|
mergeResponseHeaders(headers, response.headers);
|
|
1152
1874
|
}
|
|
1153
|
-
if (response && response.status && (!
|
|
1875
|
+
if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
|
|
1154
1876
|
status = response.status;
|
|
1877
|
+
} else if (response && response.status) {
|
|
1878
|
+
maskRedirect(headers, response, request.url);
|
|
1155
1879
|
}
|
|
1156
1880
|
metadata = response;
|
|
1157
1881
|
x = value;
|
|
@@ -1159,8 +1883,10 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1159
1883
|
if (x.headers) {
|
|
1160
1884
|
mergeResponseHeaders(headers, x.headers);
|
|
1161
1885
|
}
|
|
1162
|
-
if (x.status && (!
|
|
1886
|
+
if (x.status && (!scripted || !validRedirectStatuses.has(x.status))) {
|
|
1163
1887
|
status = x.status;
|
|
1888
|
+
} else if (x.status) {
|
|
1889
|
+
maskRedirect(headers, x, request.url);
|
|
1164
1890
|
}
|
|
1165
1891
|
metadata = x;
|
|
1166
1892
|
if (x.body == null) {
|
|
@@ -1168,7 +1894,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1168
1894
|
}
|
|
1169
1895
|
}
|
|
1170
1896
|
if (collectsFlight) {
|
|
1171
|
-
x = await foldFlightData(
|
|
1897
|
+
x = await foldFlightData(flightHooks, event, headers, {
|
|
1172
1898
|
id: functionId,
|
|
1173
1899
|
value: x,
|
|
1174
1900
|
response: metadata,
|
|
@@ -1176,32 +1902,59 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1176
1902
|
thrown: true
|
|
1177
1903
|
}, flightContext);
|
|
1178
1904
|
if (x instanceof Response && x.headers.has("X-Content-Raw")) {
|
|
1905
|
+
x = ownResponse(x);
|
|
1179
1906
|
x.headers.set(ERROR_HEADER, "true");
|
|
1180
1907
|
return x;
|
|
1181
1908
|
}
|
|
1182
1909
|
}
|
|
1183
1910
|
headers.set(ERROR_HEADER, "true");
|
|
1184
|
-
if (!
|
|
1911
|
+
if (!scripted) {
|
|
1185
1912
|
if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
|
|
1186
1913
|
if (x instanceof Response) return x;
|
|
1187
1914
|
}
|
|
1915
|
+
if (scripted && status === 304) warnScripted304(functionId);
|
|
1188
1916
|
return encodeResult(x, headers, status, codec, request.signal);
|
|
1189
1917
|
}
|
|
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);
|
|
1918
|
+
return respondThrown(x);
|
|
1201
1919
|
}
|
|
1202
1920
|
};
|
|
1203
|
-
const response = commitEventResponse(await dispatch(), event);
|
|
1204
|
-
return protectsRequest ? withCSRFVary(response) : response;
|
|
1921
|
+
const response = commitEventResponse(enforceComposedHeaderInvariants(ownResponse(await dispatch())), event);
|
|
1922
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1923
|
+
}
|
|
1924
|
+
function ownResponse(response) {
|
|
1925
|
+
try {
|
|
1926
|
+
return new Response(response.body, response);
|
|
1927
|
+
} catch {
|
|
1928
|
+
return response;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
function finalizeTransportResponse(response, method) {
|
|
1932
|
+
const stripBody = method === "HEAD" && response.body !== null;
|
|
1933
|
+
const defaultsCache = !response.headers.has("Cache-Control") && response.status !== 304;
|
|
1934
|
+
if (stripBody || defaultsCache) {
|
|
1935
|
+
try {
|
|
1936
|
+
if (defaultsCache) {
|
|
1937
|
+
response.headers.set("Cache-Control", "no-store");
|
|
1938
|
+
}
|
|
1939
|
+
if (!stripBody) return response;
|
|
1940
|
+
response.body.cancel().catch(() => {});
|
|
1941
|
+
return new Response(null, {
|
|
1942
|
+
status: response.status,
|
|
1943
|
+
statusText: response.statusText,
|
|
1944
|
+
headers: response.headers
|
|
1945
|
+
});
|
|
1946
|
+
} catch {
|
|
1947
|
+
const headers = new Headers(response.headers);
|
|
1948
|
+
if (defaultsCache && !headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
|
|
1949
|
+
if (stripBody) response.body.cancel().catch(() => {});
|
|
1950
|
+
return new Response(stripBody ? null : response.body, {
|
|
1951
|
+
status: response.status,
|
|
1952
|
+
statusText: response.statusText,
|
|
1953
|
+
headers
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
return response;
|
|
1205
1958
|
}
|
|
1206
1959
|
|
|
1207
|
-
export { ERROR_HEADER, FLASH_COOKIE,
|
|
1960
|
+
export { ERROR_HEADER, FLASH_COOKIE, GENERIC_SERVER_ERROR_MESSAGE, GET, INSTANCE_HEADER, REDIRECT_HEADER, SERVER_FUNCTION_INVOKE, SINGLE_FLIGHT_HEADER, UNKNOWN_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeRedirectHeaderValue, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getEventServerFunctionInvocation, getServerFunction, getServerFunctionInvocation, getServerFunctionMetadata, guardFailures, handleServerFunctionRequest, hasFlashCookie, invoke, isServerFunction, live, observeServerFunctionCalls, parseServerFunctionUrl, registerFlightDataSource, registerServerFunction, registerServerReference, sanitizeServerError, serializeResponseStream, serverFunctionUrl, setServerFunctionsDev, subscribeFlightData, withMeta };
|