diff --git a/docs/Reference.md b/docs/Reference.md index a7af6d4..e3ece1b 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -50,6 +50,7 @@ This document lays out the current public properties and methods for the React N - [`cacheEnabled`](Reference.md#cacheEnabled) - [`pagingEnabled`](Reference.md#pagingEnabled) - [`allowsLinkPreview`](Reference.md#allowsLinkPreview) +- [`sharedCookiesEnabled`](Reference.md#sharedCookiesEnabled) ## Methods Index @@ -833,6 +834,16 @@ A Boolean value that determines whether pressing on a link displays a preview of | ------- | -------- | -------- | | boolean | No | iOS | +--- + +### `sharedCookiesEnabled` + +Set `true` if shared cookies from `[NSHTTPCookieStorage sharedHTTPCookieStorage]` should used for every load request in the `RNCWKWebView`. The default value is `false`. + +| Type | Required | Platform | +| ------- | -------- | -------- | +| boolean | No | iOS | + ## Methods ### `extraNativeComponentConfig()` diff --git a/ios/RNCWKWebView.h b/ios/RNCWKWebView.h index 437d9bd..7cea86b 100644 --- a/ios/RNCWKWebView.h +++ b/ios/RNCWKWebView.h @@ -26,6 +26,7 @@ @property (nonatomic, assign) BOOL messagingEnabled; @property (nonatomic, copy) NSString *injectedJavaScript; @property (nonatomic, assign) BOOL scrollEnabled; +@property (nonatomic, assign) BOOL sharedCookiesEnabled; @property (nonatomic, assign) BOOL pagingEnabled; @property (nonatomic, assign) CGFloat decelerationRate; @property (nonatomic, assign) BOOL allowsInlineMediaPlayback; diff --git a/ios/RNCWKWebView.m b/ios/RNCWKWebView.m index 5307ede..2e5ad05 100644 --- a/ios/RNCWKWebView.m +++ b/ios/RNCWKWebView.m @@ -127,6 +127,68 @@ static NSURLCredential* clientAuthenticationCredential; wkWebViewConfig.mediaPlaybackRequiresUserAction = _mediaPlaybackRequiresUserAction; #endif + if(_sharedCookiesEnabled) { + // More info to sending cookies with WKWebView + // https://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview/26577303#26577303 + if (@available(iOS 11.0, *)) { + // Set Cookies in iOS 11 and above, initialize websiteDataStore before setting cookies + // See also https://forums.developer.apple.com/thread/97194 + // check if websiteDataStore has not been initialized before + if(!_incognito && !_cacheEnabled) { + wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore]; + } + for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) { + [wkWebViewConfig.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:nil]; + } + } else { + NSMutableString *script = [NSMutableString string]; + + // Clear all existing cookies in a direct called function. This ensures that no + // javascript error will break the web content javascript. + // We keep this code here, if someone requires that Cookies are also removed within the + // the WebView and want to extends the current sharedCookiesEnabled option with an + // additional property. + // Generates JS: document.cookie = "key=; Expires=Thu, 01 Jan 1970 00:00:01 GMT;" + // for each cookie which is already available in the WebView context. + /* + [script appendString:@"(function () {\n"]; + [script appendString:@" var cookies = document.cookie.split('; ');\n"]; + [script appendString:@" for (var i = 0; i < cookies.length; i++) {\n"]; + [script appendString:@" if (cookies[i].indexOf('=') !== -1) {\n"]; + [script appendString:@" document.cookie = cookies[i].split('=')[0] + '=; Expires=Thu, 01 Jan 1970 00:00:01 GMT';\n"]; + [script appendString:@" }\n"]; + [script appendString:@" }\n"]; + [script appendString:@"})();\n\n"]; + */ + + // Set cookies in a direct called function. This ensures that no + // javascript error will break the web content javascript. + // Generates JS: document.cookie = "key=value; Path=/; Expires=Thu, 01 Jan 20xx 00:00:01 GMT;" + // for each cookie which is available in the application context. + [script appendString:@"(function () {\n"]; + for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) { + [script appendFormat:@"document.cookie = %@ + '=' + %@", + RCTJSONStringify(cookie.name, NULL), + RCTJSONStringify(cookie.value, NULL)]; + if (cookie.path) { + [script appendFormat:@" + '; Path=' + %@", RCTJSONStringify(cookie.path, NULL)]; + } + if (cookie.expiresDate) { + [script appendFormat:@" + '; Expires=' + new Date(%f).toUTCString()", + cookie.expiresDate.timeIntervalSince1970 * 1000 + ]; + } + [script appendString:@";\n"]; + } + [script appendString:@"})();\n"]; + + WKUserScript* cookieInScript = [[WKUserScript alloc] initWithSource:script + injectionTime:WKUserScriptInjectionTimeAtDocumentStart + forMainFrameOnly:YES]; + [wkWebViewConfig.userContentController addUserScript:cookieInScript]; + } + } + _webView = [[WKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig]; _webView.scrollView.delegate = self; _webView.UIDelegate = self; @@ -270,37 +332,36 @@ static NSURLCredential* clientAuthenticationCredential; - (void)visitSource { - // Check for a static html source first - NSString *html = [RCTConvert NSString:_source[@"html"]]; - if (html) { - NSURL *baseURL = [RCTConvert NSURL:_source[@"baseUrl"]]; - if (!baseURL) { - baseURL = [NSURL URLWithString:@"about:blank"]; + // Check for a static html source first + NSString *html = [RCTConvert NSString:_source[@"html"]]; + if (html) { + NSURL *baseURL = [RCTConvert NSURL:_source[@"baseUrl"]]; + if (!baseURL) { + baseURL = [NSURL URLWithString:@"about:blank"]; + } + [_webView loadHTMLString:html baseURL:baseURL]; + return; } - [_webView loadHTMLString:html baseURL:baseURL]; - return; - } - - NSURLRequest *request = [RCTConvert NSURLRequest:_source]; - // Because of the way React works, as pages redirect, we actually end up - // passing the redirect urls back here, so we ignore them if trying to load - // the same url. We'll expose a call to 'reload' to allow a user to load - // the existing page. - if ([request.URL isEqual:_webView.URL]) { - return; - } - if (!request.URL) { - // Clear the webview - [_webView loadHTMLString:@"" baseURL:nil]; - return; - } - if (request.URL.host) { - [_webView loadRequest:request]; - } - else { - [_webView loadFileURL:request.URL allowingReadAccessToURL:request.URL]; - } + NSURLRequest *request = [self requestForSource:_source]; + // Because of the way React works, as pages redirect, we actually end up + // passing the redirect urls back here, so we ignore them if trying to load + // the same url. We'll expose a call to 'reload' to allow a user to load + // the existing page. + if ([request.URL isEqual:_webView.URL]) { + return; + } + if (!request.URL) { + // Clear the webview + [_webView loadHTMLString:@"" baseURL:nil]; + return; + } + if (request.URL.host) { + [_webView loadRequest:request]; + } + else { + [_webView loadFileURL:request.URL allowingReadAccessToURL:request.URL]; + } } -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView @@ -662,11 +723,11 @@ static NSURLCredential* clientAuthenticationCredential; * [_webView reload] doesn't reload the webpage. Therefore, we must * manually call [_webView loadRequest:request]. */ - NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; + NSURLRequest *request = [self requestForSource:self.source]; + if (request.URL && !_webView.URL.absoluteString.length) { [_webView loadRequest:request]; - } - else { + } else { [_webView reload]; } } @@ -681,4 +742,25 @@ static NSURLCredential* clientAuthenticationCredential; _bounces = bounces; _webView.scrollView.bounces = bounces; } + +- (NSURLRequest *)requestForSource:(id)json { + NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; + + // If sharedCookiesEnabled we automatically add all application cookies to the + // http request. This is automatically done on iOS 11+ in the WebView constructor. + // Se we need to manually add these shared cookies here only for iOS versions < 11. + if (_sharedCookiesEnabled) { + if (@available(iOS 11.0, *)) { + // see WKWebView initialization for added cookies + } else { + NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL]; + NSDictionary *cookieHeader = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; + NSMutableURLRequest *mutableRequest = [request mutableCopy]; + [mutableRequest setAllHTTPHeaderFields:cookieHeader]; + return mutableRequest; + } + } + return request; +} + @end diff --git a/ios/RNCWKWebViewManager.m b/ios/RNCWKWebViewManager.m index bc58bd2..79c4f00 100644 --- a/ios/RNCWKWebViewManager.m +++ b/ios/RNCWKWebViewManager.m @@ -81,6 +81,10 @@ RCT_CUSTOM_VIEW_PROPERTY(scrollEnabled, BOOL, RNCWKWebView) { view.scrollEnabled = json == nil ? true : [RCTConvert BOOL: json]; } +RCT_CUSTOM_VIEW_PROPERTY(sharedCookiesEnabled, BOOL, RNCWKWebView) { + view.sharedCookiesEnabled = json == nil ? false : [RCTConvert BOOL: json]; +} + RCT_CUSTOM_VIEW_PROPERTY(decelerationRate, CGFloat, RNCWKWebView) { view.decelerationRate = json == nil ? UIScrollViewDecelerationRateNormal : [RCTConvert CGFloat: json]; } diff --git a/src/WebViewTypes.ts b/src/WebViewTypes.ts index 49bab67..5b16b0c 100644 --- a/src/WebViewTypes.ts +++ b/src/WebViewTypes.ts @@ -387,12 +387,20 @@ export interface IOSWebViewProps extends WebViewSharedProps { */ allowsLinkPreview?: boolean; + /** + * Set true if shared cookies from HTTPCookieStorage should used for every load request in the + * `RNCWKWebView`. The default value is `false`. + * @platform ios + */ + sharedCookiesEnabled?: boolean; + /** * A Boolean value that determines whether scrolling is disabled in a particular direction. * The default value is `true`. * @platform ios */ directionalLockEnabled?: boolean; + } export interface AndroidWebViewProps extends WebViewSharedProps {