1import { NativeModules } from 'react-native'; 2 3import { ProxyNativeModule } from './NativeModulesProxy.types'; 4 5// Fixes `cannot find name 'global'.` in tests 6// @ts-ignore 7const ExpoNativeProxy = global.ExpoModules?.NativeModulesProxy; 8const LegacyNativeProxy = NativeModules.NativeUnimoduleProxy; 9 10const modulesConstantsKey = 'modulesConstants'; 11const exportedMethodsKey = 'exportedMethods'; 12 13const NativeModulesProxy: { [moduleName: string]: ProxyNativeModule } = {}; 14 15if (LegacyNativeProxy) { 16 // use JSI proxy if available, fallback to legacy RN proxy 17 const NativeProxy = ExpoNativeProxy ?? LegacyNativeProxy; 18 19 Object.keys(NativeProxy[exportedMethodsKey]).forEach((moduleName) => { 20 // copy constants 21 NativeModulesProxy[moduleName] = NativeProxy[modulesConstantsKey][moduleName] || {}; 22 23 // copy methods 24 NativeProxy[exportedMethodsKey][moduleName].forEach((methodInfo) => { 25 NativeModulesProxy[moduleName][methodInfo.name] = (...args: unknown[]): Promise<any> => { 26 const { key, argumentsCount } = methodInfo; 27 if (argumentsCount !== args.length) { 28 return Promise.reject( 29 new Error( 30 `Native method ${moduleName}.${methodInfo.name} expects ${argumentsCount} ${ 31 argumentsCount === 1 ? 'argument' : 'arguments' 32 } but received ${args.length}` 33 ) 34 ); 35 } 36 37 // We still want to call methods using the legacy proxy in SDK 46 38 return LegacyNativeProxy.callMethod(moduleName, key, args); 39 }; 40 }); 41 42 // These are called by EventEmitter (which is a wrapper for NativeEventEmitter) 43 // only on iOS and they use iOS-specific native module, EXReactNativeEventEmitter. 44 // 45 // On Android only {start,stop}Observing are called on the native module 46 // and these should be exported as Expo methods. 47 // 48 // Before the RN 65, addListener/removeListeners weren't called on Android. However, it no longer stays true. 49 // See https://github.com/facebook/react-native/commit/f5502fbda9fe271ff6e1d0da773a3a8ee206a453. 50 // That's why, we check if the `EXReactNativeEventEmitter` exists and only if yes, we use it in the listener implementation. 51 // Otherwise, those methods are NOOP. 52 if (NativeModules.EXReactNativeEventEmitter) { 53 NativeModulesProxy[moduleName].addListener = (...args) => 54 NativeModules.EXReactNativeEventEmitter.addProxiedListener(moduleName, ...args); 55 NativeModulesProxy[moduleName].removeListeners = (...args) => 56 NativeModules.EXReactNativeEventEmitter.removeProxiedListeners(moduleName, ...args); 57 } else { 58 // Fixes on Android: 59 // WARN `new NativeEventEmitter()` was called with a non-null argument without the required `addListener` method. 60 // WARN `new NativeEventEmitter()` was called with a non-null argument without the required `removeListeners` method. 61 NativeModulesProxy[moduleName].addListener = () => {}; 62 NativeModulesProxy[moduleName].removeListeners = () => {}; 63 } 64 }); 65} else { 66 console.warn( 67 `The "EXNativeModulesProxy" native module is not exported through NativeModules; verify that expo-modules-core's native code is linked properly` 68 ); 69} 70 71export default NativeModulesProxy; 72