diff --git a/android/capacitor/src/main/assets/native-bridge.js b/android/capacitor/src/main/assets/native-bridge.js index 38c5837879..8d23090f57 100644 --- a/android/capacitor/src/main/assets/native-bridge.js +++ b/android/capacitor/src/main/assets/native-bridge.js @@ -35,6 +35,57 @@ var nativeBridge = (function (exports) { // For removing exports for iOS/Android, keep let for reassignment // eslint-disable-next-line let dummy = {}; + const readFileAsBase64 = (file) => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => { + const data = reader.result; + resolve(btoa(data)); + }; + reader.onerror = reject; + reader.readAsBinaryString(file); + }); + const convertFormData = async (formData) => { + const newFormData = []; + for (const pair of formData.entries()) { + const [key, value] = pair; + if (value instanceof File) { + const base64File = await readFileAsBase64(value); + newFormData.push({ + key, + value: base64File, + type: 'base64File', + contentType: value.type, + fileName: value.name, + }); + } + else { + newFormData.push({ key, value, type: 'string' }); + } + } + return newFormData; + }; + const convertBody = async (body) => { + if (body instanceof FormData) { + const formData = await convertFormData(body); + const boundary = `${Date.now()}`; + return { + data: formData, + type: 'formData', + headers: { + 'Content-Type': `multipart/form-data; boundary=--${boundary}`, + }, + }; + } + 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 initBridge = (w) => { const getPlatformId = (win) => { var _a, _b; @@ -368,7 +419,6 @@ var nativeBridge = (function (exports) { if (doPatchHttp) { // fetch patch window.fetch = async (resource, options) => { - var _a; if (!(resource.toString().startsWith('http:') || resource.toString().startsWith('https:'))) { return win.CapacitorWebFetch(resource, options); @@ -377,18 +427,23 @@ var nativeBridge = (function (exports) { console.time(tag); try { // intercept request & pass to the bridge - let headers = options === null || options === void 0 ? void 0 : options.headers; + const { data: requestData, type, headers, } = await convertBody((options === null || options === void 0 ? void 0 : options.body) || undefined); + let optionHeaders = options === null || options === void 0 ? void 0 : options.headers; if ((options === null || options === void 0 ? void 0 : options.headers) instanceof Headers) { - headers = Object.fromEntries(options.headers.entries()); + optionHeaders = Object.fromEntries(options.headers.entries()); } const nativeResponse = await cap.nativePromise('CapacitorHttp', 'request', { url: resource, method: (options === null || options === void 0 ? void 0 : options.method) ? options.method : undefined, - data: (options === null || options === void 0 ? void 0 : options.body) ? options.body : undefined, - headers: headers, + data: requestData, + dataType: type, + headers: Object.assign(Object.assign({}, headers), optionHeaders), }); - let data = ((_a = nativeResponse.headers['Content-Type']) === null || _a === void 0 ? void 0 : _a.startsWith('application/json')) - ? JSON.stringify(nativeResponse.data) : nativeResponse.data; + 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; @@ -518,44 +573,49 @@ var nativeBridge = (function (exports) { console.time(tag); try { this.readyState = 2; - // intercept request & pass to the bridge - cap - .nativePromise('CapacitorHttp', 'request', { - url: this._url, - method: this._method, - data: body !== null ? body : undefined, - headers: this._headers != null && Object.keys(this._headers).length > 0 + convertBody(body).then(({ data, type, headers }) => { + const otherHeaders = this._headers != null && Object.keys(this._headers).length > 0 ? this._headers - : undefined, - }) - .then((nativeResponse) => { - var _a; - // intercept & parse response before returning - if (this.readyState == 2) { + : undefined; + // intercept request & pass to the bridge + cap + .nativePromise('CapacitorHttp', 'request', { + url: this._url, + method: this._method, + data: data !== null ? data : undefined, + headers: Object.assign(Object.assign({}, headers), otherHeaders), + dataType: type, + }) + .then((nativeResponse) => { + var _a; + // intercept & parse response before returning + if (this.readyState == 2) { + this.dispatchEvent(new Event('loadstart')); + this._headers = nativeResponse.headers; + this.status = nativeResponse.status; + this.response = nativeResponse.data; + this.responseText = ((_a = nativeResponse.headers['Content-Type']) === null || _a === void 0 ? void 0 : _a.startsWith('application/json')) + ? JSON.stringify(nativeResponse.data) + : nativeResponse.data; + this.responseURL = nativeResponse.url; + this.readyState = 4; + this.dispatchEvent(new Event('load')); + this.dispatchEvent(new Event('loadend')); + } + console.timeEnd(tag); + }) + .catch((error) => { this.dispatchEvent(new Event('loadstart')); - this._headers = nativeResponse.headers; - this.status = nativeResponse.status; - this.response = nativeResponse.data; - this.responseText = ((_a = nativeResponse.headers['Content-Type']) === null || _a === void 0 ? void 0 : _a.startsWith('application/json')) - ? JSON.stringify(nativeResponse.data) : nativeResponse.data; - this.responseURL = nativeResponse.url; + this.status = error.status; + this._headers = error.headers; + this.response = error.data; + this.responseText = JSON.stringify(error.data); + this.responseURL = error.url; this.readyState = 4; - this.dispatchEvent(new Event('load')); + this.dispatchEvent(new Event('error')); this.dispatchEvent(new Event('loadend')); - } - console.timeEnd(tag); - }) - .catch((error) => { - this.dispatchEvent(new Event('loadstart')); - this.status = error.status; - this._headers = error.headers; - this.response = error.data; - this.responseText = JSON.stringify(error.data); - this.responseURL = error.url; - this.readyState = 4; - this.dispatchEvent(new Event('error')); - this.dispatchEvent(new Event('loadend')); - console.timeEnd(tag); + console.timeEnd(tag); + }); }); } catch (error) { 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 b0a7873540..32e7502d81 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 @@ -19,6 +19,7 @@ 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; @@ -26,6 +27,7 @@ import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; import org.json.JSONException; +import org.json.JSONObject; public class CapacitorHttpUrlConnection implements ICapacitorHttpUrlConnection { @@ -173,7 +175,7 @@ public void setDoOutput(boolean shouldDoOutput) { * @throws JSONException * @throws IOException */ - public void setRequestBody(PluginCall call, JSValue body) throws JSONException, IOException { + public void setRequestBody(PluginCall call, JSValue body, String bodyType) throws JSONException, IOException { String contentType = connection.getRequestProperty("Content-Type"); String dataString = ""; @@ -192,6 +194,15 @@ public void setRequestBody(PluginCall call, JSValue body) throws JSONException, dataString = call.getString("data"); } this.writeRequestBody(dataString != null ? dataString : ""); + } else if (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.equals("formData")) { + this.writeFormDataRequestBody(contentType, body.toJSArray()); } else { this.writeRequestBody(body.toString()); } @@ -209,6 +220,47 @@ private void writeRequestBody(String body) throws IOException { } } + private void writeFormDataRequestBody(String contentType, JSArray entries) throws IOException, JSONException { + try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) { + String boundary = contentType.split(";")[1].split("=")[1]; + String lineEnd = "\r\n"; + String twoHyphens = "--"; + + for (Object e : entries.toList()) { + if (e instanceof JSONObject) { + JSONObject entry = (JSONObject) e; + String type = entry.getString("type"); + String key = entry.getString("key"); + String value = entry.getString("value"); + if (type.equals("string")) { + os.writeBytes(twoHyphens + boundary + lineEnd); + os.writeBytes("Content-Disposition: form-data; name=\"" + key + "\"" + lineEnd + lineEnd); + os.writeBytes(value); + os.writeBytes(lineEnd); + } else if (type.equals("base64File")) { + String fileName = entry.getString("fileName"); + String fileContentType = entry.getString("contentType"); + + os.writeBytes(twoHyphens + boundary + lineEnd); + os.writeBytes("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + fileName + "\"" + lineEnd); + os.writeBytes("Content-Type: " + fileContentType + lineEnd); + 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)); + } + + os.writeBytes(lineEnd); + } + } + } + + os.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); + os.flush(); + } + } + /** * Opens a communications link to the resource referenced by this * URL, if such a connection has not already been established. diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/util/HttpRequestHandler.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/util/HttpRequestHandler.java index 569ad9a2ea..8b06a2fd0f 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/util/HttpRequestHandler.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/util/HttpRequestHandler.java @@ -379,6 +379,7 @@ public static JSObject request(PluginCall call, String httpMethod, Bridge bridge Boolean disableRedirects = call.getBoolean("disableRedirects"); Boolean shouldEncode = call.getBoolean("shouldEncodeUrlParams", true); ResponseType responseType = ResponseType.parse(call.getString("responseType")); + String dataType = call.getString("dataType"); String method = httpMethod != null ? httpMethod.toUpperCase(Locale.ROOT) : call.getString("method", "GET").toUpperCase(Locale.ROOT); @@ -406,7 +407,7 @@ public static JSObject request(PluginCall call, String httpMethod, Bridge bridge JSValue data = new JSValue(call, "data"); if (data.getValue() != null) { connection.setDoOutput(true); - connection.setRequestBody(call, data); + connection.setRequestBody(call, data, dataType); } } diff --git a/core/native-bridge.ts b/core/native-bridge.ts index f994a759f7..3491eadd2b 100644 --- a/core/native-bridge.ts +++ b/core/native-bridge.ts @@ -10,6 +10,7 @@ import type { MessageCallData, PluginResult, WindowCapacitor, + CapFormDataEntry, } from './src/definitions-internal'; import { CapacitorException } from './src/util'; @@ -17,6 +18,64 @@ import { CapacitorException } from './src/util'; // eslint-disable-next-line let dummy = {}; +const readFileAsBase64 = (file: File): Promise => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => { + const data = reader.result as string; + resolve(btoa(data)); + }; + reader.onerror = reject; + + reader.readAsBinaryString(file); + }); + +const convertFormData = async (formData: FormData): Promise => { + const newFormData: CapFormDataEntry[] = []; + for (const pair of formData.entries()) { + const [key, value] = pair; + if (value instanceof File) { + const base64File = await readFileAsBase64(value); + newFormData.push({ + key, + value: base64File, + type: 'base64File', + contentType: value.type, + fileName: value.name, + }); + } else { + newFormData.push({ key, value, type: 'string' }); + } + } + + return newFormData; +}; + +const convertBody = async ( + body: Document | XMLHttpRequestBodyInit | ReadableStream | undefined, +): Promise => { + if (body instanceof FormData) { + const formData = await convertFormData(body); + const boundary = `${Date.now()}`; + return { + data: formData, + type: 'formData', + headers: { + 'Content-Type': `multipart/form-data; boundary=--${boundary}`, + }, + }; + } 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 initBridge = (w: any): void => { const getPlatformId = (win: WindowCapacitor): 'android' | 'ios' | 'web' => { if (win?.androidBridge) { @@ -422,9 +481,16 @@ const initBridge = (w: any): void => { console.time(tag); try { // intercept request & pass to the bridge - let headers = options?.headers; + const { + data: requestData, + type, + headers, + } = await convertBody(options?.body || undefined); + let optionHeaders = options?.headers; if (options?.headers instanceof Headers) { - headers = Object.fromEntries((options.headers as any).entries()); + optionHeaders = Object.fromEntries( + (options.headers as any).entries(), + ); } const nativeResponse: HttpResponse = await cap.nativePromise( 'CapacitorHttp', @@ -432,14 +498,19 @@ const initBridge = (w: any): void => { { url: resource, method: options?.method ? options.method : undefined, - data: options?.body ? options.body : undefined, - headers: headers, + data: requestData, + dataType: type, + headers: { + ...headers, + ...optionHeaders, + }, }, ); - let data = nativeResponse.headers['Content-Type']?.startsWith( - 'application/json', - ) + const contentType = + nativeResponse.headers['Content-Type'] || + nativeResponse.headers['content-type']; + let data = contentType?.startsWith('application/json') ? JSON.stringify(nativeResponse.data) : nativeResponse.data; @@ -603,49 +674,56 @@ const initBridge = (w: any): void => { try { this.readyState = 2; - - // intercept request & pass to the bridge - cap - .nativePromise('CapacitorHttp', 'request', { - url: this._url, - method: this._method, - data: body !== null ? body : undefined, - headers: - this._headers != null && Object.keys(this._headers).length > 0 - ? this._headers - : undefined, - }) - .then((nativeResponse: any) => { - // intercept & parse response before returning - if (this.readyState == 2) { + convertBody(body).then(({ data, type, headers }) => { + const otherHeaders = + this._headers != null && Object.keys(this._headers).length > 0 + ? this._headers + : undefined; + + // intercept request & pass to the bridge + cap + .nativePromise('CapacitorHttp', 'request', { + url: this._url, + method: this._method, + data: data !== null ? data : undefined, + headers: { + ...headers, + ...otherHeaders, + }, + dataType: type, + }) + .then((nativeResponse: any) => { + // intercept & parse response before returning + if (this.readyState == 2) { + this.dispatchEvent(new Event('loadstart')); + this._headers = nativeResponse.headers; + this.status = nativeResponse.status; + this.response = nativeResponse.data; + this.responseText = nativeResponse.headers[ + 'Content-Type' + ]?.startsWith('application/json') + ? JSON.stringify(nativeResponse.data) + : nativeResponse.data; + this.responseURL = nativeResponse.url; + this.readyState = 4; + this.dispatchEvent(new Event('load')); + this.dispatchEvent(new Event('loadend')); + } + console.timeEnd(tag); + }) + .catch((error: any) => { this.dispatchEvent(new Event('loadstart')); - this._headers = nativeResponse.headers; - this.status = nativeResponse.status; - this.response = nativeResponse.data; - this.responseText = nativeResponse.headers[ - 'Content-Type' - ]?.startsWith('application/json') - ? JSON.stringify(nativeResponse.data) - : nativeResponse.data; - this.responseURL = nativeResponse.url; + this.status = error.status; + this._headers = error.headers; + this.response = error.data; + this.responseText = JSON.stringify(error.data); + this.responseURL = error.url; this.readyState = 4; - this.dispatchEvent(new Event('load')); + this.dispatchEvent(new Event('error')); this.dispatchEvent(new Event('loadend')); - } - console.timeEnd(tag); - }) - .catch((error: any) => { - this.dispatchEvent(new Event('loadstart')); - this.status = error.status; - this._headers = error.headers; - this.response = error.data; - this.responseText = JSON.stringify(error.data); - this.responseURL = error.url; - this.readyState = 4; - this.dispatchEvent(new Event('error')); - this.dispatchEvent(new Event('loadend')); - console.timeEnd(tag); - }); + console.timeEnd(tag); + }); + }); } catch (error) { this.dispatchEvent(new Event('loadstart')); this.status = 500; diff --git a/core/src/core-plugins.ts b/core/src/core-plugins.ts index 47e24e8332..dcce08e63a 100644 --- a/core/src/core-plugins.ts +++ b/core/src/core-plugins.ts @@ -194,6 +194,11 @@ export interface HttpOptions { * (already encoded, azure/firebase testing, etc.). The default is _true_. */ shouldEncodeUrlParams?: boolean; + /** + * 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'; } export interface HttpParams { diff --git a/core/src/definitions-internal.ts b/core/src/definitions-internal.ts index 2f058dffab..c14afad030 100644 --- a/core/src/definitions-internal.ts +++ b/core/src/definitions-internal.ts @@ -214,3 +214,11 @@ export interface WindowCapacitor { }; }; } + +export interface CapFormDataEntry { + key: string; + value: string; + type: 'base64File' | 'string'; + contentType?: string; + fileName?: string; +} diff --git a/core/tsconfig.json b/core/tsconfig.json index 286a62aa4b..7a518ed631 100644 --- a/core/tsconfig.json +++ b/core/tsconfig.json @@ -3,7 +3,7 @@ "declaration": true, "esModuleInterop": true, "importHelpers": true, - "lib": ["dom"], + "lib": ["dom", "dom.iterable"], "module": "es2015", "moduleResolution": "node", "noEmitHelpers": true, diff --git a/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift b/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift index bfe845b595..f677382651 100644 --- a/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift +++ b/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift @@ -93,7 +93,66 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate { return normalized[index.lowercased()] } - public func getRequestData(_ body: JSValue, _ contentType: String) throws -> Data? { + func getRequestDataFromFormData(_ data: JSValue) throws -> Data? { + guard let list = data as? JSArray else { + // Throw, other data types explicitly not supported. + throw CapacitorUrlRequestError.serializationError("Data must be an array for FormData") + } + + var data = Data() + + // Update the contentType with the new boundary + let boundary = UUID().uuidString + let contentType = "multipart/form-data; boundary=\(boundary)" + request.setValue(contentType, forHTTPHeaderField: "Content-Type") + headers["Content-Type"] = contentType + + for entry in list { + guard let item = entry as? [String: String] else { + throw CapacitorUrlRequestError.serializationError("Data must be an array for FormData") + } + + let type = item["type"] + let key = item["key"] + let value = item["value"]! + + if type == "base64File" { + let fileName = item["fileName"] + let fileContentType = item["contentType"] + + data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!) + data.append("Content-Disposition: form-data; name=\"\(key!)\"; filename=\"\(fileName!)\"\r\n".data(using: .utf8)!) + data.append("Content-Type: \(fileContentType!)\r\n".data(using: .utf8)!) + data.append("Content-Transfer-Encoding: binary\r\n".data(using: .utf8)!) + data.append("\r\n".data(using: .utf8)!) + + data.append(Data(base64Encoded: value)!) + + data.append("\r\n".data(using: .utf8)!) + } else if type == "string" { + data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!) + data.append("Content-Disposition: form-data; name=\"\(key!)\"\r\n".data(using: .utf8)!) + data.append(value.data(using: .utf8)!) + data.append("\r\n".data(using: .utf8)!) + } + + } + + data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!) + + return data + } + + public func getRequestData(_ body: JSValue, _ contentType: String, _ dataType: String) throws -> Data? { + if dataType == "file" { + guard let stringData = body as? String else { + throw CapacitorUrlRequestError.serializationError("[ data ] argument could not be parsed as string") + } + return Data(base64Encoded: stringData) + } else if dataType == "formData" { + return try getRequestDataFromFormData(body) + } + // If data can be parsed directly as a string, return that without processing. if let strVal = try? getRequestDataAsString(body) { return strVal @@ -125,11 +184,11 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate { } } - public func setRequestBody(_ body: JSValue) throws { + public func setRequestBody(_ body: JSValue, _ dataType: String) throws { let contentType = self.getRequestHeader("Content-Type") as? String if contentType != nil { - request.httpBody = try getRequestData(body, contentType!) + request.httpBody = try getRequestData(body, contentType!, dataType) } } diff --git a/ios/Capacitor/Capacitor/Plugins/HttpRequestHandler.swift b/ios/Capacitor/Capacitor/Plugins/HttpRequestHandler.swift index 5b332019c0..ea990d3cc1 100644 --- a/ios/Capacitor/Capacitor/Plugins/HttpRequestHandler.swift +++ b/ios/Capacitor/Capacitor/Plugins/HttpRequestHandler.swift @@ -151,6 +151,7 @@ open class HttpRequestHandler { let responseType = call.getString("responseType") ?? "text" let connectTimeout = call.getDouble("connectTimeout") let readTimeout = call.getDouble("readTimeout") + let dataType = call.getString("dataType") ?? "any" if urlString == urlString.removingPercentEncoding { guard let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { throw URLError(.badURL) } @@ -172,7 +173,7 @@ open class HttpRequestHandler { if let data = call.options["data"] as? JSValue { do { - try request.setRequestBody(data) + try request.setRequestBody(data, dataType) } catch { // Explicitly reject if the http request body was not set successfully, // so as to not send a known malformed request, and to provide the developer with additional context. diff --git a/ios/Capacitor/Capacitor/assets/native-bridge.js b/ios/Capacitor/Capacitor/assets/native-bridge.js index 38c5837879..8d23090f57 100644 --- a/ios/Capacitor/Capacitor/assets/native-bridge.js +++ b/ios/Capacitor/Capacitor/assets/native-bridge.js @@ -35,6 +35,57 @@ var nativeBridge = (function (exports) { // For removing exports for iOS/Android, keep let for reassignment // eslint-disable-next-line let dummy = {}; + const readFileAsBase64 = (file) => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => { + const data = reader.result; + resolve(btoa(data)); + }; + reader.onerror = reject; + reader.readAsBinaryString(file); + }); + const convertFormData = async (formData) => { + const newFormData = []; + for (const pair of formData.entries()) { + const [key, value] = pair; + if (value instanceof File) { + const base64File = await readFileAsBase64(value); + newFormData.push({ + key, + value: base64File, + type: 'base64File', + contentType: value.type, + fileName: value.name, + }); + } + else { + newFormData.push({ key, value, type: 'string' }); + } + } + return newFormData; + }; + const convertBody = async (body) => { + if (body instanceof FormData) { + const formData = await convertFormData(body); + const boundary = `${Date.now()}`; + return { + data: formData, + type: 'formData', + headers: { + 'Content-Type': `multipart/form-data; boundary=--${boundary}`, + }, + }; + } + 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 initBridge = (w) => { const getPlatformId = (win) => { var _a, _b; @@ -368,7 +419,6 @@ var nativeBridge = (function (exports) { if (doPatchHttp) { // fetch patch window.fetch = async (resource, options) => { - var _a; if (!(resource.toString().startsWith('http:') || resource.toString().startsWith('https:'))) { return win.CapacitorWebFetch(resource, options); @@ -377,18 +427,23 @@ var nativeBridge = (function (exports) { console.time(tag); try { // intercept request & pass to the bridge - let headers = options === null || options === void 0 ? void 0 : options.headers; + const { data: requestData, type, headers, } = await convertBody((options === null || options === void 0 ? void 0 : options.body) || undefined); + let optionHeaders = options === null || options === void 0 ? void 0 : options.headers; if ((options === null || options === void 0 ? void 0 : options.headers) instanceof Headers) { - headers = Object.fromEntries(options.headers.entries()); + optionHeaders = Object.fromEntries(options.headers.entries()); } const nativeResponse = await cap.nativePromise('CapacitorHttp', 'request', { url: resource, method: (options === null || options === void 0 ? void 0 : options.method) ? options.method : undefined, - data: (options === null || options === void 0 ? void 0 : options.body) ? options.body : undefined, - headers: headers, + data: requestData, + dataType: type, + headers: Object.assign(Object.assign({}, headers), optionHeaders), }); - let data = ((_a = nativeResponse.headers['Content-Type']) === null || _a === void 0 ? void 0 : _a.startsWith('application/json')) - ? JSON.stringify(nativeResponse.data) : nativeResponse.data; + 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; @@ -518,44 +573,49 @@ var nativeBridge = (function (exports) { console.time(tag); try { this.readyState = 2; - // intercept request & pass to the bridge - cap - .nativePromise('CapacitorHttp', 'request', { - url: this._url, - method: this._method, - data: body !== null ? body : undefined, - headers: this._headers != null && Object.keys(this._headers).length > 0 + convertBody(body).then(({ data, type, headers }) => { + const otherHeaders = this._headers != null && Object.keys(this._headers).length > 0 ? this._headers - : undefined, - }) - .then((nativeResponse) => { - var _a; - // intercept & parse response before returning - if (this.readyState == 2) { + : undefined; + // intercept request & pass to the bridge + cap + .nativePromise('CapacitorHttp', 'request', { + url: this._url, + method: this._method, + data: data !== null ? data : undefined, + headers: Object.assign(Object.assign({}, headers), otherHeaders), + dataType: type, + }) + .then((nativeResponse) => { + var _a; + // intercept & parse response before returning + if (this.readyState == 2) { + this.dispatchEvent(new Event('loadstart')); + this._headers = nativeResponse.headers; + this.status = nativeResponse.status; + this.response = nativeResponse.data; + this.responseText = ((_a = nativeResponse.headers['Content-Type']) === null || _a === void 0 ? void 0 : _a.startsWith('application/json')) + ? JSON.stringify(nativeResponse.data) + : nativeResponse.data; + this.responseURL = nativeResponse.url; + this.readyState = 4; + this.dispatchEvent(new Event('load')); + this.dispatchEvent(new Event('loadend')); + } + console.timeEnd(tag); + }) + .catch((error) => { this.dispatchEvent(new Event('loadstart')); - this._headers = nativeResponse.headers; - this.status = nativeResponse.status; - this.response = nativeResponse.data; - this.responseText = ((_a = nativeResponse.headers['Content-Type']) === null || _a === void 0 ? void 0 : _a.startsWith('application/json')) - ? JSON.stringify(nativeResponse.data) : nativeResponse.data; - this.responseURL = nativeResponse.url; + this.status = error.status; + this._headers = error.headers; + this.response = error.data; + this.responseText = JSON.stringify(error.data); + this.responseURL = error.url; this.readyState = 4; - this.dispatchEvent(new Event('load')); + this.dispatchEvent(new Event('error')); this.dispatchEvent(new Event('loadend')); - } - console.timeEnd(tag); - }) - .catch((error) => { - this.dispatchEvent(new Event('loadstart')); - this.status = error.status; - this._headers = error.headers; - this.response = error.data; - this.responseText = JSON.stringify(error.data); - this.responseURL = error.url; - this.readyState = 4; - this.dispatchEvent(new Event('error')); - this.dispatchEvent(new Event('loadend')); - console.timeEnd(tag); + console.timeEnd(tag); + }); }); } catch (error) {