From 929c4812d6947c59a56c7b17ee2b260ba09e5d1c Mon Sep 17 00:00:00 2001 From: ItsChaceD Date: Wed, 20 Sep 2023 02:44:57 -0700 Subject: [PATCH] fix(http): parse readablestream data on fetch request objects --- .../src/main/assets/native-bridge.js | 50 ++++++++++++++++-- core/native-bridge.ts | 51 +++++++++++++++++-- .../Capacitor/assets/native-bridge.js | 50 ++++++++++++++++-- 3 files changed, 142 insertions(+), 9 deletions(-) diff --git a/android/capacitor/src/main/assets/native-bridge.js b/android/capacitor/src/main/assets/native-bridge.js index e30c8a4ec..46688040c 100644 --- a/android/capacitor/src/main/assets/native-bridge.js +++ b/android/capacitor/src/main/assets/native-bridge.js @@ -64,8 +64,52 @@ var nativeBridge = (function (exports) { } return newFormData; }; - const convertBody = async (body) => { - if (body instanceof FormData) { + const convertBody = async (body, contentType) => { + if (body instanceof ReadableStream) { + const reader = body.getReader(); + const chunks = []; + while (true) { + const { done, value } = await reader.read(); + if (done) + break; + chunks.push(value); + } + const concatenated = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0)); + let position = 0; + for (const chunk of chunks) { + concatenated.set(chunk, position); + position += chunk.length; + } + let data = new TextDecoder().decode(concatenated); + let type; + if (contentType === 'application/json') { + try { + data = JSON.parse(data); + } + catch (ignored) { + // ignore + } + type = 'json'; + } + else if (contentType === 'multipart/form-data') { + type = 'formData'; + } + else if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('image')) { + type = 'image'; + } + else if (contentType === 'application/octet-stream') { + type = 'binary'; + } + else { + type = 'text'; + } + return { + data, + type, + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } + else if (body instanceof FormData) { const formData = await convertFormData(body); const boundary = `${Date.now()}`; return { @@ -433,8 +477,8 @@ var nativeBridge = (function (exports) { console.time(tag); try { const { body, method } = request; - const { data: requestData, type, headers } = await convertBody(body || undefined); const optionHeaders = Object.fromEntries(request.headers.entries()); + const { data: requestData, type, headers, } = await convertBody((options === null || options === void 0 ? void 0 : options.body) || body || undefined, optionHeaders['Content-Type'] || optionHeaders['content-type']); const nativeResponse = await cap.nativePromise('CapacitorHttp', 'request', { url: request.url, method: method, diff --git a/core/native-bridge.ts b/core/native-bridge.ts index 4a6ceb7b3..ae23b0fda 100644 --- a/core/native-bridge.ts +++ b/core/native-bridge.ts @@ -53,8 +53,50 @@ const convertFormData = async (formData: FormData): Promise => { const convertBody = async ( body: Document | XMLHttpRequestBodyInit | ReadableStream | undefined, + contentType?: string, ): Promise => { - if (body instanceof FormData) { + if (body instanceof ReadableStream) { + const reader = body.getReader(); + const chunks: any[] = []; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(value); + } + const concatenated = new Uint8Array( + chunks.reduce((acc, chunk) => acc + chunk.length, 0), + ); + let position = 0; + for (const chunk of chunks) { + concatenated.set(chunk, position); + position += chunk.length; + } + + let data = new TextDecoder().decode(concatenated); + let type; + if (contentType === 'application/json') { + try { + data = JSON.parse(data); + } catch (ignored) { + // ignore + } + type = 'json'; + } else if (contentType === 'multipart/form-data') { + type = 'formData'; + } else if (contentType?.startsWith('image')) { + type = 'image'; + } else if (contentType === 'application/octet-stream') { + type = 'binary'; + } else { + type = 'text'; + } + + return { + data, + type, + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } else if (body instanceof FormData) { const formData = await convertFormData(body); const boundary = `${Date.now()}`; return { @@ -488,13 +530,16 @@ const initBridge = (w: any): void => { try { const { body, method } = request; + const optionHeaders = Object.fromEntries(request.headers.entries()); const { data: requestData, type, headers, - } = await convertBody(body || undefined); + } = await convertBody( + options?.body || body || undefined, + optionHeaders['Content-Type'] || optionHeaders['content-type'], + ); - const optionHeaders = Object.fromEntries(request.headers.entries()); const nativeResponse: HttpResponse = await cap.nativePromise( 'CapacitorHttp', 'request', diff --git a/ios/Capacitor/Capacitor/assets/native-bridge.js b/ios/Capacitor/Capacitor/assets/native-bridge.js index e30c8a4ec..46688040c 100644 --- a/ios/Capacitor/Capacitor/assets/native-bridge.js +++ b/ios/Capacitor/Capacitor/assets/native-bridge.js @@ -64,8 +64,52 @@ var nativeBridge = (function (exports) { } return newFormData; }; - const convertBody = async (body) => { - if (body instanceof FormData) { + const convertBody = async (body, contentType) => { + if (body instanceof ReadableStream) { + const reader = body.getReader(); + const chunks = []; + while (true) { + const { done, value } = await reader.read(); + if (done) + break; + chunks.push(value); + } + const concatenated = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0)); + let position = 0; + for (const chunk of chunks) { + concatenated.set(chunk, position); + position += chunk.length; + } + let data = new TextDecoder().decode(concatenated); + let type; + if (contentType === 'application/json') { + try { + data = JSON.parse(data); + } + catch (ignored) { + // ignore + } + type = 'json'; + } + else if (contentType === 'multipart/form-data') { + type = 'formData'; + } + else if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('image')) { + type = 'image'; + } + else if (contentType === 'application/octet-stream') { + type = 'binary'; + } + else { + type = 'text'; + } + return { + data, + type, + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } + else if (body instanceof FormData) { const formData = await convertFormData(body); const boundary = `${Date.now()}`; return { @@ -433,8 +477,8 @@ var nativeBridge = (function (exports) { console.time(tag); try { const { body, method } = request; - const { data: requestData, type, headers } = await convertBody(body || undefined); const optionHeaders = Object.fromEntries(request.headers.entries()); + const { data: requestData, type, headers, } = await convertBody((options === null || options === void 0 ? void 0 : options.body) || body || undefined, optionHeaders['Content-Type'] || optionHeaders['content-type']); const nativeResponse = await cap.nativePromise('CapacitorHttp', 'request', { url: request.url, method: method,