浏览代码

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.
SebiVPS 5 年前
父节点
当前提交
cdbfc19cd2
共有 5 个文件被更改,包括 138 次插入32 次删除
  1. 11
    0
      docs/Reference.md
  2. 1
    0
      ios/RNCWKWebView.h
  3. 114
    32
      ios/RNCWKWebView.m
  4. 4
    0
      ios/RNCWKWebViewManager.m
  5. 8
    0
      src/WebViewTypes.ts

+ 11
- 0
docs/Reference.md 查看文件

@@ -50,6 +50,7 @@ This document lays out the current public properties and methods for the React N
50 50
 - [`cacheEnabled`](Reference.md#cacheEnabled)
51 51
 - [`pagingEnabled`](Reference.md#pagingEnabled)
52 52
 - [`allowsLinkPreview`](Reference.md#allowsLinkPreview)
53
+- [`sharedCookiesEnabled`](Reference.md#sharedCookiesEnabled)
53 54
 
54 55
 ## Methods Index
55 56
 
@@ -833,6 +834,16 @@ A Boolean value that determines whether pressing on a link displays a preview of
833 834
 | ------- | -------- | -------- |
834 835
 | boolean | No       | iOS      |
835 836
 
837
+---
838
+
839
+### `sharedCookiesEnabled`
840
+
841
+Set `true` if shared cookies from `[NSHTTPCookieStorage sharedHTTPCookieStorage]` should used for every load request in the `RNCWKWebView`. The default value is `false`.
842
+
843
+| Type    | Required | Platform |
844
+| ------- | -------- | -------- |
845
+| boolean | No       | iOS      |
846
+
836 847
 ## Methods
837 848
 
838 849
 ### `extraNativeComponentConfig()`

+ 1
- 0
ios/RNCWKWebView.h 查看文件

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

+ 114
- 32
ios/RNCWKWebView.m 查看文件

@@ -127,6 +127,68 @@ static NSURLCredential* clientAuthenticationCredential;
127 127
     wkWebViewConfig.mediaPlaybackRequiresUserAction = _mediaPlaybackRequiresUserAction;
128 128
 #endif
129 129
 
130
+    if(_sharedCookiesEnabled) {
131
+      // More info to sending cookies with WKWebView
132
+      // https://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview/26577303#26577303
133
+      if (@available(iOS 11.0, *)) {
134
+        // Set Cookies in iOS 11 and above, initialize websiteDataStore before setting cookies
135
+        // See also https://forums.developer.apple.com/thread/97194
136
+        // check if websiteDataStore has not been initialized before
137
+        if(!_incognito && !_cacheEnabled) {
138
+          wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
139
+        }
140
+        for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
141
+          [wkWebViewConfig.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:nil];
142
+        }
143
+      } else {
144
+        NSMutableString *script = [NSMutableString string];
145
+
146
+        // Clear all existing cookies in a direct called function. This ensures that no
147
+        // javascript error will break the web content javascript.
148
+        // We keep this code here, if someone requires that Cookies are also removed within the
149
+        // the WebView and want to extends the current sharedCookiesEnabled option with an
150
+        // additional property.
151
+        // Generates JS: document.cookie = "key=; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"
152
+        // for each cookie which is already available in the WebView context.
153
+        /*
154
+        [script appendString:@"(function () {\n"];
155
+        [script appendString:@"  var cookies = document.cookie.split('; ');\n"];
156
+        [script appendString:@"  for (var i = 0; i < cookies.length; i++) {\n"];
157
+        [script appendString:@"    if (cookies[i].indexOf('=') !== -1) {\n"];
158
+        [script appendString:@"      document.cookie = cookies[i].split('=')[0] + '=; Expires=Thu, 01 Jan 1970 00:00:01 GMT';\n"];
159
+        [script appendString:@"    }\n"];
160
+        [script appendString:@"  }\n"];
161
+        [script appendString:@"})();\n\n"];
162
+        */
163
+
164
+        // Set cookies in a direct called function. This ensures that no
165
+        // javascript error will break the web content javascript.
166
+          // Generates JS: document.cookie = "key=value; Path=/; Expires=Thu, 01 Jan 20xx 00:00:01 GMT;"
167
+        // for each cookie which is available in the application context.
168
+        [script appendString:@"(function () {\n"];
169
+        for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
170
+          [script appendFormat:@"document.cookie = %@ + '=' + %@",
171
+            RCTJSONStringify(cookie.name, NULL),
172
+            RCTJSONStringify(cookie.value, NULL)];
173
+          if (cookie.path) {
174
+            [script appendFormat:@" + '; Path=' + %@", RCTJSONStringify(cookie.path, NULL)];
175
+          }
176
+          if (cookie.expiresDate) {
177
+            [script appendFormat:@" + '; Expires=' + new Date(%f).toUTCString()",
178
+              cookie.expiresDate.timeIntervalSince1970 * 1000
179
+            ];
180
+          }
181
+          [script appendString:@";\n"];
182
+        }
183
+        [script appendString:@"})();\n"];
184
+
185
+        WKUserScript* cookieInScript = [[WKUserScript alloc] initWithSource:script
186
+                                                              injectionTime:WKUserScriptInjectionTimeAtDocumentStart
187
+                                                           forMainFrameOnly:YES];
188
+        [wkWebViewConfig.userContentController addUserScript:cookieInScript];
189
+      }
190
+    }
191
+
130 192
     _webView = [[WKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig];
131 193
     _webView.scrollView.delegate = self;
132 194
     _webView.UIDelegate = self;
@@ -270,37 +332,36 @@ static NSURLCredential* clientAuthenticationCredential;
270 332
 
271 333
 - (void)visitSource
272 334
 {
273
-  // Check for a static html source first
274
-  NSString *html = [RCTConvert NSString:_source[@"html"]];
275
-  if (html) {
276
-    NSURL *baseURL = [RCTConvert NSURL:_source[@"baseUrl"]];
277
-    if (!baseURL) {
278
-      baseURL = [NSURL URLWithString:@"about:blank"];
335
+    // Check for a static html source first
336
+    NSString *html = [RCTConvert NSString:_source[@"html"]];
337
+    if (html) {
338
+        NSURL *baseURL = [RCTConvert NSURL:_source[@"baseUrl"]];
339
+        if (!baseURL) {
340
+            baseURL = [NSURL URLWithString:@"about:blank"];
341
+        }
342
+        [_webView loadHTMLString:html baseURL:baseURL];
343
+        return;
279 344
     }
280
-    [_webView loadHTMLString:html baseURL:baseURL];
281
-    return;
282
-  }
283
-
284
-  NSURLRequest *request = [RCTConvert NSURLRequest:_source];
285
-  // Because of the way React works, as pages redirect, we actually end up
286
-  // passing the redirect urls back here, so we ignore them if trying to load
287
-  // the same url. We'll expose a call to 'reload' to allow a user to load
288
-  // the existing page.
289
-  if ([request.URL isEqual:_webView.URL]) {
290
-    return;
291
-  }
292
-  if (!request.URL) {
293
-    // Clear the webview
294
-    [_webView loadHTMLString:@"" baseURL:nil];
295
-    return;
296
-  }
297
-  if (request.URL.host) {
298
-    [_webView loadRequest:request];
299
-  }
300
-  else {
301
-    [_webView loadFileURL:request.URL allowingReadAccessToURL:request.URL];
302
-  }
303 345
 
346
+    NSURLRequest *request = [self requestForSource:_source];
347
+    // Because of the way React works, as pages redirect, we actually end up
348
+    // passing the redirect urls back here, so we ignore them if trying to load
349
+    // the same url. We'll expose a call to 'reload' to allow a user to load
350
+    // the existing page.
351
+    if ([request.URL isEqual:_webView.URL]) {
352
+        return;
353
+    }
354
+    if (!request.URL) {
355
+        // Clear the webview
356
+        [_webView loadHTMLString:@"" baseURL:nil];
357
+        return;
358
+    }
359
+    if (request.URL.host) {
360
+        [_webView loadRequest:request];
361
+    }
362
+    else {
363
+        [_webView loadFileURL:request.URL allowingReadAccessToURL:request.URL];
364
+    }
304 365
 }
305 366
 
306 367
 -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
@@ -662,11 +723,11 @@ static NSURLCredential* clientAuthenticationCredential;
662 723
    * [_webView reload] doesn't reload the webpage. Therefore, we must
663 724
    * manually call [_webView loadRequest:request].
664 725
    */
665
-  NSURLRequest *request = [RCTConvert NSURLRequest:self.source];
726
+  NSURLRequest *request = [self requestForSource:self.source];
727
+
666 728
   if (request.URL && !_webView.URL.absoluteString.length) {
667 729
     [_webView loadRequest:request];
668
-  }
669
-  else {
730
+  } else {
670 731
     [_webView reload];
671 732
   }
672 733
 }
@@ -681,4 +742,25 @@ static NSURLCredential* clientAuthenticationCredential;
681 742
   _bounces = bounces;
682 743
   _webView.scrollView.bounces = bounces;
683 744
 }
745
+
746
+- (NSURLRequest *)requestForSource:(id)json {
747
+  NSURLRequest *request = [RCTConvert NSURLRequest:self.source];
748
+
749
+  // If sharedCookiesEnabled we automatically add all application cookies to the
750
+  // http request. This is automatically done on iOS 11+ in the WebView constructor.
751
+  // Se we need to manually add these shared cookies here only for iOS versions < 11.
752
+  if (_sharedCookiesEnabled) {
753
+    if (@available(iOS 11.0, *)) {
754
+      // see WKWebView initialization for added cookies
755
+    } else {
756
+      NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL];
757
+      NSDictionary<NSString *, NSString *> *cookieHeader = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
758
+      NSMutableURLRequest *mutableRequest = [request mutableCopy];
759
+      [mutableRequest setAllHTTPHeaderFields:cookieHeader];
760
+      return mutableRequest;
761
+    }
762
+  }
763
+  return request;
764
+}
765
+
684 766
 @end

+ 4
- 0
ios/RNCWKWebViewManager.m 查看文件

@@ -81,6 +81,10 @@ RCT_CUSTOM_VIEW_PROPERTY(scrollEnabled, BOOL, RNCWKWebView) {
81 81
   view.scrollEnabled = json == nil ? true : [RCTConvert BOOL: json];
82 82
 }
83 83
 
84
+RCT_CUSTOM_VIEW_PROPERTY(sharedCookiesEnabled, BOOL, RNCWKWebView) {
85
+    view.sharedCookiesEnabled = json == nil ? false : [RCTConvert BOOL: json];
86
+}
87
+
84 88
 RCT_CUSTOM_VIEW_PROPERTY(decelerationRate, CGFloat, RNCWKWebView) {
85 89
   view.decelerationRate = json == nil ? UIScrollViewDecelerationRateNormal : [RCTConvert CGFloat: json];
86 90
 }

+ 8
- 0
src/WebViewTypes.ts 查看文件

@@ -387,12 +387,20 @@ export interface IOSWebViewProps extends WebViewSharedProps {
387 387
    */
388 388
   allowsLinkPreview?: boolean;
389 389
 
390
+  /**
391
+   * Set true if shared cookies from HTTPCookieStorage should used for every load request in the
392
+   * `RNCWKWebView`. The default value is `false`.
393
+   * @platform ios
394
+   */
395
+  sharedCookiesEnabled?: boolean;
396
+
390 397
   /**
391 398
    * A Boolean value that determines whether scrolling is disabled in a particular direction.
392 399
    * The default value is `true`.
393 400
    * @platform ios
394 401
    */
395 402
   directionalLockEnabled?: boolean;
403
+
396 404
 }
397 405
 
398 406
 export interface AndroidWebViewProps extends WebViewSharedProps {