137917d47SŁukasz Kosmatyimport { NativeModules } from 'react-native'; 237917d47SŁukasz Kosmaty 3bd0cffe7STomasz Sapetaimport { ProxyNativeModule } from './NativeModulesProxy.types'; 437917d47SŁukasz Kosmaty 5d16cdc51SŁukasz Kosmatyconst LegacyNativeProxy = NativeModules.NativeUnimoduleProxy; 68bd57a9aSŁukasz Kosmaty// Fixes `cannot find name 'global'.` in tests 78bd57a9aSŁukasz Kosmaty// @ts-ignore 8*1bed2683STomasz Sapetaconst ExpoNativeProxy = global.expo?.modules?.NativeModulesProxy; 9380376eaSBartłomiej Klocek 1037917d47SŁukasz Kosmatyconst modulesConstantsKey = 'modulesConstants'; 1137917d47SŁukasz Kosmatyconst exportedMethodsKey = 'exportedMethods'; 1237917d47SŁukasz Kosmaty 1337917d47SŁukasz Kosmatyconst NativeModulesProxy: { [moduleName: string]: ProxyNativeModule } = {}; 1437917d47SŁukasz Kosmaty 15380376eaSBartłomiej Klocekif (LegacyNativeProxy) { 16380376eaSBartłomiej Klocek // use JSI proxy if available, fallback to legacy RN proxy 17380376eaSBartłomiej Klocek const NativeProxy = ExpoNativeProxy ?? LegacyNativeProxy; 18380376eaSBartłomiej Klocek 1935f78160SBartosz Kaszubowski Object.keys(NativeProxy[exportedMethodsKey]).forEach((moduleName) => { 20380376eaSBartłomiej Klocek // copy constants 2137917d47SŁukasz Kosmaty NativeModulesProxy[moduleName] = NativeProxy[modulesConstantsKey][moduleName] || {}; 22380376eaSBartłomiej Klocek 23380376eaSBartłomiej Klocek // copy methods 2435f78160SBartosz Kaszubowski NativeProxy[exportedMethodsKey][moduleName].forEach((methodInfo) => { 2537917d47SŁukasz Kosmaty NativeModulesProxy[moduleName][methodInfo.name] = (...args: unknown[]): Promise<any> => { 26370e2621STomasz Sapeta // Use the new proxy to call methods on legacy modules, if possible. 27370e2621STomasz Sapeta if (ExpoNativeProxy?.callMethod) { 28370e2621STomasz Sapeta return ExpoNativeProxy.callMethod(moduleName, methodInfo.name, args); 29370e2621STomasz Sapeta } 30370e2621STomasz Sapeta 31370e2621STomasz Sapeta // Otherwise fall back to the legacy proxy. 32370e2621STomasz Sapeta // This is deprecated and might be removed in SDK47 or later. 3337917d47SŁukasz Kosmaty const { key, argumentsCount } = methodInfo; 3437917d47SŁukasz Kosmaty if (argumentsCount !== args.length) { 3537917d47SŁukasz Kosmaty return Promise.reject( 3637917d47SŁukasz Kosmaty new Error( 3737917d47SŁukasz Kosmaty `Native method ${moduleName}.${methodInfo.name} expects ${argumentsCount} ${ 3837917d47SŁukasz Kosmaty argumentsCount === 1 ? 'argument' : 'arguments' 3937917d47SŁukasz Kosmaty } but received ${args.length}` 4037917d47SŁukasz Kosmaty ) 4137917d47SŁukasz Kosmaty ); 4237917d47SŁukasz Kosmaty } 43380376eaSBartłomiej Klocek return LegacyNativeProxy.callMethod(moduleName, key, args); 4437917d47SŁukasz Kosmaty }; 4537917d47SŁukasz Kosmaty }); 4637917d47SŁukasz Kosmaty 4737917d47SŁukasz Kosmaty // These are called by EventEmitter (which is a wrapper for NativeEventEmitter) 4837917d47SŁukasz Kosmaty // only on iOS and they use iOS-specific native module, EXReactNativeEventEmitter. 4937917d47SŁukasz Kosmaty // 5037917d47SŁukasz Kosmaty // On Android only {start,stop}Observing are called on the native module 5137917d47SŁukasz Kosmaty // and these should be exported as Expo methods. 521036dd0aSŁukasz Kosmaty // 531036dd0aSŁukasz Kosmaty // Before the RN 65, addListener/removeListeners weren't called on Android. However, it no longer stays true. 541036dd0aSŁukasz Kosmaty // See https://github.com/facebook/react-native/commit/f5502fbda9fe271ff6e1d0da773a3a8ee206a453. 551036dd0aSŁukasz Kosmaty // That's why, we check if the `EXReactNativeEventEmitter` exists and only if yes, we use it in the listener implementation. 561036dd0aSŁukasz Kosmaty // Otherwise, those methods are NOOP. 571036dd0aSŁukasz Kosmaty if (NativeModules.EXReactNativeEventEmitter) { 5837917d47SŁukasz Kosmaty NativeModulesProxy[moduleName].addListener = (...args) => 5970c51132SKudo Chien NativeModules.EXReactNativeEventEmitter.addProxiedListener(moduleName, ...args); 6037917d47SŁukasz Kosmaty NativeModulesProxy[moduleName].removeListeners = (...args) => 6170c51132SKudo Chien NativeModules.EXReactNativeEventEmitter.removeProxiedListeners(moduleName, ...args); 621036dd0aSŁukasz Kosmaty } else { 631036dd0aSŁukasz Kosmaty // Fixes on Android: 641036dd0aSŁukasz Kosmaty // WARN `new NativeEventEmitter()` was called with a non-null argument without the required `addListener` method. 651036dd0aSŁukasz Kosmaty // WARN `new NativeEventEmitter()` was called with a non-null argument without the required `removeListeners` method. 661036dd0aSŁukasz Kosmaty NativeModulesProxy[moduleName].addListener = () => {}; 671036dd0aSŁukasz Kosmaty NativeModulesProxy[moduleName].removeListeners = () => {}; 681036dd0aSŁukasz Kosmaty } 6937917d47SŁukasz Kosmaty }); 7037917d47SŁukasz Kosmaty} else { 7137917d47SŁukasz Kosmaty console.warn( 72e7f22797STomasz Sapeta `The "EXNativeModulesProxy" native module is not exported through NativeModules; verify that expo-modules-core's native code is linked properly` 7337917d47SŁukasz Kosmaty ); 7437917d47SŁukasz Kosmaty} 7537917d47SŁukasz Kosmaty 7637917d47SŁukasz Kosmatyexport default NativeModulesProxy; 77