feat(iOS cookies): implement sharedCookiesEnabled prop for iOS RNCWKWebView (#175)

We had the problem on iOS WebViews that local cookies (stored in local HTTPCookieStorage, set with  [react-native-cookie](https://github.com/shimohq/react-native-cookie) ) were not added in loadRequests. On Android the local stored cookies were sent like expected.
This kinda "hacky" solution is the only way we found, that works for us.
The stack overview link is in the code below.
If someone finds a better solution we would very much like to accept that.
This commit is contained in:
SebiVPS 2019-04-15 10:19:10 +02:00 committed by Thibault Malbranche
parent a9c00faf37
commit cdbfc19cd2
5 changed files with 138 additions and 32 deletions

View File

@ -50,6 +50,7 @@ This document lays out the current public properties and methods for the React N
- [`cacheEnabled`](Reference.md#cacheEnabled) - [`cacheEnabled`](Reference.md#cacheEnabled)
- [`pagingEnabled`](Reference.md#pagingEnabled) - [`pagingEnabled`](Reference.md#pagingEnabled)
- [`allowsLinkPreview`](Reference.md#allowsLinkPreview) - [`allowsLinkPreview`](Reference.md#allowsLinkPreview)
- [`sharedCookiesEnabled`](Reference.md#sharedCookiesEnabled)
## Methods Index ## Methods Index
@ -833,6 +834,16 @@ A Boolean value that determines whether pressing on a link displays a preview of
| ------- | -------- | -------- | | ------- | -------- | -------- |
| boolean | No | iOS | | 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 ## Methods
### `extraNativeComponentConfig()` ### `extraNativeComponentConfig()`

View File

@ -26,6 +26,7 @@
@property (nonatomic, assign) BOOL messagingEnabled; @property (nonatomic, assign) BOOL messagingEnabled;
@property (nonatomic, copy) NSString *injectedJavaScript; @property (nonatomic, copy) NSString *injectedJavaScript;
@property (nonatomic, assign) BOOL scrollEnabled; @property (nonatomic, assign) BOOL scrollEnabled;
@property (nonatomic, assign) BOOL sharedCookiesEnabled;
@property (nonatomic, assign) BOOL pagingEnabled; @property (nonatomic, assign) BOOL pagingEnabled;
@property (nonatomic, assign) CGFloat decelerationRate; @property (nonatomic, assign) CGFloat decelerationRate;
@property (nonatomic, assign) BOOL allowsInlineMediaPlayback; @property (nonatomic, assign) BOOL allowsInlineMediaPlayback;

View File

@ -127,6 +127,68 @@ static NSURLCredential* clientAuthenticationCredential;
wkWebViewConfig.mediaPlaybackRequiresUserAction = _mediaPlaybackRequiresUserAction; wkWebViewConfig.mediaPlaybackRequiresUserAction = _mediaPlaybackRequiresUserAction;
#endif #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 = [[WKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig];
_webView.scrollView.delegate = self; _webView.scrollView.delegate = self;
_webView.UIDelegate = self; _webView.UIDelegate = self;
@ -270,37 +332,36 @@ static NSURLCredential* clientAuthenticationCredential;
- (void)visitSource - (void)visitSource
{ {
// Check for a static html source first // Check for a static html source first
NSString *html = [RCTConvert NSString:_source[@"html"]]; NSString *html = [RCTConvert NSString:_source[@"html"]];
if (html) { if (html) {
NSURL *baseURL = [RCTConvert NSURL:_source[@"baseUrl"]]; NSURL *baseURL = [RCTConvert NSURL:_source[@"baseUrl"]];
if (!baseURL) { if (!baseURL) {
baseURL = [NSURL URLWithString:@"about:blank"]; 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 -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
@ -662,11 +723,11 @@ static NSURLCredential* clientAuthenticationCredential;
* [_webView reload] doesn't reload the webpage. Therefore, we must * [_webView reload] doesn't reload the webpage. Therefore, we must
* manually call [_webView loadRequest:request]. * manually call [_webView loadRequest:request].
*/ */
NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; NSURLRequest *request = [self requestForSource:self.source];
if (request.URL && !_webView.URL.absoluteString.length) { if (request.URL && !_webView.URL.absoluteString.length) {
[_webView loadRequest:request]; [_webView loadRequest:request];
} } else {
else {
[_webView reload]; [_webView reload];
} }
} }
@ -681,4 +742,25 @@ static NSURLCredential* clientAuthenticationCredential;
_bounces = bounces; _bounces = bounces;
_webView.scrollView.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<NSString *, NSString *> *cookieHeader = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[mutableRequest setAllHTTPHeaderFields:cookieHeader];
return mutableRequest;
}
}
return request;
}
@end @end

View File

@ -81,6 +81,10 @@ RCT_CUSTOM_VIEW_PROPERTY(scrollEnabled, BOOL, RNCWKWebView) {
view.scrollEnabled = json == nil ? true : [RCTConvert BOOL: json]; 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) { RCT_CUSTOM_VIEW_PROPERTY(decelerationRate, CGFloat, RNCWKWebView) {
view.decelerationRate = json == nil ? UIScrollViewDecelerationRateNormal : [RCTConvert CGFloat: json]; view.decelerationRate = json == nil ? UIScrollViewDecelerationRateNormal : [RCTConvert CGFloat: json];
} }

View File

@ -387,12 +387,20 @@ export interface IOSWebViewProps extends WebViewSharedProps {
*/ */
allowsLinkPreview?: boolean; 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. * A Boolean value that determines whether scrolling is disabled in a particular direction.
* The default value is `true`. * The default value is `true`.
* @platform ios * @platform ios
*/ */
directionalLockEnabled?: boolean; directionalLockEnabled?: boolean;
} }
export interface AndroidWebViewProps extends WebViewSharedProps { export interface AndroidWebViewProps extends WebViewSharedProps {