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