1- /* eslint-disable dot-notation */
21import * as React from 'react' ;
32import type { ComponentType , ElementRef , ComponentPropsWithRef } from 'react' ;
4- import type { NativeSyntheticEvent } from 'react-native' ;
53import { Feature } from './Feature' ;
64import type {
75 WebshellProps ,
8- WebshellInvariantProps ,
96 MinimalWebViewProps ,
107 WebshellComponent
118} from './types' ;
12- import { FeatureRegistry } from './FeatureRegistry' ;
13- import { BufferedWebRMIHandle } from './web/BufferedWebRMIHandle' ;
14- import { WebFeaturesLoader } from './web/WebFeaturesLoader' ;
15- import { Reporter } from './Reporter' ;
16-
17- interface WebViewMessage {
18- data : string ;
19- }
20-
21- interface PostMessage {
22- identifier : string ;
23- eventId : string ;
24- type : 'feature' | 'error' | 'log' | 'init' ;
25- severity : 'warn' | 'info' ;
26- body : any ;
27- }
28-
29- function parseJSONSafe ( text : string ) {
30- try {
31- return ( JSON . parse ( text ) as unknown ) ?? null ;
32- } catch ( e ) {
33- return null ;
34- }
35- }
36-
37- function isPostMessageObject ( o : unknown ) : o is PostMessage {
38- return (
39- typeof o === 'object' &&
40- o !== null &&
41- typeof o [ 'type' ] === 'string' &&
42- o [ '__isWebshellPostMessage' ] === true
43- ) ;
44- }
45-
46- function useWebMessageBus (
47- registry : FeatureRegistry < any > ,
48- reporter : Reporter ,
49- {
50- webshellDebug,
51- onWebFeatureError,
52- onMessage,
53- ...otherProps
54- } : WebshellInvariantProps & MinimalWebViewProps
55- ) {
56- const [ isLoaderReady , setIsLoaderReady ] = React . useState ( false ) ;
57- const domHandlers = React . useMemo ( ( ) => registry . getWebHandlers ( otherProps ) , [
58- otherProps ,
59- registry
60- ] ) ;
61-
62- const handleOnWebMessage = React . useCallback (
63- ( { nativeEvent } : NativeSyntheticEvent < WebViewMessage > ) => {
64- const parsedJSON = parseJSONSafe ( nativeEvent . data ) ;
65- if ( isPostMessageObject ( parsedJSON ) ) {
66- const { type, identifier, body, eventId, severity } = parsedJSON ;
67- if ( type === 'init' ) {
68- setIsLoaderReady ( true ) ;
69- return ;
70- }
71- if ( type === 'feature' ) {
72- const propDef = registry . getPropDefFromId ( identifier , eventId ) ;
73- if ( ! propDef ) {
74- reporter . dispatchError (
75- 'WEBSH_MISSING_SHELL_HANDLER' ,
76- identifier ,
77- eventId
78- ) ;
79- return ;
80- }
81- const handlerName = propDef . name ;
82- const handler =
83- typeof eventId === 'string' ? domHandlers [ handlerName ] : null ;
84- if ( typeof handler === 'function' ) {
85- handler ( body ) ;
86- }
87- } else if ( type === 'error' ) {
88- // Handle as an error message
89- typeof onWebFeatureError === 'function' &&
90- onWebFeatureError ( identifier , body ) ;
91- reporter . dispatchError ( 'WEBSH_SCRIPT_ERROR' , identifier , body ) ;
92- } else if ( type === 'log' ) {
93- reporter . dispatchWebLog ( severity , identifier , body ) ;
94- }
95- } else {
96- typeof onMessage === 'function' && onMessage ( nativeEvent ) ;
97- }
98- } ,
99- // eslint-disable-next-line react-hooks/exhaustive-deps
100- [ ...Object . values ( domHandlers ) , onWebFeatureError , onMessage ]
101- ) ;
102- return {
103- handleOnWebMessage,
104- isLoaderReady
105- } ;
106- }
107-
108- function useWebHandle (
109- webViewRef : React . RefObject < any > ,
110- registry : FeatureRegistry < any > ,
111- reporter : Reporter
112- ) {
113- return React . useMemo (
114- ( ) : BufferedWebRMIHandle =>
115- new BufferedWebRMIHandle ( webViewRef , registry , reporter ) ,
116- [ webViewRef , registry , reporter ]
117- ) ;
118- }
119-
120- function useJavaScript (
121- loader : WebFeaturesLoader < any > ,
122- injectedJavaScript : string
123- ) {
124- return React . useMemo ( ( ) => {
125- const safeUserscript =
126- typeof injectedJavaScript === 'string' ? injectedJavaScript : '' ;
127- return `(function(){\n${ safeUserscript } \n${ loader . assembledFeaturesScript } ;\n})();true;` ;
128- } , [ injectedJavaScript , loader ] ) ;
129- }
9+ import useWebshell from './useWebshell' ;
13010
13111const defaultProps = {
13212 webshellDebug : __DEV__ ,
@@ -156,10 +36,14 @@ const defaultProps = {
15636 * HandleVisualViewportFeature
15737 * } from '@formidable-webview/webshell';
15838 *
159- * const Webshell = makeWebshell(
160- * WebView,
39+ * const features = [
16140 * new HandleHashChangeFeature(),
16241 * new HandleVisualViewportFeature()
42+ * ]
43+ *
44+ * const Webshell = makeWebshell(
45+ * WebView,
46+ * ...features
16347 * );
16448 * ```
16549 *
@@ -169,58 +53,14 @@ export function makeWebshell<
16953 C extends ComponentType < any > ,
17054 F extends Feature < { } , { } , { } > [ ]
17155> ( WebView : C , ...features : F ) : WebshellComponent < C , F > {
172- const filteredFeatures = features . filter ( ( f ) => ! ! f ) ;
173- const loader = new WebFeaturesLoader ( filteredFeatures ) ;
174- const Webshell = (
175- props : WebshellProps < MinimalWebViewProps , F > & {
176- webViewRef : ElementRef < any > ;
177- }
178- ) => {
179- const {
180- webViewRef,
181- webHandleRef,
182- injectedJavaScript : userInjectedJavaScript ,
183- webshellDebug = defaultProps . webshellDebug ,
184- webshellStrictMode = defaultProps . webshellStrictMode ,
185- ...webViewProps
186- } = props ;
187- const reporter = React . useMemo (
188- ( ) => new Reporter ( webshellDebug , webshellStrictMode ) ,
189- [ webshellDebug , webshellStrictMode ]
190- ) ;
191- const registry = React . useMemo (
192- ( ) => new FeatureRegistry ( filteredFeatures , reporter ) ,
193- [ reporter ]
194- ) ;
195- const { handleOnWebMessage, isLoaderReady } = useWebMessageBus (
196- registry ,
197- reporter ,
198- props
199- ) ;
200- const injectedJavaScript = useJavaScript (
201- loader ,
202- userInjectedJavaScript as string
203- ) ;
204- const webHandle = useWebHandle ( webViewRef as any , registry , reporter ) ;
205-
206- React . useImperativeHandle ( webHandleRef , ( ) => webHandle ) ;
207- React . useEffect ( ( ) => {
208- webHandle . setDebug ( webshellDebug ) ;
209- } , [ webshellDebug , webHandle ] ) ;
210- React . useEffect ( ( ) => {
211- if ( isLoaderReady ) {
212- webHandle . flushPendingMessages ( ) ;
213- }
214- } , [ isLoaderReady , webHandle ] ) ;
215- return (
216- < WebView
217- { ...registry . filterWebViewProps ( webViewProps ) }
218- ref = { webViewRef }
219- injectedJavaScript = { injectedJavaScript }
220- javaScriptEnabled = { true }
221- onMessage = { handleOnWebMessage }
222- />
223- ) ;
56+ const Webshell = ( {
57+ webViewRef,
58+ ...props
59+ } : WebshellProps < MinimalWebViewProps , F > & {
60+ webViewRef : ElementRef < any > ;
61+ } ) => {
62+ const webViewProps = useWebshell ( { features, props, webViewRef } ) ;
63+ return React . createElement ( WebView , webViewProps ) ;
22464 } ;
22565 Webshell . defaultProps = defaultProps ;
22666 return React . forwardRef <
0 commit comments