diff --git a/android/capacitor/src/main/assets/native-bridge.js b/android/capacitor/src/main/assets/native-bridge.js index 1fadb70b74..8db84c3b99 100644 --- a/android/capacitor/src/main/assets/native-bridge.js +++ b/android/capacitor/src/main/assets/native-bridge.js @@ -130,6 +130,26 @@ var nativeBridge = (function (exports) { } return { data: body, type: 'json' }; }; + const CAPACITOR_HTTP_INTERCEPTOR = '/_capacitor_http_interceptor_'; + const CAPACITOR_HTTPS_INTERCEPTOR = '/_capacitor_https_interceptor_'; + // TODO: export as Cap function + const isRelativeOrProxyUrl = (url) => !url || + !(url.startsWith('http:') || url.startsWith('https:')) || + url.indexOf(CAPACITOR_HTTP_INTERCEPTOR) > -1 || + url.indexOf(CAPACITOR_HTTPS_INTERCEPTOR) > -1; + // TODO: export as Cap function + const createProxyUrl = (url, win) => { + var _a, _b; + if (isRelativeOrProxyUrl(url)) + return url; + let proxyUrl = new URL(url); + const isHttps = proxyUrl.protocol === 'https:'; + const originalHostname = proxyUrl.hostname; + const originalPathname = proxyUrl.pathname; + proxyUrl = new URL((_b = (_a = win.Capacitor) === null || _a === void 0 ? void 0 : _a.getServerUrl()) !== null && _b !== void 0 ? _b : ''); + proxyUrl.pathname = `${isHttps ? CAPACITOR_HTTPS_INTERCEPTOR : CAPACITOR_HTTP_INTERCEPTOR}/${originalHostname}${originalPathname}`; + return proxyUrl.toString(); + }; const initBridge = (w) => { const getPlatformId = (win) => { var _a, _b; @@ -476,6 +496,15 @@ var nativeBridge = (function (exports) { if (request.url.startsWith(`${cap.getServerUrl()}/`)) { return win.CapacitorWebFetch(resource, options); } + if (!(options === null || options === void 0 ? void 0 : options.method) || + options.method.toLocaleUpperCase() === 'GET' || + options.method.toLocaleUpperCase() === 'HEAD' || + options.method.toLocaleUpperCase() === 'OPTIONS' || + options.method.toLocaleUpperCase() === 'TRACE') { + const modifiedResource = createProxyUrl(resource.toString(), win); + const response = await win.CapacitorWebFetch(modifiedResource, options); + return response; + } const tag = `CapacitorHttp fetch ${Date.now()} ${resource}`; console.time(tag); try { @@ -545,12 +574,11 @@ var nativeBridge = (function (exports) { }); xhr.readyState = 0; const prototype = win.CapacitorWebXMLHttpRequest.prototype; - const isRelativeURL = (url) => !url || !(url.startsWith('http:') || url.startsWith('https:')); const isProgressEventAvailable = () => typeof ProgressEvent !== 'undefined' && ProgressEvent.prototype instanceof Event; // XHR patch abort prototype.abort = function () { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.abort.call(this); } this.readyState = 0; @@ -561,10 +589,18 @@ var nativeBridge = (function (exports) { }; // XHR patch open prototype.open = function (method, url) { + this._method = method.toLocaleUpperCase(); this._url = url; - this._method = method; - if (isRelativeURL(url)) { - return win.CapacitorWebXMLHttpRequest.open.call(this, method, url); + if (!this._method || + this._method === 'GET' || + this._method === 'HEAD' || + this._method === 'OPTIONS' || + this._method === 'TRACE') { + if (isRelativeOrProxyUrl(url)) { + return win.CapacitorWebXMLHttpRequest.open.call(this, method, url); + } + this._url = createProxyUrl(this._url, win); + return win.CapacitorWebXMLHttpRequest.open.call(this, method, this._url); } setTimeout(() => { this.dispatchEvent(new Event('loadstart')); @@ -573,14 +609,14 @@ var nativeBridge = (function (exports) { }; // XHR patch set request header prototype.setRequestHeader = function (header, value) { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.setRequestHeader.call(this, header, value); } this._headers[header] = value; }; // XHR patch send prototype.send = function (body) { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.send.call(this, body); } const tag = `CapacitorHttp XMLHttpRequest ${Date.now()} ${this._url}`; @@ -699,7 +735,7 @@ var nativeBridge = (function (exports) { }; // XHR patch getAllResponseHeaders prototype.getAllResponseHeaders = function () { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.getAllResponseHeaders.call(this); } let returnString = ''; @@ -712,7 +748,7 @@ var nativeBridge = (function (exports) { }; // XHR patch getResponseHeader prototype.getResponseHeader = function (name) { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.getResponseHeader.call(this, name); } return this._headers[name]; diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index a1d79e2c5a..b5bc69eab2 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -91,6 +91,9 @@ public class Bridge { public static final String CAPACITOR_HTTPS_SCHEME = "https"; public static final String CAPACITOR_FILE_START = "/_capacitor_file_"; public static final String CAPACITOR_CONTENT_START = "/_capacitor_content_"; + public static final String CAPACITOR_HTTP_INTERCEPTOR_START = "/_capacitor_http_interceptor_"; + public static final String CAPACITOR_HTTPS_INTERCEPTOR_START = "/_capacitor_https_interceptor_"; + public static final int DEFAULT_ANDROID_WEBVIEW_VERSION = 60; public static final int MINIMUM_ANDROID_WEBVIEW_VERSION = 55; public static final int DEFAULT_HUAWEI_WEBVIEW_VERSION = 10; diff --git a/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java b/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java index 0f43aec00f..9e893a0fa8 100755 --- a/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java +++ b/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java @@ -15,12 +15,16 @@ */ package com.getcapacitor; +import static com.getcapacitor.plugin.util.HttpRequestHandler.isDomainExcludedFromSSL; + import android.content.Context; import android.net.Uri; import android.util.Base64; import android.webkit.CookieManager; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; +import com.getcapacitor.plugin.util.CapacitorHttpUrlConnection; +import com.getcapacitor.plugin.util.HttpRequestHandler; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; @@ -29,6 +33,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -165,6 +170,22 @@ private static Uri parseAndVerifyUrl(String url) { */ public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) { Uri loadingUrl = request.getUrl(); + + if ( + null != loadingUrl.getPath() && + ( + loadingUrl.getPath().startsWith(Bridge.CAPACITOR_HTTP_INTERCEPTOR_START) || + loadingUrl.getPath().startsWith(Bridge.CAPACITOR_HTTPS_INTERCEPTOR_START) + ) + ) { + Logger.debug("Handling CapacitorHttp request: " + loadingUrl); + try { + return handleCapacitorHttpRequest(request); + } catch (Exception e) { + Logger.error(e.getLocalizedMessage()); + } + } + PathHandler handler; synchronized (uriMatcher) { handler = (PathHandler) uriMatcher.match(request.getUrl()); @@ -199,6 +220,110 @@ private boolean isAllowedUrl(Uri loadingUrl) { return !(bridge.getServerUrl() == null && !bridge.getAppAllowNavigationMask().matches(loadingUrl.getHost())); } + private String getReasonPhraseFromResponseCode(int code) { + return switch (code) { + case 100 -> "Continue"; + case 101 -> "Switching Protocols"; + case 200 -> "OK"; + case 201 -> "Created"; + case 202 -> "Accepted"; + case 203 -> "Non-Authoritative Information"; + case 204 -> "No Content"; + case 205 -> "Reset Content"; + case 206 -> "Partial Content"; + case 300 -> "Multiple Choices"; + case 301 -> "Moved Permanently"; + case 302 -> "Found"; + case 303 -> "See Other"; + case 304 -> "Not Modified"; + case 400 -> "Bad Request"; + case 401 -> "Unauthorized"; + case 403 -> "Forbidden"; + case 404 -> "Not Found"; + case 405 -> "Method Not Allowed"; + case 406 -> "Not Acceptable"; + case 407 -> "Proxy Authentication Required"; + case 408 -> "Request Timeout"; + case 409 -> "Conflict"; + case 410 -> "Gone"; + case 500 -> "Internal Server Error"; + case 501 -> "Not Implemented"; + case 502 -> "Bad Gateway"; + case 503 -> "Service Unavailable"; + case 504 -> "Gateway Timeout"; + case 505 -> "HTTP Version Not Supported"; + default -> "Unknown"; + }; + } + + private WebResourceResponse handleCapacitorHttpRequest(WebResourceRequest request) throws IOException { + boolean isHttps = + request.getUrl().getPath() != null && request.getUrl().getPath().startsWith(Bridge.CAPACITOR_HTTPS_INTERCEPTOR_START); + + String urlString = request + .getUrl() + .toString() + .replace(bridge.getLocalUrl(), isHttps ? "https:/" : "http:/") + .replace(Bridge.CAPACITOR_HTTP_INTERCEPTOR_START, "") + .replace(Bridge.CAPACITOR_HTTPS_INTERCEPTOR_START, ""); + URL url = new URL(urlString); + JSObject headers = new JSObject(); + + for (Map.Entry header : request.getRequestHeaders().entrySet()) { + headers.put(header.getKey(), header.getValue()); + } + + HttpRequestHandler.HttpURLConnectionBuilder connectionBuilder = new HttpRequestHandler.HttpURLConnectionBuilder() + .setUrl(url) + .setMethod(request.getMethod()) + .setHeaders(headers) + .openConnection(); + + CapacitorHttpUrlConnection connection = connectionBuilder.build(); + + if (!isDomainExcludedFromSSL(bridge, url)) { + connection.setSSLSocketFactory(bridge); + } + + connection.connect(); + + String mimeType = null; + String encoding = null; + Map responseHeaders = new LinkedHashMap<>(); + for (Map.Entry> entry : connection.getHeaderFields().entrySet()) { + StringBuilder builder = new StringBuilder(); + for (String value : entry.getValue()) { + builder.append(value); + builder.append(", "); + } + builder.setLength(builder.length() - 2); + + if ("Content-Type".equalsIgnoreCase(entry.getKey())) { + String[] contentTypeParts = builder.toString().split(";"); + mimeType = contentTypeParts[0].trim(); + if (contentTypeParts.length > 1) { + String[] encodingParts = contentTypeParts[1].split("="); + if (encodingParts.length > 1) { + encoding = encodingParts[1].trim(); + } + } + } else { + responseHeaders.put(entry.getKey(), builder.toString()); + } + } + + InputStream inputStream = connection.getInputStream(); + + if (null == mimeType) { + mimeType = getMimeType(request.getUrl().getPath(), inputStream); + } + + int responseCode = connection.getResponseCode(); + String reasonPhrase = getReasonPhraseFromResponseCode(responseCode); + + return new WebResourceResponse(mimeType, encoding, responseCode, reasonPhrase, responseHeaders, inputStream); + } + private WebResourceResponse handleLocalRequest(WebResourceRequest request, PathHandler handler) { String path = request.getUrl().getPath(); 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 4bd5df66de..7859613356 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 @@ -426,7 +426,7 @@ public static JSObject request(PluginCall call, String httpMethod, Bridge bridge return response; } - private static Boolean isDomainExcludedFromSSL(Bridge bridge, URL url) { + public static Boolean isDomainExcludedFromSSL(Bridge bridge, URL url) { try { Class sslPinningImpl = Class.forName("io.ionic.sslpinning.SSLPinning"); Method method = sslPinningImpl.getDeclaredMethod("isDomainExcluded", Bridge.class, URL.class); diff --git a/core/native-bridge.ts b/core/native-bridge.ts index e623f1653a..1e1af3dc51 100644 --- a/core/native-bridge.ts +++ b/core/native-bridge.ts @@ -118,6 +118,32 @@ const convertBody = async ( return { data: body, type: 'json' }; }; +const CAPACITOR_HTTP_INTERCEPTOR = '/_capacitor_http_interceptor_'; +const CAPACITOR_HTTPS_INTERCEPTOR = '/_capacitor_https_interceptor_'; + +// TODO: export as Cap function +const isRelativeOrProxyUrl = (url: string | undefined): boolean => + !url || + !(url.startsWith('http:') || url.startsWith('https:')) || + url.indexOf(CAPACITOR_HTTP_INTERCEPTOR) > -1 || + url.indexOf(CAPACITOR_HTTPS_INTERCEPTOR) > -1; + +// TODO: export as Cap function +const createProxyUrl = (url: string, win: WindowCapacitor): string => { + if (isRelativeOrProxyUrl(url)) return url; + + let proxyUrl = new URL(url); + const isHttps = proxyUrl.protocol === 'https:'; + const originalHostname = proxyUrl.hostname; + const originalPathname = proxyUrl.pathname; + proxyUrl = new URL(win.Capacitor?.getServerUrl() ?? ''); + + proxyUrl.pathname = `${ + isHttps ? CAPACITOR_HTTPS_INTERCEPTOR : CAPACITOR_HTTP_INTERCEPTOR + }/${originalHostname}${originalPathname}`; + return proxyUrl.toString(); +}; + const initBridge = (w: any): void => { const getPlatformId = (win: WindowCapacitor): 'android' | 'ios' | 'web' => { if (win?.androidBridge) { @@ -523,6 +549,22 @@ const initBridge = (w: any): void => { return win.CapacitorWebFetch(resource, options); } + if ( + !options?.method || + options.method.toLocaleUpperCase() === 'GET' || + options.method.toLocaleUpperCase() === 'HEAD' || + options.method.toLocaleUpperCase() === 'OPTIONS' || + options.method.toLocaleUpperCase() === 'TRACE' + ) { + const modifiedResource = createProxyUrl(resource.toString(), win); + const response = await win.CapacitorWebFetch( + modifiedResource, + options, + ); + + return response; + } + const tag = `CapacitorHttp fetch ${Date.now()} ${resource}`; console.time(tag); @@ -621,15 +663,13 @@ const initBridge = (w: any): void => { xhr.readyState = 0; const prototype = win.CapacitorWebXMLHttpRequest.prototype; - const isRelativeURL = (url: string | undefined) => - !url || !(url.startsWith('http:') || url.startsWith('https:')); const isProgressEventAvailable = () => typeof ProgressEvent !== 'undefined' && ProgressEvent.prototype instanceof Event; // XHR patch abort prototype.abort = function () { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.abort.call(this); } this.readyState = 0; @@ -641,14 +681,30 @@ const initBridge = (w: any): void => { // XHR patch open prototype.open = function (method: string, url: string) { + this._method = method.toLocaleUpperCase(); this._url = url; - this._method = method; - if (isRelativeURL(url)) { + if ( + !this._method || + this._method === 'GET' || + this._method === 'HEAD' || + this._method === 'OPTIONS' || + this._method === 'TRACE' + ) { + if (isRelativeOrProxyUrl(url)) { + return win.CapacitorWebXMLHttpRequest.open.call( + this, + method, + url, + ); + } + + this._url = createProxyUrl(this._url, win); + return win.CapacitorWebXMLHttpRequest.open.call( this, method, - url, + this._url, ); } @@ -663,7 +719,7 @@ const initBridge = (w: any): void => { header: string, value: string, ) { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.setRequestHeader.call( this, header, @@ -675,7 +731,7 @@ const initBridge = (w: any): void => { // XHR patch send prototype.send = function (body?: Document | XMLHttpRequestBodyInit) { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.send.call(this, body); } @@ -813,7 +869,7 @@ const initBridge = (w: any): void => { // XHR patch getAllResponseHeaders prototype.getAllResponseHeaders = function () { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.getAllResponseHeaders.call( this, ); @@ -830,7 +886,7 @@ const initBridge = (w: any): void => { // XHR patch getResponseHeader prototype.getResponseHeader = function (name: string) { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.getResponseHeader.call( this, name, diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index 2d0a1488aa..495a747d4d 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -94,6 +94,8 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { static let tmpVCAppeared = Notification(name: Notification.Name(rawValue: "tmpViewControllerAppeared")) public static let capacitorSite = "https://capacitorjs.com/" public static let fileStartIdentifier = "/_capacitor_file_" + public static let httpInterceptorStartIdentifier = "/_capacitor_http_interceptor_" + public static let httpsInterceptorStartIdentifier = "/_capacitor_https_interceptor_" public static let defaultScheme = "capacitor" public private(set) var webViewAssetHandler: WebViewAssetHandler @@ -288,7 +290,7 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol { if let pluginJSON = Bundle.main.url(forResource: "capacitor.config", withExtension: "json") { let pluginData = try Data(contentsOf: pluginJSON) let registrationList = try JSONDecoder().decode(RegistrationList.self, from: pluginData) - + for plugin in registrationList.packageClassList { if let pluginClass = NSClassFromString(plugin) { if class_getSuperclass(pluginClass) == CDVPlugin.self { diff --git a/ios/Capacitor/Capacitor/WebViewAssetHandler.swift b/ios/Capacitor/Capacitor/WebViewAssetHandler.swift index 8414dbaf57..8c980ac26b 100644 --- a/ios/Capacitor/Capacitor/WebViewAssetHandler.swift +++ b/ios/Capacitor/Capacitor/WebViewAssetHandler.swift @@ -20,10 +20,25 @@ open class WebViewAssetHandler: NSObject, WKURLSchemeHandler { self.serverUrl = serverUrl } + private func isUsingLiveReload(_ localUrl: URL) -> Bool { + return self.serverUrl != nil && self.serverUrl?.scheme != localUrl.scheme + } + open func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { let startPath: String let url = urlSchemeTask.request.url! let stringToLoad = url.path + let localUrl = URL.init(string: url.absoluteString)! + + if url.path.starts(with: CapacitorBridge.httpInterceptorStartIdentifier) { + handleCapacitorHttpRequest(urlSchemeTask, localUrl, false) + return + } + + if url.path.starts(with: CapacitorBridge.httpsInterceptorStartIdentifier) { + handleCapacitorHttpRequest(urlSchemeTask, localUrl, true) + return + } if stringToLoad.starts(with: CapacitorBridge.fileStartIdentifier) { startPath = stringToLoad.replacingOccurrences(of: CapacitorBridge.fileStartIdentifier, with: "") @@ -31,7 +46,6 @@ open class WebViewAssetHandler: NSObject, WKURLSchemeHandler { startPath = router.route(for: stringToLoad) } - let localUrl = URL.init(string: url.absoluteString)! let fileUrl = URL.init(fileURLWithPath: startPath) do { @@ -43,9 +57,9 @@ open class WebViewAssetHandler: NSObject, WKURLSchemeHandler { ] // if using live reload, then set CORS headers - if self.serverUrl != nil && self.serverUrl?.scheme != localUrl.scheme { + if isUsingLiveReload(localUrl) { headers["Access-Control-Allow-Origin"] = self.serverUrl?.absoluteString - headers["Access-Control-Allow-Methods"] = "GET, OPTIONS" + headers["Access-Control-Allow-Methods"] = "GET, HEAD, OPTIONS, TRACE" } if let rangeString = urlSchemeTask.request.value(forHTTPHeaderField: "Range"), @@ -121,6 +135,69 @@ open class WebViewAssetHandler: NSObject, WKURLSchemeHandler { return false } + func handleCapacitorHttpRequest(_ urlSchemeTask: WKURLSchemeTask, _ localUrl: URL, _ isHttpsRequest: Bool) { + var urlRequest = urlSchemeTask.request + guard let url = urlRequest.url else { return } + var targetUrl = url.absoluteString + .replacingOccurrences(of: CapacitorBridge.httpInterceptorStartIdentifier, with: "") + .replacingOccurrences(of: CapacitorBridge.httpsInterceptorStartIdentifier, with: "") + + // Only replace first occurrence of the scheme + if let range = targetUrl.range(of: localUrl.scheme ?? InstanceDescriptorDefaults.scheme) { + targetUrl = targetUrl.replacingCharacters(in: range, with: isHttpsRequest ? "https" : "http") + } + + // Only replace first occurrence of the hostname + if let range = targetUrl.range(of: (localUrl.host ?? InstanceDescriptorDefaults.hostname) + "/") { + targetUrl = targetUrl.replacingCharacters(in: range, with: "") + } + + urlRequest.url = URL(string: targetUrl) + + let urlSession = URLSession.shared + let task = urlSession.dataTask(with: urlRequest) { (data, response, error) in + if let error = error { + urlSchemeTask.didFailWithError(error) + return + } + + if let response = response as? HTTPURLResponse { + let existingHeaders = response.allHeaderFields + var newHeaders: [AnyHashable: Any] = [:] + + // if using live reload, then set CORS headers + if self.isUsingLiveReload(url) { + newHeaders = [ + "Access-Control-Allow-Origin": self.serverUrl?.absoluteString ?? "", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS, TRACE" + ] + } + + if let mergedHeaders = existingHeaders.merging(newHeaders, uniquingKeysWith: { (current, _) in current }) as? [String: String] { + + if let responseUrl = response.url { + if let modifiedResponse = HTTPURLResponse( + url: responseUrl, + statusCode: response.statusCode, + httpVersion: nil, + headerFields: mergedHeaders + ) { + urlSchemeTask.didReceive(modifiedResponse) + } + } + + if let data = data { + urlSchemeTask.didReceive(data) + } + } + } + urlSchemeTask.didFinish() + return + } + + task.resume() + } + public let mimeTypes = [ "aaf": "application/octet-stream", "aca": "application/octet-stream", diff --git a/ios/Capacitor/Capacitor/assets/native-bridge.js b/ios/Capacitor/Capacitor/assets/native-bridge.js index 1fadb70b74..8db84c3b99 100644 --- a/ios/Capacitor/Capacitor/assets/native-bridge.js +++ b/ios/Capacitor/Capacitor/assets/native-bridge.js @@ -130,6 +130,26 @@ var nativeBridge = (function (exports) { } return { data: body, type: 'json' }; }; + const CAPACITOR_HTTP_INTERCEPTOR = '/_capacitor_http_interceptor_'; + const CAPACITOR_HTTPS_INTERCEPTOR = '/_capacitor_https_interceptor_'; + // TODO: export as Cap function + const isRelativeOrProxyUrl = (url) => !url || + !(url.startsWith('http:') || url.startsWith('https:')) || + url.indexOf(CAPACITOR_HTTP_INTERCEPTOR) > -1 || + url.indexOf(CAPACITOR_HTTPS_INTERCEPTOR) > -1; + // TODO: export as Cap function + const createProxyUrl = (url, win) => { + var _a, _b; + if (isRelativeOrProxyUrl(url)) + return url; + let proxyUrl = new URL(url); + const isHttps = proxyUrl.protocol === 'https:'; + const originalHostname = proxyUrl.hostname; + const originalPathname = proxyUrl.pathname; + proxyUrl = new URL((_b = (_a = win.Capacitor) === null || _a === void 0 ? void 0 : _a.getServerUrl()) !== null && _b !== void 0 ? _b : ''); + proxyUrl.pathname = `${isHttps ? CAPACITOR_HTTPS_INTERCEPTOR : CAPACITOR_HTTP_INTERCEPTOR}/${originalHostname}${originalPathname}`; + return proxyUrl.toString(); + }; const initBridge = (w) => { const getPlatformId = (win) => { var _a, _b; @@ -476,6 +496,15 @@ var nativeBridge = (function (exports) { if (request.url.startsWith(`${cap.getServerUrl()}/`)) { return win.CapacitorWebFetch(resource, options); } + if (!(options === null || options === void 0 ? void 0 : options.method) || + options.method.toLocaleUpperCase() === 'GET' || + options.method.toLocaleUpperCase() === 'HEAD' || + options.method.toLocaleUpperCase() === 'OPTIONS' || + options.method.toLocaleUpperCase() === 'TRACE') { + const modifiedResource = createProxyUrl(resource.toString(), win); + const response = await win.CapacitorWebFetch(modifiedResource, options); + return response; + } const tag = `CapacitorHttp fetch ${Date.now()} ${resource}`; console.time(tag); try { @@ -545,12 +574,11 @@ var nativeBridge = (function (exports) { }); xhr.readyState = 0; const prototype = win.CapacitorWebXMLHttpRequest.prototype; - const isRelativeURL = (url) => !url || !(url.startsWith('http:') || url.startsWith('https:')); const isProgressEventAvailable = () => typeof ProgressEvent !== 'undefined' && ProgressEvent.prototype instanceof Event; // XHR patch abort prototype.abort = function () { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.abort.call(this); } this.readyState = 0; @@ -561,10 +589,18 @@ var nativeBridge = (function (exports) { }; // XHR patch open prototype.open = function (method, url) { + this._method = method.toLocaleUpperCase(); this._url = url; - this._method = method; - if (isRelativeURL(url)) { - return win.CapacitorWebXMLHttpRequest.open.call(this, method, url); + if (!this._method || + this._method === 'GET' || + this._method === 'HEAD' || + this._method === 'OPTIONS' || + this._method === 'TRACE') { + if (isRelativeOrProxyUrl(url)) { + return win.CapacitorWebXMLHttpRequest.open.call(this, method, url); + } + this._url = createProxyUrl(this._url, win); + return win.CapacitorWebXMLHttpRequest.open.call(this, method, this._url); } setTimeout(() => { this.dispatchEvent(new Event('loadstart')); @@ -573,14 +609,14 @@ var nativeBridge = (function (exports) { }; // XHR patch set request header prototype.setRequestHeader = function (header, value) { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.setRequestHeader.call(this, header, value); } this._headers[header] = value; }; // XHR patch send prototype.send = function (body) { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.send.call(this, body); } const tag = `CapacitorHttp XMLHttpRequest ${Date.now()} ${this._url}`; @@ -699,7 +735,7 @@ var nativeBridge = (function (exports) { }; // XHR patch getAllResponseHeaders prototype.getAllResponseHeaders = function () { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.getAllResponseHeaders.call(this); } let returnString = ''; @@ -712,7 +748,7 @@ var nativeBridge = (function (exports) { }; // XHR patch getResponseHeader prototype.getResponseHeader = function (name) { - if (isRelativeURL(this._url)) { + if (isRelativeOrProxyUrl(this._url)) { return win.CapacitorWebXMLHttpRequest.getResponseHeader.call(this, name); } return this._headers[name];