@uniflowed/router 0.6.0 → 0.8.0
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.
|
@@ -233,13 +233,13 @@ export function createActionDispatcher(options: {|
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
const body = await readBoundedText(request, MAX_ACTION_BODY_BYTES);
|
|
236
|
-
if (body
|
|
237
|
-
return refusal(413);
|
|
236
|
+
if (body.kind !== "text") {
|
|
237
|
+
return refusal(body.kind === "too-large" ? 413 : 400);
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
let args: Array<ActionArgument>;
|
|
241
241
|
try {
|
|
242
|
-
args = decodeActionArguments(body);
|
|
242
|
+
args = decodeActionArguments(body.value);
|
|
243
243
|
} catch (error) {
|
|
244
244
|
// The reason is the sender's own payload described back to them, which
|
|
245
245
|
// is a thing to write in a log and not a thing to answer with.
|
|
@@ -344,10 +344,10 @@ async function nativeFormPost(
|
|
|
344
344
|
// A copy, so that a form which turns out not to be an action post reaches the
|
|
345
345
|
// route handler with its body unread.
|
|
346
346
|
const text = await readBoundedText(request.clone(), MAX_ACTION_BODY_BYTES);
|
|
347
|
-
if (text
|
|
347
|
+
if (text.kind !== "text") {
|
|
348
348
|
return null;
|
|
349
349
|
}
|
|
350
|
-
const post = readFormPost(new URLSearchParams(text));
|
|
350
|
+
const post = readFormPost(new URLSearchParams(text.value));
|
|
351
351
|
if (post == null) {
|
|
352
352
|
return null;
|
|
353
353
|
}
|
|
@@ -659,12 +659,20 @@ function isMedia(declared: string | null, expected: string): boolean {
|
|
|
659
659
|
* each bad sequence with U+FFFD, which is `docs/security.md`'s row about
|
|
660
660
|
* non-UTF-8 input applied at the one place uf decodes bytes a client sent.
|
|
661
661
|
*/
|
|
662
|
-
|
|
662
|
+
type BoundedText =
|
|
663
|
+
| {| readonly kind: "text", readonly value: string |}
|
|
664
|
+
| {| readonly kind: "too-large" |}
|
|
665
|
+
| {| readonly kind: "invalid" |};
|
|
666
|
+
|
|
667
|
+
async function readBoundedText(request: Request, limit: number): Promise<BoundedText> {
|
|
663
668
|
const declared = request.headers.get("content-length");
|
|
664
669
|
if (declared != null) {
|
|
665
670
|
const size = Number(declared);
|
|
666
|
-
if (!Number.isInteger(size) || size < 0
|
|
667
|
-
return
|
|
671
|
+
if (!Number.isInteger(size) || size < 0) {
|
|
672
|
+
return { kind: "invalid" };
|
|
673
|
+
}
|
|
674
|
+
if (size > limit) {
|
|
675
|
+
return { kind: "too-large" };
|
|
668
676
|
}
|
|
669
677
|
}
|
|
670
678
|
|
|
@@ -675,7 +683,7 @@ async function readBoundedText(request: Request, limit: number): Promise<string
|
|
|
675
683
|
// rather than written here.
|
|
676
684
|
const body: ReadableStream | null = (request as $FlowFixMe).body;
|
|
677
685
|
if (body == null) {
|
|
678
|
-
return "";
|
|
686
|
+
return { kind: "text", value: "" };
|
|
679
687
|
}
|
|
680
688
|
|
|
681
689
|
const reader = body.getReader();
|
|
@@ -695,12 +703,12 @@ async function readBoundedText(request: Request, limit: number): Promise<string
|
|
|
695
703
|
// declarations uf checks against require one, and "too large" is what
|
|
696
704
|
// a cancelled read of an oversized body is about.
|
|
697
705
|
await reader.cancel("the body is larger than a server action accepts");
|
|
698
|
-
return
|
|
706
|
+
return { kind: "too-large" };
|
|
699
707
|
}
|
|
700
708
|
chunks.push(chunk);
|
|
701
709
|
}
|
|
702
710
|
} catch {
|
|
703
|
-
return
|
|
711
|
+
return { kind: "invalid" };
|
|
704
712
|
}
|
|
705
713
|
|
|
706
714
|
const joined = new Uint8Array(total);
|
|
@@ -710,9 +718,9 @@ async function readBoundedText(request: Request, limit: number): Promise<string
|
|
|
710
718
|
at += chunk.byteLength;
|
|
711
719
|
}
|
|
712
720
|
try {
|
|
713
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(joined);
|
|
721
|
+
return { kind: "text", value: new TextDecoder("utf-8", { fatal: true }).decode(joined) };
|
|
714
722
|
} catch {
|
|
715
|
-
return
|
|
723
|
+
return { kind: "invalid" };
|
|
716
724
|
}
|
|
717
725
|
}
|
|
718
726
|
|
|
@@ -24,12 +24,19 @@ export function hydrationOptions(
|
|
|
24
24
|
recovery: ?Recovery,
|
|
25
25
|
formState: FormState | void,
|
|
26
26
|
): HydrationOptions | void {
|
|
27
|
-
|
|
27
|
+
// React's default reporter dispatches a global error for a boundary it
|
|
28
|
+
// recovers by rendering on the client. Deno's Node compatibility layer
|
|
29
|
+
// treats that dispatch as an uncaught exception and exits the process.
|
|
30
|
+
// Keep the error visible without turning successful recovery into a crash.
|
|
31
|
+
const deno = "Deno" in globalThis;
|
|
32
|
+
if (recovery == null && formState == null && !deno) {
|
|
28
33
|
return undefined;
|
|
29
34
|
}
|
|
30
35
|
const options: HydrationOptions = {};
|
|
31
36
|
if (recovery != null) {
|
|
32
37
|
options.onRecoverableError = recovery;
|
|
38
|
+
} else if (deno) {
|
|
39
|
+
options.onRecoverableError = (error) => console.error(error);
|
|
33
40
|
}
|
|
34
41
|
if (formState != null) {
|
|
35
42
|
options.formState = formState;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniflowed/router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "The file-system router for Flow React applications: matching, layouts, loaders, navigation, server rendering and hydration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
|
-
"@uniflowed/hooks": "0.
|
|
77
|
-
"@uniflowed/server": "0.
|
|
78
|
-
"@uniflowed/validator": "0.
|
|
76
|
+
"@uniflowed/hooks": "0.8.0",
|
|
77
|
+
"@uniflowed/server": "0.8.0",
|
|
78
|
+
"@uniflowed/validator": "0.8.0"
|
|
79
79
|
}
|
|
80
80
|
}
|