From 7b72ec6ec2a30d8a3e1eb1fa501cfa55e7870d64 Mon Sep 17 00:00:00 2001 From: dario Date: Mon, 2 Jun 2025 17:47:51 +0200 Subject: [PATCH 1/2] fix readable stream as body check on safari --- src/utils.ts | 44 +++++++++++++++----------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index b2685f1..053ed45 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -85,34 +85,20 @@ function createMultipartStream(entries: readonly MultipartEntry[]): MultipartFor }; } -async function detectStreamSupport(): Promise { - try { - const testStream = new ReadableStream({ - start(controller) { - controller.enqueue(new Uint8Array([0])); // Minimal stream chunk - controller.close(); - }, - }); - - const request = new Request('data:text/plain;charset=utf-8,42', { - method: 'POST', - body: testStream, - // Required for streaming request body in some browsers, - // or it will fail and assume it's not supported - // @ts-ignore - duplex: 'half', - }); - - // If fails to handle fetch(Request), it's likely not a native implementation of - // Fetch API, so it's not supported. - await fetch(request.clone()); - - const body = await request.text(); - // if different from '\x00', it's likely not supported - return body === '\x00'; - } catch { - return false; - } +function detectStreamSupport() { + let duplexAccessed = false; + + const hasContentType = new Request('data:text/plain;charset=utf-8,42', { + body: new ReadableStream(), + method: 'POST', + // @ts-ignore + get duplex() { + duplexAccessed = true; + return 'half'; + }, + }).headers.has('Content-Type'); + + return duplexAccessed && !hasContentType; } function createFormData(entries: readonly MultipartEntry[]): FormData { @@ -163,7 +149,7 @@ export async function createMultipartRequestInit( method: 'POST' | 'PUT', entries: readonly MultipartEntry[], ): Promise { - if (await detectStreamSupport()) { + if (detectStreamSupport()) { const { stream, boundary } = createMultipartStream(entries); return { From 8f17913895b626d2fc37ac04144e38cffa2229cb Mon Sep 17 00:00:00 2001 From: Dario Bugmann Date: Wed, 4 Jun 2025 07:08:28 +0200 Subject: [PATCH 2/2] add fetch call again to detect non-native 'fetch' envs and remove data url in the support check --- src/utils.ts | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 053ed45..a70ddc9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -85,20 +85,35 @@ function createMultipartStream(entries: readonly MultipartEntry[]): MultipartFor }; } -function detectStreamSupport() { - let duplexAccessed = false; - - const hasContentType = new Request('data:text/plain;charset=utf-8,42', { - body: new ReadableStream(), - method: 'POST', - // @ts-ignore - get duplex() { - duplexAccessed = true; - return 'half'; - }, - }).headers.has('Content-Type'); - - return duplexAccessed && !hasContentType; +async function detectStreamSupport() { + try { + let duplexAccessed = false; + const hasContentType = new Request('https://', { + body: new ReadableStream(), + method: 'POST', + // @ts-ignore + get duplex() { + duplexAccessed = true; + return 'half'; + }, + }).headers.has('Content-Type'); + + // If fails to handle fetch(Request), it's likely not a native implementation of + // Fetch API, so it's not supported. + const res = await fetch( + new Request('data:text/plain;charset=utf-8,42', { + method: 'POST', + body: new ReadableStream(), + // @ts-ignore + duplex: 'half', + }), + ); + const body = await res.text(); + + return duplexAccessed && !hasContentType && body === '\x00'; + } catch (error) { + return false; + } } function createFormData(entries: readonly MultipartEntry[]): FormData { @@ -149,7 +164,7 @@ export async function createMultipartRequestInit( method: 'POST' | 'PUT', entries: readonly MultipartEntry[], ): Promise { - if (detectStreamSupport()) { + if (await detectStreamSupport()) { const { stream, boundary } = createMultipartStream(entries); return {