Browse Source

feat(new prop): onHttpError callback (#885)

Jason Chia-Hsien Ho 5 years ago
parent
commit
552472c414

+ 22
- 0
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java View File

@@ -32,6 +32,7 @@ import android.webkit.URLUtil;
32 32
 import android.webkit.ValueCallback;
33 33
 import android.webkit.WebChromeClient;
34 34
 import android.webkit.WebResourceRequest;
35
+import android.webkit.WebResourceResponse;
35 36
 import android.webkit.WebSettings;
36 37
 import android.webkit.WebView;
37 38
 import android.webkit.WebViewClient;
@@ -58,6 +59,7 @@ import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
58 59
 import com.facebook.react.uimanager.events.Event;
59 60
 import com.facebook.react.uimanager.events.EventDispatcher;
60 61
 import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
62
+import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
61 63
 import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
62 64
 import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
63 65
 import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
@@ -512,6 +514,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
512 514
     export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
513 515
     export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
514 516
     export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
517
+    export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
515 518
     return export;
516 519
   }
517 520
 
@@ -725,6 +728,25 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
725 728
         new TopLoadingErrorEvent(webView.getId(), eventData));
726 729
     }
727 730
 
731
+    @RequiresApi(api = Build.VERSION_CODES.M)
732
+    @Override
733
+    public void onReceivedHttpError(
734
+      WebView webView,
735
+      WebResourceRequest request,
736
+      WebResourceResponse errorResponse) {
737
+      super.onReceivedHttpError(webView, request, errorResponse);
738
+
739
+      if (request.isForMainFrame()) {
740
+        WritableMap eventData = createWebViewEvent(webView, request.getUrl().toString());
741
+        eventData.putInt("statusCode", errorResponse.getStatusCode());
742
+        eventData.putString("description", errorResponse.getReasonPhrase());
743
+
744
+        dispatchEvent(
745
+          webView,
746
+          new TopHttpErrorEvent(webView.getId(), eventData));
747
+      }
748
+    }
749
+
728 750
     protected void emitFinishEvent(WebView webView, String url) {
729 751
       dispatchEvent(
730 752
         webView,

+ 25
- 0
android/src/main/java/com/reactnativecommunity/webview/events/TopHttpErrorEvent.kt View File

@@ -0,0 +1,25 @@
1
+package com.reactnativecommunity.webview.events
2
+
3
+import com.facebook.react.bridge.WritableMap
4
+import com.facebook.react.uimanager.events.Event
5
+import com.facebook.react.uimanager.events.RCTEventEmitter
6
+
7
+/**
8
+ * Event emitted when a http error is received from the server.
9
+ */
10
+class TopHttpErrorEvent(viewId: Int, private val mEventData: WritableMap) :
11
+  Event<TopHttpErrorEvent>(viewId) {
12
+  companion object {
13
+    const val EVENT_NAME = "topHttpError"
14
+  }
15
+
16
+  override fun getEventName(): String = EVENT_NAME
17
+
18
+  override fun canCoalesce(): Boolean = false
19
+
20
+  override fun getCoalescingKey(): Short = 0
21
+
22
+  override fun dispatch(rctEventEmitter: RCTEventEmitter) =
23
+    rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
24
+
25
+}

+ 41
- 0
docs/Reference.md View File

@@ -14,6 +14,7 @@ This document lays out the current public properties and methods for the React N
14 14
 - [`onLoadEnd`](Reference.md#onloadend)
15 15
 - [`onLoadStart`](Reference.md#onloadstart)
16 16
 - [`onLoadProgress`](Reference.md#onloadprogress)
17
+- [`onHttpError`](Reference.md#onhttperror)
17 18
 - [`onMessage`](Reference.md#onmessage)
18 19
 - [`onNavigationStateChange`](Reference.md#onnavigationstatechange)
19 20
 - [`originWhitelist`](Reference.md#originwhitelist)
@@ -342,6 +343,46 @@ url
342 343
 
343 344
 ---
344 345
 
346
+### `onHttpError`
347
+
348
+Function that is invoked when the `WebView` receives an http error.
349
+> **_Note_**
350
+> Android API minimum level 23.
351
+
352
+| Type     | Required |
353
+| -------- | -------- |
354
+| function | No       |
355
+
356
+Example:
357
+
358
+```jsx
359
+<WebView
360
+  source={{ uri: 'https://facebook.github.io/react-native' }}
361
+  onHttpError={syntheticEvent => {
362
+    const { nativeEvent } = syntheticEvent;
363
+    console.warn('WebView received error status code: ', nativeEvent.statusCode);
364
+  }}
365
+/>
366
+```
367
+
368
+Function passed to `onHttpError` is called with a SyntheticEvent wrapping a nativeEvent with these properties:
369
+
370
+```
371
+canGoBack
372
+canGoForward
373
+description
374
+loading
375
+statusCode
376
+target
377
+title
378
+url
379
+```
380
+
381
+> **_Note_**
382
+> Description is only used on Android
383
+
384
+---
385
+
345 386
 ### `onMessage`
346 387
 
347 388
 Function that is invoked when the webview calls `window.ReactNativeWebView.postMessage`. Setting this property will inject this global into your webview.

+ 29
- 0
ios/RNCWebView.m View File

@@ -33,6 +33,7 @@ static NSURLCredential* clientAuthenticationCredential;
33 33
 @property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
34 34
 @property (nonatomic, copy) RCTDirectEventBlock onLoadingProgress;
35 35
 @property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
36
+@property (nonatomic, copy) RCTDirectEventBlock onHttpError;
36 37
 @property (nonatomic, copy) RCTDirectEventBlock onMessage;
37 38
 @property (nonatomic, copy) RCTDirectEventBlock onScroll;
38 39
 @property (nonatomic, copy) WKWebView *webView;
@@ -806,6 +807,34 @@ static NSURLCredential* clientAuthenticationCredential;
806 807
   decisionHandler(WKNavigationResponsePolicyAllow);
807 808
 }
808 809
 
810
+/**
811
+ * Decides whether to allow or cancel a navigation after its response is known.
812
+ * @see https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview?language=objc
813
+ */
814
+- (void)                    webView:(WKWebView *)webView
815
+  decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
816
+                    decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
817
+{
818
+  if (_onHttpError && navigationResponse.forMainFrame) {
819
+    if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
820
+      NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
821
+      NSInteger statusCode = response.statusCode;
822
+
823
+      if (statusCode >= 400) {
824
+        NSMutableDictionary<NSString *, id> *event = [self baseEvent];
825
+        [event addEntriesFromDictionary: @{
826
+          @"url": response.URL.absoluteString,
827
+          @"statusCode": @(statusCode)
828
+        }];
829
+
830
+        _onHttpError(event);
831
+      }
832
+    }
833
+  }  
834
+
835
+  decisionHandler(WKNavigationResponsePolicyAllow);
836
+}
837
+
809 838
 /**
810 839
  * Called when an error occurs while the web view is loading content.
811 840
  * @see https://fburl.com/km6vqenw

+ 1
- 0
ios/RNCWebViewManager.m View File

@@ -47,6 +47,7 @@ RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock)
47 47
 RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock)
48 48
 RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
49 49
 RCT_EXPORT_VIEW_PROPERTY(onLoadingProgress, RCTDirectEventBlock)
50
+RCT_EXPORT_VIEW_PROPERTY(onHttpError, RCTDirectEventBlock)
50 51
 RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
51 52
 RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
52 53
 RCT_EXPORT_VIEW_PROPERTY(javaScriptEnabled, BOOL)

+ 9
- 0
src/WebView.android.tsx View File

@@ -20,6 +20,7 @@ import {
20 20
 } from './WebViewShared';
21 21
 import {
22 22
   WebViewErrorEvent,
23
+  WebViewHttpErrorEvent,
23 24
   WebViewMessageEvent,
24 25
   WebViewNavigationEvent,
25 26
   WebViewProgressEvent,
@@ -178,6 +179,13 @@ class WebView extends React.Component<AndroidWebViewProps, State> {
178 179
     });
179 180
   };
180 181
 
182
+  onHttpError = (event: WebViewHttpErrorEvent) => {
183
+    const { onHttpError } = this.props;
184
+    if (onHttpError) {
185
+      onHttpError(event);
186
+    }
187
+  }
188
+
181 189
   onLoadingFinish = (event: WebViewNavigationEvent) => {
182 190
     const { onLoad, onLoadEnd } = this.props;
183 191
     if (onLoad) {
@@ -281,6 +289,7 @@ class WebView extends React.Component<AndroidWebViewProps, State> {
281 289
         onLoadingFinish={this.onLoadingFinish}
282 290
         onLoadingProgress={this.onLoadingProgress}
283 291
         onLoadingStart={this.onLoadingStart}
292
+        onHttpError={this.onHttpError}
284 293
         onMessage={this.onMessage}
285 294
         onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
286 295
         ref={this.webViewRef}

+ 9
- 0
src/WebView.ios.tsx View File

@@ -18,6 +18,7 @@ import {
18 18
 } from './WebViewShared';
19 19
 import {
20 20
   WebViewErrorEvent,
21
+  WebViewHttpErrorEvent,
21 22
   WebViewMessageEvent,
22 23
   WebViewNavigationEvent,
23 24
   WebViewProgressEvent,
@@ -207,6 +208,13 @@ class WebView extends React.Component<IOSWebViewProps, State> {
207 208
     });
208 209
   };
209 210
 
211
+  onHttpError = (event: WebViewHttpErrorEvent) => {
212
+    const { onHttpError } = this.props;
213
+    if (onHttpError) {
214
+      onHttpError(event);
215
+    }
216
+  }
217
+
210 218
   onLoadingFinish = (event: WebViewNavigationEvent) => {
211 219
     const { onLoad, onLoadEnd } = this.props;
212 220
     if (onLoad) {
@@ -321,6 +329,7 @@ class WebView extends React.Component<IOSWebViewProps, State> {
321 329
         onLoadingFinish={this.onLoadingFinish}
322 330
         onLoadingProgress={this.onLoadingProgress}
323 331
         onLoadingStart={this.onLoadingStart}
332
+        onHttpError={this.onHttpError}
324 333
         onMessage={this.onMessage}
325 334
         onScroll={this.props.onScroll}
326 335
         onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}

+ 14
- 0
src/WebViewTypes.ts View File

@@ -109,6 +109,11 @@ export interface WebViewError extends WebViewNativeEvent {
109 109
   description: string;
110 110
 }
111 111
 
112
+export interface WebViewHttpError extends WebViewNativeEvent {
113
+  description: string;
114
+  statusCode: number;
115
+}
116
+
112 117
 export type WebViewEvent = NativeSyntheticEvent<WebViewNativeEvent>;
113 118
 
114 119
 export type WebViewProgressEvent = NativeSyntheticEvent<
@@ -121,6 +126,8 @@ export type WebViewMessageEvent = NativeSyntheticEvent<WebViewMessage>;
121 126
 
122 127
 export type WebViewErrorEvent = NativeSyntheticEvent<WebViewError>;
123 128
 
129
+export type WebViewHttpErrorEvent = NativeSyntheticEvent<WebViewHttpError>;
130
+
124 131
 export type DataDetectorTypes =
125 132
   | 'phoneNumber'
126 133
   | 'link'
@@ -210,6 +217,7 @@ export interface CommonNativeWebViewProps extends ViewProps {
210 217
   onLoadingFinish: (event: WebViewNavigationEvent) => void;
211 218
   onLoadingProgress: (event: WebViewProgressEvent) => void;
212 219
   onLoadingStart: (event: WebViewNavigationEvent) => void;
220
+  onHttpError: (event: WebViewHttpErrorEvent) => void;
213 221
   onMessage: (event: WebViewMessageEvent) => void;
214 222
   onShouldStartLoadWithRequest: (event: WebViewNavigationEvent) => void;
215 223
   showsHorizontalScrollIndicator?: boolean;
@@ -598,6 +606,12 @@ export interface WebViewSharedProps extends ViewProps {
598 606
    */
599 607
   onError?: (event: WebViewErrorEvent) => void;
600 608
 
609
+  /**
610
+   * Function that is invoked when the `WebView` receives an error status code.
611
+   * Works on iOS and Android (minimum API level 23).
612
+   */
613
+  onHttpError?: (event: WebViewHttpErrorEvent) => void;
614
+
601 615
   /**
602 616
    * Function that is invoked when the `WebView` loading starts or ends.
603 617
    */