import React, { cloneElement, FC, useEffect, useRef, useState } from 'react'; import { DeviceEventEmitter, View } from 'react-native'; import Logger from '../core/logging/logger'; import CSQMethodFilter from '../csq/CSQMethodFilter'; import { CSWebViewProps } from '../types/types'; import { hasOnlyOneChild, mergeCallbacks } from '../utils/utils'; import { injectWebView, removeWebViewInjection } from './webView'; /** * CSWebView is a wrapper around the React Native WebView component. * * @param children - The child webview component to render. */ export const CSWebView: FC = ({ children }) => { const nativeTag = useRef(undefined); const injectedWebViewTag = useRef(undefined); const reactNativeWebviewSource = React.isValidElement(children) ? (children as React.ReactElement).props.source : null; const hasOneChild = hasOnlyOneChild(children); const initialSource = {}; const [currentSource, setCurrentSource] = useState(initialSource); const onTagInjected = () => { injectedWebViewTag.current = nativeTag.current; setCurrentSource(reactNativeWebviewSource); }; useEffect(() => { CSQMethodFilter.warnIfDisabled('default', 'CSWebView'); return () => { if (injectedWebViewTag.current != null) { removeWebViewInjection(injectedWebViewTag.current); } }; }, []); useEffect(() => { if (children && !hasOneChild) { Logger.error( 'CSWebView component expects exactly 1 child, but received multiple. Ensure that only a single child element is passed' ); } }, [children]); useEffect(() => { const onCSWebViewInjectedListener = DeviceEventEmitter.addListener( 'onCSWebViewInjected', () => { onTagInjected(); } ); return () => { if (onCSWebViewInjectedListener) { onCSWebViewInjectedListener.remove(); } }; }, [reactNativeWebviewSource]); useEffect(() => { if (injectedWebViewTag.current) { // Update url only after the webView has been injected setCurrentSource(reactNativeWebviewSource); } }, [reactNativeWebviewSource]); // We need to clone React Native WebView so we can control when the // source is updated. We clone it and set the source and onLayout prop accordingly const renderChildWebView = () => { if (children && hasOneChild) { const userOnLayout = (children as React.ReactElement).props.onLayout; const originalApplicationNameForUserAgent = (children as React.ReactElement).props.applicationNameForUserAgent; const webViewApplicationNameForUserAgent = originalApplicationNameForUserAgent ? `${originalApplicationNameForUserAgent} CS_WebView` : `CS_WebView`; // We need to be sure to call our internal onLayout method which is the responsible of starting // the injection process but also we need to keep React Native WebView onLayout prop that could // have been defined by the user const mergedOnLayout = mergeCallbacks(onLayout, userOnLayout); return cloneElement(children, { ...(children as React.ReactElement).props, onLayout: mergedOnLayout, source: currentSource, applicationNameForUserAgent: webViewApplicationNameForUserAgent, }); } }; const onLayout = (event: any) => { nativeTag.current = event.nativeEvent.target; if (nativeTag.current && nativeTag.current !== injectedWebViewTag.current) { injectWebView(nativeTag.current); } }; return {renderChildWebView()}; };