Skip to content

Commit

Permalink
fix: onFallback should be triggered when a throw occurs in the method (
Browse files Browse the repository at this point in the history
…gronxb#40)

* fix: onFallback should be triggered when a throw occurs in the method

* docs: example
  • Loading branch information
gronxb authored Apr 28, 2024
1 parent 79b7118 commit 20f885a
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 33 deletions.
9 changes: 7 additions & 2 deletions docs/backward-compatibility/new-method.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,13 @@ The `onFallback` method can be used for batch processing of errors. It's suitabl

```tsx
const bridge = linkBridge<AppBridge>({
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);
}
},
});
```
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/web/link-bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
3 changes: 3 additions & 0 deletions example/native-method/react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const bridge = linkBridge<AppBridge>({
onReady: () => {
console.log("bridge is ready");
},
onFallback: (methodName, args) => {
console.log("fallback", methodName, args);
},
});

function App() {
Expand Down
17 changes: 12 additions & 5 deletions packages/react-native/src/integrations/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -96,12 +107,8 @@ export const handleBridge = async ({
true;
`);
} catch (error) {
handleThrow();
console.error(error);
webview.injectJavaScript(`
window.nativeEmitter.emit('${method}-${eventId}', {}, true);
true;
`);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -35,8 +35,8 @@ export const handleRegisterWebMethod = (
`,
);
},
new WebMethodError(methodName),
),
failHandler: new WebMethodError(methodName),
}),
timeout(responseTimeout),
]);
};
Expand Down
45 changes: 31 additions & 14 deletions packages/web/src/linkBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,33 @@ export interface LinkBridgeOptions<
> {
timeout?: number;
throwOnError?: boolean | (keyof ExtractStore<T>)[] | string[];
onFallback?: (methodName: string) => void;
onFallback?: (methodName: string, args: unknown[]) => void;
onReady?: (
method: LinkBridge<ExcludePrimitive<ExtractStore<T>>, Omit<T, "setState">>,
) => 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",
Expand All @@ -50,8 +60,11 @@ const createNativeMethod =
}),
);
},
throwOnError && new NativeMethodError(methodName),
),
onFallback: () => {
onFallback?.(methodName, args);
},
failHandler: throwOnError && new NativeMethodError(methodName),
}),
timeout(timeoutMs, throwOnError),
]);
};
Expand Down Expand Up @@ -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<ExtractStore<T>, Omit<T, "setState">>,
Expand All @@ -121,11 +135,12 @@ export const linkBridge = <
) {
return target[methodName];
}
return createNativeMethod(
return createNativeMethod({
methodName,
timeoutMs,
willMethodThrowOnError(methodName),
);
throwOnError: willMethodThrowOnError(methodName),
onFallback,
});
},
});

Expand Down Expand Up @@ -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.`,
Expand Down
25 changes: 18 additions & 7 deletions shared/util/src/createEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,23 @@ export const createEvents = <
},
});

export const createResolver = (
emitter: EventEmitter<DefaultEvents>,
methodName: string,
eventId: string,
evaluate: () => void,
failHandler: Error | false = false,
) => {
export interface CreateResolverOptions {
emitter: EventEmitter<DefaultEvents>;
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}`,
Expand All @@ -52,6 +62,7 @@ export const createResolver = (

if (throwOccurred) {
if (failHandler instanceof Error) {
onFallback?.();
reject(failHandler);
} else {
resolve(void 0);
Expand Down

0 comments on commit 20f885a

Please sign in to comment.