diff --git a/android/capacitor/src/main/assets/native-bridge.js b/android/capacitor/src/main/assets/native-bridge.js index f5e7cc4403..1458690a76 100644 --- a/android/capacitor/src/main/assets/native-bridge.js +++ b/android/capacitor/src/main/assets/native-bridge.js @@ -44,6 +44,33 @@ var nativeBridge = (function (exports) { reader.onerror = reject; reader.readAsBinaryString(file); }); + const readArrayBufferAsBase64 = (arrayBuffer) => { + const bytes = new Uint8Array(arrayBuffer); + const chunkSize = 0x8000; + let binary = ''; + for (let i = 0; i < bytes.length; i += chunkSize) { + binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize)); + } + return btoa(binary); + }; + const readBlobAsBase64 = (blob) => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const result = reader.result; + resolve(result.indexOf(',') >= 0 ? result.split(',')[1] : result); + }; + reader.onerror = reject; + reader.readAsDataURL(blob); + }); + const readDataViewAsBase64 = (dataView) => readArrayBufferAsBase64(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)); + const base64StringToArrayBuffer = (base64) => { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes.buffer; + }; const convertFormData = async (formData) => { const newFormData = []; for (const pair of formData.entries()) { @@ -65,9 +92,38 @@ var nativeBridge = (function (exports) { return newFormData; }; const convertBody = async (body, contentType) => { - if (body instanceof ReadableStream || body instanceof Uint8Array) { + if (body instanceof File) { + const fileData = await readFileAsBase64(body); + return { + data: fileData, + type: 'file', + headers: { 'Content-Type': body.type }, + }; + } + else if (body instanceof Blob) { + return { + data: await readBlobAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || body.type || 'application/octet-stream' }, + }; + } + else if (body instanceof ArrayBuffer) { + return { + data: readArrayBufferAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } + else if (ArrayBuffer.isView(body)) { + return { + data: readDataViewAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } + if ((typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) || body instanceof Uint8Array) { let encodedData; - if (body instanceof ReadableStream) { + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) { const reader = body.getReader(); const chunks = []; while (true) { @@ -87,7 +143,9 @@ var nativeBridge = (function (exports) { else { encodedData = body; } - let data = new TextDecoder().decode(encodedData); + let data = (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('image')) || contentType === 'application/octet-stream' + ? readDataViewAsBase64(encodedData) + : new TextDecoder().decode(encodedData); let type; if (contentType === 'application/json') { try { @@ -102,7 +160,7 @@ var nativeBridge = (function (exports) { type = 'formData'; } else if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('image')) { - type = 'image'; + type = 'binary'; } else if (contentType === 'application/octet-stream') { type = 'binary'; @@ -128,14 +186,6 @@ var nativeBridge = (function (exports) { type: 'formData', }; } - else if (body instanceof File) { - const fileData = await readFileAsBase64(body); - return { - data: fileData, - type: 'file', - headers: { 'Content-Type': body.type }, - }; - } return { data: body, type: 'json' }; }; const CAPACITOR_HTTP_INTERCEPTOR = '/_capacitor_http_interceptor_'; @@ -535,15 +585,15 @@ var nativeBridge = (function (exports) { data: requestData, dataType: type, headers: nativeHeaders, + responseType: 'arraybuffer', }); const contentType = nativeResponse.headers['Content-Type'] || nativeResponse.headers['content-type']; - let data = (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/json')) - ? JSON.stringify(nativeResponse.data) - : nativeResponse.data; // use null data for 204 No Content HTTP response - if (nativeResponse.status === 204) { - data = null; - } + const data = nativeResponse.status === 204 + ? null + : (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/json')) + ? JSON.stringify(nativeResponse.data) + : base64StringToArrayBuffer(nativeResponse.data); // intercept & parse response before returning const response = new Response(data, { headers: nativeResponse.headers, diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java index 6180d64200..c2f966ae25 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java @@ -1,6 +1,5 @@ package com.getcapacitor.plugin.util; -import android.os.Build; import android.os.LocaleList; import android.text.TextUtils; import com.getcapacitor.Bridge; @@ -19,7 +18,6 @@ import java.net.URLEncoder; import java.net.UnknownServiceException; import java.nio.charset.StandardCharsets; -import java.util.Base64; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -209,13 +207,8 @@ public void setRequestBody(PluginCall call, JSValue body, String bodyType) throw dataString = call.getString("data"); } this.writeRequestBody(dataString != null ? dataString : ""); - } else if (bodyType != null && bodyType.equals("file")) { - try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - os.write(Base64.getDecoder().decode(body.toString())); - } - os.flush(); - } + } else if (bodyType != null && (bodyType.equals("file") || bodyType.equals("binary"))) { + this.writeRequestBody(decodeBase64RequestBody(body.toString())); } else if (contentType.contains("application/x-www-form-urlencoded")) { try { JSObject obj = body.toJSObject(); @@ -252,6 +245,17 @@ private void writeRequestBody(String body) throws IOException { } } + private void writeRequestBody(byte[] body) throws IOException { + try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) { + os.write(body); + os.flush(); + } + } + + static byte[] decodeBase64RequestBody(String body) { + return android.util.Base64.decode(body, android.util.Base64.DEFAULT); + } + private void writeObjectRequestBody(JSObject object) throws IOException, JSONException { try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) { Iterator keys = object.keys(); @@ -296,11 +300,7 @@ private void writeFormDataRequestBody(String boundary, JSArray entries) throws I os.writeBytes("Content-Transfer-Encoding: binary" + lineEnd); os.writeBytes(lineEnd); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - os.write(Base64.getDecoder().decode(value)); - } else { - os.write(android.util.Base64.decode(value, android.util.Base64.DEFAULT)); - } + os.write(decodeBase64RequestBody(value)); os.writeBytes(lineEnd); } diff --git a/core/http.md b/core/http.md index f459597c1f..0e00305949 100644 --- a/core/http.md +++ b/core/http.md @@ -233,7 +233,7 @@ Make a Http DELETE Request to a server using native libraries. | **`webFetchExtra`** | RequestInit | Extra arguments for fetch when running on the web | | **`responseType`** | HttpResponseType | This is used to parse the response appropriately before returning it to the requestee. If the response content-type is "json", this value is ignored. | | **`shouldEncodeUrlParams`** | boolean | Use this option if you need to keep the URL unencoded in certain cases (already encoded, azure/firebase testing, etc.). The default is _true_. | -| **`dataType`** | 'file' \| 'formData' | This is used if we've had to convert the data from a JS type that needs special handling in the native layer | +| **`dataType`** | 'file' \| 'formData' \| 'binary' | This is used if we've had to convert the data from a JS type that needs special handling in the native layer | #### HttpParams diff --git a/core/native-bridge.ts b/core/native-bridge.ts index 4fc028f379..f9916625d1 100644 --- a/core/native-bridge.ts +++ b/core/native-bridge.ts @@ -30,6 +30,44 @@ const readFileAsBase64 = (file: File): Promise => reader.readAsBinaryString(file); }); +const readArrayBufferAsBase64 = (arrayBuffer: ArrayBufferLike): string => { + const bytes = new Uint8Array(arrayBuffer); + const chunkSize = 0x8000; + let binary = ''; + + for (let i = 0; i < bytes.length; i += chunkSize) { + binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize)); + } + + return btoa(binary); +}; + +const readBlobAsBase64 = (blob: Blob): Promise => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const result = reader.result as string; + resolve(result.indexOf(',') >= 0 ? result.split(',')[1] : result); + }; + reader.onerror = reject; + + reader.readAsDataURL(blob); + }); + +const readDataViewAsBase64 = (dataView: ArrayBufferView): string => + readArrayBufferAsBase64(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)); + +const base64StringToArrayBuffer = (base64: string): ArrayBuffer => { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + + return bytes.buffer as ArrayBuffer; +}; + const convertFormData = async (formData: FormData): Promise => { const newFormData: CapFormDataEntry[] = []; for (const pair of formData.entries()) { @@ -55,11 +93,38 @@ const convertBody = async ( body: Document | XMLHttpRequestBodyInit | ReadableStream | undefined, contentType?: string, ): Promise => { - if (body instanceof ReadableStream || body instanceof Uint8Array) { - let encodedData; - if (body instanceof ReadableStream) { + if (body instanceof File) { + const fileData = await readFileAsBase64(body); + return { + data: fileData, + type: 'file', + headers: { 'Content-Type': body.type }, + }; + } else if (body instanceof Blob) { + return { + data: await readBlobAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || body.type || 'application/octet-stream' }, + }; + } else if (body instanceof ArrayBuffer) { + return { + data: readArrayBufferAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } else if (ArrayBuffer.isView(body)) { + return { + data: readDataViewAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } + + if ((typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) || body instanceof Uint8Array) { + let encodedData: Uint8Array; + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) { const reader = body.getReader(); - const chunks: any[] = []; + const chunks: Uint8Array[] = []; while (true) { const { done, value } = await reader.read(); if (done) break; @@ -73,10 +138,13 @@ const convertBody = async ( } encodedData = concatenated; } else { - encodedData = body; + encodedData = body as Uint8Array; } - let data = new TextDecoder().decode(encodedData); + let data = + contentType?.startsWith('image') || contentType === 'application/octet-stream' + ? readDataViewAsBase64(encodedData) + : new TextDecoder().decode(encodedData); let type; if (contentType === 'application/json') { try { @@ -88,7 +156,7 @@ const convertBody = async ( } else if (contentType === 'multipart/form-data') { type = 'formData'; } else if (contentType?.startsWith('image')) { - type = 'image'; + type = 'binary'; } else if (contentType === 'application/octet-stream') { type = 'binary'; } else { @@ -110,13 +178,6 @@ const convertBody = async ( data: await convertFormData(body), type: 'formData', }; - } else if (body instanceof File) { - const fileData = await readFileAsBase64(body); - return { - data: fileData, - type: 'file', - headers: { 'Content-Type': body.type }, - }; } return { data: body, type: 'json' }; @@ -577,17 +638,17 @@ const initBridge = (w: any): void => { data: requestData, dataType: type, headers: nativeHeaders, + responseType: 'arraybuffer', }); const contentType = nativeResponse.headers['Content-Type'] || nativeResponse.headers['content-type']; - let data = contentType?.startsWith('application/json') - ? JSON.stringify(nativeResponse.data) - : nativeResponse.data; - // use null data for 204 No Content HTTP response - if (nativeResponse.status === 204) { - data = null; - } + const data = + nativeResponse.status === 204 + ? null + : contentType?.startsWith('application/json') + ? JSON.stringify(nativeResponse.data) + : base64StringToArrayBuffer(nativeResponse.data); // intercept & parse response before returning const response = new Response(data, { diff --git a/core/src/core-plugins.ts b/core/src/core-plugins.ts index a1efe9c901..2614745515 100644 --- a/core/src/core-plugins.ts +++ b/core/src/core-plugins.ts @@ -245,7 +245,7 @@ export interface HttpOptions { * This is used if we've had to convert the data from a JS type that needs * special handling in the native layer */ - dataType?: 'file' | 'formData'; + dataType?: 'file' | 'formData' | 'binary'; } export interface HttpParams { diff --git a/core/src/tests/bridge.spec.ts b/core/src/tests/bridge.spec.ts index daf7f0328e..99a7fd59a9 100644 --- a/core/src/tests/bridge.spec.ts +++ b/core/src/tests/bridge.spec.ts @@ -9,8 +9,16 @@ import { createCapacitor } from '../runtime'; describe('bridge', () => { let win: WindowCapacitor; let cap: CapacitorInstance; + let originalFetch: typeof window.fetch; + let originalHeaders: typeof Headers; + let originalRequest: typeof Request; + let originalResponse: typeof Response; beforeEach(() => { + originalFetch = window.fetch; + originalHeaders = globalThis.Headers; + originalRequest = globalThis.Request; + originalResponse = globalThis.Response; win = {}; initBridge(win); // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -19,6 +27,13 @@ describe('bridge', () => { window.prompt = () => {}; }); + afterEach(() => { + window.fetch = originalFetch; + globalThis.Headers = originalHeaders; + globalThis.Request = originalRequest; + globalThis.Response = originalResponse; + }); + it('android nativePromise error', (done) => { mockAndroidPluginResult({ success: false, @@ -115,6 +130,121 @@ describe('bridge', () => { }); }); + it('android patched fetch sends Blob bodies as base64 binary data', async () => { + mockFetchApi(); + let callData: any; + (win as any).androidBridge = { + postMessage: (m: string) => { + callData = JSON.parse(m); + Promise.resolve().then(() => { + cap.fromNative!({ + callbackId: callData.callbackId, + methodName: callData.methodName, + pluginId: callData.pluginId, + success: true, + data: { + data: 'ok', + headers: { 'Content-Type': 'text/plain' }, + status: 200, + url: 'https://example.com/upload', + }, + }); + }); + }, + }; + (win as any).CapacitorHttpAndroidInterface = { + isEnabled: () => true, + }; + initBridge(win); + cap = createCapacitor(win); + + await window.fetch('https://example.com/upload', { + body: new Blob([new Uint8Array([1, 2])], { type: 'application/octet-stream' }), + headers: { 'Content-Type': 'application/octet-stream' }, + method: 'POST', + }); + + expect(callData.options.data).toBe('AQI='); + expect(callData.options.dataType).toBe('binary'); + expect(callData.options.headers['content-type']).toBe('application/octet-stream'); + }); + + it('android patched fetch sends ArrayBuffer bodies as base64 binary data', async () => { + mockFetchApi(); + let callData: any; + (win as any).androidBridge = { + postMessage: (m: string) => { + callData = JSON.parse(m); + Promise.resolve().then(() => { + cap.fromNative!({ + callbackId: callData.callbackId, + methodName: callData.methodName, + pluginId: callData.pluginId, + success: true, + data: { + data: 'ok', + headers: { 'Content-Type': 'text/plain' }, + status: 200, + url: 'https://example.com/upload', + }, + }); + }); + }, + }; + (win as any).CapacitorHttpAndroidInterface = { + isEnabled: () => true, + }; + initBridge(win); + cap = createCapacitor(win); + + await window.fetch('https://example.com/upload', { + body: new Uint8Array([3, 4]).buffer, + headers: { 'Content-Type': 'application/octet-stream' }, + method: 'POST', + }); + + expect(callData.options.data).toBe('AwQ='); + expect(callData.options.dataType).toBe('binary'); + expect(callData.options.headers['content-type']).toBe('application/octet-stream'); + }); + + it('android patched fetch returns binary responses byte-for-byte', async () => { + mockFetchApi(); + let callData: any; + (win as any).androidBridge = { + postMessage: (m: string) => { + callData = JSON.parse(m); + Promise.resolve().then(() => { + cap.fromNative!({ + callbackId: callData.callbackId, + methodName: callData.methodName, + pluginId: callData.pluginId, + success: true, + data: { + data: 'AQI=', + headers: { 'Content-Type': 'application/octet-stream' }, + status: 200, + url: 'https://example.com/upload', + }, + }); + }); + }, + }; + (win as any).CapacitorHttpAndroidInterface = { + isEnabled: () => true, + }; + initBridge(win); + cap = createCapacitor(win); + + const response = await window.fetch('https://example.com/upload', { + body: 'body', + method: 'POST', + }); + + expect(callData.options.responseType).toBe('arraybuffer'); + expect(Array.from(new Uint8Array(await response.arrayBuffer()))).toEqual([1, 2]); + }); + const mockAndroidPluginResult = (pluginResult: PluginResult) => { win.androidBridge = { postMessage: (m) => { @@ -122,7 +252,7 @@ describe('bridge', () => { Promise.resolve().then(() => { pluginResult.callbackId = d.callbackId; pluginResult.methodName = d.methodName; - cap.fromNative(pluginResult); + cap.fromNative!(pluginResult); }); }, }; @@ -136,11 +266,88 @@ describe('bridge', () => { Promise.resolve().then(() => { pluginResult.callbackId = m.callbackId; pluginResult.methodName = m.methodName; - cap.fromNative(pluginResult); + cap.fromNative!(pluginResult); }); }, }, }, }; }; + + const mockFetchApi = () => { + class MockHeaders { + private readonly headers: Record = {}; + + constructor(init?: HeadersInit) { + if (init == null) { + return; + } + + Object.entries(init as Record).forEach(([key, value]) => { + this.headers[key.toLocaleLowerCase()] = value; + }); + } + + delete(key: string) { + delete this.headers[key.toLocaleLowerCase()]; + } + + entries() { + return Object.entries(this.headers)[Symbol.iterator](); + } + + get(key: string) { + return this.headers[key.toLocaleLowerCase()] ?? null; + } + + has(key: string) { + return this.headers[key.toLocaleLowerCase()] != null; + } + + set(key: string, value: string) { + this.headers[key.toLocaleLowerCase()] = value; + } + } + + class MockRequest { + readonly body?: BodyInit | null; + readonly headers: Headers; + readonly method: string; + readonly url: string; + + constructor(resource: RequestInfo | URL, init?: RequestInit) { + this.body = init?.body; + this.headers = new globalThis.Headers(init?.headers); + this.method = init?.method ?? 'GET'; + this.url = resource.toString(); + } + } + + class MockResponse { + private readonly bytes: Uint8Array; + + constructor( + body?: BodyInit | null, + readonly init?: ResponseInit, + ) { + if (body instanceof ArrayBuffer) { + this.bytes = new Uint8Array(body); + } else if (ArrayBuffer.isView(body)) { + this.bytes = new Uint8Array(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength)); + } else if (typeof body === 'string') { + this.bytes = new TextEncoder().encode(body); + } else { + this.bytes = new Uint8Array(); + } + } + + async arrayBuffer() { + return this.bytes.buffer.slice(this.bytes.byteOffset, this.bytes.byteOffset + this.bytes.byteLength); + } + } + + globalThis.Headers = MockHeaders as any; + globalThis.Request = MockRequest as any; + globalThis.Response = MockResponse as any; + }; }); diff --git a/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift b/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift index 344333688a..8308c3d73b 100644 --- a/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift +++ b/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift @@ -172,7 +172,7 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate { } public func getRequestData(_ body: JSValue, _ contentType: String, _ dataType: String? = nil) throws -> Data? { - if dataType == "file" { + if dataType == "file" || dataType == "binary" { guard let stringData = body as? String else { throw CapacitorUrlRequestError.serializationError("[ data ] argument could not be parsed as string") } diff --git a/ios/Capacitor/Capacitor/assets/native-bridge.js b/ios/Capacitor/Capacitor/assets/native-bridge.js index f5e7cc4403..1458690a76 100644 --- a/ios/Capacitor/Capacitor/assets/native-bridge.js +++ b/ios/Capacitor/Capacitor/assets/native-bridge.js @@ -44,6 +44,33 @@ var nativeBridge = (function (exports) { reader.onerror = reject; reader.readAsBinaryString(file); }); + const readArrayBufferAsBase64 = (arrayBuffer) => { + const bytes = new Uint8Array(arrayBuffer); + const chunkSize = 0x8000; + let binary = ''; + for (let i = 0; i < bytes.length; i += chunkSize) { + binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize)); + } + return btoa(binary); + }; + const readBlobAsBase64 = (blob) => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const result = reader.result; + resolve(result.indexOf(',') >= 0 ? result.split(',')[1] : result); + }; + reader.onerror = reject; + reader.readAsDataURL(blob); + }); + const readDataViewAsBase64 = (dataView) => readArrayBufferAsBase64(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)); + const base64StringToArrayBuffer = (base64) => { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes.buffer; + }; const convertFormData = async (formData) => { const newFormData = []; for (const pair of formData.entries()) { @@ -65,9 +92,38 @@ var nativeBridge = (function (exports) { return newFormData; }; const convertBody = async (body, contentType) => { - if (body instanceof ReadableStream || body instanceof Uint8Array) { + if (body instanceof File) { + const fileData = await readFileAsBase64(body); + return { + data: fileData, + type: 'file', + headers: { 'Content-Type': body.type }, + }; + } + else if (body instanceof Blob) { + return { + data: await readBlobAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || body.type || 'application/octet-stream' }, + }; + } + else if (body instanceof ArrayBuffer) { + return { + data: readArrayBufferAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } + else if (ArrayBuffer.isView(body)) { + return { + data: readDataViewAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } + if ((typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) || body instanceof Uint8Array) { let encodedData; - if (body instanceof ReadableStream) { + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) { const reader = body.getReader(); const chunks = []; while (true) { @@ -87,7 +143,9 @@ var nativeBridge = (function (exports) { else { encodedData = body; } - let data = new TextDecoder().decode(encodedData); + let data = (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('image')) || contentType === 'application/octet-stream' + ? readDataViewAsBase64(encodedData) + : new TextDecoder().decode(encodedData); let type; if (contentType === 'application/json') { try { @@ -102,7 +160,7 @@ var nativeBridge = (function (exports) { type = 'formData'; } else if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('image')) { - type = 'image'; + type = 'binary'; } else if (contentType === 'application/octet-stream') { type = 'binary'; @@ -128,14 +186,6 @@ var nativeBridge = (function (exports) { type: 'formData', }; } - else if (body instanceof File) { - const fileData = await readFileAsBase64(body); - return { - data: fileData, - type: 'file', - headers: { 'Content-Type': body.type }, - }; - } return { data: body, type: 'json' }; }; const CAPACITOR_HTTP_INTERCEPTOR = '/_capacitor_http_interceptor_'; @@ -535,15 +585,15 @@ var nativeBridge = (function (exports) { data: requestData, dataType: type, headers: nativeHeaders, + responseType: 'arraybuffer', }); const contentType = nativeResponse.headers['Content-Type'] || nativeResponse.headers['content-type']; - let data = (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/json')) - ? JSON.stringify(nativeResponse.data) - : nativeResponse.data; // use null data for 204 No Content HTTP response - if (nativeResponse.status === 204) { - data = null; - } + const data = nativeResponse.status === 204 + ? null + : (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/json')) + ? JSON.stringify(nativeResponse.data) + : base64StringToArrayBuffer(nativeResponse.data); // intercept & parse response before returning const response = new Response(data, { headers: nativeResponse.headers,