├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── plugin.xml ├── src └── ios │ └── WebviewProxy.m └── www └── WebviewProxy.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Logs** 21 |
22 |
[Please insert any logs here]
23 |
24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature-request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 GEDYS IntraWare GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cordova-plugin-webview-proxy 2 | 3 | > Work in progress plugin. 4 | ## Cordova Plugin to proxy http(s) requests on iOS without CORS and Cookie restrictions 5 | 6 | With this plugin you can do requests to remote servers just like you would do normally. Cookies and CORS restrictions don't apply here because the requests is performed by native code. 7 | 8 | You just need to change the URL: 9 | 10 | ```javascript 11 | const response = await fetch(window.WebviewProxy.convertProxyUrl(url)); 12 | console.debug(response); 13 | ``` 14 | 15 | #### To delete all Cookies use this: 16 | ```javascript 17 | window.WebviewProxy.clearCookie(); 18 | ``` 19 | 20 | # Make sure you are using a custom scheme with your iOS platform 21 | 22 | This plugin uses the WKURLSchemeHandler provided by WKWebView. It requires the latest version of cordova-ios. 23 | 24 | **You enable the custom scheme by setting these preferences in config.xml** 25 | 26 | ```xml 27 | 28 | 29 | ``` 30 | # Testing this plugin 31 | 32 | [This test app](https://github.com/NiklasMerz/cors-cookie-proxy-test-app) with custom pages and a simple backend is helpful for testing and developing this plugin. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-webview-proxy", 3 | "version": "0.0.5", 4 | "description": "Proxy HTTP(S) requests through native code to make CORS and Cookies work in cordova-ios", 5 | "cordova": { 6 | "id": "cordova-plugin-webview-proxy", 7 | "platforms": [ 8 | "ios" 9 | ] 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/GEDYSIntraWare/cordova-plugin-webview-proxy.git" 14 | }, 15 | "keywords": [ 16 | "ecosystem:cordova", 17 | "cordova-ios", 18 | "cordova", 19 | "webview", 20 | "wkwebview", 21 | "ios" 22 | ], 23 | "scripts": { 24 | "test": "paramedic.." 25 | }, 26 | "author": "GEDYS IntraWare GmbH", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/GEDYSIntraWare/cordova-plugin-webview-proxy/issues" 30 | }, 31 | "homepage": "https://github.com/GEDYSIntraWare/cordova-plugin-webview-proxy" 32 | } 33 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | cordova-plugin-webview-proxy 8 | GEDYS IntraWare GmbH 9 | Proxy HTTP(S) requests through native code to make CORS and Cookies work in cordova-ios 10 | MIT 11 | 12 | 13 | 14 | https://github.com/GEDYSIntraWare/cordova-plugin-webview-proxy.git 15 | https://github.com/GEDYSIntraWare/cordova-plugin-webview-proxy/issues 16 | cordova,webview,wkwebview,ios 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/ios/WebviewProxy.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | @interface WebviewProxy : CDVPlugin { 6 | 7 | } 8 | 9 | @property (nonatomic) NSMutableArray* stoppedTasks; 10 | 11 | - (void)clearCookie:(CDVInvokedUrlCommand*)command; 12 | 13 | @end 14 | 15 | @implementation WebviewProxy 16 | 17 | - (void) pluginInitialize { 18 | NSLog(@"Proxy active on /_https_proxy and /_http_proxy_"); 19 | self.stoppedTasks = [[NSMutableArray alloc] init]; 20 | } 21 | 22 | - (BOOL) overrideSchemeTask: (id )urlSchemeTask { 23 | NSURL * url = urlSchemeTask.request.URL; 24 | NSDictionary * header = urlSchemeTask.request.allHTTPHeaderFields; 25 | // In Versions below iOS 16 the appendString method seems to encode the string in a way that works for the request URL 26 | NSMutableString * stringToLoad = [NSMutableString string]; 27 | [stringToLoad appendString:url.path]; 28 | NSString * method = urlSchemeTask.request.HTTPMethod; 29 | NSData * body = urlSchemeTask.request.HTTPBody; 30 | 31 | // Request should be handled by this plugin if the url contains the proxy path 32 | if ([stringToLoad hasPrefix:@"/_http_proxy_"]||[stringToLoad hasPrefix:@"/_https_proxy_"]) { 33 | // First sync all cookies from the WKHTTPCookieStore to the NSHTTPCookieStorage.. 34 | // .. to make cookies from main or IAB webview available for proxy requests 35 | WKWebsiteDataStore* dataStore = [WKWebsiteDataStore defaultDataStore]; 36 | WKHTTPCookieStore* cookieStore = dataStore.httpCookieStore; 37 | [cookieStore getAllCookies:^(NSArray* cookies) { 38 | NSHTTPCookie* cookie; 39 | for(cookie in cookies) { 40 | NSMutableDictionary* cookieDict = [cookie.properties mutableCopy]; 41 | [cookieDict removeObjectForKey:NSHTTPCookieDiscard]; // Remove the discard flag. If it is set (even to false), the expires date will NOT be kept. 42 | NSHTTPCookie* newCookie = [NSHTTPCookie cookieWithProperties:cookieDict]; 43 | [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:newCookie]; 44 | 45 | } 46 | 47 | NSString * startPath = @""; 48 | if(url.query) { 49 | [stringToLoad appendString:@"?"]; 50 | [stringToLoad appendString:url.query]; 51 | } 52 | startPath = [stringToLoad stringByReplacingOccurrencesOfString:@"/_http_proxy_" withString:@"http://"]; 53 | startPath = [startPath stringByReplacingOccurrencesOfString:@"/_https_proxy_" withString:@"https://"]; 54 | // Since iOS 16 the url is not automatically encoded anymore. 55 | // Here it is decoded and encoded again, so it is always encoded as needed regardless of the iOS Version. 56 | startPath = [startPath stringByRemovingPercentEncoding]; 57 | startPath = [startPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 58 | NSURL * requestUrl = [NSURL URLWithString:startPath]; 59 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; 60 | [request setHTTPMethod:method]; 61 | [request setURL:requestUrl]; 62 | if (body) { 63 | [request setHTTPBody:body]; 64 | } 65 | [request setAllHTTPHeaderFields:header]; 66 | [request setHTTPShouldHandleCookies:YES]; 67 | [request setTimeoutInterval:1800]; 68 | 69 | [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 70 | if(error && (self.stoppedTasks == nil || ![self.stoppedTasks containsObject:urlSchemeTask])) { 71 | @try { 72 | NSLog(@"WebviewProxy error: %@", error); 73 | [urlSchemeTask didFailWithError:error]; 74 | return; 75 | } @catch (NSException *exception) { 76 | NSLog(@"WebViewProxy send error exception: %@", exception.debugDescription); 77 | } 78 | } 79 | 80 | // Copy cookies from the native requests cookie storage to other cookie storage to make them available to the webviews 81 | NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; 82 | if(httpResponse) { 83 | NSArray* cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[httpResponse allHeaderFields] forURL:response.URL]; 84 | [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookies forURL:httpResponse.URL mainDocumentURL:nil]; 85 | cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; 86 | 87 | for (NSHTTPCookie* c in cookies) 88 | { 89 | dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){ 90 | //running in background thread is necessary because setCookie otherwise fails 91 | dispatch_async(dispatch_get_main_queue(), ^(void){ 92 | [cookieStore setCookie:c completionHandler:nil]; 93 | }); 94 | }); 95 | }; 96 | } 97 | 98 | // Do not use urlSchemeTask if it has been closed in stopURLSchemeTask. Otherwise the app will crash. 99 | @try { 100 | if(self.stoppedTasks == nil || ![self.stoppedTasks containsObject:urlSchemeTask]) { 101 | [urlSchemeTask didReceiveResponse:response]; 102 | [urlSchemeTask didReceiveData:data]; 103 | [urlSchemeTask didFinish]; 104 | } else { 105 | NSLog(@"Task stopped %@", startPath); 106 | } 107 | } @catch (NSException *exception) { 108 | NSLog(@"WebViewProxy send response exception: %@", exception.debugDescription); 109 | } @finally { 110 | // Cleanup 111 | [self.stoppedTasks removeObject:urlSchemeTask]; 112 | } 113 | }] resume]; 114 | }]; 115 | 116 | return YES; 117 | } 118 | 119 | return NO; 120 | } 121 | 122 | - (void) stopSchemeTask: (id )urlSchemeTask { 123 | NSLog(@"Stop WevViewProxy %@", urlSchemeTask.debugDescription); 124 | [self.stoppedTasks addObject:urlSchemeTask]; 125 | } 126 | 127 | - (void) clearCookie:(CDVInvokedUrlCommand*)command { 128 | CDVPluginResult* pluginResult = nil; 129 | 130 | WKWebsiteDataStore* dataStore = [WKWebsiteDataStore defaultDataStore]; 131 | WKHTTPCookieStore* cookieStore = dataStore.httpCookieStore; 132 | [cookieStore getAllCookies:^(NSArray * cookies) { 133 | for (NSHTTPCookie* _c in cookies) 134 | { 135 | [cookieStore deleteCookie:_c completionHandler:nil]; 136 | }; 137 | }]; 138 | 139 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 140 | } 141 | 142 | @end 143 | -------------------------------------------------------------------------------- /www/WebviewProxy.js: -------------------------------------------------------------------------------- 1 | /* global cordova */ 2 | 3 | function WebviewProxy() { 4 | } 5 | 6 | WebviewProxy.prototype.convertProxyUrl = function (path) { 7 | if (!path || !window.CDV_ASSETS_URL) { 8 | return path; 9 | } 10 | if (path.startsWith('http://')) { 11 | return window.CDV_ASSETS_URL + '/_http_proxy_' + encodeURIComponent(path.replace('http://', '')); 12 | } 13 | if (path.startsWith('https://')) { 14 | return window.CDV_ASSETS_URL + '/_https_proxy_' + encodeURIComponent(path.replace('https://', '')); 15 | } 16 | return path; 17 | } 18 | 19 | WebviewProxy.prototype.clearCookie = function (successCallback, errorCallback) { 20 | cordova.exec(successCallback, errorCallback, "WebviewProxy", "clearCookie", []); 21 | } 22 | 23 | module.exports = new WebviewProxy(); --------------------------------------------------------------------------------