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