@solidjs/web 2.0.0-rc.4 → 2.0.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev.cjs +163 -36
- package/dist/dev.js +161 -37
- package/dist/server.cjs +178 -37
- package/dist/server.js +177 -38
- package/dist/web.cjs +143 -36
- package/dist/web.js +141 -37
- package/frames/dist/client.cjs +41 -8
- package/frames/dist/client.dev.cjs +41 -8
- package/frames/dist/client.dev.js +41 -8
- package/frames/dist/client.js +41 -8
- package/frames/dist/server.cjs +497 -44
- package/frames/dist/server.js +497 -44
- package/package.json +2 -2
- package/serialization/dist/decode.cjs +4 -2
- package/serialization/dist/decode.js +4 -2
- package/serialization/dist/serialization.cjs +12 -8
- package/serialization/dist/serialization.js +12 -8
- package/serialization/types/index.d.ts +7 -0
- package/serialization/types/serializer-decode.d.ts +14 -1
- package/serialization/types/serializer.d.ts +7 -0
- package/serialization/types-cjs/index.d.cts +7 -0
- package/serialization/types-cjs/serializer-decode.d.cts +14 -1
- package/serialization/types-cjs/serializer.d.cts +7 -0
- package/server-functions/dist/client.cjs +164 -35
- package/server-functions/dist/client.js +161 -36
- package/server-functions/dist/server.cjs +899 -136
- package/server-functions/dist/server.dev.cjs +919 -136
- package/server-functions/dist/server.dev.js +915 -137
- package/server-functions/dist/server.js +895 -137
- package/types/client.d.ts +2 -1
- package/types/constants.d.ts +3 -1
- package/types/cookies.d.ts +6 -14
- package/types/frames/frame-client.d.ts +4 -0
- package/types/frames/serializer-decode.d.ts +14 -1
- package/types/frames/serializer.d.ts +7 -0
- package/types/jsx.d.ts +11 -16
- package/types/response.d.ts +11 -0
- package/types/serializer-decode.d.ts +14 -1
- package/types/serializer.d.ts +7 -0
- package/types/server-functions/client.d.ts +22 -2
- package/types/server-functions/flash.d.ts +9 -0
- package/types/server-functions/server.d.ts +131 -11
- package/types/server-functions/shared.d.ts +77 -14
- package/types/server-mock.d.ts +17 -2
- package/types/server.d.ts +16 -2
- package/types-cjs/client.d.cts +2 -1
- package/types-cjs/constants.d.cts +3 -1
- package/types-cjs/cookies.d.cts +6 -14
- package/types-cjs/frames/frame-client.d.cts +4 -0
- package/types-cjs/frames/serializer-decode.d.cts +14 -1
- package/types-cjs/frames/serializer.d.cts +7 -0
- package/types-cjs/jsx.d.cts +11 -16
- package/types-cjs/response.d.cts +11 -0
- package/types-cjs/serializer-decode.d.cts +14 -1
- package/types-cjs/serializer.d.cts +7 -0
- package/types-cjs/server-functions/client.d.cts +22 -2
- package/types-cjs/server-functions/flash.d.cts +9 -0
- package/types-cjs/server-functions/server.d.cts +131 -11
- package/types-cjs/server-functions/shared.d.cts +77 -14
- package/types-cjs/server-mock.d.cts +17 -2
- package/types-cjs/server.d.cts +16 -2
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { sharedConfig } from 'solid-js';
|
|
2
2
|
|
|
3
|
+
const COMPOSED_BODY_FRAMING = /*#__PURE__*/new Set(["content-length", "content-encoding", "transfer-encoding"]);
|
|
4
|
+
function isHttpNavigationTarget(target) {
|
|
5
|
+
try {
|
|
6
|
+
const protocol = new URL(target, "http://base.invalid").protocol;
|
|
7
|
+
return protocol === "http:" || protocol === "https:";
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
const ENVELOPE = Symbol.for("solid.ResponseEnvelope");
|
|
4
14
|
function isResponseEnvelope(value) {
|
|
5
15
|
return !!(value && typeof value === "object" && value[ENVELOPE]);
|
|
@@ -9,6 +19,8 @@ function isSafeError(value) {
|
|
|
9
19
|
return !!(value && (typeof value === "object" || typeof value === "function") && value[SAFE_ERROR]);
|
|
10
20
|
}
|
|
11
21
|
const REVALIDATE_HEADER = "X-Revalidate";
|
|
22
|
+
const RESPONSE_HEADER_VALUE_LIMIT = 4096;
|
|
23
|
+
const NULL_BODY_STATUSES = new Set([204, 205, 304]);
|
|
12
24
|
|
|
13
25
|
const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
|
|
14
26
|
function getServerFunctionMetadata(fn) {
|
|
@@ -88,6 +100,7 @@ function serializeCookie(name, value, options = {}) {
|
|
|
88
100
|
if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
89
101
|
if (options.httpOnly) cookie += "; HttpOnly";
|
|
90
102
|
if (options.secure) cookie += "; Secure";
|
|
103
|
+
if (options.partitioned) cookie += "; Partitioned";
|
|
91
104
|
if (options.sameSite) {
|
|
92
105
|
const sameSite = options.sameSite.toLowerCase();
|
|
93
106
|
cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
|
|
@@ -112,8 +125,23 @@ function configureServerFunctionsCodec(codec) {
|
|
|
112
125
|
function getServerFunctionsCodec() {
|
|
113
126
|
return codecConfig.codec;
|
|
114
127
|
}
|
|
115
|
-
|
|
128
|
+
const UNNAMED_FLIGHT_SOURCE = "true";
|
|
129
|
+
const flightConfig = {
|
|
130
|
+
consumers: new Map()
|
|
131
|
+
};
|
|
132
|
+
function assertFlightSource(source) {
|
|
133
|
+
if (source === UNNAMED_FLIGHT_SOURCE || source === "" || source.includes(",")) {
|
|
134
|
+
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).`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function subscribeFlightData(sourceOrConsumer, maybeConsumer) {
|
|
138
|
+
const named = typeof sourceOrConsumer === "string";
|
|
139
|
+
if (named) assertFlightSource(sourceOrConsumer);
|
|
140
|
+
const source = named ? sourceOrConsumer : UNNAMED_FLIGHT_SOURCE;
|
|
141
|
+
const consumer = named ? maybeConsumer : sourceOrConsumer;
|
|
142
|
+
flightConfig.consumers.set(source, consumer);
|
|
116
143
|
return () => {
|
|
144
|
+
if (flightConfig.consumers.get(source) === consumer) flightConfig.consumers.delete(source);
|
|
117
145
|
};
|
|
118
146
|
}
|
|
119
147
|
function serverFunctionAddress(endpoint, id) {
|
|
@@ -125,10 +153,18 @@ function parseServerFunctionAddress(pathname, endpoint) {
|
|
|
125
153
|
if (!pathname.startsWith(mount)) return null;
|
|
126
154
|
const rest = pathname.slice(mount.length);
|
|
127
155
|
if (!rest.startsWith("/")) return null;
|
|
128
|
-
|
|
156
|
+
let segment = rest.slice(1);
|
|
157
|
+
let data = false;
|
|
158
|
+
if (segment.startsWith("data/")) {
|
|
159
|
+
segment = segment.slice(5);
|
|
160
|
+
data = true;
|
|
161
|
+
}
|
|
129
162
|
if (!segment || segment.includes("/")) return null;
|
|
130
163
|
try {
|
|
131
|
-
return
|
|
164
|
+
return {
|
|
165
|
+
id: decodeURIComponent(segment),
|
|
166
|
+
data
|
|
167
|
+
};
|
|
132
168
|
} catch {
|
|
133
169
|
return null;
|
|
134
170
|
}
|
|
@@ -160,6 +196,28 @@ function decodeErrorHeaderValue(value) {
|
|
|
160
196
|
}
|
|
161
197
|
const INSTANCE_HEADER = "X-Server-Function-Instance";
|
|
162
198
|
const BODY_FORMAT_HEADER = "X-Server-Function-Format";
|
|
199
|
+
const UNKNOWN_HEADER = "X-Server-Function-Unknown";
|
|
200
|
+
const REDIRECT_HEADER = "X-Server-Function-Redirect";
|
|
201
|
+
function decodeRedirectHeaderValue(value) {
|
|
202
|
+
if (typeof value !== "string") return undefined;
|
|
203
|
+
const at = value.indexOf(" ");
|
|
204
|
+
if (at < 0) return undefined;
|
|
205
|
+
const status = Number(value.slice(0, at));
|
|
206
|
+
const url = value.slice(at + 1);
|
|
207
|
+
if (!Number.isInteger(status) || !url) return undefined;
|
|
208
|
+
if (status !== 301 && status !== 302 && status !== 303 && status !== 307 && status !== 308) return undefined;
|
|
209
|
+
let parsed;
|
|
210
|
+
try {
|
|
211
|
+
parsed = new URL(url);
|
|
212
|
+
} catch {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
|
|
216
|
+
return {
|
|
217
|
+
status,
|
|
218
|
+
url
|
|
219
|
+
};
|
|
220
|
+
}
|
|
163
221
|
const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
|
|
164
222
|
const FILE_FORM_KEY = "__server_function_file__";
|
|
165
223
|
const BodyFormat = {
|
|
@@ -202,7 +260,11 @@ function isJSONSafe(value) {
|
|
|
202
260
|
const proto = Object.getPrototypeOf(v);
|
|
203
261
|
if (proto !== Object.prototype && proto !== null) return false;
|
|
204
262
|
if (Symbol.asyncIterator in v || Symbol.iterator in v) return false;
|
|
205
|
-
for (const k in v)
|
|
263
|
+
for (const k in v) {
|
|
264
|
+
const descriptor = Object.getOwnPropertyDescriptor(v, k);
|
|
265
|
+
if (descriptor === undefined || !("value" in descriptor)) return false;
|
|
266
|
+
stack.push(descriptor.value);
|
|
267
|
+
}
|
|
206
268
|
}
|
|
207
269
|
}
|
|
208
270
|
return true;
|
|
@@ -311,18 +373,34 @@ function createChunk(data) {
|
|
|
311
373
|
class ChunkReader {
|
|
312
374
|
constructor(stream) {
|
|
313
375
|
this.reader = stream.getReader();
|
|
314
|
-
this.
|
|
376
|
+
this.store = new Uint8Array(0);
|
|
377
|
+
this.buffer = this.store;
|
|
315
378
|
this.done = false;
|
|
316
379
|
}
|
|
317
380
|
async readChunk() {
|
|
318
381
|
const chunk = await this.reader.read();
|
|
319
|
-
if (
|
|
320
|
-
const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
|
|
321
|
-
newBuffer.set(this.buffer);
|
|
322
|
-
newBuffer.set(chunk.value, this.buffer.length);
|
|
323
|
-
this.buffer = newBuffer;
|
|
324
|
-
} else {
|
|
382
|
+
if (chunk.done) {
|
|
325
383
|
this.done = true;
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const incoming = chunk.value;
|
|
387
|
+
const store = this.store;
|
|
388
|
+
const start = this.buffer.byteOffset;
|
|
389
|
+
const end = start + this.buffer.length;
|
|
390
|
+
const needed = this.buffer.length + incoming.length;
|
|
391
|
+
if (end + incoming.length <= store.length) {
|
|
392
|
+
store.set(incoming, end);
|
|
393
|
+
this.buffer = store.subarray(start, end + incoming.length);
|
|
394
|
+
} else if (needed <= store.length) {
|
|
395
|
+
store.copyWithin(0, start, end);
|
|
396
|
+
store.set(incoming, this.buffer.length);
|
|
397
|
+
this.buffer = store.subarray(0, needed);
|
|
398
|
+
} else {
|
|
399
|
+
const grown = new Uint8Array(Math.max(needed, store.length * 2));
|
|
400
|
+
grown.set(this.buffer);
|
|
401
|
+
grown.set(incoming, this.buffer.length);
|
|
402
|
+
this.store = grown;
|
|
403
|
+
this.buffer = grown.subarray(0, needed);
|
|
326
404
|
}
|
|
327
405
|
}
|
|
328
406
|
async next() {
|
|
@@ -364,6 +442,27 @@ class ChunkReader {
|
|
|
364
442
|
}
|
|
365
443
|
}
|
|
366
444
|
}
|
|
445
|
+
const ERROR_TRAILER_PREFIX = "!";
|
|
446
|
+
function encodeErrorTrailer(error) {
|
|
447
|
+
const shaped = error instanceof Error ? error : new Error(String(error));
|
|
448
|
+
return ERROR_TRAILER_PREFIX + JSON.stringify(shaped.name && shaped.name !== "Error" ? {
|
|
449
|
+
name: shaped.name,
|
|
450
|
+
message: shaped.message
|
|
451
|
+
} : {
|
|
452
|
+
message: shaped.message
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
function errorFromTrailer(payload) {
|
|
456
|
+
let shape;
|
|
457
|
+
try {
|
|
458
|
+
shape = JSON.parse(payload.slice(1));
|
|
459
|
+
} catch {
|
|
460
|
+
shape = null;
|
|
461
|
+
}
|
|
462
|
+
const error = new Error(shape && typeof shape.message === "string" ? shape.message : "Server function result could not be delivered.");
|
|
463
|
+
if (shape && typeof shape.name === "string") error.name = shape.name;
|
|
464
|
+
return error;
|
|
465
|
+
}
|
|
367
466
|
async function deserializeStream(source, codecOptions) {
|
|
368
467
|
if (!source.body) {
|
|
369
468
|
throw new Error("missing body");
|
|
@@ -371,11 +470,17 @@ async function deserializeStream(source, codecOptions) {
|
|
|
371
470
|
const reader = new ChunkReader(source.body);
|
|
372
471
|
const result = await reader.next();
|
|
373
472
|
if (!result.done) {
|
|
473
|
+
if (result.value.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
474
|
+
throw errorFromTrailer(result.value);
|
|
475
|
+
}
|
|
374
476
|
const {
|
|
375
477
|
createJSONDeserializer
|
|
376
478
|
} = await import('@solidjs/web/serialization/decode');
|
|
377
479
|
const deserializeChunk = createJSONDeserializer(codecOptions);
|
|
378
480
|
function interpretChunk(chunk) {
|
|
481
|
+
if (chunk.startsWith(ERROR_TRAILER_PREFIX)) {
|
|
482
|
+
throw errorFromTrailer(chunk);
|
|
483
|
+
}
|
|
379
484
|
return deserializeChunk(JSON.parse(chunk));
|
|
380
485
|
}
|
|
381
486
|
reader.drain(interpretChunk).then(() => deserializeChunk.abort(new Error("Server function stream ended unexpectedly.")), error => deserializeChunk.abort(error));
|
|
@@ -439,7 +544,8 @@ function copyInitHeaders(init) {
|
|
|
439
544
|
for (const cookie of init.getSetCookie()) headers.append("Set-Cookie", cookie);
|
|
440
545
|
return headers;
|
|
441
546
|
}
|
|
442
|
-
const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, "Location"
|
|
547
|
+
const STUB_GAP_FILL_EXCLUDED = /*#__PURE__*/new Set([ERROR_HEADER, BODY_FORMAT_HEADER, SINGLE_FLIGHT_HEADER, REVALIDATE_HEADER, REDIRECT_HEADER, "Location",
|
|
548
|
+
...COMPOSED_BODY_FRAMING].map(header => header.toLowerCase()));
|
|
443
549
|
function fillsStubGap(key, headers, response) {
|
|
444
550
|
if (key === "set-cookie" || STUB_GAP_FILL_EXCLUDED.has(key)) return false;
|
|
445
551
|
if (response.body === null && (key === "content-type" || key === "content-length")) return false;
|
|
@@ -455,24 +561,16 @@ function commitEventResponse(response, event = getRequestEvent()) {
|
|
|
455
561
|
if (fillsStubGap(key, response.headers, response)) hasGaps = true;
|
|
456
562
|
});
|
|
457
563
|
if (!cookies.length && !hasGaps) return response;
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if (fillsStubGap(key, headers, response)) headers.set(key, value);
|
|
469
|
-
});
|
|
470
|
-
return new Response(response.body, {
|
|
471
|
-
status: response.status,
|
|
472
|
-
statusText: response.statusText,
|
|
473
|
-
headers
|
|
474
|
-
});
|
|
475
|
-
}
|
|
564
|
+
const headers = copyInitHeaders(response.headers);
|
|
565
|
+
for (const cookie of cookies) headers.append("Set-Cookie", cookie);
|
|
566
|
+
stub.headers.forEach((value, key) => {
|
|
567
|
+
if (fillsStubGap(key, headers, response)) headers.set(key, value);
|
|
568
|
+
});
|
|
569
|
+
return new Response(response.body, {
|
|
570
|
+
status: response.status,
|
|
571
|
+
statusText: response.statusText,
|
|
572
|
+
headers
|
|
573
|
+
});
|
|
476
574
|
}
|
|
477
575
|
|
|
478
576
|
function encodeInputValue(value) {
|
|
@@ -504,11 +602,35 @@ function encodeFlashCookie(url, result, input, thrown) {
|
|
|
504
602
|
thrown: !!thrown,
|
|
505
603
|
input: input.map(encodeInputValue)
|
|
506
604
|
};
|
|
605
|
+
if (fitsCookie(payload)) return flashCookie(payload);
|
|
606
|
+
payload.truncated = true;
|
|
607
|
+
payload.input = [];
|
|
608
|
+
if (!fitsCookie(payload)) {
|
|
609
|
+
if (typeof payload.result === "string") {
|
|
610
|
+
let prefix = payload.result;
|
|
611
|
+
while (prefix.length > 0 && !fitsCookie({
|
|
612
|
+
...payload,
|
|
613
|
+
result: prefix
|
|
614
|
+
})) {
|
|
615
|
+
prefix = prefix.slice(0, prefix.length >> 1);
|
|
616
|
+
}
|
|
617
|
+
payload.result = prefix.length > 0 ? prefix : true;
|
|
618
|
+
} else {
|
|
619
|
+
payload.result = true;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return flashCookie(payload);
|
|
623
|
+
}
|
|
624
|
+
function flashCookie(payload) {
|
|
507
625
|
return serializeCookie(FLASH_COOKIE, JSON.stringify(payload), {
|
|
508
626
|
secure: true,
|
|
509
627
|
httpOnly: true
|
|
510
628
|
});
|
|
511
629
|
}
|
|
630
|
+
const COOKIE_PAIR_BUDGET = 4000;
|
|
631
|
+
function fitsCookie(payload) {
|
|
632
|
+
return FLASH_COOKIE.length + 1 + encodeURIComponent(JSON.stringify(payload)).length <= COOKIE_PAIR_BUDGET;
|
|
633
|
+
}
|
|
512
634
|
function decodeFlashCookie(cookieHeader) {
|
|
513
635
|
const match = parseCookieHeader(cookieHeader)[FLASH_COOKIE];
|
|
514
636
|
if (!match) return;
|
|
@@ -516,12 +638,14 @@ function decodeFlashCookie(cookieHeader) {
|
|
|
516
638
|
const payload = JSON.parse(match);
|
|
517
639
|
if (!payload || !payload.result) return;
|
|
518
640
|
const result = payload.error ? new Error(payload.result) : payload.result;
|
|
519
|
-
|
|
641
|
+
const submission = {
|
|
520
642
|
input: Array.isArray(payload.input) ? payload.input.map(decodeInputValue) : [],
|
|
521
643
|
url: payload.url,
|
|
522
644
|
result: payload.thrown ? undefined : result,
|
|
523
645
|
error: payload.thrown ? result : undefined
|
|
524
646
|
};
|
|
647
|
+
if (payload.truncated) submission.truncated = true;
|
|
648
|
+
return submission;
|
|
525
649
|
} catch (error) {
|
|
526
650
|
console.error(error);
|
|
527
651
|
}
|
|
@@ -536,7 +660,9 @@ const config = {
|
|
|
536
660
|
transformDirectResult: undefined,
|
|
537
661
|
handleNoJS: undefined,
|
|
538
662
|
endpoint: "/_server",
|
|
539
|
-
csrf: true
|
|
663
|
+
csrf: true,
|
|
664
|
+
bodySizeLimit: 1_048_576,
|
|
665
|
+
maxArguments: 1000
|
|
540
666
|
};
|
|
541
667
|
function configureServerFunctionsServer({
|
|
542
668
|
provideEvent,
|
|
@@ -548,7 +674,9 @@ function configureServerFunctionsServer({
|
|
|
548
674
|
handleNoJS,
|
|
549
675
|
endpoint,
|
|
550
676
|
csrf,
|
|
551
|
-
codec
|
|
677
|
+
codec,
|
|
678
|
+
bodySizeLimit,
|
|
679
|
+
maxArguments
|
|
552
680
|
} = {}) {
|
|
553
681
|
if (provideEvent !== undefined) config.provideEvent = provideEvent;
|
|
554
682
|
if (wrapInvocation !== undefined) config.wrapInvocation = wrapInvocation;
|
|
@@ -560,6 +688,16 @@ function configureServerFunctionsServer({
|
|
|
560
688
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
561
689
|
if (csrf !== undefined) config.csrf = csrf;
|
|
562
690
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
691
|
+
if (bodySizeLimit !== undefined) config.bodySizeLimit = bodySizeLimit;
|
|
692
|
+
if (maxArguments !== undefined) config.maxArguments = maxArguments;
|
|
693
|
+
}
|
|
694
|
+
const flightSources = new Map();
|
|
695
|
+
function registerFlightDataSource(source, hook) {
|
|
696
|
+
assertFlightSource(source);
|
|
697
|
+
flightSources.set(source, hook);
|
|
698
|
+
return () => {
|
|
699
|
+
if (flightSources.get(source) === hook) flightSources.delete(source);
|
|
700
|
+
};
|
|
563
701
|
}
|
|
564
702
|
function provideEvent(event, fn) {
|
|
565
703
|
if (config.provideEvent) return config.provideEvent(event, fn);
|
|
@@ -567,6 +705,52 @@ function provideEvent(event, fn) {
|
|
|
567
705
|
if (ctx) return ctx.run(event, fn);
|
|
568
706
|
throw new Error("No request event provider. Configure one with configureServerFunctionsServer({ provideEvent }).");
|
|
569
707
|
}
|
|
708
|
+
function scopeDeferredResult(value, scope) {
|
|
709
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") return value;
|
|
710
|
+
const promised = scope(() => nativePromise(value));
|
|
711
|
+
if (promised) {
|
|
712
|
+
return promised.then(result => scope(() => scopeDeferredResult(result, scope)));
|
|
713
|
+
}
|
|
714
|
+
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
715
|
+
let reader;
|
|
716
|
+
return new ReadableStream({
|
|
717
|
+
async pull(controller) {
|
|
718
|
+
try {
|
|
719
|
+
const step = await scope(() => {
|
|
720
|
+
if (!reader) reader = value.getReader();
|
|
721
|
+
return reader.read();
|
|
722
|
+
});
|
|
723
|
+
if (step.done) controller.close();else controller.enqueue(step.value);
|
|
724
|
+
} catch (error) {
|
|
725
|
+
controller.error(error);
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
cancel(reason) {
|
|
729
|
+
return scope(() => reader ? reader.cancel(reason) : value.cancel(reason));
|
|
730
|
+
}
|
|
731
|
+
}, {
|
|
732
|
+
highWaterMark: 0
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
const scopedIterator = symbol => ({
|
|
736
|
+
[symbol]() {
|
|
737
|
+
const iterator = scope(() => value[symbol]());
|
|
738
|
+
return new Proxy(iterator, {
|
|
739
|
+
get(target, property) {
|
|
740
|
+
const member = Reflect.get(target, property, target);
|
|
741
|
+
return typeof member === "function" && (property === "next" || property === "return" || property === "throw") ? (...args) => scope(() => member.apply(target, args)) : member;
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
if (typeof value[Symbol.asyncIterator] === "function") {
|
|
747
|
+
return scopedIterator(Symbol.asyncIterator);
|
|
748
|
+
}
|
|
749
|
+
if (typeof value[Symbol.iterator] === "function" && !Array.isArray(value) && !(value instanceof Map) && !(value instanceof Set) && !ArrayBuffer.isView(value)) {
|
|
750
|
+
return scopedIterator(Symbol.iterator);
|
|
751
|
+
}
|
|
752
|
+
return value;
|
|
753
|
+
}
|
|
570
754
|
const REGISTRATIONS = new Map();
|
|
571
755
|
const METHODS = new Map();
|
|
572
756
|
const INVOCATIONS = new WeakMap();
|
|
@@ -581,6 +765,7 @@ function provideRPC() {
|
|
|
581
765
|
}
|
|
582
766
|
function registerServerFunction(id, callback) {
|
|
583
767
|
provideRPC();
|
|
768
|
+
if (REGISTRATIONS.get(id) !== callback) METHODS.delete(id);
|
|
584
769
|
REGISTRATIONS.set(id, callback);
|
|
585
770
|
return callback;
|
|
586
771
|
}
|
|
@@ -654,13 +839,17 @@ function createServerReference({
|
|
|
654
839
|
const ogEvt = getRequestEvent();
|
|
655
840
|
if (!ogEvt) throw new Error("Cannot call server function outside of a request");
|
|
656
841
|
const evt = {
|
|
657
|
-
...ogEvt
|
|
842
|
+
...ogEvt,
|
|
843
|
+
locals: {
|
|
844
|
+
...ogEvt.locals
|
|
845
|
+
}
|
|
658
846
|
};
|
|
659
847
|
INVOCATIONS.set(evt, {
|
|
660
848
|
id
|
|
661
849
|
});
|
|
662
850
|
evt.serverOnly = true;
|
|
663
|
-
const
|
|
851
|
+
const scope = run => provideEvent(evt, run);
|
|
852
|
+
let result = provideEvent(evt, () => {
|
|
664
853
|
const run = () => fn.apply(thisArg, args);
|
|
665
854
|
return config.wrapInvocation ? config.wrapInvocation(run, {
|
|
666
855
|
id,
|
|
@@ -669,19 +858,20 @@ function createServerReference({
|
|
|
669
858
|
direct: true
|
|
670
859
|
}) : run();
|
|
671
860
|
});
|
|
861
|
+
result = scopeDeferredResult(result, scope);
|
|
672
862
|
const transform = config.transformDirectResult;
|
|
673
863
|
if (transform && result && typeof result.then === "function") {
|
|
674
|
-
return result.then(value => transform(value, {
|
|
864
|
+
return result.then(value => scopeDeferredResult(transform(value, {
|
|
675
865
|
id,
|
|
676
866
|
args,
|
|
677
867
|
event: evt
|
|
678
|
-
}));
|
|
868
|
+
}), scope));
|
|
679
869
|
}
|
|
680
|
-
return transform ? transform(result, {
|
|
870
|
+
return transform ? scopeDeferredResult(transform(result, {
|
|
681
871
|
id,
|
|
682
872
|
args,
|
|
683
873
|
event: evt
|
|
684
|
-
}) : result;
|
|
874
|
+
}), scope) : result;
|
|
685
875
|
}
|
|
686
876
|
});
|
|
687
877
|
return proxy;
|
|
@@ -725,18 +915,107 @@ function getServerFunctionInvocation() {
|
|
|
725
915
|
function getEventServerFunctionInvocation(event) {
|
|
726
916
|
return event && INVOCATIONS.get(event);
|
|
727
917
|
}
|
|
728
|
-
function
|
|
918
|
+
function resolveAddress(url) {
|
|
729
919
|
return parseServerFunctionAddress(url.pathname, config.endpoint);
|
|
730
920
|
}
|
|
731
|
-
|
|
921
|
+
const DECODE_DEPTH_LIMIT = 64;
|
|
922
|
+
function assertDecodeDepth(value) {
|
|
923
|
+
let level = [value];
|
|
924
|
+
for (let depth = 0; level.length > 0; depth++) {
|
|
925
|
+
if (depth > DECODE_DEPTH_LIMIT) {
|
|
926
|
+
throw new TypeError("Server function arguments exceed the decode depth limit");
|
|
927
|
+
}
|
|
928
|
+
const next = [];
|
|
929
|
+
for (const node of level) {
|
|
930
|
+
if (node === null || typeof node !== "object") continue;
|
|
931
|
+
if (Array.isArray(node)) {
|
|
932
|
+
for (const child of node) next.push(child);
|
|
933
|
+
} else {
|
|
934
|
+
for (const key of Object.keys(node)) next.push(node[key]);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
level = next;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
const UNSAFE_ARGUMENT_KEYS = ["__proto__", "constructor", "prototype"];
|
|
941
|
+
function stripUnsafeArgumentKeys(value) {
|
|
942
|
+
const stack = [value];
|
|
943
|
+
const seen = new Set();
|
|
944
|
+
while (stack.length) {
|
|
945
|
+
const v = stack.pop();
|
|
946
|
+
if (v === null || typeof v !== "object" || seen.has(v)) continue;
|
|
947
|
+
seen.add(v);
|
|
948
|
+
for (const key of UNSAFE_ARGUMENT_KEYS) {
|
|
949
|
+
delete v[key];
|
|
950
|
+
}
|
|
951
|
+
for (const key of Object.keys(v)) stack.push(v[key]);
|
|
952
|
+
if (v instanceof Map) {
|
|
953
|
+
for (const [k, entry] of v) stack.push(k, entry);
|
|
954
|
+
} else if (v instanceof Set) {
|
|
955
|
+
for (const member of v) stack.push(member);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return value;
|
|
959
|
+
}
|
|
960
|
+
async function bufferBodyWithin(request, limit) {
|
|
961
|
+
const reader = request.body.getReader();
|
|
962
|
+
const signal = request.signal;
|
|
963
|
+
const chunks = [];
|
|
964
|
+
let total = 0;
|
|
965
|
+
const onAbort = () => {
|
|
966
|
+
reader.cancel(signal.reason).catch(() => {});
|
|
967
|
+
};
|
|
968
|
+
if (signal.aborted) onAbort();else signal.addEventListener("abort", onAbort, {
|
|
969
|
+
once: true
|
|
970
|
+
});
|
|
971
|
+
try {
|
|
972
|
+
for (;;) {
|
|
973
|
+
const {
|
|
974
|
+
done,
|
|
975
|
+
value
|
|
976
|
+
} = await reader.read();
|
|
977
|
+
if (signal.aborted) throw signal.reason;
|
|
978
|
+
if (done) break;
|
|
979
|
+
total += value.byteLength;
|
|
980
|
+
if (total > limit) {
|
|
981
|
+
reader.cancel().catch(() => {});
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
chunks.push(value);
|
|
985
|
+
}
|
|
986
|
+
} catch (error) {
|
|
987
|
+
reader.cancel(error).catch(() => {});
|
|
988
|
+
throw error;
|
|
989
|
+
} finally {
|
|
990
|
+
signal.removeEventListener("abort", onAbort);
|
|
991
|
+
reader.releaseLock();
|
|
992
|
+
}
|
|
993
|
+
const body = new Uint8Array(total);
|
|
994
|
+
let offset = 0;
|
|
995
|
+
for (const chunk of chunks) {
|
|
996
|
+
body.set(chunk, offset);
|
|
997
|
+
offset += chunk.byteLength;
|
|
998
|
+
}
|
|
999
|
+
return new Request(request, {
|
|
1000
|
+
body
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
async function parseArguments(request, url, scripted, codec) {
|
|
732
1004
|
const parsed = [];
|
|
733
1005
|
const bodyFormat = request.method === "POST" ? request.headers.get(BODY_FORMAT_HEADER) : null;
|
|
734
1006
|
const args = url.searchParams.get("args");
|
|
735
|
-
if (args && (!
|
|
736
|
-
|
|
1007
|
+
if (args && (!scripted || request.method === "GET" || bodyFormat !== BodyFormat.Serialized)) {
|
|
1008
|
+
let result;
|
|
1009
|
+
if (args.startsWith(";0x")) {
|
|
1010
|
+
result = await deserializeString(args, codec);
|
|
1011
|
+
} else {
|
|
1012
|
+
result = JSON.parse(args);
|
|
1013
|
+
assertDecodeDepth(result);
|
|
1014
|
+
}
|
|
737
1015
|
if (!Array.isArray(result)) {
|
|
738
1016
|
throw new TypeError("Server function arguments must encode an array");
|
|
739
1017
|
}
|
|
1018
|
+
stripUnsafeArgumentKeys(result);
|
|
740
1019
|
for (const arg of result) {
|
|
741
1020
|
parsed.push(arg);
|
|
742
1021
|
}
|
|
@@ -746,18 +1025,37 @@ async function parseArguments(request, url, instance, codec) {
|
|
|
746
1025
|
if (request.method === "POST" && request.body !== null) {
|
|
747
1026
|
const decoded = await extractBody(request.clone(), codec);
|
|
748
1027
|
if (bodyFormat === BodyFormat.Serialized || bodyFormat === BodyFormat.Json) {
|
|
749
|
-
|
|
1028
|
+
if (bodyFormat === BodyFormat.Json) assertDecodeDepth(decoded);
|
|
1029
|
+
if (!Array.isArray(decoded)) {
|
|
1030
|
+
throw new TypeError("Server function arguments must encode an array");
|
|
1031
|
+
}
|
|
1032
|
+
return stripUnsafeArgumentKeys(decoded);
|
|
1033
|
+
}
|
|
1034
|
+
if (decoded === undefined) {
|
|
1035
|
+
if (bodyFormat === null && (await request.clone().arrayBuffer()).byteLength === 0) {
|
|
1036
|
+
return parsed;
|
|
1037
|
+
}
|
|
1038
|
+
throw new TypeError("Server function body carries no usable encoding");
|
|
750
1039
|
}
|
|
751
1040
|
parsed.push(decoded);
|
|
752
1041
|
}
|
|
753
1042
|
return parsed;
|
|
754
1043
|
}
|
|
755
|
-
async function foldFlightData(
|
|
1044
|
+
async function foldFlightData(hooks, event, headers, outcome, context = {}) {
|
|
756
1045
|
if (outcome.value instanceof Response && outcome.value.body) return outcome.value;
|
|
757
1046
|
digestOutcome(event, outcome);
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
1047
|
+
const folded = [];
|
|
1048
|
+
for (const [source, hook] of hooks) {
|
|
1049
|
+
try {
|
|
1050
|
+
const slice = await hook(event, outcome);
|
|
1051
|
+
if (slice !== undefined) folded.push([source, slice]);
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
console.error(`Error collecting flight data for source "${source}"`, error);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (folded.length === 0) return outcome.value;
|
|
1057
|
+
const data = Object.fromEntries(folded);
|
|
1058
|
+
headers.set(SINGLE_FLIGHT_HEADER, folded.map(([source]) => source).join(","));
|
|
761
1059
|
if (context.transformFlightResult) {
|
|
762
1060
|
const transformed = await context.transformFlightResult(event, {
|
|
763
1061
|
value: outcome.value,
|
|
@@ -837,7 +1135,7 @@ function foldSetCookies(headers, setCookies) {
|
|
|
837
1135
|
}
|
|
838
1136
|
function mergeResponseHeaders(target, source) {
|
|
839
1137
|
source.forEach((value, key) => {
|
|
840
|
-
if (key !== "set-cookie") target.append(key, value);
|
|
1138
|
+
if (key !== "set-cookie" && !COMPOSED_BODY_FRAMING.has(key)) target.append(key, value);
|
|
841
1139
|
});
|
|
842
1140
|
if (source.getSetCookie) {
|
|
843
1141
|
for (const cookie of source.getSetCookie()) target.append("Set-Cookie", cookie);
|
|
@@ -846,6 +1144,48 @@ function mergeResponseHeaders(target, source) {
|
|
|
846
1144
|
}
|
|
847
1145
|
}
|
|
848
1146
|
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
1147
|
+
function maskRedirect(headers, response, requestUrl) {
|
|
1148
|
+
const target = response.headers && response.headers.get("Location");
|
|
1149
|
+
if (target) {
|
|
1150
|
+
headers.set(REDIRECT_HEADER, `${response.status} ${new URL(target, requestUrl)}`);
|
|
1151
|
+
}
|
|
1152
|
+
headers.delete("Location");
|
|
1153
|
+
}
|
|
1154
|
+
const BOUNDED_COMPOSED_HEADERS = [REDIRECT_HEADER, "Location", REVALIDATE_HEADER];
|
|
1155
|
+
function enforceComposedHeaderInvariants(response) {
|
|
1156
|
+
for (const name of BOUNDED_COMPOSED_HEADERS) {
|
|
1157
|
+
const value = response.headers.get(name);
|
|
1158
|
+
if (value === null) continue;
|
|
1159
|
+
if (value.length > RESPONSE_HEADER_VALUE_LIMIT) {
|
|
1160
|
+
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);
|
|
1161
|
+
}
|
|
1162
|
+
if (name === REVALIDATE_HEADER) continue;
|
|
1163
|
+
const target = name === REDIRECT_HEADER ? value.slice(value.indexOf(" ") + 1) : value;
|
|
1164
|
+
if (!isHttpNavigationTarget(target)) {
|
|
1165
|
+
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);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
return response;
|
|
1169
|
+
}
|
|
1170
|
+
function refuseComposedHeader(response, name, headerMessage, body) {
|
|
1171
|
+
if (response.body) {
|
|
1172
|
+
try {
|
|
1173
|
+
const cancelled = response.body.cancel();
|
|
1174
|
+
if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
|
|
1175
|
+
} catch {}
|
|
1176
|
+
}
|
|
1177
|
+
const headers = new Headers();
|
|
1178
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(DEV ? headerMessage : GENERIC_SERVER_ERROR_MESSAGE));
|
|
1179
|
+
return new Response(body, {
|
|
1180
|
+
status: 500,
|
|
1181
|
+
headers
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
function warnScripted304(functionId) {
|
|
1185
|
+
if (DEV) {
|
|
1186
|
+
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.`);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
849
1189
|
function createNoJSHandler({
|
|
850
1190
|
base = ""
|
|
851
1191
|
} = {}) {
|
|
@@ -889,44 +1229,291 @@ function isFormPost(request) {
|
|
|
889
1229
|
const type = request.headers.get("content-type") || "";
|
|
890
1230
|
return type.startsWith("application/x-www-form-urlencoded") || type.startsWith("multipart/form-data");
|
|
891
1231
|
}
|
|
892
|
-
function
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
let onAbort = null;
|
|
897
|
-
const teardown = () => {
|
|
898
|
-
if (closed) return;
|
|
899
|
-
closed = true;
|
|
900
|
-
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
901
|
-
if (cancelSerialize) cancelSerialize();
|
|
902
|
-
if (closeIterator) closeIterator();
|
|
1232
|
+
function guardFailures(value, state) {
|
|
1233
|
+
if (!state) state = {
|
|
1234
|
+
seen: new WeakMap(),
|
|
1235
|
+
cyclic: new WeakSet()
|
|
903
1236
|
};
|
|
904
|
-
|
|
1237
|
+
const entered = enterGuard(value, state);
|
|
1238
|
+
if (!(entered instanceof Frame)) return entered;
|
|
1239
|
+
const stack = [entered];
|
|
1240
|
+
let delivered = NOTHING;
|
|
1241
|
+
for (;;) {
|
|
1242
|
+
const top = stack[stack.length - 1];
|
|
1243
|
+
const items = top.items;
|
|
1244
|
+
let pushed = null;
|
|
1245
|
+
while (top.i < items.length) {
|
|
1246
|
+
const i = top.i;
|
|
1247
|
+
let original;
|
|
1248
|
+
if (top.kind === OBJECT) {
|
|
1249
|
+
const descriptor = top.descriptors[items[i]];
|
|
1250
|
+
if ("value" in descriptor) {
|
|
1251
|
+
original = descriptor.value;
|
|
1252
|
+
} else if (typeof descriptor.get === "function") {
|
|
1253
|
+
if (top.accessorRead !== i) {
|
|
1254
|
+
try {
|
|
1255
|
+
top.accessorValue = descriptor.get.call(top.value);
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
throw sanitizeServerError(error);
|
|
1258
|
+
}
|
|
1259
|
+
top.accessorRead = i;
|
|
1260
|
+
}
|
|
1261
|
+
original = top.accessorValue;
|
|
1262
|
+
} else {
|
|
1263
|
+
top.i++;
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
} else {
|
|
1267
|
+
original = items[i];
|
|
1268
|
+
}
|
|
1269
|
+
let guarded;
|
|
1270
|
+
if (delivered !== NOTHING) {
|
|
1271
|
+
guarded = delivered;
|
|
1272
|
+
delivered = NOTHING;
|
|
1273
|
+
} else {
|
|
1274
|
+
guarded = enterGuard(original, state);
|
|
1275
|
+
if (guarded instanceof Frame) {
|
|
1276
|
+
pushed = guarded;
|
|
1277
|
+
break;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
if (top.kind === ARRAY) {
|
|
1281
|
+
if (guarded !== original) {
|
|
1282
|
+
top.next[i] = guarded;
|
|
1283
|
+
top.changed = true;
|
|
1284
|
+
}
|
|
1285
|
+
} else if (top.kind === MAP) {
|
|
1286
|
+
if ((i & 1) === 0) top.pendingKey = guarded;else top.next.set(top.pendingKey, guarded);
|
|
1287
|
+
if (guarded !== original) top.changed = true;
|
|
1288
|
+
} else if (top.kind === SET) {
|
|
1289
|
+
top.next.add(guarded);
|
|
1290
|
+
if (guarded !== original) top.changed = true;
|
|
1291
|
+
} else if (guarded !== original || top.accessorRead === i) {
|
|
1292
|
+
Object.defineProperty(top.next, items[i], {
|
|
1293
|
+
value: guarded,
|
|
1294
|
+
writable: true,
|
|
1295
|
+
configurable: true,
|
|
1296
|
+
enumerable: top.descriptors[items[i]].enumerable
|
|
1297
|
+
});
|
|
1298
|
+
top.changed = true;
|
|
1299
|
+
}
|
|
1300
|
+
top.i++;
|
|
1301
|
+
}
|
|
1302
|
+
if (pushed !== null) {
|
|
1303
|
+
stack.push(pushed);
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
stack.pop();
|
|
1307
|
+
const out = keepGuarded(top.value, top.next, top.changed, state);
|
|
1308
|
+
if (stack.length === 0) return out;
|
|
1309
|
+
delivered = out;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
const NOTHING = Symbol();
|
|
1313
|
+
const ARRAY = 0;
|
|
1314
|
+
const MAP = 1;
|
|
1315
|
+
const SET = 2;
|
|
1316
|
+
const OBJECT = 3;
|
|
1317
|
+
class Frame {
|
|
1318
|
+
constructor(kind, value, next, items, descriptors) {
|
|
1319
|
+
this.kind = kind;
|
|
1320
|
+
this.value = value;
|
|
1321
|
+
this.next = next;
|
|
1322
|
+
this.items = items;
|
|
1323
|
+
this.descriptors = descriptors;
|
|
1324
|
+
this.i = 0;
|
|
1325
|
+
this.changed = false;
|
|
1326
|
+
this.accessorRead = -1;
|
|
1327
|
+
this.accessorValue = undefined;
|
|
1328
|
+
this.pendingKey = undefined;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
function guardOperation(state, run) {
|
|
1332
|
+
return state.scope ? state.scope(run) : run();
|
|
1333
|
+
}
|
|
1334
|
+
function enterGuard(value, state) {
|
|
1335
|
+
if (value === null || typeof value !== "object") return value;
|
|
1336
|
+
if (state.seen.has(value)) {
|
|
1337
|
+
state.cyclic.add(value);
|
|
1338
|
+
return state.seen.get(value);
|
|
1339
|
+
}
|
|
1340
|
+
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
1341
|
+
let reader;
|
|
1342
|
+
const gate = state.gate;
|
|
1343
|
+
let finished = false;
|
|
1344
|
+
const close = () => {
|
|
1345
|
+
if (finished) return;
|
|
1346
|
+
finished = true;
|
|
1347
|
+
try {
|
|
1348
|
+
const cancelled = guardOperation(state, () => reader ? reader.cancel() : value.cancel());
|
|
1349
|
+
if (cancelled && typeof cancelled.then === "function") cancelled.then(undefined, () => {});
|
|
1350
|
+
} catch {}
|
|
1351
|
+
};
|
|
1352
|
+
const guardedStream = new ReadableStream({
|
|
1353
|
+
async pull(controller) {
|
|
1354
|
+
try {
|
|
1355
|
+
if (gate && !finished && !gate.wantsMore()) await gate.awaitDemand();
|
|
1356
|
+
if (finished) {
|
|
1357
|
+
controller.close();
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
if (!reader) reader = guardOperation(state, () => value.getReader());
|
|
1361
|
+
const {
|
|
1362
|
+
done,
|
|
1363
|
+
value: chunk
|
|
1364
|
+
} = await guardOperation(state, () => reader.read());
|
|
1365
|
+
done ? controller.close() : controller.enqueue(guardOperation(state, () => guardFailures(chunk, state)));
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
controller.error(guardOperation(state, () => sanitizeServerError(error)));
|
|
1368
|
+
}
|
|
1369
|
+
},
|
|
1370
|
+
cancel(reason) {
|
|
1371
|
+
finished = true;
|
|
1372
|
+
return guardOperation(state, () => reader ? reader.cancel(reason) : value.cancel(reason));
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
if (gate) gate.onOpen(close);
|
|
1376
|
+
state.seen.set(value, guardedStream);
|
|
1377
|
+
return guardedStream;
|
|
1378
|
+
}
|
|
1379
|
+
if (typeof value.then === "function") {
|
|
1380
|
+
const guardedPromise = Promise.resolve(value).then(resolved => guardOperation(state, () => guardFailures(resolved, state)), error => {
|
|
1381
|
+
throw guardOperation(state, () => sanitizeServerError(error));
|
|
1382
|
+
});
|
|
1383
|
+
guardedPromise.catch(() => {});
|
|
1384
|
+
state.seen.set(value, guardedPromise);
|
|
1385
|
+
return guardedPromise;
|
|
1386
|
+
}
|
|
1387
|
+
if (typeof value[Symbol.asyncIterator] === "function") {
|
|
905
1388
|
const source = value;
|
|
906
|
-
|
|
1389
|
+
const gate = state.gate;
|
|
1390
|
+
const guardedIterable = {
|
|
907
1391
|
[Symbol.asyncIterator]() {
|
|
908
|
-
const
|
|
1392
|
+
const iterator = guardOperation(state, () => source[Symbol.asyncIterator]());
|
|
909
1393
|
let finished = false;
|
|
910
|
-
|
|
1394
|
+
const close = () => {
|
|
911
1395
|
if (finished) return;
|
|
912
1396
|
finished = true;
|
|
913
1397
|
try {
|
|
914
|
-
const returned =
|
|
1398
|
+
const returned = iterator.return && guardOperation(state, () => iterator.return());
|
|
915
1399
|
if (returned && typeof returned.then === "function") returned.then(undefined, () => {});
|
|
916
1400
|
} catch {}
|
|
917
1401
|
};
|
|
918
|
-
if (
|
|
1402
|
+
if (gate) gate.onOpen(close);
|
|
1403
|
+
const step = () => finished ? Promise.resolve({
|
|
1404
|
+
done: true,
|
|
1405
|
+
value: undefined
|
|
1406
|
+
}) : guardOperation(state, () => iterator.next()).then(step => {
|
|
1407
|
+
if (step.done) {
|
|
1408
|
+
finished = true;
|
|
1409
|
+
return step;
|
|
1410
|
+
}
|
|
1411
|
+
return {
|
|
1412
|
+
done: false,
|
|
1413
|
+
value: guardOperation(state, () => guardFailures(step.value, state))
|
|
1414
|
+
};
|
|
1415
|
+
}, error => {
|
|
1416
|
+
throw guardOperation(state, () => sanitizeServerError(error));
|
|
1417
|
+
});
|
|
919
1418
|
return {
|
|
920
|
-
next: () => finished ?
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1419
|
+
next: () => finished || !gate || gate.wantsMore() ? step() : gate.awaitDemand().then(step),
|
|
1420
|
+
return: () => {
|
|
1421
|
+
close();
|
|
1422
|
+
return Promise.resolve({
|
|
1423
|
+
done: true,
|
|
1424
|
+
value: undefined
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
924
1427
|
};
|
|
925
1428
|
}
|
|
926
1429
|
};
|
|
1430
|
+
state.seen.set(value, guardedIterable);
|
|
1431
|
+
return guardedIterable;
|
|
1432
|
+
}
|
|
1433
|
+
if (state.scope && typeof value[Symbol.iterator] === "function" && !Array.isArray(value) && !(value instanceof Map) && !(value instanceof Set) && !ArrayBuffer.isView(value)) {
|
|
1434
|
+
const scopedIterable = scopeDeferredResult(value, state.scope);
|
|
1435
|
+
state.seen.set(value, scopedIterable);
|
|
1436
|
+
return scopedIterable;
|
|
1437
|
+
}
|
|
1438
|
+
if (Array.isArray(value)) {
|
|
1439
|
+
const next = value.slice();
|
|
1440
|
+
state.seen.set(value, next);
|
|
1441
|
+
return new Frame(ARRAY, value, next, value, null);
|
|
1442
|
+
}
|
|
1443
|
+
if (value instanceof Map) {
|
|
1444
|
+
const next = new Map();
|
|
1445
|
+
state.seen.set(value, next);
|
|
1446
|
+
const items = [];
|
|
1447
|
+
for (const entry of value) items.push(entry[0], entry[1]);
|
|
1448
|
+
return new Frame(MAP, value, next, items, null);
|
|
1449
|
+
}
|
|
1450
|
+
if (value instanceof Set) {
|
|
1451
|
+
const next = new Set();
|
|
1452
|
+
state.seen.set(value, next);
|
|
1453
|
+
return new Frame(SET, value, next, [...value], null);
|
|
1454
|
+
}
|
|
1455
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1456
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
1457
|
+
state.seen.set(value, value);
|
|
1458
|
+
return value;
|
|
927
1459
|
}
|
|
1460
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1461
|
+
for (const key of Object.keys(descriptors)) {
|
|
1462
|
+
descriptors[key].configurable = true;
|
|
1463
|
+
if ("value" in descriptors[key]) descriptors[key].writable = true;
|
|
1464
|
+
}
|
|
1465
|
+
const next = Object.create(prototype, descriptors);
|
|
1466
|
+
state.seen.set(value, next);
|
|
1467
|
+
return new Frame(OBJECT, value, next, Object.keys(value), descriptors);
|
|
1468
|
+
}
|
|
1469
|
+
function keepGuarded(value, next, changed, state) {
|
|
1470
|
+
if (changed || state.cyclic.has(value)) return next;
|
|
1471
|
+
state.seen.set(value, value);
|
|
1472
|
+
return value;
|
|
1473
|
+
}
|
|
1474
|
+
function serializeResponseStream(value, codecOptions, signal, scope) {
|
|
1475
|
+
let closed = false;
|
|
1476
|
+
let streamController = null;
|
|
1477
|
+
let demandWaiters = null;
|
|
1478
|
+
const wantsMore = () => streamController !== null && streamController.desiredSize > 0;
|
|
1479
|
+
const awaitDemand = () => new Promise(resolve => (demandWaiters ??= []).push(resolve));
|
|
1480
|
+
const supplyDemand = () => {
|
|
1481
|
+
const resolvers = demandWaiters;
|
|
1482
|
+
demandWaiters = null;
|
|
1483
|
+
if (resolvers) for (const resolve of resolvers) resolve();
|
|
1484
|
+
};
|
|
1485
|
+
const sourceClosers = new Set();
|
|
1486
|
+
const gate = {
|
|
1487
|
+
wantsMore,
|
|
1488
|
+
awaitDemand,
|
|
1489
|
+
onOpen(close) {
|
|
1490
|
+
if (closed) close();else sourceClosers.add(close);
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
const guardState = {
|
|
1494
|
+
seen: new WeakMap(),
|
|
1495
|
+
cyclic: new WeakSet(),
|
|
1496
|
+
gate,
|
|
1497
|
+
scope
|
|
1498
|
+
};
|
|
1499
|
+
value = guardOperation(guardState, () => guardFailures(value, guardState));
|
|
1500
|
+
let cancelSerialize = null;
|
|
1501
|
+
let onAbort = null;
|
|
1502
|
+
const finishSource = () => {
|
|
1503
|
+
for (const close of sourceClosers) close();
|
|
1504
|
+
sourceClosers.clear();
|
|
1505
|
+
supplyDemand();
|
|
1506
|
+
};
|
|
1507
|
+
const teardown = () => {
|
|
1508
|
+
if (closed) return;
|
|
1509
|
+
closed = true;
|
|
1510
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1511
|
+
if (cancelSerialize) cancelSerialize();
|
|
1512
|
+
finishSource();
|
|
1513
|
+
};
|
|
928
1514
|
return new ReadableStream({
|
|
929
1515
|
async start(controller) {
|
|
1516
|
+
streamController = controller;
|
|
930
1517
|
if (signal) {
|
|
931
1518
|
if (signal.aborted) {
|
|
932
1519
|
teardown();
|
|
@@ -962,29 +1549,54 @@ function serializeResponseStream(value, codecOptions, signal) {
|
|
|
962
1549
|
if (closed) return;
|
|
963
1550
|
closed = true;
|
|
964
1551
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1552
|
+
finishSource();
|
|
965
1553
|
controller.close();
|
|
966
1554
|
},
|
|
967
1555
|
onError(error) {
|
|
968
1556
|
if (closed) return;
|
|
969
1557
|
closed = true;
|
|
970
1558
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
971
|
-
|
|
1559
|
+
finishSource();
|
|
1560
|
+
try {
|
|
1561
|
+
const delivered = sanitizeServerError(DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error);
|
|
1562
|
+
controller.enqueue(createChunk(encodeErrorTrailer(delivered)));
|
|
1563
|
+
controller.close();
|
|
1564
|
+
} catch {
|
|
1565
|
+
try {
|
|
1566
|
+
controller.error(error);
|
|
1567
|
+
} catch {}
|
|
1568
|
+
}
|
|
972
1569
|
}
|
|
973
1570
|
});
|
|
974
1571
|
},
|
|
1572
|
+
pull() {
|
|
1573
|
+
supplyDemand();
|
|
1574
|
+
},
|
|
975
1575
|
cancel() {
|
|
976
1576
|
teardown();
|
|
977
1577
|
}
|
|
978
1578
|
});
|
|
979
1579
|
}
|
|
980
|
-
function serializedResponse(value, headers, codec, signal) {
|
|
1580
|
+
function serializedResponse(value, headers, codec, signal, scope) {
|
|
981
1581
|
headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
|
|
982
1582
|
headers.set("Content-Type", "text/plain");
|
|
983
|
-
return new Response(serializeResponseStream(value, codec, signal), {
|
|
1583
|
+
return new Response(serializeResponseStream(value, codec, signal, scope), {
|
|
984
1584
|
headers
|
|
985
1585
|
});
|
|
986
1586
|
}
|
|
987
|
-
function encodeResult(value, headers, status, codec, signal) {
|
|
1587
|
+
function encodeResult(value, headers, status, codec, signal, scope) {
|
|
1588
|
+
if (NULL_BODY_STATUSES.has(status)) {
|
|
1589
|
+
if (value === undefined || value === null) {
|
|
1590
|
+
headers.set(BODY_FORMAT_HEADER, BodyFormat.Void);
|
|
1591
|
+
return new Response(null, {
|
|
1592
|
+
status,
|
|
1593
|
+
headers
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
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.`);
|
|
1597
|
+
headers.set(ERROR_HEADER, encodeErrorHeaderValue(error.message));
|
|
1598
|
+
return encodeResult(error, headers, 500, codec, signal, scope);
|
|
1599
|
+
}
|
|
988
1600
|
const direct = getHeadersAndBody(value);
|
|
989
1601
|
if (direct) {
|
|
990
1602
|
for (const [key, val] of Object.entries(direct.headers || {})) {
|
|
@@ -1003,21 +1615,37 @@ function encodeResult(value, headers, status, codec, signal) {
|
|
|
1003
1615
|
});
|
|
1004
1616
|
}
|
|
1005
1617
|
try {
|
|
1006
|
-
|
|
1618
|
+
const jsonSafe = scope ? scope(() => isJSONSafe(value)) : isJSONSafe(value);
|
|
1619
|
+
if (jsonSafe) {
|
|
1007
1620
|
headers.set(BODY_FORMAT_HEADER, BodyFormat.Json);
|
|
1008
1621
|
headers.set("Content-Type", "application/json");
|
|
1009
|
-
|
|
1622
|
+
const body = scope ? scope(() => JSON.stringify(value)) : JSON.stringify(value);
|
|
1623
|
+
return new Response(body, {
|
|
1010
1624
|
status,
|
|
1011
1625
|
headers
|
|
1012
1626
|
});
|
|
1013
1627
|
}
|
|
1014
1628
|
} catch {
|
|
1015
1629
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
status,
|
|
1019
|
-
|
|
1020
|
-
|
|
1630
|
+
try {
|
|
1631
|
+
const response = serializedResponse(value, headers, codec, signal, scope);
|
|
1632
|
+
return status === 200 ? response : new Response(response.body, {
|
|
1633
|
+
status,
|
|
1634
|
+
headers
|
|
1635
|
+
});
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
throw DEV && error instanceof Error ? new Error(`Server function result could not be encoded: ${error.message}`) : error;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
const ERROR_HEADER_VALUE_LIMIT = 1024;
|
|
1641
|
+
function boundedErrorHeaderValue(message) {
|
|
1642
|
+
let label = message.length > 256 ? message.slice(0, 256) : message;
|
|
1643
|
+
let encoded = encodeErrorHeaderValue(label);
|
|
1644
|
+
while (encoded.length > ERROR_HEADER_VALUE_LIMIT && label.length > 1) {
|
|
1645
|
+
label = label.slice(0, Math.ceil(label.length / 2));
|
|
1646
|
+
encoded = encodeErrorHeaderValue(label);
|
|
1647
|
+
}
|
|
1648
|
+
return encoded;
|
|
1021
1649
|
}
|
|
1022
1650
|
const GENERIC_SERVER_ERROR_MESSAGE = "Internal Server Error";
|
|
1023
1651
|
let DEV = false === true;
|
|
@@ -1041,11 +1669,12 @@ function serverFunctionUrl(id, boundArgs) {
|
|
|
1041
1669
|
return `${address}?args=${encodeURIComponent(JSON.stringify(boundArgs))}`;
|
|
1042
1670
|
}
|
|
1043
1671
|
function parseServerFunctionUrl(url) {
|
|
1044
|
-
|
|
1672
|
+
const parsed = parseServerFunctionAddress(new URL(url, "http://localhost").pathname, config.endpoint);
|
|
1673
|
+
return parsed && parsed.id;
|
|
1045
1674
|
}
|
|
1046
1675
|
async function matchesOrigin(origin, request, matcher) {
|
|
1047
1676
|
if (matcher === undefined) return origin === new URL(request.url).origin;
|
|
1048
|
-
if (typeof matcher === "function") return
|
|
1677
|
+
if (typeof matcher === "function") return (await matcher(origin, request)) === true;
|
|
1049
1678
|
return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
|
|
1050
1679
|
}
|
|
1051
1680
|
async function allowsServerFunctionRequest(request, options) {
|
|
@@ -1097,14 +1726,37 @@ function forbiddenResponse() {
|
|
|
1097
1726
|
}
|
|
1098
1727
|
}));
|
|
1099
1728
|
}
|
|
1729
|
+
function nativePromise(value) {
|
|
1730
|
+
if (value instanceof Promise) return value;
|
|
1731
|
+
try {
|
|
1732
|
+
if (Object.prototype.toString.call(value) === "[object Promise]") return Promise.prototype.then.call(value, value => value);
|
|
1733
|
+
} catch {}
|
|
1734
|
+
}
|
|
1100
1735
|
async function handleServerFunctionRequest(request, options = {}) {
|
|
1101
|
-
const codec =
|
|
1736
|
+
const codec = {
|
|
1737
|
+
...(options.codec !== undefined ? options.codec : getServerFunctionsCodec())
|
|
1738
|
+
};
|
|
1739
|
+
codec.serializeErrorStacks ??= DEV;
|
|
1102
1740
|
const url = new URL(request.url);
|
|
1103
1741
|
const method = request.method;
|
|
1104
|
-
const
|
|
1742
|
+
const address = resolveAddress(url);
|
|
1743
|
+
const functionId = address && address.id;
|
|
1105
1744
|
const declaredRead = (method === "GET" || method === "HEAD") && functionId !== null && METHODS.get(functionId) === "GET";
|
|
1106
1745
|
const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
|
|
1107
|
-
const protectsRequest = csrf !== false && !declaredRead;
|
|
1746
|
+
const protectsRequest = csrf !== false && (!declaredRead || typeof csrf === "object" && csrf.protectDeclaredReads === true);
|
|
1747
|
+
let serverFunction;
|
|
1748
|
+
if (functionId) {
|
|
1749
|
+
try {
|
|
1750
|
+
serverFunction = getServerFunction(functionId);
|
|
1751
|
+
} catch {
|
|
1752
|
+
return finalizeTransportResponse(new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
1753
|
+
status: 404,
|
|
1754
|
+
headers: {
|
|
1755
|
+
[UNKNOWN_HEADER]: "true"
|
|
1756
|
+
}
|
|
1757
|
+
}), method);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1108
1760
|
if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
|
|
1109
1761
|
return finalizeTransportResponse(forbiddenResponse(), method);
|
|
1110
1762
|
}
|
|
@@ -1115,15 +1767,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1115
1767
|
});
|
|
1116
1768
|
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1117
1769
|
}
|
|
1118
|
-
|
|
1119
|
-
try {
|
|
1120
|
-
serverFunction = getServerFunction(functionId);
|
|
1121
|
-
} catch {
|
|
1122
|
-
const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
1123
|
-
status: 404
|
|
1124
|
-
});
|
|
1125
|
-
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1126
|
-
}
|
|
1770
|
+
const scripted = address.data;
|
|
1127
1771
|
if (method !== "POST" && !declaredRead) {
|
|
1128
1772
|
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
1129
1773
|
status: 405,
|
|
@@ -1133,25 +1777,103 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1133
1777
|
});
|
|
1134
1778
|
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1135
1779
|
}
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1780
|
+
const bodySizeLimit = options.bodySizeLimit !== undefined ? options.bodySizeLimit : config.bodySizeLimit;
|
|
1781
|
+
const argsEncoding = url.searchParams.get("args");
|
|
1782
|
+
if (argsEncoding !== null && argsEncoding.length > bodySizeLimit) {
|
|
1783
|
+
const response = new Response(DEV ? "Server function arguments exceed the configured bodySizeLimit" : null, {
|
|
1784
|
+
status: 413
|
|
1785
|
+
});
|
|
1786
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1787
|
+
}
|
|
1788
|
+
if (method === "POST" && request.body !== null && bodySizeLimit !== Infinity) {
|
|
1789
|
+
const raw = request.headers.get("content-length");
|
|
1790
|
+
const declared = raw !== null && /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
1791
|
+
if (declared > bodySizeLimit) {
|
|
1792
|
+
const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
|
|
1793
|
+
status: 413
|
|
1794
|
+
});
|
|
1795
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1796
|
+
}
|
|
1797
|
+
if (!(declared > 0)) {
|
|
1798
|
+
let bounded;
|
|
1799
|
+
try {
|
|
1800
|
+
bounded = await bufferBodyWithin(request, bodySizeLimit);
|
|
1801
|
+
} catch {
|
|
1802
|
+
const response = new Response(DEV ? "Malformed server function arguments" : null, {
|
|
1803
|
+
status: 400
|
|
1804
|
+
});
|
|
1805
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1806
|
+
}
|
|
1807
|
+
if (bounded === null) {
|
|
1808
|
+
const response = new Response(DEV ? "Server function request body exceeds the configured bodySizeLimit" : null, {
|
|
1809
|
+
status: 413
|
|
1810
|
+
});
|
|
1811
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1812
|
+
}
|
|
1813
|
+
request = bounded;
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
let event;
|
|
1817
|
+
try {
|
|
1818
|
+
event = options.createEvent ? options.createEvent(request) : {
|
|
1819
|
+
request,
|
|
1820
|
+
locals: {}
|
|
1821
|
+
};
|
|
1822
|
+
const promised = nativePromise(event);
|
|
1823
|
+
if (promised) event = await promised;
|
|
1824
|
+
} catch (error) {
|
|
1825
|
+
const safe = sanitizeServerError(error);
|
|
1826
|
+
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1827
|
+
const headers = new Headers();
|
|
1828
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(message));
|
|
1829
|
+
const response = scripted ? encodeResult(safe, headers, 500, codec, request.signal) : new Response(DEV ? message : null, {
|
|
1830
|
+
status: 500
|
|
1831
|
+
});
|
|
1832
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1833
|
+
}
|
|
1834
|
+
const refuseCommitted = raw => {
|
|
1835
|
+
const response = commitEventResponse(raw, event);
|
|
1836
|
+
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1139
1837
|
};
|
|
1140
1838
|
const provide = options.provideEvent || provideEvent;
|
|
1839
|
+
const scope = run => provide(event, run);
|
|
1141
1840
|
const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
|
|
1142
1841
|
const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
|
|
1143
1842
|
const wrapInvocation = options.wrapInvocation !== undefined ? options.wrapInvocation : config.wrapInvocation;
|
|
1144
1843
|
const transformFlightResult = options.transformFlightResult !== undefined ? options.transformFlightResult : config.transformFlightResult;
|
|
1145
|
-
|
|
1146
|
-
|
|
1844
|
+
let handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS;
|
|
1845
|
+
if (handleNoJS === undefined && !scripted && isFormPost(request)) {
|
|
1846
|
+
const fetchMode = request.headers.get("Sec-Fetch-Mode");
|
|
1847
|
+
if (fetchMode === null || fetchMode === "navigate") {
|
|
1848
|
+
handleNoJS = defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler());
|
|
1849
|
+
} else {
|
|
1850
|
+
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, {
|
|
1851
|
+
status: 400
|
|
1852
|
+
});
|
|
1853
|
+
return refuseCommitted(response);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
const flightHeader = scripted && method === "POST" ? request.headers.get(SINGLE_FLIGHT_HEADER) : null;
|
|
1857
|
+
const flightHooks = flightHeader ? flightHeader.split(",").flatMap(source => {
|
|
1858
|
+
const hook = source === "true" ? flightHook : flightSources.get(source);
|
|
1859
|
+
return hook ? [[source, hook]] : [];
|
|
1860
|
+
}) : [];
|
|
1861
|
+
const collectsFlight = flightHooks.length > 0;
|
|
1147
1862
|
let parsed;
|
|
1148
1863
|
try {
|
|
1149
|
-
parsed = await parseArguments(request, url,
|
|
1864
|
+
parsed = await parseArguments(request, url, scripted, codec);
|
|
1150
1865
|
} catch {
|
|
1151
1866
|
const response = new Response(DEV ? "Malformed server function arguments" : null, {
|
|
1152
1867
|
status: 400
|
|
1153
1868
|
});
|
|
1154
|
-
return
|
|
1869
|
+
return refuseCommitted(response);
|
|
1870
|
+
}
|
|
1871
|
+
const maxArguments = options.maxArguments !== undefined ? options.maxArguments : config.maxArguments;
|
|
1872
|
+
if (parsed.length > maxArguments) {
|
|
1873
|
+
const response = new Response(DEV ? "Server function call exceeds the configured maxArguments" : null, {
|
|
1874
|
+
status: 400
|
|
1875
|
+
});
|
|
1876
|
+
return refuseCommitted(response);
|
|
1155
1877
|
}
|
|
1156
1878
|
const flightContext = {
|
|
1157
1879
|
id: functionId,
|
|
@@ -1165,7 +1887,8 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1165
1887
|
const headers = new Headers();
|
|
1166
1888
|
const dispatch = async () => {
|
|
1167
1889
|
try {
|
|
1168
|
-
let
|
|
1890
|
+
let invocations = 0;
|
|
1891
|
+
const invokeOnce = async () => {
|
|
1169
1892
|
INVOCATIONS.set(event, {
|
|
1170
1893
|
id: functionId
|
|
1171
1894
|
});
|
|
@@ -1177,7 +1900,16 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1177
1900
|
request,
|
|
1178
1901
|
direct: false
|
|
1179
1902
|
}) : run();
|
|
1903
|
+
};
|
|
1904
|
+
let result = await provide(event, () => {
|
|
1905
|
+
if (++invocations > 1) {
|
|
1906
|
+
throw new Error("provideEvent invoked the server function callback more than once: a second " + "invocation would commit the call's side effects twice. The hook must call " + "fn exactly once and return its result.");
|
|
1907
|
+
}
|
|
1908
|
+
return invokeOnce();
|
|
1180
1909
|
});
|
|
1910
|
+
if (invocations !== 1) {
|
|
1911
|
+
throw new Error(invocations === 0 ? "provideEvent returned without invoking the server function callback: the call " + "would have answered as a void success without running the function. The hook " + "must call fn exactly once and return its result." : "provideEvent invoked the server function callback more than once: a second " + "invocation would commit the call's side effects twice. The hook must call " + "fn exactly once and return its result.");
|
|
1912
|
+
}
|
|
1181
1913
|
if (transformResult) {
|
|
1182
1914
|
result = await transformResult(event, result, flightContext);
|
|
1183
1915
|
}
|
|
@@ -1188,25 +1920,29 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1188
1920
|
response,
|
|
1189
1921
|
value
|
|
1190
1922
|
} = result;
|
|
1191
|
-
if (!
|
|
1923
|
+
if (!scripted && !handleNoJS && response && response.body) {
|
|
1192
1924
|
return response;
|
|
1193
1925
|
}
|
|
1194
1926
|
if (response && response.headers) {
|
|
1195
1927
|
mergeResponseHeaders(headers, response.headers);
|
|
1196
1928
|
}
|
|
1197
|
-
if (response && response.status && (
|
|
1929
|
+
if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
|
|
1198
1930
|
status = response.status;
|
|
1931
|
+
} else if (response && response.status) {
|
|
1932
|
+
maskRedirect(headers, response, request.url);
|
|
1199
1933
|
}
|
|
1200
1934
|
metadata = response;
|
|
1201
1935
|
result = value;
|
|
1202
1936
|
} else if (result instanceof Response) {
|
|
1203
1937
|
if (result.headers && result.headers.has("X-Content-Raw")) return result;
|
|
1204
|
-
if (
|
|
1938
|
+
if (scripted) {
|
|
1205
1939
|
if (result.headers) {
|
|
1206
1940
|
mergeResponseHeaders(headers, result.headers);
|
|
1207
1941
|
}
|
|
1208
|
-
if (result.status && (result.status
|
|
1942
|
+
if (result.status && !validRedirectStatuses.has(result.status)) {
|
|
1209
1943
|
status = result.status;
|
|
1944
|
+
} else if (result.status) {
|
|
1945
|
+
maskRedirect(headers, result, request.url);
|
|
1210
1946
|
}
|
|
1211
1947
|
metadata = result;
|
|
1212
1948
|
if (result.body == null) {
|
|
@@ -1215,7 +1951,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1215
1951
|
}
|
|
1216
1952
|
}
|
|
1217
1953
|
if (collectsFlight) {
|
|
1218
|
-
result = await foldFlightData(
|
|
1954
|
+
result = await foldFlightData(flightHooks, event, headers, {
|
|
1219
1955
|
id: functionId,
|
|
1220
1956
|
value: result,
|
|
1221
1957
|
response: metadata,
|
|
@@ -1224,19 +1960,37 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1224
1960
|
}, flightContext);
|
|
1225
1961
|
if (result instanceof Response && result.headers.has("X-Content-Raw")) return result;
|
|
1226
1962
|
}
|
|
1227
|
-
if (!
|
|
1228
|
-
if (handleNoJS) return handleNoJS(result, request, parsed);
|
|
1963
|
+
if (!scripted) {
|
|
1964
|
+
if (handleNoJS) return handleNoJS(result ?? metadata, request, parsed);
|
|
1229
1965
|
if (result instanceof Response) return result;
|
|
1230
|
-
return encodeResult(result, headers,
|
|
1966
|
+
return encodeResult(result, headers, status, codec, request.signal, scope);
|
|
1231
1967
|
}
|
|
1232
|
-
|
|
1968
|
+
if (status === 304) warnScripted304(functionId);
|
|
1969
|
+
return encodeResult(result, headers, status, codec, request.signal, scope);
|
|
1233
1970
|
} catch (x) {
|
|
1971
|
+
const respondThrown = value => {
|
|
1972
|
+
const safe = sanitizeServerError(value);
|
|
1973
|
+
if (!scripted) {
|
|
1974
|
+
if (handleNoJS) return handleNoJS(safe, request, parsed, true);
|
|
1975
|
+
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1976
|
+
return new Response(DEV ? message : null, {
|
|
1977
|
+
status: 500
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
const error = safe instanceof Error ? safe.message : typeof safe === "string" ? safe : "true";
|
|
1981
|
+
headers.set(ERROR_HEADER, boundedErrorHeaderValue(error));
|
|
1982
|
+
return encodeResult(safe, headers, 500, codec, request.signal, scope);
|
|
1983
|
+
};
|
|
1234
1984
|
if (x instanceof Response || isResponseEnvelope(x)) {
|
|
1235
1985
|
if (transformResult) {
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1986
|
+
try {
|
|
1987
|
+
x = await transformResult(event, x, {
|
|
1988
|
+
...flightContext,
|
|
1989
|
+
thrown: true
|
|
1990
|
+
});
|
|
1991
|
+
} catch (hookError) {
|
|
1992
|
+
return respondThrown(hookError);
|
|
1993
|
+
}
|
|
1240
1994
|
}
|
|
1241
1995
|
let status = 200;
|
|
1242
1996
|
let metadata;
|
|
@@ -1248,8 +2002,10 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1248
2002
|
if (response && response.headers) {
|
|
1249
2003
|
mergeResponseHeaders(headers, response.headers);
|
|
1250
2004
|
}
|
|
1251
|
-
if (response && response.status && (!
|
|
2005
|
+
if (response && response.status && (!scripted || !validRedirectStatuses.has(response.status))) {
|
|
1252
2006
|
status = response.status;
|
|
2007
|
+
} else if (response && response.status) {
|
|
2008
|
+
maskRedirect(headers, response, request.url);
|
|
1253
2009
|
}
|
|
1254
2010
|
metadata = response;
|
|
1255
2011
|
x = value;
|
|
@@ -1257,8 +2013,10 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1257
2013
|
if (x.headers) {
|
|
1258
2014
|
mergeResponseHeaders(headers, x.headers);
|
|
1259
2015
|
}
|
|
1260
|
-
if (x.status && (!
|
|
2016
|
+
if (x.status && (!scripted || !validRedirectStatuses.has(x.status))) {
|
|
1261
2017
|
status = x.status;
|
|
2018
|
+
} else if (x.status) {
|
|
2019
|
+
maskRedirect(headers, x, request.url);
|
|
1262
2020
|
}
|
|
1263
2021
|
metadata = x;
|
|
1264
2022
|
if (x.body == null) {
|
|
@@ -1266,7 +2024,7 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1266
2024
|
}
|
|
1267
2025
|
}
|
|
1268
2026
|
if (collectsFlight) {
|
|
1269
|
-
x = await foldFlightData(
|
|
2027
|
+
x = await foldFlightData(flightHooks, event, headers, {
|
|
1270
2028
|
id: functionId,
|
|
1271
2029
|
value: x,
|
|
1272
2030
|
response: metadata,
|
|
@@ -1274,38 +2032,38 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1274
2032
|
thrown: true
|
|
1275
2033
|
}, flightContext);
|
|
1276
2034
|
if (x instanceof Response && x.headers.has("X-Content-Raw")) {
|
|
2035
|
+
x = ownResponse(x);
|
|
1277
2036
|
x.headers.set(ERROR_HEADER, "true");
|
|
1278
2037
|
return x;
|
|
1279
2038
|
}
|
|
1280
2039
|
}
|
|
1281
2040
|
headers.set(ERROR_HEADER, "true");
|
|
1282
|
-
if (!
|
|
2041
|
+
if (!scripted) {
|
|
1283
2042
|
if (handleNoJS) return handleNoJS(x ?? metadata, request, parsed, true);
|
|
1284
2043
|
if (x instanceof Response) return x;
|
|
1285
2044
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
const safe = sanitizeServerError(x);
|
|
1289
|
-
if (!instance) {
|
|
1290
|
-
if (handleNoJS) return handleNoJS(safe, request, parsed, true);
|
|
1291
|
-
const message = safe instanceof Error ? safe.message : String(safe);
|
|
1292
|
-
return new Response(DEV ? message : null, {
|
|
1293
|
-
status: 500
|
|
1294
|
-
});
|
|
2045
|
+
if (scripted && status === 304) warnScripted304(functionId);
|
|
2046
|
+
return encodeResult(x, headers, status, codec, request.signal, scope);
|
|
1295
2047
|
}
|
|
1296
|
-
|
|
1297
|
-
headers.set(ERROR_HEADER, encodeErrorHeaderValue(error));
|
|
1298
|
-
return encodeResult(safe, headers, 200, codec, request.signal);
|
|
2048
|
+
return respondThrown(x);
|
|
1299
2049
|
}
|
|
1300
2050
|
};
|
|
1301
|
-
const response = commitEventResponse(await dispatch(), event);
|
|
2051
|
+
const response = commitEventResponse(enforceComposedHeaderInvariants(ownResponse(await dispatch())), event);
|
|
1302
2052
|
return finalizeTransportResponse(protectsRequest ? withCSRFVary(response) : response, method);
|
|
1303
2053
|
}
|
|
2054
|
+
function ownResponse(response) {
|
|
2055
|
+
try {
|
|
2056
|
+
return new Response(response.body, response);
|
|
2057
|
+
} catch {
|
|
2058
|
+
return response;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
1304
2061
|
function finalizeTransportResponse(response, method) {
|
|
1305
2062
|
const stripBody = method === "HEAD" && response.body !== null;
|
|
1306
|
-
|
|
2063
|
+
const defaultsCache = !response.headers.has("Cache-Control") && response.status !== 304;
|
|
2064
|
+
if (stripBody || defaultsCache) {
|
|
1307
2065
|
try {
|
|
1308
|
-
if (
|
|
2066
|
+
if (defaultsCache) {
|
|
1309
2067
|
response.headers.set("Cache-Control", "no-store");
|
|
1310
2068
|
}
|
|
1311
2069
|
if (!stripBody) return response;
|
|
@@ -1317,7 +2075,7 @@ function finalizeTransportResponse(response, method) {
|
|
|
1317
2075
|
});
|
|
1318
2076
|
} catch {
|
|
1319
2077
|
const headers = new Headers(response.headers);
|
|
1320
|
-
if (!headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
|
|
2078
|
+
if (defaultsCache && !headers.has("Cache-Control")) headers.set("Cache-Control", "no-store");
|
|
1321
2079
|
if (stripBody) response.body.cancel().catch(() => {});
|
|
1322
2080
|
return new Response(stripBody ? null : response.body, {
|
|
1323
2081
|
status: response.status,
|
|
@@ -1329,4 +2087,4 @@ function finalizeTransportResponse(response, method) {
|
|
|
1329
2087
|
return response;
|
|
1330
2088
|
}
|
|
1331
2089
|
|
|
1332
|
-
export { ERROR_HEADER, FLASH_COOKIE, GENERIC_SERVER_ERROR_MESSAGE, GET, INSTANCE_HEADER, SERVER_FUNCTION_INVOKE, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getEventServerFunctionInvocation, getServerFunction, getServerFunctionInvocation, getServerFunctionMetadata, handleServerFunctionRequest, hasFlashCookie, invoke, isServerFunction, live, observeServerFunctionCalls, parseServerFunctionUrl, registerServerFunction, registerServerReference, sanitizeServerError, serializeResponseStream, serverFunctionUrl, setServerFunctionsDev, subscribeFlightData, withMeta };
|
|
2090
|
+
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 };
|