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
 import android.webkit.ValueCallback;
32
 import android.webkit.ValueCallback;
33
 import android.webkit.WebChromeClient;
33
 import android.webkit.WebChromeClient;
34
 import android.webkit.WebResourceRequest;
34
 import android.webkit.WebResourceRequest;
35
+import android.webkit.WebResourceResponse;
35
 import android.webkit.WebSettings;
36
 import android.webkit.WebSettings;
36
 import android.webkit.WebView;
37
 import android.webkit.WebView;
37
 import android.webkit.WebViewClient;
38
 import android.webkit.WebViewClient;
58
 import com.facebook.react.uimanager.events.Event;
59
 import com.facebook.react.uimanager.events.Event;
59
 import com.facebook.react.uimanager.events.EventDispatcher;
60
 import com.facebook.react.uimanager.events.EventDispatcher;
60
 import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
61
 import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
62
+import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
61
 import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
63
 import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
62
 import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
64
 import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
63
 import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
65
 import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
512
     export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
514
     export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
513
     export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
515
     export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
514
     export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
516
     export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
517
+    export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
515
     return export;
518
     return export;
516
   }
519
   }
517
 
520
 
725
         new TopLoadingErrorEvent(webView.getId(), eventData));
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
     protected void emitFinishEvent(WebView webView, String url) {
750
     protected void emitFinishEvent(WebView webView, String url) {
729
       dispatchEvent(
751
       dispatchEvent(
730
         webView,
752
         webView,

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

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
 - [`onLoadEnd`](Reference.md#onloadend)
14
 - [`onLoadEnd`](Reference.md#onloadend)
15
 - [`onLoadStart`](Reference.md#onloadstart)
15
 - [`onLoadStart`](Reference.md#onloadstart)
16
 - [`onLoadProgress`](Reference.md#onloadprogress)
16
 - [`onLoadProgress`](Reference.md#onloadprogress)
17
+- [`onHttpError`](Reference.md#onhttperror)
17
 - [`onMessage`](Reference.md#onmessage)
18
 - [`onMessage`](Reference.md#onmessage)
18
 - [`onNavigationStateChange`](Reference.md#onnavigationstatechange)
19
 - [`onNavigationStateChange`](Reference.md#onnavigationstatechange)
19
 - [`originWhitelist`](Reference.md#originwhitelist)
20
 - [`originWhitelist`](Reference.md#originwhitelist)
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
 ### `onMessage`
386
 ### `onMessage`
346
 
387
 
347
 Function that is invoked when the webview calls `window.ReactNativeWebView.postMessage`. Setting this property will inject this global into your webview.
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
 @property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
33
 @property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
34
 @property (nonatomic, copy) RCTDirectEventBlock onLoadingProgress;
34
 @property (nonatomic, copy) RCTDirectEventBlock onLoadingProgress;
35
 @property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
35
 @property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
36
+@property (nonatomic, copy) RCTDirectEventBlock onHttpError;
36
 @property (nonatomic, copy) RCTDirectEventBlock onMessage;
37
 @property (nonatomic, copy) RCTDirectEventBlock onMessage;
37
 @property (nonatomic, copy) RCTDirectEventBlock onScroll;
38
 @property (nonatomic, copy) RCTDirectEventBlock onScroll;
38
 @property (nonatomic, copy) WKWebView *webView;
39
 @property (nonatomic, copy) WKWebView *webView;
806
   decisionHandler(WKNavigationResponsePolicyAllow);
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
  * Called when an error occurs while the web view is loading content.
839
  * Called when an error occurs while the web view is loading content.
811
  * @see https://fburl.com/km6vqenw
840
  * @see https://fburl.com/km6vqenw

+ 1
- 0
ios/RNCWebViewManager.m View File

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

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

20
 } from './WebViewShared';
20
 } from './WebViewShared';
21
 import {
21
 import {
22
   WebViewErrorEvent,
22
   WebViewErrorEvent,
23
+  WebViewHttpErrorEvent,
23
   WebViewMessageEvent,
24
   WebViewMessageEvent,
24
   WebViewNavigationEvent,
25
   WebViewNavigationEvent,
25
   WebViewProgressEvent,
26
   WebViewProgressEvent,
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
   onLoadingFinish = (event: WebViewNavigationEvent) => {
189
   onLoadingFinish = (event: WebViewNavigationEvent) => {
182
     const { onLoad, onLoadEnd } = this.props;
190
     const { onLoad, onLoadEnd } = this.props;
183
     if (onLoad) {
191
     if (onLoad) {
281
         onLoadingFinish={this.onLoadingFinish}
289
         onLoadingFinish={this.onLoadingFinish}
282
         onLoadingProgress={this.onLoadingProgress}
290
         onLoadingProgress={this.onLoadingProgress}
283
         onLoadingStart={this.onLoadingStart}
291
         onLoadingStart={this.onLoadingStart}
292
+        onHttpError={this.onHttpError}
284
         onMessage={this.onMessage}
293
         onMessage={this.onMessage}
285
         onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
294
         onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
286
         ref={this.webViewRef}
295
         ref={this.webViewRef}

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

18
 } from './WebViewShared';
18
 } from './WebViewShared';
19
 import {
19
 import {
20
   WebViewErrorEvent,
20
   WebViewErrorEvent,
21
+  WebViewHttpErrorEvent,
21
   WebViewMessageEvent,
22
   WebViewMessageEvent,
22
   WebViewNavigationEvent,
23
   WebViewNavigationEvent,
23
   WebViewProgressEvent,
24
   WebViewProgressEvent,
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
   onLoadingFinish = (event: WebViewNavigationEvent) => {
218
   onLoadingFinish = (event: WebViewNavigationEvent) => {
211
     const { onLoad, onLoadEnd } = this.props;
219
     const { onLoad, onLoadEnd } = this.props;
212
     if (onLoad) {
220
     if (onLoad) {
321
         onLoadingFinish={this.onLoadingFinish}
329
         onLoadingFinish={this.onLoadingFinish}
322
         onLoadingProgress={this.onLoadingProgress}
330
         onLoadingProgress={this.onLoadingProgress}
323
         onLoadingStart={this.onLoadingStart}
331
         onLoadingStart={this.onLoadingStart}
332
+        onHttpError={this.onHttpError}
324
         onMessage={this.onMessage}
333
         onMessage={this.onMessage}
325
         onScroll={this.props.onScroll}
334
         onScroll={this.props.onScroll}
326
         onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
335
         onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}

+ 14
- 0
src/WebViewTypes.ts View File

109
   description: string;
109
   description: string;
110
 }
110
 }
111
 
111
 
112
+export interface WebViewHttpError extends WebViewNativeEvent {
113
+  description: string;
114
+  statusCode: number;
115
+}
116
+
112
 export type WebViewEvent = NativeSyntheticEvent<WebViewNativeEvent>;
117
 export type WebViewEvent = NativeSyntheticEvent<WebViewNativeEvent>;
113
 
118
 
114
 export type WebViewProgressEvent = NativeSyntheticEvent<
119
 export type WebViewProgressEvent = NativeSyntheticEvent<
121
 
126
 
122
 export type WebViewErrorEvent = NativeSyntheticEvent<WebViewError>;
127
 export type WebViewErrorEvent = NativeSyntheticEvent<WebViewError>;
123
 
128
 
129
+export type WebViewHttpErrorEvent = NativeSyntheticEvent<WebViewHttpError>;
130
+
124
 export type DataDetectorTypes =
131
 export type DataDetectorTypes =
125
   | 'phoneNumber'
132
   | 'phoneNumber'
126
   | 'link'
133
   | 'link'
210
   onLoadingFinish: (event: WebViewNavigationEvent) => void;
217
   onLoadingFinish: (event: WebViewNavigationEvent) => void;
211
   onLoadingProgress: (event: WebViewProgressEvent) => void;
218
   onLoadingProgress: (event: WebViewProgressEvent) => void;
212
   onLoadingStart: (event: WebViewNavigationEvent) => void;
219
   onLoadingStart: (event: WebViewNavigationEvent) => void;
220
+  onHttpError: (event: WebViewHttpErrorEvent) => void;
213
   onMessage: (event: WebViewMessageEvent) => void;
221
   onMessage: (event: WebViewMessageEvent) => void;
214
   onShouldStartLoadWithRequest: (event: WebViewNavigationEvent) => void;
222
   onShouldStartLoadWithRequest: (event: WebViewNavigationEvent) => void;
215
   showsHorizontalScrollIndicator?: boolean;
223
   showsHorizontalScrollIndicator?: boolean;
598
    */
606
    */
599
   onError?: (event: WebViewErrorEvent) => void;
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
    * Function that is invoked when the `WebView` loading starts or ends.
616
    * Function that is invoked when the `WebView` loading starts or ends.
603
    */
617
    */