2019-03-20 12:35:13 +00:00
|
|
|
import React from 'react';
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
Image,
|
|
|
|
|
requireNativeComponent,
|
|
|
|
|
UIManager as NotTypedUIManager,
|
|
|
|
|
View,
|
|
|
|
|
NativeModules,
|
|
|
|
|
ImageSourcePropType,
|
|
|
|
|
findNodeHandle,
|
|
|
|
|
} from 'react-native';
|
|
|
|
|
|
2020-04-06 20:25:28 +03:00
|
|
|
import BatchedBridge from 'react-native/Libraries/BatchedBridge/BatchedBridge';
|
|
|
|
|
|
2019-03-20 12:35:13 +00:00
|
|
|
import invariant from 'invariant';
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
defaultOriginWhitelist,
|
|
|
|
|
createOnShouldStartLoadWithRequest,
|
2019-04-02 16:05:56 +02:00
|
|
|
defaultRenderError,
|
|
|
|
|
defaultRenderLoading,
|
2019-03-20 12:35:13 +00:00
|
|
|
} from './WebViewShared';
|
|
|
|
|
import {
|
|
|
|
|
WebViewErrorEvent,
|
2019-09-20 17:43:28 -07:00
|
|
|
WebViewHttpErrorEvent,
|
2019-03-20 12:35:13 +00:00
|
|
|
WebViewMessageEvent,
|
|
|
|
|
WebViewNavigationEvent,
|
|
|
|
|
WebViewProgressEvent,
|
|
|
|
|
AndroidWebViewProps,
|
|
|
|
|
NativeWebViewAndroid,
|
|
|
|
|
State,
|
2019-11-12 12:09:16 +03:00
|
|
|
RNCWebViewUIManagerAndroid,
|
2019-03-20 12:35:13 +00:00
|
|
|
} from './WebViewTypes';
|
|
|
|
|
|
|
|
|
|
import styles from './WebView.styles';
|
|
|
|
|
|
2019-11-12 12:09:16 +03:00
|
|
|
const UIManager = NotTypedUIManager as RNCWebViewUIManagerAndroid;
|
2019-03-20 12:35:13 +00:00
|
|
|
|
|
|
|
|
const RNCWebView = requireNativeComponent(
|
|
|
|
|
'RNCWebView',
|
|
|
|
|
) as typeof NativeWebViewAndroid;
|
|
|
|
|
const { resolveAssetSource } = Image;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Renders a native WebView.
|
|
|
|
|
*/
|
|
|
|
|
class WebView extends React.Component<AndroidWebViewProps, State> {
|
|
|
|
|
static defaultProps = {
|
|
|
|
|
overScrollMode: 'always',
|
|
|
|
|
javaScriptEnabled: true,
|
|
|
|
|
thirdPartyCookiesEnabled: true,
|
|
|
|
|
scalesPageToFit: true,
|
2019-05-16 15:22:08 -07:00
|
|
|
allowsFullscreenVideo: false,
|
2019-03-20 12:35:13 +00:00
|
|
|
allowFileAccess: false,
|
|
|
|
|
saveFormDataDisabled: false,
|
|
|
|
|
cacheEnabled: true,
|
|
|
|
|
androidHardwareAccelerationDisabled: false,
|
|
|
|
|
originWhitelist: defaultOriginWhitelist,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static isFileUploadSupported = async () => {
|
|
|
|
|
// native implementation should return "true" only for Android 5+
|
|
|
|
|
return NativeModules.RNCWebView.isFileUploadSupported();
|
|
|
|
|
};
|
|
|
|
|
|
2019-09-23 01:44:26 -07:00
|
|
|
startUrl: string | null = null;
|
|
|
|
|
|
2019-03-20 12:35:13 +00:00
|
|
|
state: State = {
|
|
|
|
|
viewState: this.props.startInLoadingState ? 'LOADING' : 'IDLE',
|
|
|
|
|
lastErrorEvent: null,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
webViewRef = React.createRef<NativeWebViewAndroid>();
|
|
|
|
|
|
2020-04-06 20:25:28 +03:00
|
|
|
componentDidMount = () => {
|
|
|
|
|
BatchedBridge.registerCallableModule('WebViewMessageHandler', this);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-30 12:04:32 +02:00
|
|
|
getCommands = () => UIManager.getViewManagerConfig('RNCWebView').Commands;
|
2019-03-20 12:35:13 +00:00
|
|
|
|
|
|
|
|
goForward = () => {
|
|
|
|
|
UIManager.dispatchViewManagerCommand(
|
|
|
|
|
this.getWebViewHandle(),
|
|
|
|
|
this.getCommands().goForward,
|
2019-09-20 17:40:53 -07:00
|
|
|
undefined
|
2019-03-20 12:35:13 +00:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
goBack = () => {
|
|
|
|
|
UIManager.dispatchViewManagerCommand(
|
|
|
|
|
this.getWebViewHandle(),
|
|
|
|
|
this.getCommands().goBack,
|
2019-09-20 17:40:53 -07:00
|
|
|
undefined
|
2019-03-20 12:35:13 +00:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
reload = () => {
|
|
|
|
|
this.setState({
|
|
|
|
|
viewState: 'LOADING',
|
|
|
|
|
});
|
|
|
|
|
UIManager.dispatchViewManagerCommand(
|
|
|
|
|
this.getWebViewHandle(),
|
|
|
|
|
this.getCommands().reload,
|
2019-09-20 17:40:53 -07:00
|
|
|
undefined
|
2019-03-20 12:35:13 +00:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
stopLoading = () => {
|
|
|
|
|
UIManager.dispatchViewManagerCommand(
|
|
|
|
|
this.getWebViewHandle(),
|
|
|
|
|
this.getCommands().stopLoading,
|
2019-09-20 17:40:53 -07:00
|
|
|
undefined
|
2019-03-20 12:35:13 +00:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2019-08-06 11:56:59 +03:00
|
|
|
requestFocus = () => {
|
|
|
|
|
UIManager.dispatchViewManagerCommand(
|
2019-08-30 12:04:32 +02:00
|
|
|
this.getWebViewHandle(),
|
|
|
|
|
this.getCommands().requestFocus,
|
2019-09-20 17:40:53 -07:00
|
|
|
undefined
|
2019-08-06 11:56:59 +03:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2019-03-20 12:35:13 +00:00
|
|
|
postMessage = (data: string) => {
|
|
|
|
|
UIManager.dispatchViewManagerCommand(
|
|
|
|
|
this.getWebViewHandle(),
|
|
|
|
|
this.getCommands().postMessage,
|
|
|
|
|
[String(data)],
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2019-11-12 12:09:16 +03:00
|
|
|
clearFormData = () => {
|
|
|
|
|
UIManager.dispatchViewManagerCommand(
|
|
|
|
|
this.getWebViewHandle(),
|
|
|
|
|
this.getCommands().clearFormData,
|
|
|
|
|
undefined,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clearCache = (includeDiskFiles: boolean) => {
|
|
|
|
|
UIManager.dispatchViewManagerCommand(
|
|
|
|
|
this.getWebViewHandle(),
|
|
|
|
|
this.getCommands().clearCache,
|
|
|
|
|
[includeDiskFiles],
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
clearHistory = () => {
|
|
|
|
|
UIManager.dispatchViewManagerCommand(
|
|
|
|
|
this.getWebViewHandle(),
|
|
|
|
|
this.getCommands().clearHistory,
|
|
|
|
|
undefined,
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2019-03-20 12:35:13 +00:00
|
|
|
/**
|
|
|
|
|
* Injects a javascript string into the referenced WebView. Deliberately does not
|
|
|
|
|
* return a response because using eval() to return a response breaks this method
|
|
|
|
|
* on pages with a Content Security Policy that disallows eval(). If you need that
|
|
|
|
|
* functionality, look into postMessage/onMessage.
|
|
|
|
|
*/
|
|
|
|
|
injectJavaScript = (data: string) => {
|
|
|
|
|
UIManager.dispatchViewManagerCommand(
|
|
|
|
|
this.getWebViewHandle(),
|
|
|
|
|
this.getCommands().injectJavaScript,
|
|
|
|
|
[data],
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* We return an event with a bunch of fields including:
|
|
|
|
|
* url, title, loading, canGoBack, canGoForward
|
|
|
|
|
*/
|
|
|
|
|
updateNavigationState = (event: WebViewNavigationEvent) => {
|
|
|
|
|
if (this.props.onNavigationStateChange) {
|
|
|
|
|
this.props.onNavigationStateChange(event.nativeEvent);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the native `WebView` node.
|
|
|
|
|
*/
|
|
|
|
|
getWebViewHandle = () => {
|
|
|
|
|
const nodeHandle = findNodeHandle(this.webViewRef.current);
|
|
|
|
|
invariant(nodeHandle != null, 'nodeHandle expected to be non-null');
|
|
|
|
|
return nodeHandle as number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onLoadingStart = (event: WebViewNavigationEvent) => {
|
|
|
|
|
const { onLoadStart } = this.props;
|
2019-09-23 01:44:26 -07:00
|
|
|
const { nativeEvent: { url } } = event;
|
|
|
|
|
this.startUrl = url;
|
2019-03-20 12:35:13 +00:00
|
|
|
if (onLoadStart) {
|
|
|
|
|
onLoadStart(event);
|
|
|
|
|
}
|
|
|
|
|
this.updateNavigationState(event);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onLoadingError = (event: WebViewErrorEvent) => {
|
|
|
|
|
event.persist(); // persist this event because we need to store it
|
|
|
|
|
const { onError, onLoadEnd } = this.props;
|
|
|
|
|
if (onError) {
|
|
|
|
|
onError(event);
|
|
|
|
|
}
|
|
|
|
|
if (onLoadEnd) {
|
|
|
|
|
onLoadEnd(event);
|
|
|
|
|
}
|
|
|
|
|
console.warn('Encountered an error loading page', event.nativeEvent);
|
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
|
lastErrorEvent: event.nativeEvent,
|
|
|
|
|
viewState: 'ERROR',
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2019-09-20 17:43:28 -07:00
|
|
|
onHttpError = (event: WebViewHttpErrorEvent) => {
|
|
|
|
|
const { onHttpError } = this.props;
|
|
|
|
|
if (onHttpError) {
|
|
|
|
|
onHttpError(event);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-20 12:35:13 +00:00
|
|
|
onLoadingFinish = (event: WebViewNavigationEvent) => {
|
|
|
|
|
const { onLoad, onLoadEnd } = this.props;
|
2019-09-23 01:44:26 -07:00
|
|
|
const { nativeEvent: { url } } = event;
|
2019-03-20 12:35:13 +00:00
|
|
|
if (onLoad) {
|
|
|
|
|
onLoad(event);
|
|
|
|
|
}
|
|
|
|
|
if (onLoadEnd) {
|
|
|
|
|
onLoadEnd(event);
|
|
|
|
|
}
|
2019-09-23 01:44:26 -07:00
|
|
|
if (url === this.startUrl) {
|
|
|
|
|
this.setState({
|
|
|
|
|
viewState: 'IDLE',
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-03-20 12:35:13 +00:00
|
|
|
this.updateNavigationState(event);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onMessage = (event: WebViewMessageEvent) => {
|
|
|
|
|
const { onMessage } = this.props;
|
|
|
|
|
if (onMessage) {
|
|
|
|
|
onMessage(event);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onLoadingProgress = (event: WebViewProgressEvent) => {
|
|
|
|
|
const { onLoadProgress } = this.props;
|
2019-09-23 01:44:26 -07:00
|
|
|
const { nativeEvent: { progress } } = event;
|
|
|
|
|
if (progress === 1) {
|
2019-09-29 21:40:12 +08:00
|
|
|
this.setState((state) => {
|
|
|
|
|
if (state.viewState === 'LOADING') {
|
|
|
|
|
return { viewState: 'IDLE' };
|
|
|
|
|
}
|
|
|
|
|
return null;
|
2019-09-23 01:44:26 -07:00
|
|
|
});
|
|
|
|
|
}
|
2019-03-20 12:35:13 +00:00
|
|
|
if (onLoadProgress) {
|
|
|
|
|
onLoadProgress(event);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onShouldStartLoadWithRequestCallback = (
|
|
|
|
|
shouldStart: boolean,
|
|
|
|
|
url: string,
|
|
|
|
|
) => {
|
|
|
|
|
if (shouldStart) {
|
|
|
|
|
UIManager.dispatchViewManagerCommand(
|
|
|
|
|
this.getWebViewHandle(),
|
|
|
|
|
this.getCommands().loadUrl,
|
|
|
|
|
[String(url)],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
const {
|
|
|
|
|
onMessage,
|
|
|
|
|
onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
|
|
|
|
|
originWhitelist,
|
|
|
|
|
renderError,
|
|
|
|
|
renderLoading,
|
|
|
|
|
source,
|
|
|
|
|
style,
|
2019-09-29 15:43:48 +02:00
|
|
|
containerStyle,
|
2019-03-20 12:35:13 +00:00
|
|
|
nativeConfig = {},
|
|
|
|
|
...otherProps
|
|
|
|
|
} = this.props;
|
2019-04-02 16:05:56 +02:00
|
|
|
|
2019-03-20 12:35:13 +00:00
|
|
|
let otherView = null;
|
|
|
|
|
|
|
|
|
|
if (this.state.viewState === 'LOADING') {
|
|
|
|
|
otherView = (renderLoading || defaultRenderLoading)();
|
|
|
|
|
} else if (this.state.viewState === 'ERROR') {
|
|
|
|
|
const errorEvent = this.state.lastErrorEvent;
|
|
|
|
|
invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
|
2019-04-02 16:05:56 +02:00
|
|
|
otherView = (renderError || defaultRenderError)(
|
|
|
|
|
errorEvent.domain,
|
|
|
|
|
errorEvent.code,
|
|
|
|
|
errorEvent.description,
|
|
|
|
|
);
|
2019-03-20 12:35:13 +00:00
|
|
|
} else if (this.state.viewState !== 'IDLE') {
|
|
|
|
|
console.error(
|
|
|
|
|
`RNCWebView invalid state encountered: ${this.state.viewState}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-02 16:05:56 +02:00
|
|
|
const webViewStyles = [styles.container, styles.webView, style];
|
2019-09-29 15:43:48 +02:00
|
|
|
const webViewContainerStyle = [styles.container, containerStyle];
|
2019-03-20 12:35:13 +00:00
|
|
|
|
2019-11-12 17:05:29 +08:00
|
|
|
if (typeof source !== "number" && source && 'method' in source) {
|
2019-03-20 12:35:13 +00:00
|
|
|
if (source.method === 'POST' && source.headers) {
|
|
|
|
|
console.warn(
|
|
|
|
|
'WebView: `source.headers` is not supported when using POST.',
|
|
|
|
|
);
|
|
|
|
|
} else if (source.method === 'GET' && source.body) {
|
|
|
|
|
console.warn('WebView: `source.body` is not supported when using GET.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const NativeWebView
|
|
|
|
|
= (nativeConfig.component as typeof NativeWebViewAndroid) || RNCWebView;
|
|
|
|
|
|
|
|
|
|
const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
|
|
|
|
|
this.onShouldStartLoadWithRequestCallback,
|
|
|
|
|
// casting cause it's in the default props
|
2019-09-19 11:40:51 +02:00
|
|
|
originWhitelist as readonly string[],
|
2019-03-20 12:35:13 +00:00
|
|
|
onShouldStartLoadWithRequestProp,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const webView = (
|
|
|
|
|
<NativeWebView
|
|
|
|
|
key="webViewKey"
|
|
|
|
|
{...otherProps}
|
|
|
|
|
messagingEnabled={typeof onMessage === 'function'}
|
|
|
|
|
onLoadingError={this.onLoadingError}
|
|
|
|
|
onLoadingFinish={this.onLoadingFinish}
|
|
|
|
|
onLoadingProgress={this.onLoadingProgress}
|
|
|
|
|
onLoadingStart={this.onLoadingStart}
|
2019-09-20 17:43:28 -07:00
|
|
|
onHttpError={this.onHttpError}
|
2019-03-20 12:35:13 +00:00
|
|
|
onMessage={this.onMessage}
|
|
|
|
|
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
|
|
|
|
ref={this.webViewRef}
|
|
|
|
|
// TODO: find a better way to type this.
|
|
|
|
|
source={resolveAssetSource(source as ImageSourcePropType)}
|
|
|
|
|
style={webViewStyles}
|
|
|
|
|
{...nativeConfig.props}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
2019-09-29 15:43:48 +02:00
|
|
|
<View style={webViewContainerStyle}>
|
2019-03-20 12:35:13 +00:00
|
|
|
{webView}
|
|
|
|
|
{otherView}
|
|
|
|
|
</View>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default WebView;
|