From f791c6b4cd5420264d2436335c9a12038e7de9a4 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Mon, 13 Mar 2023 14:21:08 +0100 Subject: [PATCH] feat: Better Native Module Error Detection (#1515) * feat: Add more Error insights when the Camera Module cannot be found * Assert JSI is available * Update error description * fix * Update CameraError.ts --- src/Camera.tsx | 24 +++++---------------- src/CameraError.ts | 2 +- src/FrameProcessorPlugins.ts | 3 +++ src/JSIHelper.ts | 22 +++++++++++++++++++ src/NativeCameraModule.ts | 41 ++++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 src/JSIHelper.ts create mode 100644 src/NativeCameraModule.ts diff --git a/src/Camera.tsx b/src/Camera.tsx index e4d966b..07427e2 100644 --- a/src/Camera.tsx +++ b/src/Camera.tsx @@ -1,11 +1,13 @@ import React from 'react'; -import { requireNativeComponent, NativeModules, NativeSyntheticEvent, findNodeHandle, NativeMethods, Platform } from 'react-native'; +import { requireNativeComponent, NativeSyntheticEvent, findNodeHandle, NativeMethods, Platform } from 'react-native'; import type { VideoFileType } from '.'; import type { CameraDevice } from './CameraDevice'; import type { ErrorWithCause } from './CameraError'; import { CameraCaptureError, CameraRuntimeError, tryParseNativeCameraError, isErrorWithCause } from './CameraError'; import type { CameraProps } from './CameraProps'; import type { Frame } from './Frame'; +import { assertFrameProcessorsAvailable } from './JSIHelper'; +import { CameraModule } from './NativeCameraModule'; import type { PhotoFile, TakePhotoOptions } from './PhotoFile'; import type { Point } from './Point'; import type { TakeSnapshotOptions } from './Snapshot'; @@ -30,11 +32,6 @@ type NativeCameraViewProps = Omit & Readonly; //#endregion -// NativeModules automatically resolves 'CameraView' to 'CameraViewModule' -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -const CameraModule = NativeModules.CameraView; -if (CameraModule == null) console.error("Camera: Native Module 'CameraView' was null! Did you run pod install?"); - //#region Camera Component /** * ### A powerful `` component. @@ -419,25 +416,14 @@ export class Camera extends React.PureComponent { //#endregion //#region Lifecycle - /** @internal */ - private assertFrameProcessorsEnabled(): void { - // @ts-expect-error JSI functions aren't typed - if (global.setFrameProcessor == null || global.unsetFrameProcessor == null) { - throw new CameraRuntimeError( - 'frame-processor/unavailable', - 'Frame Processors are not enabled. See https://mrousavy.github.io/react-native-vision-camera/docs/guides/troubleshooting', - ); - } - } - private setFrameProcessor(frameProcessor: (frame: Frame) => void): void { - this.assertFrameProcessorsEnabled(); + assertFrameProcessorsAvailable(); // @ts-expect-error JSI functions aren't typed global.setFrameProcessor(this.handle, frameProcessor); } private unsetFrameProcessor(): void { - this.assertFrameProcessorsEnabled(); + assertFrameProcessorsAvailable(); // @ts-expect-error JSI functions aren't typed global.unsetFrameProcessor(this.handle); } diff --git a/src/CameraError.ts b/src/CameraError.ts index e434a45..322331c 100644 --- a/src/CameraError.ts +++ b/src/CameraError.ts @@ -50,7 +50,7 @@ export type CaptureError = | 'capture/photo-not-enabled' | 'capture/aborted' | 'capture/unknown'; -export type SystemError = 'system/no-camera-manager' | 'system/view-not-found'; +export type SystemError = 'system/camera-module-not-found' | 'system/no-camera-manager' | 'system/view-not-found'; export type UnknownError = 'unknown/unknown'; /** diff --git a/src/FrameProcessorPlugins.ts b/src/FrameProcessorPlugins.ts index ac1d341..e30f19c 100644 --- a/src/FrameProcessorPlugins.ts +++ b/src/FrameProcessorPlugins.ts @@ -1,6 +1,9 @@ import type { Frame, FrameInternal } from './Frame'; import { Camera } from './Camera'; import { Worklets } from 'react-native-worklets/src'; +import { assertJSIAvailable } from './JSIHelper'; + +assertJSIAvailable(); // Install VisionCamera Frame Processor JSI Bindings and Plugins Camera.installFrameProcessorBindings(); diff --git a/src/JSIHelper.ts b/src/JSIHelper.ts new file mode 100644 index 0000000..1634c2b --- /dev/null +++ b/src/JSIHelper.ts @@ -0,0 +1,22 @@ +import { CameraRuntimeError } from './CameraError'; + +export function assertJSIAvailable(): void { + // Check if we are running on-device (JSI) + // @ts-expect-error JSI functions aren't typed + if (global.nativeCallSyncHook == null) { + throw new CameraRuntimeError( + 'frame-processor/unavailable', + 'Failed to initialize VisionCamera Frame Processors: React Native is not running on-device. Frame Processors can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead.', + ); + } +} + +export function assertFrameProcessorsAvailable(): void { + // @ts-expect-error JSI functions aren't typed + if (global.setFrameProcessor == null || global.unsetFrameProcessor == null) { + throw new CameraRuntimeError( + 'frame-processor/unavailable', + 'Frame Processors are not enabled. See https://mrousavy.github.io/react-native-vision-camera/docs/guides/troubleshooting', + ); + } +} diff --git a/src/NativeCameraModule.ts b/src/NativeCameraModule.ts new file mode 100644 index 0000000..0feed58 --- /dev/null +++ b/src/NativeCameraModule.ts @@ -0,0 +1,41 @@ +import { NativeModules, Platform } from 'react-native'; +import { CameraRuntimeError } from './CameraError'; + +const supportedPlatforms = ['ios', 'android', 'macos']; + +// NativeModules automatically resolves 'CameraView' to 'CameraViewModule' +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +export const CameraModule = NativeModules.CameraView; +if (CameraModule == null) { + if (!supportedPlatforms.includes(Platform.OS)) { + throw new CameraRuntimeError( + 'system/camera-module-not-found', + `Failed to initialize VisionCamera: VisionCamera currently does not work on ${Platform.OS}.`, + ); + } + + let message = 'Failed to initialize VisionCamera: The native Camera Module (`NativeModules.CameraView`) could not be found.'; + message += '\n* Make sure react-native-vision-camera is correctly autolinked (run `npx react-native config` to verify)'; + if (Platform.OS === 'ios' || Platform.OS === 'macos') message += '\n* Make sure you ran `pod install` in the ios/ directory.'; + + if (Platform.OS === 'android') message += '\n* Make sure gradle is synced.'; + + // check if Expo + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const ExpoConstants = NativeModules.NativeUnimoduleProxy?.modulesConstants?.ExponentConstants; + if (ExpoConstants != null) { + if (ExpoConstants.appOwnership === 'expo') { + // We're running Expo Go + throw new CameraRuntimeError( + 'system/camera-module-not-found', + `react-native-vision-camera is not supported in Expo Go! Use EAS/expo prebuild instead (\`expo run:${Platform.OS}\`). For more info, see https://docs.expo.dev/workflow/prebuild/.`, + ); + } else { + // We're running Expo bare / standalone + message += '\n* Make sure you ran `expo prebuild`.'; + } + } + + message += '\n* Make sure you rebuilt the app.'; + throw new CameraRuntimeError('system/camera-module-not-found', message); +}