Skip to content

Commit 87a5e5e

Browse files
Allow fetching ArrayBufferView request body types (#7065)
* Allow all supported request body types * Calculating a hash from the body of a cloned request * Add Blob to globals * Hash FormData * Remove unnecessary await * Only a string and ArrayBufferView as request body * Create itchy-jobs-report.md * Create tricky-experts-tan.md Co-authored-by: Rich Harris <[email protected]>
1 parent 2f107df commit 87a5e5e

File tree

16 files changed

+100
-22
lines changed

16 files changed

+100
-22
lines changed

.changeset/itchy-jobs-report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sveltejs/kit": patch
3+
---
4+
5+
Allow TypedArray request bodies in `fetch` in `load`

.changeset/tricky-experts-tan.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sveltejs/kit": patch
3+
---
4+
5+
Use `FormData` polyfill from `undici` rather than `node-fetch`

packages/kit/src/exports/node/polyfills.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { fetch, Response, Request, Headers } from 'undici';
1+
import { fetch, Response, Request, Headers, FormData } from 'undici';
22
import { ReadableStream, TransformStream, WritableStream } from 'stream/web';
33
import { Readable } from 'stream';
4-
import { Request as NodeFetchRequest, FormData } from 'node-fetch';
4+
import { Request as NodeFetchRequest } from 'node-fetch';
55
import { webcrypto as crypto } from 'crypto';
66

77
/** @type {Record<string, any>} */
@@ -13,12 +13,18 @@ const globals = {
1313
// https://github.com/nodejs/undici/issues/974
1414
Request: class extends Request {
1515
// @ts-expect-error
16-
formData() {
17-
return new NodeFetchRequest(this.url, {
16+
async formData() {
17+
const nodeFetchFormData = await new NodeFetchRequest(this.url, {
1818
method: this.method,
1919
headers: this.headers,
2020
body: this.body && Readable.from(this.body)
2121
}).formData();
22+
23+
const formData = new FormData();
24+
for (const [name, value] of nodeFetchFormData.entries()) {
25+
formData.append(name, value);
26+
}
27+
return formData;
2228
}
2329
},
2430
Headers,

packages/kit/src/runtime/client/fetcher.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export function initial_fetch(resource, resolved, opts) {
7171

7272
let selector = `script[data-sveltekit-fetched][data-url=${url}]`;
7373

74-
if (opts && typeof opts.body === 'string') {
74+
if (opts?.body && (typeof opts.body === 'string' || ArrayBuffer.isView(opts.body))) {
7575
selector += `[data-hash="${hash(opts.body)}"]`;
7676
}
7777

packages/kit/src/runtime/hash.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
*/
55
export function hash(value) {
66
let hash = 5381;
7-
let i = value.length;
87

98
if (typeof value === 'string') {
9+
let i = value.length;
1010
while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
11+
} else if (ArrayBuffer.isView(value)) {
12+
const buffer = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
13+
let i = buffer.length;
14+
while (i) hash = (hash * 33) ^ buffer[--i];
1115
} else {
12-
while (i) hash = (hash * 33) ^ value[--i];
16+
throw new TypeError('value must be a string or TypedArray');
1317
}
1418

1519
return (hash >>> 0).toString(36);

packages/kit/src/runtime/server/page/fetch.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,13 @@ export function create_fetch({ event, options, state, route, prerender_default,
165165
}
166166
}
167167

168-
if (request_body && typeof request_body !== 'string') {
168+
if (request_body && typeof request_body !== 'string' && !ArrayBuffer.isView(request_body)) {
169169
// TODO is this still necessary? we just bail out below
170170
// per https://developer.mozilla.org/en-US/docs/Web/API/Request/Request, this can be a
171171
// Blob, BufferSource, FormData, URLSearchParams, USVString, or ReadableStream object.
172172
// non-string bodies are irksome to deal with, but luckily aren't particularly useful
173173
// in this context anyway, so we take the easy route and ban them
174-
throw new Error('Request body must be a string');
174+
throw new Error('Request body must be a string or TypedArray');
175175
}
176176

177177
response = await respond(request, options, {
@@ -220,7 +220,7 @@ export function create_fetch({ event, options, state, route, prerender_default,
220220
? request.url.slice(event.url.origin.length)
221221
: request.url,
222222
method: request.method,
223-
request_body: /** @type {string | undefined} */ (request_body),
223+
request_body: /** @type {string | ArrayBufferView | undefined} */ (request_body),
224224
response_body: body,
225225
response: response
226226
});

packages/kit/src/runtime/server/page/types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { SSRNode, CspDirectives } from 'types';
44
export interface Fetched {
55
url: string;
66
method: string;
7-
request_body?: string | null;
7+
request_body?: string | ArrayBufferView | null;
88
response_body: string;
99
response: Response;
1010
}
Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
<script>
2-
/** @type {import('./$types').PageData} */
3-
export let data;
4-
</script>
5-
6-
<pre class="parsed">{JSON.stringify(data.body)}</pre>
7-
<pre class="raw">{data.rawBody}</pre>
1+
<a href="/load/raw-body/dataview">DataView</a>
2+
<a href="/load/raw-body/string">String</a>
3+
<a href="/load/raw-body/uint8array">Uint8Array</a>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/** @type {import('@sveltejs/kit').Load} */
2+
export async function load({ fetch }) {
3+
const data = new TextEncoder().encode('{ "oddly" : { "formatted" : "json" } }');
4+
const res = await fetch('/load/raw-body.json', {
5+
method: 'POST',
6+
headers: {
7+
'content-type': 'application/octet-stream'
8+
},
9+
body: new DataView(data.buffer, data.byteOffset, data.byteLength)
10+
});
11+
12+
return await res.json();
13+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
/** @type {import('./$types').PageData} */
3+
export let data;
4+
</script>
5+
6+
<pre class="parsed">{JSON.stringify(data.body)}</pre>
7+
<pre class="raw">{data.rawBody}</pre>

0 commit comments

Comments
 (0)