@reckona/mreact-server 0.0.180 → 0.0.181
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/flight.d.ts.map +1 -1
- package/dist/flight.js +102 -35
- package/dist/flight.js.map +1 -1
- package/package.json +3 -3
- package/src/flight.ts +194 -88
package/src/flight.ts
CHANGED
|
@@ -88,6 +88,10 @@ export interface ServerActionHandlerOptions {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
|
|
91
|
+
const MAX_SERVER_ACTION_ARGUMENT_DEPTH = 64;
|
|
92
|
+
const MAX_SERVER_ACTION_ARGUMENT_ARRAY_LENGTH = 2_000;
|
|
93
|
+
const MAX_SERVER_ACTION_ARGUMENT_OBJECT_KEYS = 200;
|
|
94
|
+
const SERVER_ACTION_FORBIDDEN_JSON_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
91
95
|
|
|
92
96
|
/** Options for embedding a serialized Flight response in a script tag. */
|
|
93
97
|
export interface FlightScriptOptions {
|
|
@@ -274,8 +278,38 @@ export type FlightTypedArrayName =
|
|
|
274
278
|
| "BigInt64Array"
|
|
275
279
|
| "BigUint64Array";
|
|
276
280
|
|
|
277
|
-
const reactFlightBinaryRowTags = [
|
|
278
|
-
|
|
281
|
+
const reactFlightBinaryRowTags = [
|
|
282
|
+
"A",
|
|
283
|
+
"O",
|
|
284
|
+
"o",
|
|
285
|
+
"U",
|
|
286
|
+
"S",
|
|
287
|
+
"s",
|
|
288
|
+
"L",
|
|
289
|
+
"l",
|
|
290
|
+
"G",
|
|
291
|
+
"g",
|
|
292
|
+
"M",
|
|
293
|
+
"m",
|
|
294
|
+
"V",
|
|
295
|
+
] as const;
|
|
296
|
+
const reactFlightRowTags = [
|
|
297
|
+
"C",
|
|
298
|
+
"D",
|
|
299
|
+
"E",
|
|
300
|
+
"F",
|
|
301
|
+
"H",
|
|
302
|
+
"I",
|
|
303
|
+
"J",
|
|
304
|
+
"N",
|
|
305
|
+
"P",
|
|
306
|
+
"R",
|
|
307
|
+
"T",
|
|
308
|
+
"W",
|
|
309
|
+
"X",
|
|
310
|
+
"x",
|
|
311
|
+
"r",
|
|
312
|
+
] as const;
|
|
279
313
|
const reactFlightModelTokens = [
|
|
280
314
|
"$",
|
|
281
315
|
"$$",
|
|
@@ -425,8 +459,7 @@ export function createServerActionHandler(
|
|
|
425
459
|
const allowedActionKeys = options.allowedActions?.map((reference) =>
|
|
426
460
|
serverActionKey(reference.moduleId, reference.exportName),
|
|
427
461
|
);
|
|
428
|
-
const allowedActionSet =
|
|
429
|
-
allowedActionKeys === undefined ? undefined : new Set(allowedActionKeys);
|
|
462
|
+
const allowedActionSet = allowedActionKeys === undefined ? undefined : new Set(allowedActionKeys);
|
|
430
463
|
|
|
431
464
|
return async (request: Request): Promise<Response> => {
|
|
432
465
|
if (request.method !== "POST") {
|
|
@@ -485,7 +518,20 @@ export function createServerActionHandler(
|
|
|
485
518
|
const action = getServerAction(actionEntry);
|
|
486
519
|
const validateArgs = getServerActionArgsValidator(actionEntry);
|
|
487
520
|
const boundArgs = Array.isArray(payload.bound) ? payload.bound : [];
|
|
488
|
-
const
|
|
521
|
+
const extraArgs = Array.isArray(payload.args) ? payload.args : [];
|
|
522
|
+
const argsStructure = validateServerActionJsonArgumentStructure(boundArgs, extraArgs);
|
|
523
|
+
|
|
524
|
+
if (!argsStructure.valid) {
|
|
525
|
+
return jsonResponse(
|
|
526
|
+
{
|
|
527
|
+
ok: false,
|
|
528
|
+
error: "Invalid server action argument structure.",
|
|
529
|
+
},
|
|
530
|
+
400,
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const args = [...boundArgs, ...extraArgs];
|
|
489
535
|
const validationResult = validateArgs?.(args);
|
|
490
536
|
|
|
491
537
|
if (validationResult !== undefined && validationResult !== true) {
|
|
@@ -501,11 +547,7 @@ export function createServerActionHandler(
|
|
|
501
547
|
);
|
|
502
548
|
}
|
|
503
549
|
|
|
504
|
-
const authorizationResult = await options.authorize?.(
|
|
505
|
-
request,
|
|
506
|
-
reference,
|
|
507
|
-
args,
|
|
508
|
-
);
|
|
550
|
+
const authorizationResult = await options.authorize?.(request, reference, args);
|
|
509
551
|
|
|
510
552
|
if (authorizationResult !== undefined && authorizationResult !== true) {
|
|
511
553
|
return jsonResponse(
|
|
@@ -580,9 +622,10 @@ export function toReactFlightRows(response: FlightResponse): string {
|
|
|
580
622
|
rows.push(
|
|
581
623
|
`${formatReactFlightId(wireId)}:F${JSON.stringify({
|
|
582
624
|
id: serverActionKey(reference.moduleId, reference.exportName),
|
|
583
|
-
bound:
|
|
584
|
-
|
|
585
|
-
|
|
625
|
+
bound:
|
|
626
|
+
reference.bound === undefined
|
|
627
|
+
? null
|
|
628
|
+
: reference.bound.map((value) => encodeReactFlightModel(value, state)),
|
|
586
629
|
name: reference.exportName,
|
|
587
630
|
})}`,
|
|
588
631
|
);
|
|
@@ -637,7 +680,9 @@ export function fromReactFlightRows(rows: string): FlightResponse {
|
|
|
637
680
|
}
|
|
638
681
|
|
|
639
682
|
if (row.tag === "F") {
|
|
640
|
-
serverReferences.push(
|
|
683
|
+
serverReferences.push(
|
|
684
|
+
parseReactFlightServerReference(row.id, row.payload, modelChunks, errorChunks),
|
|
685
|
+
);
|
|
641
686
|
continue;
|
|
642
687
|
}
|
|
643
688
|
|
|
@@ -687,10 +732,7 @@ export function fromReactFlightRows(rows: string): FlightResponse {
|
|
|
687
732
|
}
|
|
688
733
|
|
|
689
734
|
/** Merges additional React Flight row text into an existing Flight response. */
|
|
690
|
-
export function mergeReactFlightRows(
|
|
691
|
-
response: FlightResponse,
|
|
692
|
-
rows: string,
|
|
693
|
-
): FlightResponse {
|
|
735
|
+
export function mergeReactFlightRows(response: FlightResponse, rows: string): FlightResponse {
|
|
694
736
|
// Issue 081 note: same finding as `fromReactFlightRows` — the native
|
|
695
737
|
// merge path is slower than the JS one given the double-JSON tax.
|
|
696
738
|
const modelChunks = new Map<number, unknown>();
|
|
@@ -707,7 +749,9 @@ export function mergeReactFlightRows(
|
|
|
707
749
|
}
|
|
708
750
|
|
|
709
751
|
if (row.tag === "F") {
|
|
710
|
-
serverReferences.push(
|
|
752
|
+
serverReferences.push(
|
|
753
|
+
parseReactFlightServerReference(row.id, row.payload, modelChunks, errorChunks),
|
|
754
|
+
);
|
|
711
755
|
continue;
|
|
712
756
|
}
|
|
713
757
|
|
|
@@ -773,9 +817,7 @@ export function renderFlightPreloadLinks(
|
|
|
773
817
|
seen.add(chunk);
|
|
774
818
|
return true;
|
|
775
819
|
})
|
|
776
|
-
.map((chunk) =>
|
|
777
|
-
`<link rel="modulepreload" href="${escapeAttribute(chunk)}"${nonceAttribute}>`,
|
|
778
|
-
)
|
|
820
|
+
.map((chunk) => `<link rel="modulepreload" href="${escapeAttribute(chunk)}"${nonceAttribute}>`)
|
|
779
821
|
.join("");
|
|
780
822
|
}
|
|
781
823
|
|
|
@@ -885,11 +927,7 @@ interface ReactFlightEncodingState {
|
|
|
885
927
|
}
|
|
886
928
|
|
|
887
929
|
function encodeReactFlightModel(model: FlightModel, state: ReactFlightEncodingState): unknown {
|
|
888
|
-
if (
|
|
889
|
-
model === null ||
|
|
890
|
-
typeof model === "number" ||
|
|
891
|
-
typeof model === "boolean"
|
|
892
|
-
) {
|
|
930
|
+
if (model === null || typeof model === "number" || typeof model === "boolean") {
|
|
893
931
|
return model;
|
|
894
932
|
}
|
|
895
933
|
|
|
@@ -967,7 +1005,9 @@ function encodeReactFlightModel(model: FlightModel, state: ReactFlightEncodingSt
|
|
|
967
1005
|
if (model.kind === "error") {
|
|
968
1006
|
const id = state.nextWireId;
|
|
969
1007
|
state.nextWireId += 1;
|
|
970
|
-
state.outlineRows.push(
|
|
1008
|
+
state.outlineRows.push(
|
|
1009
|
+
`${formatReactFlightId(id)}:E${JSON.stringify(encodeReactFlightError(model))}`,
|
|
1010
|
+
);
|
|
971
1011
|
return `$Z${formatReactFlightId(id)}`;
|
|
972
1012
|
}
|
|
973
1013
|
|
|
@@ -999,10 +1039,7 @@ function encodeReactFlightModel(model: FlightModel, state: ReactFlightEncodingSt
|
|
|
999
1039
|
return encodeReactFlightProps(model, state);
|
|
1000
1040
|
}
|
|
1001
1041
|
|
|
1002
|
-
function allocateReactFlightOutlineRow(
|
|
1003
|
-
state: ReactFlightEncodingState,
|
|
1004
|
-
payload: unknown,
|
|
1005
|
-
): number {
|
|
1042
|
+
function allocateReactFlightOutlineRow(state: ReactFlightEncodingState, payload: unknown): number {
|
|
1006
1043
|
const id = state.nextWireId;
|
|
1007
1044
|
state.nextWireId += 1;
|
|
1008
1045
|
state.outlineRows.push(`${formatReactFlightId(id)}:${JSON.stringify(payload)}`);
|
|
@@ -1031,10 +1068,7 @@ function encodeReactFlightProps(
|
|
|
1031
1068
|
return Object.fromEntries(
|
|
1032
1069
|
Object.entries(props)
|
|
1033
1070
|
.filter((entry): entry is [string, FlightModel] => entry[1] !== undefined)
|
|
1034
|
-
.map(([key, value]) => [
|
|
1035
|
-
key,
|
|
1036
|
-
encodeReactFlightModel(value, state),
|
|
1037
|
-
]),
|
|
1071
|
+
.map(([key, value]) => [key, encodeReactFlightModel(value, state)]),
|
|
1038
1072
|
);
|
|
1039
1073
|
}
|
|
1040
1074
|
|
|
@@ -1083,7 +1117,7 @@ function parseReactFlightRow(line: string): ReactFlightRow {
|
|
|
1083
1117
|
}
|
|
1084
1118
|
|
|
1085
1119
|
function looksLikeUnsupportedReactFlightTag(tag: string, body: string): boolean {
|
|
1086
|
-
return /^[A-Z]$/.test(tag) && (body[1] === "{" || body[1] === "[" || body[1] === "
|
|
1120
|
+
return /^[A-Z]$/.test(tag) && (body[1] === "{" || body[1] === "[" || body[1] === '"');
|
|
1087
1121
|
}
|
|
1088
1122
|
|
|
1089
1123
|
function parseReactFlightTextChunk(payload: string): string {
|
|
@@ -1201,7 +1235,9 @@ function createReactFlightBinaryModel(
|
|
|
1201
1235
|
};
|
|
1202
1236
|
}
|
|
1203
1237
|
|
|
1204
|
-
function getReactFlightTypedArrayName(
|
|
1238
|
+
function getReactFlightTypedArrayName(
|
|
1239
|
+
tag: Exclude<ReactFlightBinaryRowTag, "A" | "V">,
|
|
1240
|
+
): FlightTypedArrayName {
|
|
1205
1241
|
switch (tag) {
|
|
1206
1242
|
case "O":
|
|
1207
1243
|
return "Int8Array";
|
|
@@ -1344,11 +1380,7 @@ function decodeReactFlightModel(
|
|
|
1344
1380
|
context: FlightDecodeContext = createFlightDecodeContext(),
|
|
1345
1381
|
): FlightModel {
|
|
1346
1382
|
if (depth > MAX_FLIGHT_DECODE_DEPTH) flightTooDeep();
|
|
1347
|
-
if (
|
|
1348
|
-
value === null ||
|
|
1349
|
-
typeof value === "number" ||
|
|
1350
|
-
typeof value === "boolean"
|
|
1351
|
-
) {
|
|
1383
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1352
1384
|
return value;
|
|
1353
1385
|
}
|
|
1354
1386
|
|
|
@@ -1457,10 +1489,18 @@ function decodeReactFlightString(
|
|
|
1457
1489
|
}
|
|
1458
1490
|
|
|
1459
1491
|
if (/^\$Q[0-9a-f]+$/i.test(value)) {
|
|
1460
|
-
const decoded = decodeReactFlightChunk(
|
|
1492
|
+
const decoded = decodeReactFlightChunk(
|
|
1493
|
+
value.slice(2),
|
|
1494
|
+
modelChunks,
|
|
1495
|
+
errorChunks,
|
|
1496
|
+
depth + 1,
|
|
1497
|
+
context,
|
|
1498
|
+
);
|
|
1461
1499
|
const entries = Array.isArray(decoded)
|
|
1462
1500
|
? decoded.map((entry): [FlightModel, FlightModel] =>
|
|
1463
|
-
Array.isArray(entry)
|
|
1501
|
+
Array.isArray(entry)
|
|
1502
|
+
? [entry[0] ?? { kind: "undefined" }, entry[1] ?? { kind: "undefined" }]
|
|
1503
|
+
: [entry, { kind: "undefined" }],
|
|
1464
1504
|
)
|
|
1465
1505
|
: [];
|
|
1466
1506
|
|
|
@@ -1471,7 +1511,13 @@ function decodeReactFlightString(
|
|
|
1471
1511
|
}
|
|
1472
1512
|
|
|
1473
1513
|
if (/^\$W[0-9a-f]+$/i.test(value)) {
|
|
1474
|
-
const decoded = decodeReactFlightChunk(
|
|
1514
|
+
const decoded = decodeReactFlightChunk(
|
|
1515
|
+
value.slice(2),
|
|
1516
|
+
modelChunks,
|
|
1517
|
+
errorChunks,
|
|
1518
|
+
depth + 1,
|
|
1519
|
+
context,
|
|
1520
|
+
);
|
|
1475
1521
|
|
|
1476
1522
|
return {
|
|
1477
1523
|
kind: "set",
|
|
@@ -1480,7 +1526,13 @@ function decodeReactFlightString(
|
|
|
1480
1526
|
}
|
|
1481
1527
|
|
|
1482
1528
|
if (/^\$K[0-9a-f]+$/i.test(value)) {
|
|
1483
|
-
const decoded = decodeReactFlightChunk(
|
|
1529
|
+
const decoded = decodeReactFlightChunk(
|
|
1530
|
+
value.slice(2),
|
|
1531
|
+
modelChunks,
|
|
1532
|
+
errorChunks,
|
|
1533
|
+
depth + 1,
|
|
1534
|
+
context,
|
|
1535
|
+
);
|
|
1484
1536
|
const entries = Array.isArray(decoded)
|
|
1485
1537
|
? decoded.flatMap((entry): [string, FlightModel][] =>
|
|
1486
1538
|
Array.isArray(entry) && typeof entry[0] === "string"
|
|
@@ -1496,7 +1548,13 @@ function decodeReactFlightString(
|
|
|
1496
1548
|
}
|
|
1497
1549
|
|
|
1498
1550
|
if (/^\$i[0-9a-f]+$/i.test(value)) {
|
|
1499
|
-
const decoded = decodeReactFlightChunk(
|
|
1551
|
+
const decoded = decodeReactFlightChunk(
|
|
1552
|
+
value.slice(2),
|
|
1553
|
+
modelChunks,
|
|
1554
|
+
errorChunks,
|
|
1555
|
+
depth + 1,
|
|
1556
|
+
context,
|
|
1557
|
+
);
|
|
1500
1558
|
|
|
1501
1559
|
return {
|
|
1502
1560
|
kind: "iterable",
|
|
@@ -1505,11 +1563,13 @@ function decodeReactFlightString(
|
|
|
1505
1563
|
}
|
|
1506
1564
|
|
|
1507
1565
|
if (/^\$Z[0-9a-f]+$/i.test(value)) {
|
|
1508
|
-
return
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1566
|
+
return (
|
|
1567
|
+
errorChunks.get(parseReactFlightId(value.slice(2))) ?? {
|
|
1568
|
+
kind: "error",
|
|
1569
|
+
name: "Error",
|
|
1570
|
+
message: "Unknown React Flight error.",
|
|
1571
|
+
}
|
|
1572
|
+
);
|
|
1513
1573
|
}
|
|
1514
1574
|
|
|
1515
1575
|
if (value === "$Y" || value.startsWith("$E")) {
|
|
@@ -1628,10 +1688,7 @@ function encodeReactFlightError(model: FlightErrorModel): Record<string, unknown
|
|
|
1628
1688
|
|
|
1629
1689
|
function isFlightErrorModel(model: FlightModel): model is FlightErrorModel {
|
|
1630
1690
|
return (
|
|
1631
|
-
typeof model === "object" &&
|
|
1632
|
-
model !== null &&
|
|
1633
|
-
!Array.isArray(model) &&
|
|
1634
|
-
model.kind === "error"
|
|
1691
|
+
typeof model === "object" && model !== null && !Array.isArray(model) && model.kind === "error"
|
|
1635
1692
|
);
|
|
1636
1693
|
}
|
|
1637
1694
|
|
|
@@ -1664,10 +1721,7 @@ async function serializeFlightValue(
|
|
|
1664
1721
|
return { kind: "undefined" };
|
|
1665
1722
|
}
|
|
1666
1723
|
|
|
1667
|
-
if (
|
|
1668
|
-
typeof awaited === "string" ||
|
|
1669
|
-
typeof awaited === "boolean"
|
|
1670
|
-
) {
|
|
1724
|
+
if (typeof awaited === "string" || typeof awaited === "boolean") {
|
|
1671
1725
|
return awaited;
|
|
1672
1726
|
}
|
|
1673
1727
|
|
|
@@ -1722,10 +1776,13 @@ async function serializeFlightValue(
|
|
|
1722
1776
|
return {
|
|
1723
1777
|
kind: "map",
|
|
1724
1778
|
entries: await Promise.all(
|
|
1725
|
-
Array.from(awaited.entries()).map(
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1779
|
+
Array.from(awaited.entries()).map(
|
|
1780
|
+
async ([key, value]) =>
|
|
1781
|
+
[
|
|
1782
|
+
await serializeFlightValue(key, state, depth + 1),
|
|
1783
|
+
await serializeFlightValue(value, state, depth + 1),
|
|
1784
|
+
] as [FlightModel, FlightModel],
|
|
1785
|
+
),
|
|
1729
1786
|
),
|
|
1730
1787
|
};
|
|
1731
1788
|
}
|
|
@@ -1743,10 +1800,10 @@ async function serializeFlightValue(
|
|
|
1743
1800
|
return {
|
|
1744
1801
|
kind: "form-data",
|
|
1745
1802
|
entries: await Promise.all(
|
|
1746
|
-
Array.from(awaited.entries()).map(
|
|
1747
|
-
key,
|
|
1748
|
-
|
|
1749
|
-
|
|
1803
|
+
Array.from(awaited.entries()).map(
|
|
1804
|
+
async ([key, value]) =>
|
|
1805
|
+
[key, await serializeFlightValue(value, state, depth + 1)] as [string, FlightModel],
|
|
1806
|
+
),
|
|
1750
1807
|
),
|
|
1751
1808
|
};
|
|
1752
1809
|
}
|
|
@@ -1823,10 +1880,9 @@ async function serializeProps(
|
|
|
1823
1880
|
depth: number,
|
|
1824
1881
|
): Promise<Record<string, FlightModel>> {
|
|
1825
1882
|
const entries = await Promise.all(
|
|
1826
|
-
Object.entries(props).map(
|
|
1827
|
-
key,
|
|
1828
|
-
|
|
1829
|
-
] as const),
|
|
1883
|
+
Object.entries(props).map(
|
|
1884
|
+
async ([key, value]) => [key, await serializeFlightValue(value, state, depth + 1)] as const,
|
|
1885
|
+
),
|
|
1830
1886
|
);
|
|
1831
1887
|
|
|
1832
1888
|
return Object.fromEntries(entries);
|
|
@@ -1839,18 +1895,14 @@ async function serializeObject(
|
|
|
1839
1895
|
): Promise<FlightObjectModel> {
|
|
1840
1896
|
return Object.fromEntries(
|
|
1841
1897
|
await Promise.all(
|
|
1842
|
-
Object.entries(object).map(
|
|
1843
|
-
key,
|
|
1844
|
-
|
|
1845
|
-
] as const),
|
|
1898
|
+
Object.entries(object).map(
|
|
1899
|
+
async ([key, value]) => [key, await serializeFlightValue(value, state, depth + 1)] as const,
|
|
1900
|
+
),
|
|
1846
1901
|
),
|
|
1847
1902
|
) as FlightObjectModel;
|
|
1848
1903
|
}
|
|
1849
1904
|
|
|
1850
|
-
function getClientReferenceId(
|
|
1851
|
-
reference: ClientReference,
|
|
1852
|
-
state: FlightSerializationState,
|
|
1853
|
-
): number {
|
|
1905
|
+
function getClientReferenceId(reference: ClientReference, state: FlightSerializationState): number {
|
|
1854
1906
|
const key = `${reference.moduleId}:${reference.exportName}`;
|
|
1855
1907
|
const existing = state.clientReferenceIndexes.get(key);
|
|
1856
1908
|
|
|
@@ -1873,9 +1925,10 @@ async function getServerReferenceId(
|
|
|
1873
1925
|
reference: ServerReference,
|
|
1874
1926
|
state: FlightSerializationState,
|
|
1875
1927
|
): Promise<number> {
|
|
1876
|
-
const serializedBound =
|
|
1877
|
-
|
|
1878
|
-
|
|
1928
|
+
const serializedBound =
|
|
1929
|
+
reference.bound === undefined
|
|
1930
|
+
? undefined
|
|
1931
|
+
: await Promise.all(reference.bound.map((value) => serializeFlightValue(value, state, 0)));
|
|
1879
1932
|
const key = `${reference.moduleId}:${reference.exportName}:${JSON.stringify(serializedBound ?? null)}`;
|
|
1880
1933
|
const existing = state.serverReferenceIndexes.get(key);
|
|
1881
1934
|
|
|
@@ -1971,6 +2024,62 @@ async function readServerActionPayload(
|
|
|
1971
2024
|
}
|
|
1972
2025
|
}
|
|
1973
2026
|
|
|
2027
|
+
function validateServerActionJsonArgumentStructure(
|
|
2028
|
+
boundArgs: readonly unknown[],
|
|
2029
|
+
args: readonly unknown[],
|
|
2030
|
+
): { valid: true } | { valid: false } {
|
|
2031
|
+
const stack: { depth: number; value: unknown }[] = [
|
|
2032
|
+
{ depth: 0, value: boundArgs },
|
|
2033
|
+
{ depth: 0, value: args },
|
|
2034
|
+
];
|
|
2035
|
+
const seen = new WeakSet<object>();
|
|
2036
|
+
|
|
2037
|
+
while (stack.length > 0) {
|
|
2038
|
+
const current = stack.pop();
|
|
2039
|
+
if (current === undefined) {
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
if (current.depth > MAX_SERVER_ACTION_ARGUMENT_DEPTH) {
|
|
2044
|
+
return { valid: false };
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
if (current.value === null || typeof current.value !== "object") {
|
|
2048
|
+
continue;
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
if (seen.has(current.value)) {
|
|
2052
|
+
continue;
|
|
2053
|
+
}
|
|
2054
|
+
seen.add(current.value);
|
|
2055
|
+
|
|
2056
|
+
if (Array.isArray(current.value)) {
|
|
2057
|
+
if (current.value.length > MAX_SERVER_ACTION_ARGUMENT_ARRAY_LENGTH) {
|
|
2058
|
+
return { valid: false };
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
for (const item of current.value) {
|
|
2062
|
+
stack.push({ depth: current.depth + 1, value: item });
|
|
2063
|
+
}
|
|
2064
|
+
continue;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
const entries = Object.entries(current.value);
|
|
2068
|
+
if (entries.length > MAX_SERVER_ACTION_ARGUMENT_OBJECT_KEYS) {
|
|
2069
|
+
return { valid: false };
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
for (const [key, value] of entries) {
|
|
2073
|
+
if (SERVER_ACTION_FORBIDDEN_JSON_KEYS.has(key)) {
|
|
2074
|
+
return { valid: false };
|
|
2075
|
+
}
|
|
2076
|
+
stack.push({ depth: current.depth + 1, value });
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
return { valid: true };
|
|
2081
|
+
}
|
|
2082
|
+
|
|
1974
2083
|
function getServerAction(entry: ServerAction | ServerActionDescriptor): ServerAction {
|
|
1975
2084
|
return typeof entry === "function" ? entry : entry.action;
|
|
1976
2085
|
}
|
|
@@ -2078,10 +2187,7 @@ function validateServerActionNonce(
|
|
|
2078
2187
|
|
|
2079
2188
|
if (replayProtection.seen.has(nonce)) {
|
|
2080
2189
|
return {
|
|
2081
|
-
response: jsonResponse(
|
|
2082
|
-
{ ok: false, error: "Server action nonce was already used." },
|
|
2083
|
-
409,
|
|
2084
|
-
),
|
|
2190
|
+
response: jsonResponse({ ok: false, error: "Server action nonce was already used." }, 409),
|
|
2085
2191
|
};
|
|
2086
2192
|
}
|
|
2087
2193
|
|