137917d47SŁukasz Kosmatyimport { NativeModules } from 'react-native';
237917d47SŁukasz Kosmaty
3bd0cffe7STomasz Sapetaimport { ProxyNativeModule } from './NativeModulesProxy.types';
437917d47SŁukasz Kosmaty
5*d16cdc51SŁukasz Kosmatyconst LegacyNativeProxy = NativeModules.NativeUnimoduleProxy;
68bd57a9aSŁukasz Kosmaty// Fixes `cannot find name 'global'.` in tests
78bd57a9aSŁukasz Kosmaty// @ts-ignore
8380376eaSBartłomiej Klocekconst ExpoNativeProxy = global.ExpoModules?.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