diff --git a/docs/backward-compatibility/new-method.md b/docs/backward-compatibility/new-method.md index cfb0908a..fc7bade2 100644 --- a/docs/backward-compatibility/new-method.md +++ b/docs/backward-compatibility/new-method.md @@ -71,8 +71,13 @@ The `onFallback` method can be used for batch processing of errors. It's suitabl ```tsx const bridge = linkBridge({ - onFallback: (method) => { - Alert.alert("The app is outdated. Please update."); + onFallback: (methodName, args) => { + alert("The app is outdated. Please update."); + + // Backward compatibility + if(methodName === "sum") { + bridge.loose.oldSum(...args); + } }, }); ``` diff --git a/docs/reference/web/link-bridge.md b/docs/reference/web/link-bridge.md index 9628c7b7..4e103b58 100644 --- a/docs/reference/web/link-bridge.md +++ b/docs/reference/web/link-bridge.md @@ -8,5 +8,5 @@ The `linkBridge` is used to access methods set up in the React Native bridge. |------------------|--------------------------------|----------|---------|---------------------------------------------------------------------------| | `timeout` | number | false | 2000 | Specifies the maximum time (in milliseconds) to wait for a response from the native code before timing out. | | `throwOnError` | boolean \| T[] | false | false | Determines whether to throw an error if the native method call fails. Default is false. | -| `onFallback` | (methodName: T) => void | false | X |This event is triggered when a method is executed but not found in native, with the executed method passed as an argument. | +| `onFallback` | (methodName: T, args: unknown[]) => void | false | X |This event is triggered when a method is executed but not found in native, with the executed method passed as an argument. | | `onReady` | (method: NativeMethod) => void | false | X | Runs when the native method is ready to use. \ No newline at end of file diff --git a/example/native-method/react/src/App.tsx b/example/native-method/react/src/App.tsx index 1ee35600..14328856 100644 --- a/example/native-method/react/src/App.tsx +++ b/example/native-method/react/src/App.tsx @@ -8,6 +8,9 @@ const bridge = linkBridge({ onReady: () => { console.log("bridge is ready"); }, + onFallback: (methodName, args) => { + console.log("fallback", methodName, args); + }, }); function App() { diff --git a/packages/react-native/src/integrations/bridge.ts b/packages/react-native/src/integrations/bridge.ts index 7e9d4e26..a7d5b0dc 100644 --- a/packages/react-native/src/integrations/bridge.ts +++ b/packages/react-native/src/integrations/bridge.ts @@ -81,6 +81,17 @@ export const handleBridge = async ({ const _bridge = bridge.getState(); const _method = _bridge[method]; + const handleThrow = () => { + webview.injectJavaScript(` + window.nativeEmitter.emit('${method}-${eventId}', {}, true); + + true; + `); + }; + if (!(method in _bridge)) { + handleThrow(); + return; + } if (typeof _method !== "function") { return; } @@ -96,12 +107,8 @@ export const handleBridge = async ({ true; `); } catch (error) { + handleThrow(); console.error(error); - webview.injectJavaScript(` - window.nativeEmitter.emit('${method}-${eventId}', {}, true); - - true; - `); } }; diff --git a/packages/react-native/src/integrations/handleRegisterWebMethod.ts b/packages/react-native/src/integrations/handleRegisterWebMethod.ts index e15d95d2..b4ffee89 100644 --- a/packages/react-native/src/integrations/handleRegisterWebMethod.ts +++ b/packages/react-native/src/integrations/handleRegisterWebMethod.ts @@ -20,11 +20,11 @@ export const handleRegisterWebMethod = ( const eventId = createRandomId(); return Promise.race([ - createResolver( + createResolver({ emitter, methodName, eventId, - () => { + evaluate: () => { webview.injectJavaScript( ` window.webEmitter.emit('${methodName}', '${eventId}', ${JSON.stringify( @@ -35,8 +35,8 @@ export const handleRegisterWebMethod = ( `, ); }, - new WebMethodError(methodName), - ), + failHandler: new WebMethodError(methodName), + }), timeout(responseTimeout), ]); }; diff --git a/packages/web/src/linkBridge.ts b/packages/web/src/linkBridge.ts index 09ea67ed..453408e8 100644 --- a/packages/web/src/linkBridge.ts +++ b/packages/web/src/linkBridge.ts @@ -22,23 +22,33 @@ export interface LinkBridgeOptions< > { timeout?: number; throwOnError?: boolean | (keyof ExtractStore)[] | string[]; - onFallback?: (methodName: string) => void; + onFallback?: (methodName: string, args: unknown[]) => void; onReady?: ( method: LinkBridge>, Omit>, ) => void; } const createNativeMethod = - (methodName: string, timeoutMs: number, throwOnError: boolean) => + ({ + methodName, + throwOnError, + timeoutMs, + onFallback, + }: { + methodName: string; + timeoutMs: number; + throwOnError: boolean; + onFallback?: (methodName: string, args: unknown[]) => void; + }) => (...args: unknown[]) => { const eventId = createRandomId(); return Promise.race([ - createResolver( + createResolver({ emitter, methodName, eventId, - () => { + evaluate: () => { window.ReactNativeWebView?.postMessage( JSON.stringify({ type: "bridge", @@ -50,8 +60,11 @@ const createNativeMethod = }), ); }, - throwOnError && new NativeMethodError(methodName), - ), + onFallback: () => { + onFallback?.(methodName, args); + }, + failHandler: throwOnError && new NativeMethodError(methodName), + }), timeout(timeoutMs, throwOnError), ]); }; @@ -101,11 +114,12 @@ export const linkBridge = < (acc, methodName) => { return { ...acc, - [methodName]: createNativeMethod( + [methodName]: createNativeMethod({ methodName, timeoutMs, - willMethodThrowOnError(methodName), - ), + throwOnError: willMethodThrowOnError(methodName), + onFallback, + }), }; }, {} as LinkBridge, Omit>, @@ -121,11 +135,12 @@ export const linkBridge = < ) { return target[methodName]; } - return createNativeMethod( + return createNativeMethod({ methodName, timeoutMs, - willMethodThrowOnError(methodName), - ); + throwOnError: willMethodThrowOnError(methodName), + onFallback, + }); }, }); @@ -156,10 +171,12 @@ export const linkBridge = < }, }), ); - onFallback?.(methodName); if (willMethodThrowOnError(methodName)) { - return () => Promise.reject(new MethodNotFoundError(methodName)); + return (...args: unknown[]) => { + onFallback?.(methodName, args); + Promise.reject(new MethodNotFoundError(methodName)); + }; } else { console.warn( `[WebViewBridge] ${methodName} is not defined, using fallback.`, diff --git a/shared/util/src/createEvents.ts b/shared/util/src/createEvents.ts index adacd0ab..21d97fe4 100644 --- a/shared/util/src/createEvents.ts +++ b/shared/util/src/createEvents.ts @@ -37,13 +37,23 @@ export const createEvents = < }, }); -export const createResolver = ( - emitter: EventEmitter, - methodName: string, - eventId: string, - evaluate: () => void, - failHandler: Error | false = false, -) => { +export interface CreateResolverOptions { + emitter: EventEmitter; + evaluate: () => void; + eventId: string; + failHandler?: Error | false; + methodName: string; + onFallback?: () => void; +} + +export const createResolver = ({ + emitter, + evaluate, + eventId, + failHandler = false, + methodName, + onFallback, +}: CreateResolverOptions) => { return new Promise((resolve, reject) => { const unbind = emitter.on( `${methodName}-${eventId}`, @@ -52,6 +62,7 @@ export const createResolver = ( if (throwOccurred) { if (failHandler instanceof Error) { + onFallback?.(); reject(failHandler); } else { resolve(void 0);