Browse Source

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 years ago
parent
commit
cdbfc19cd2
5 changed files with 138 additions and 32 deletions
  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 View File

50
 - [`cacheEnabled`](Reference.md#cacheEnabled)
50
 - [`cacheEnabled`](Reference.md#cacheEnabled)
51
 - [`pagingEnabled`](Reference.md#pagingEnabled)
51
 - [`pagingEnabled`](Reference.md#pagingEnabled)
52
 - [`allowsLinkPreview`](Reference.md#allowsLinkPreview)
52
 - [`allowsLinkPreview`](Reference.md#allowsLinkPreview)
53
+- [`sharedCookiesEnabled`](Reference.md#sharedCookiesEnabled)
53
 
54
 
54
 ## Methods Index
55
 ## Methods Index
55
 
56
 
833
 | ------- | -------- | -------- |
834
 | ------- | -------- | -------- |
834
 | boolean | No       | iOS      |
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
 ## Methods
847
 ## Methods
837
 
848
 
838
 ### `extraNativeComponentConfig()`
849
 ### `extraNativeComponentConfig()`

+ 1
- 0
ios/RNCWKWebView.h View File

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

+ 114
- 32
ios/RNCWKWebView.m View File

127
     wkWebViewConfig.mediaPlaybackRequiresUserAction = _mediaPlaybackRequiresUserAction;
127
     wkWebViewConfig.mediaPlaybackRequiresUserAction = _mediaPlaybackRequiresUserAction;
128
 #endif
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
     _webView = [[WKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig];
192
     _webView = [[WKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig];
131
     _webView.scrollView.delegate = self;
193
     _webView.scrollView.delegate = self;
132
     _webView.UIDelegate = self;
194
     _webView.UIDelegate = self;
270
 
332
 
271
 - (void)visitSource
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
 -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
367
 -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
662
    * [_webView reload] doesn't reload the webpage. Therefore, we must
723
    * [_webView reload] doesn't reload the webpage. Therefore, we must
663
    * manually call [_webView loadRequest:request].
724
    * manually call [_webView loadRequest:request].
664
    */
725
    */
665
-  NSURLRequest *request = [RCTConvert NSURLRequest:self.source];
726
+  NSURLRequest *request = [self requestForSource:self.source];
727
+
666
   if (request.URL && !_webView.URL.absoluteString.length) {
728
   if (request.URL && !_webView.URL.absoluteString.length) {
667
     [_webView loadRequest:request];
729
     [_webView loadRequest:request];
668
-  }
669
-  else {
730
+  } else {
670
     [_webView reload];
731
     [_webView reload];
671
   }
732
   }
672
 }
733
 }
681
   _bounces = bounces;
742
   _bounces = bounces;
682
   _webView.scrollView.bounces = bounces;
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
 @end
766
 @end

+ 4
- 0
ios/RNCWKWebViewManager.m View File

81
   view.scrollEnabled = json == nil ? true : [RCTConvert BOOL: json];
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
 RCT_CUSTOM_VIEW_PROPERTY(decelerationRate, CGFloat, RNCWKWebView) {
88
 RCT_CUSTOM_VIEW_PROPERTY(decelerationRate, CGFloat, RNCWKWebView) {
85
   view.decelerationRate = json == nil ? UIScrollViewDecelerationRateNormal : [RCTConvert CGFloat: json];
89
   view.decelerationRate = json == nil ? UIScrollViewDecelerationRateNormal : [RCTConvert CGFloat: json];
86
 }
90
 }

+ 8
- 0
src/WebViewTypes.ts View File

387
    */
387
    */
388
   allowsLinkPreview?: boolean;
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
    * A Boolean value that determines whether scrolling is disabled in a particular direction.
398
    * A Boolean value that determines whether scrolling is disabled in a particular direction.
392
    * The default value is `true`.
399
    * The default value is `true`.
393
    * @platform ios
400
    * @platform ios
394
    */
401
    */
395
   directionalLockEnabled?: boolean;
402
   directionalLockEnabled?: boolean;
403
+
396
 }
404
 }
397
 
405
 
398
 export interface AndroidWebViewProps extends WebViewSharedProps {
406
 export interface AndroidWebViewProps extends WebViewSharedProps {