diff --git a/README.md b/README.md index c3d5ac54..23c2d018 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,14 @@ -# Ionic Web View +# Ionic Web View for Cordova -The Web View plugin for Cordova that is specialized for Ionic apps. +A Web View plugin for Cordova, focused on providing the highest performance experience for Ionic apps (but can be used with any Cordova app). -This is for `cordova-plugin-ionic-webview` @ `2.x`, which uses the latest and greatest features and may not work with all apps. See [Requirements](#requirements) and [Migrating to 2.x](#migrating-to-2x). +This plugin defaults to using WKWebView on iOS and the latest evergreen webview on Android. Additionally, this plugin makes it easy to use HTML5 style routing +that web developers expect for building single-page apps. + +Note: This repo and its documentation are for `cordova-plugin-ionic-webview` @ `2.x`, which uses the new features that may not work with all apps. See [Requirements](#requirements) and [Migrating to 2.x](#migrating-to-2x). :book: **Documentation**: [https://beta.ionicframework.com/docs/building/webview][ionic-webview-docs] @@ -31,12 +34,58 @@ This is for `cordova-plugin-ionic-webview` @ `2.x`, which uses the latest and gr :sparkling_heart: **Want to contribute?** Please see [CONTRIBUTING.md](https://github.com/ionic-team/cordova-plugin-ionic-webview/blob/master/CONTRIBUTING.md). -### Requirements +## Configuration + +This plugin has several configuration options that can be set in `config.xml`. Important: some configuration options should be adjusted for production apps, especially `WKPort`: + +### iOS and Android Preferences + +Preferences available for both iOS and Android platforms + +#### WKPort + +```xml + +``` + +The default port the server will listen on. _You should change this to a random port number!_ + +### iOS Preferences + +Preferences only available for iOS platform + +#### WKSuspendInBackground + +```xml + +``` + +Whether to try to keep the server running when the app is backgrounded. Note: the server will likely be suspended by the OS after a few minutes. In particular, long-lived background tasks are not allowed on iOS outside of select audio and geolocation tasks. + +#### WKBind + +```xml + +``` + +The hostname the server will bind to. There aren't a lot of other valid options, but some prefer binding to "127.0.0.1" + +#### WKInternalConnectionsOnly (New in 2.2.0) + +```xml + +``` + +Whether to restrict access to this server to the app itself. Previous versions of this plugin did not restrict access to the app itself. In 2.2.0 and above, +the plugin now restricts access to only the app itself. + + +## Plugin Requirements * **iOS**: iOS 10+ and `cordova-ios` 4+ -* **Android**: Android 5.0+ and `cordova-android` 6.4+ +* **Android**: Android 4.4+ and `cordova-android` 6.4+ -### Migrating to 2.x +## Migrating to 2.x 1. Remove and re-add the Web View plugin: diff --git a/package.json b/package.json index 748fd7bb..95d7ebd7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ionic-webview", - "version": "2.1.4", + "version": "2.2.0", "description": "The official Ionic's WKWebView Engine Plugin", "main": "index.js", "scripts": { diff --git a/plugin.xml b/plugin.xml index ea8cdc4f..0302b3f8 100644 --- a/plugin.xml +++ b/plugin.xml @@ -19,7 +19,7 @@ under the License. --> - + cordova-plugin-ionic-webview The official Ionic's WKWebView Engine Plugin Apache-2.0 diff --git a/src/ios/CDVWKWebViewEngine.m b/src/ios/CDVWKWebViewEngine.m index c8ce47f1..d81e695d 100644 --- a/src/ios/CDVWKWebViewEngine.m +++ b/src/ios/CDVWKWebViewEngine.m @@ -105,6 +105,8 @@ @interface CDVWKWebViewEngine () @property (nonatomic, weak) id weakScriptMessageHandler; @property (nonatomic, strong) GCDWebServer *webServer; @property (nonatomic, readwrite) CGRect frame; +@property (nonatomic, strong) NSString *userAgentCreds; +@property (nonatomic, assign) BOOL internalConnectionsOnly; @property (nonatomic, readwrite) NSString *CDV_LOCAL_SERVER; @end @@ -259,6 +261,8 @@ - (void)pluginInitialize { // viewController would be available now. we attempt to set all possible delegates to it, by default NSDictionary* settings = self.commandDelegate.settings; + self.internalConnectionsOnly = [settings cordovaBoolSettingForKey:@"WKInternalConnectionsOnly" defaultValue:YES]; + [self initWebServer]; self.uiDelegate = [[CDVWKWebViewUIDelegate alloc] initWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]]; @@ -319,6 +323,9 @@ - (void)pluginInitialize if (IsAtLeastiOSVersion(@"9.0") && [self.viewController isKindOfClass:[CDVViewController class]]) { wkWebView.customUserAgent = ((CDVViewController*) self.viewController).userAgent; } + if (self.internalConnectionsOnly) { + wkWebView.customUserAgent = [NSString stringWithFormat:@"%@/%@",wkWebView.customUserAgent, [self getUserAgentCredentials]]; + } if ([self.viewController conformsToProtocol:@protocol(WKUIDelegate)]) { wkWebView.UIDelegate = (id )self.viewController; @@ -376,6 +383,21 @@ - (void) keyboardDisplayDoesNotRequireUserAction { } } +- (NSString*)getUserAgentCredentials { + if (self.userAgentCreds == nil) { + self.userAgentCreds = [self generateRandomString:32]; + } + return self.userAgentCreds; +} + +- (NSString*)generateRandomString:(int)num { + NSMutableString* string = [NSMutableString stringWithCapacity:num]; + for (int i = 0; i < num; i++) { + [string appendFormat:@"%C", (unichar)('a' + arc4random_uniform(26))]; + } + return string; +} + - (void)onReset { [self addURLObserver]; @@ -799,7 +821,11 @@ -(void)setServerPath:(NSString *) path } __block NSString* serverUrl = self.CDV_LOCAL_SERVER; - [self.webServer addGETHandlerForBasePath:@"/" directoryPath:path indexFilename:((CDVViewController *)self.viewController).startPage cacheAge:0 allowRangeRequests:YES]; + if (self.internalConnectionsOnly) { + [self internalConnectionsGetHandlerForPath:path]; + } else { + [self.webServer addGETHandlerForBasePath:@"/" directoryPath:path indexFilename:((CDVViewController *)self.viewController).startPage cacheAge:0 allowRangeRequests:YES]; + } [self.webServer addHandlerForMethod:@"GET" pathRegex:@"_file_/" requestClass:GCDWebServerFileRequest.class asyncProcessBlock:^(__kindof GCDWebServerRequest * _Nonnull request, GCDWebServerCompletionBlock _Nonnull completionBlock) { NSString *urlToRemove = [serverUrl stringByAppendingString:@"/_file_"]; NSString *absUrl = [[[request URL] absoluteString] stringByReplacingOccurrencesOfString:urlToRemove withString:@""]; @@ -810,7 +836,7 @@ -(void)setServerPath:(NSString *) path } NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:absUrl]) { - GCDWebServerResponse* response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound];; + GCDWebServerResponse* response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound]; completionBlock(response); } else { GCDWebServerFileResponse *response = [GCDWebServerFileResponse responseWithFile:absUrl byteRange:request.byteRange]; @@ -823,6 +849,45 @@ -(void)setServerPath:(NSString *) path } } +-(void) internalConnectionsGetHandlerForPath:(NSString*)directoryPath { + __weak CDVWKWebViewEngine * weakSelf = self; + [self.webServer addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { + if (![requestMethod isEqualToString:@"GET"]) { + return nil; + } + return [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; + } + processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) { + GCDWebServerResponse* response = nil; + NSString* userAgent = [request.headers objectForKey:@"User-Agent"]; + if ([userAgent containsString:[weakSelf getUserAgentCredentials]]) { + NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:1]]; + NSString* fileType = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] fileType]; + if (fileType) { + if ([fileType isEqualToString:NSFileTypeDirectory]) { + NSString* indexPath = [filePath stringByAppendingPathComponent:((CDVViewController *)weakSelf.viewController).startPage]; + NSString* indexType = [[[NSFileManager defaultManager] attributesOfItemAtPath:indexPath error:NULL] fileType]; + if ([indexType isEqualToString:NSFileTypeRegular]) { + response = [GCDWebServerFileResponse responseWithFile:indexPath]; + } + } else if ([fileType isEqualToString:NSFileTypeRegular]) { + response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange]; + [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"]; + } + } + if (response) { + response.cacheControlMaxAge = 0; + } else { + response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound]; + } + [response setValue:@"*" forAdditionalHeader:@"Access-Control-Allow-Origin"]; + } else { + response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized]; + } + return response; + }]; +} + -(void)persistServerBasePath:(CDVInvokedUrlCommand*)command { NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];