Skip to content

Commit 1325968

Browse files
committed
feat: new useWebshell hook, which extracts the logic of makeWebshell HOC
1 parent 4c0cef3 commit 1325968

File tree

3 files changed

+278
-175
lines changed

3 files changed

+278
-175
lines changed

packages/webshell/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,7 @@ export * from './FeatureBuilder';
3131
export * from './Feature';
3232
export * from './features';
3333
export * from './hooks/autoheigh';
34+
export { default as useWebshell } from './useWebshell';
35+
export type { UseWebshellParams } from './useWebshell';
3436
export { makeWebshell };
3537
export default makeWebshell;

packages/webshell/src/make-webshell.tsx

Lines changed: 15 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,12 @@
1-
/* eslint-disable dot-notation */
21
import * as React from 'react';
32
import type { ComponentType, ElementRef, ComponentPropsWithRef } from 'react';
4-
import type { NativeSyntheticEvent } from 'react-native';
53
import { Feature } from './Feature';
64
import 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

13111
const 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

Comments
 (0)