1// Copyright 2018-present 650 Industries. All rights reserved.
2
3#import <objc/runtime.h>
4
5#import <React/RCTLog.h>
6#import <React/RCTUIManager.h>
7#import <React/RCTComponentData.h>
8#import <React/RCTModuleData.h>
9#import <React/RCTEventDispatcherProtocol.h>
10
11#import <jsi/jsi.h>
12
13#import <ExpoModulesCore/EXNativeModulesProxy.h>
14#import <ExpoModulesCore/EXEventEmitter.h>
15#import <ExpoModulesCore/EXViewManager.h>
16#import <ExpoModulesCore/EXViewManagerAdapter.h>
17#import <ExpoModulesCore/EXViewManagerAdapterClassesRegistry.h>
18#import <ExpoModulesCore/EXModuleRegistryProvider.h>
19#import <ExpoModulesCore/EXReactNativeEventEmitter.h>
20#import <ExpoModulesCore/EXJSIInstaller.h>
21#import <ExpoModulesCore/Swift.h>
22
23static const NSString *exportedMethodsNamesKeyPath = @"exportedMethods";
24static const NSString *viewManagersMetadataKeyPath = @"viewManagersMetadata";
25static const NSString *exportedConstantsKeyPath = @"modulesConstants";
26
27static const NSString *methodInfoKeyKey = @"key";
28static const NSString *methodInfoNameKey = @"name";
29static const NSString *methodInfoArgumentsCountKey = @"argumentsCount";
30
31@interface RCTBridge (RegisterAdditionalModuleClasses)
32
33- (NSArray<RCTModuleData *> *)registerModulesForClasses:(NSArray<Class> *)moduleClasses;
34- (void)registerAdditionalModuleClasses:(NSArray<Class> *)modules;
35
36@end
37
38@interface RCTBridge (JSIRuntime)
39
40- (void *)runtime;
41
42@end
43
44@interface EXNativeModulesProxy ()
45
46@property (nonatomic, strong) NSRegularExpression *regexp;
47@property (nonatomic, strong) EXModuleRegistry *exModuleRegistry;
48@property (nonatomic, strong) NSMutableDictionary<const NSString *, NSMutableDictionary<NSString *, NSNumber *> *> *exportedMethodsKeys;
49@property (nonatomic, strong) NSMutableDictionary<const NSString *, NSMutableDictionary<NSNumber *, NSString *> *> *exportedMethodsReverseKeys;
50@property (nonatomic) BOOL ownsModuleRegistry;
51
52@end
53
54@implementation EXNativeModulesProxy {
55  __weak EXAppContext * _Nullable _appContext;
56}
57
58@synthesize bridge = _bridge;
59
60RCT_EXPORT_MODULE(NativeUnimoduleProxy)
61
62/**
63 The designated initializer. It's used in the old setup where the native modules proxy
64 is registered in `extraModulesForBridge:` by the bridge delegate.
65 */
66- (instancetype)initWithModuleRegistry:(nullable EXModuleRegistry *)moduleRegistry
67{
68  if (self = [super init]) {
69    _exModuleRegistry = moduleRegistry != nil ? moduleRegistry : [[EXModuleRegistryProvider new] moduleRegistry];
70    _exportedMethodsKeys = [NSMutableDictionary dictionary];
71    _exportedMethodsReverseKeys = [NSMutableDictionary dictionary];
72    _ownsModuleRegistry = moduleRegistry == nil;
73  }
74  return self;
75}
76
77/**
78 The initializer for Expo Go to pass a custom `EXModuleRegistry`
79 other than the default one from `EXModuleRegistryProvider`.
80 The `EXModuleRegistry` is still owned by this class.
81 */
82- (instancetype)initWithCustomModuleRegistry:(nonnull EXModuleRegistry *)moduleRegistry
83{
84  self = [self initWithModuleRegistry:moduleRegistry];
85  self.ownsModuleRegistry = YES;
86  return self;
87}
88
89/**
90 Convenience initializer used by React Native in the new setup, where the modules are registered automatically.
91 */
92- (instancetype)init
93{
94  return [self initWithModuleRegistry:nil];
95}
96
97# pragma mark - React API
98
99+ (BOOL)requiresMainQueueSetup
100{
101  return YES;
102}
103
104- (NSDictionary *)constantsToExport
105{
106  NSMutableDictionary <NSString *, id> *exportedModulesConstants = [NSMutableDictionary dictionary];
107  // Grab all the constants exported by modules
108  for (EXExportedModule *exportedModule in [_exModuleRegistry getAllExportedModules]) {
109    @try {
110      exportedModulesConstants[[[exportedModule class] exportedModuleName]] = [exportedModule constantsToExport] ?: [NSNull null];
111    } @catch (NSException *exception) {
112      continue;
113    }
114  }
115  [exportedModulesConstants addEntriesFromDictionary:[_appContext exportedModulesConstants]];
116
117  // Also add `exportedMethodsNames`
118  NSMutableDictionary<const NSString *, NSMutableArray<NSMutableDictionary<const NSString *, id> *> *> *exportedMethodsNamesAccumulator = [NSMutableDictionary dictionary];
119  for (EXExportedModule *exportedModule in [_exModuleRegistry getAllExportedModules]) {
120    const NSString *exportedModuleName = [[exportedModule class] exportedModuleName];
121    exportedMethodsNamesAccumulator[exportedModuleName] = [NSMutableArray array];
122    [[exportedModule getExportedMethods] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull exportedName, NSString * _Nonnull selectorName, BOOL * _Nonnull stop) {
123      NSMutableDictionary<const NSString *, id> *methodInfo = [NSMutableDictionary dictionaryWithDictionary:@{
124                                                                                                              methodInfoNameKey: exportedName,
125                                                                                                              // - 3 is for resolver and rejecter of the promise and the last, empty component
126                                                                                                              methodInfoArgumentsCountKey: @([[selectorName componentsSeparatedByString:@":"] count] - 3)
127                                                                                                              }];
128      [exportedMethodsNamesAccumulator[exportedModuleName] addObject:methodInfo];
129    }];
130    [self assignExportedMethodsKeys:exportedMethodsNamesAccumulator[exportedModuleName] forModuleName:exportedModuleName];
131  }
132
133  // Add entries from Swift modules
134  [exportedMethodsNamesAccumulator addEntriesFromDictionary:[_appContext exportedFunctionNames]];
135
136  // Also, add `viewManagersMetadata` for sanity check and testing purposes -- with names we know what managers to mock on UIManager
137  NSArray<EXViewManager *> *viewManagers = [_exModuleRegistry getAllViewManagers];
138  NSMutableDictionary<NSString *, NSDictionary *> *viewManagersMetadata = [[NSMutableDictionary alloc] initWithCapacity:[viewManagers count]];
139
140  for (EXViewManager *viewManager in viewManagers) {
141    viewManagersMetadata[viewManager.viewName] = @{
142      @"propsNames": [[viewManager getPropsNames] allKeys]
143    };
144  }
145
146  // Add entries from Swift view managers
147  [viewManagersMetadata addEntriesFromDictionary:[_appContext viewManagersMetadata]];
148
149  NSMutableDictionary <NSString *, id> *constantsAccumulator = [NSMutableDictionary dictionary];
150  constantsAccumulator[viewManagersMetadataKeyPath] = viewManagersMetadata;
151  constantsAccumulator[exportedConstantsKeyPath] = exportedModulesConstants;
152  constantsAccumulator[exportedMethodsNamesKeyPath] = exportedMethodsNamesAccumulator;
153
154  return constantsAccumulator;
155}
156
157- (void)setBridge:(RCTBridge *)bridge
158{
159  _appContext = [(ExpoBridgeModule *)[bridge moduleForClass:ExpoBridgeModule.class] appContext];
160  [_appContext setLegacyModuleRegistry:_exModuleRegistry];
161
162  if (!_bridge) {
163    // The `setBridge` can be called during module setup or after. Registering more modules
164    // during setup causes a crash due to mutating `_moduleDataByID` while it's being enumerated.
165    // In that case we register them asynchronously.
166    if ([[bridge valueForKey:@"_moduleSetupComplete"] boolValue]) {
167      [self registerExpoModulesInBridge:bridge];
168    } else {
169      dispatch_async(dispatch_get_main_queue(), ^{
170        [self registerExpoModulesInBridge:bridge];
171      });
172    }
173  }
174  _bridge = bridge;
175}
176
177RCT_EXPORT_METHOD(callMethod:(NSString *)moduleName methodNameOrKey:(id)methodNameOrKey arguments:(NSArray *)arguments resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
178{
179  // Backwards compatibility for the new architecture
180  if ([_appContext hasModule:moduleName]) {
181    [_appContext callFunction:methodNameOrKey onModule:moduleName withArgs:arguments resolve:resolve reject:reject];
182    return;
183  }
184
185  EXExportedModule *module = [_exModuleRegistry getExportedModuleForName:moduleName];
186  if (module == nil) {
187    NSString *reason = [NSString stringWithFormat:@"No exported module was found for name '%@'. Are you sure all the packages are linked correctly?", moduleName];
188    reject(@"E_NO_MODULE", reason, nil);
189    return;
190  }
191
192  if (!methodNameOrKey) {
193    reject(@"E_NO_METHOD", @"No method key or name provided", nil);
194    return;
195  }
196
197  NSString *methodName;
198  if ([methodNameOrKey isKindOfClass:[NSString class]]) {
199    methodName = (NSString *)methodNameOrKey;
200  } else if ([methodNameOrKey isKindOfClass:[NSNumber class]]) {
201    methodName = _exportedMethodsReverseKeys[moduleName][(NSNumber *)methodNameOrKey];
202  } else {
203    reject(@"E_INV_MKEY", @"Method key is neither a String nor an Integer -- don't know how to map it to method name.", nil);
204    return;
205  }
206
207  dispatch_async([module methodQueue], ^{
208    @try {
209      [module callExportedMethod:methodName withArguments:arguments resolver:resolve rejecter:reject];
210    } @catch (NSException *e) {
211      NSString *message = [NSString stringWithFormat:@"An exception was thrown while calling `%@.%@` with arguments `%@`: %@", moduleName, methodName, arguments, e];
212      reject(@"E_EXC", message, nil);
213    }
214  });
215}
216
217#pragma mark - Privates
218
219- (void)registerExpoModulesInBridge:(RCTBridge *)bridge
220{
221  // Registering expo modules (excluding Swifty view managers!) in bridge is needed only when the proxy module owns
222  // the registry (was autoinitialized by React Native). Otherwise they're registered by the registry adapter.
223  BOOL ownsModuleRegistry = _ownsModuleRegistry && ![bridge moduleIsInitialized:[EXReactNativeEventEmitter class]];
224
225  // An array of `RCTBridgeModule` classes to register.
226  NSMutableArray<Class<RCTBridgeModule>> *additionalModuleClasses = [NSMutableArray new];
227  NSMutableSet *visitedSweetModules = [NSMutableSet new];
228
229  // Add dynamic wrappers for view modules written in Sweet API.
230  for (ViewModuleWrapper *swiftViewModule in [_appContext getViewManagers]) {
231    Class wrappedViewModuleClass = [self registerComponentData:swiftViewModule inBridge:bridge];
232    [additionalModuleClasses addObject:wrappedViewModuleClass];
233    [visitedSweetModules addObject:swiftViewModule.name];
234  }
235
236  [additionalModuleClasses addObject:[ViewModuleWrapper class]];
237  [self registerLegacyComponentData:[ViewModuleWrapper class] inBridge:bridge];
238
239  // Add modules from legacy module registry only when the NativeModulesProxy owns the registry.
240  if (ownsModuleRegistry) {
241    // Event emitter is a bridge module, however it's also needed by expo modules,
242    // so later we'll register an instance created by React Native as expo module.
243    [additionalModuleClasses addObject:[EXReactNativeEventEmitter class]];
244
245    // Add dynamic wrappers for the classic view managers.
246    for (EXViewManager *viewManager in [_exModuleRegistry getAllViewManagers]) {
247      if (![visitedSweetModules containsObject:viewManager.viewName]) {
248        Class viewManagerWrapperClass = [EXViewManagerAdapterClassesRegistry createViewManagerAdapterClassForViewManager:viewManager];
249        [additionalModuleClasses addObject:viewManagerWrapperClass];
250        [self registerLegacyComponentData:viewManagerWrapperClass inBridge:bridge];
251      }
252    }
253
254    // View manager wrappers don't have their own prop configs, so we must register
255    // their base view managers that provides common props such as `proxiedProperties`.
256    // Otherwise, React Native may treat these props as invalid in subclassing views.
257    [additionalModuleClasses addObject:[EXViewManagerAdapter class]];
258    // Also, we have to register component data for the View Adapter.
259    // Otherwise, it won't be recognized by the UIManager.
260    [self registerLegacyComponentData:[EXViewManagerAdapter class] inBridge:bridge];
261
262    // Some modules might need access to the bridge.
263    for (id module in [_exModuleRegistry getAllInternalModules]) {
264      if ([module conformsToProtocol:@protocol(RCTBridgeModule)]) {
265        [module setValue:bridge forKey:@"bridge"];
266      }
267    }
268  }
269
270  // `registerAdditionalModuleClasses:` call below is not thread-safe if RCTUIManager is not initialized.
271  // The case happens especially with reanimated which accesses `bridge.uiManager` and initialize bridge in js thread.
272  // Accessing uiManager here, we try to make sure RCTUIManager is initialized.
273  [bridge uiManager];
274
275  // Register the view managers as additional modules.
276  [self registerAdditionalModuleClasses:additionalModuleClasses inBridge:bridge];
277
278  // Get the instance of `EXReactEventEmitter` bridge module and give it access to the interop bridge.
279  EXReactNativeEventEmitter *eventEmitter = [bridge moduleForClass:[EXReactNativeEventEmitter class]];
280  [eventEmitter setAppContext:_appContext];
281
282  // As the last step, when the registry is owned,
283  // register the event emitter and initialize the registry.
284  if (ownsModuleRegistry) {
285    [_exModuleRegistry registerInternalModule:eventEmitter];
286
287    // Let the modules consume the registry :)
288    // It calls `setModuleRegistry:` on all `EXModuleRegistryConsumer`s.
289    [_exModuleRegistry initialize];
290  }
291}
292
293- (void)registerAdditionalModuleClasses:(NSArray<Class> *)moduleClasses inBridge:(RCTBridge *)bridge
294{
295  // In remote debugging mode, i.e. executorClass is `RCTWebSocketExecutor`,
296  // there is a deadlock issue in `registerAdditionalModuleClasses:` and causes app freezed.
297  //   - The JS thread acquired the `RCTCxxBridge._moduleRegistryLock` lock in `RCTCxxBridge._initializeBridgeLocked`
298  //      = it further goes into RCTObjcExecutor and tries to get module config from main thread
299  //   - The main thread is pending in `RCTCxxBridge.registerAdditionalModuleClasses` where trying to acquire the same lock.
300  // To workaround the deadlock, we tend to use the non-locked registration and mutate the bridge internal module data.
301  // Since JS thread in this situation is waiting for main thread, it's safe to mutate module data without lock.
302  // The only risk should be the internal `_moduleRegistryCreated` flag without lock protection.
303  // As we just workaround in `RCTWebSocketExecutor` case, the risk of `_moduleRegistryCreated` race condition should be lower.
304  //
305  // Learn more about the non-locked initialization:
306  // https://github.com/facebook/react-native/blob/757bb75fbf837714725d7b2af62149e8e2a7ee51/React/CxxBridge/RCTCxxBridge.mm#L922-L935
307  // See the `_moduleRegistryCreated` false case
308  if ([NSStringFromClass([bridge executorClass]) isEqualToString:@"RCTWebSocketExecutor"]) {
309    NSNumber *moduleRegistryCreated = [bridge valueForKey:@"_moduleRegistryCreated"];
310    if (![moduleRegistryCreated boolValue]) {
311      [bridge registerModulesForClasses:moduleClasses];
312      return;
313    }
314  }
315
316  [bridge registerAdditionalModuleClasses:moduleClasses];
317}
318
319- (Class)registerComponentData:(ViewModuleWrapper *)viewModule inBridge:(RCTBridge *)bridge
320{
321  // Hacky way to get a dictionary with `RCTComponentData` from UIManager.
322  NSMutableDictionary<NSString *, RCTComponentData *> *componentDataByName = [bridge.uiManager valueForKey:@"_componentDataByName"];
323  Class wrappedViewModuleClass = [ViewModuleWrapper createViewModuleWrapperClassWithModule:viewModule];
324  NSString *className = NSStringFromClass(wrappedViewModuleClass);
325
326  if (componentDataByName[className]) {
327    // Just in case the component was already registered, let's leave a log that we're overriding it.
328    NSLog(@"Overriding ComponentData for view %@", className);
329  }
330
331  EXComponentData *componentData = [[EXComponentData alloc] initWithViewModule:viewModule
332                                                                  managerClass:wrappedViewModuleClass
333                                                                        bridge:bridge];
334  componentDataByName[className] = componentData;
335  return wrappedViewModuleClass;
336}
337
338/**
339 Bridge's `registerAdditionalModuleClasses:` method doesn't register
340 components in UIManager — we need to register them on our own.
341 */
342- (void)registerLegacyComponentData:(Class)moduleClass inBridge:(RCTBridge *)bridge
343{
344  // Hacky way to get a dictionary with `RCTComponentData` from UIManager.
345  NSMutableDictionary<NSString *, RCTComponentData *> *componentDataByName = [bridge.uiManager valueForKey:@"_componentDataByName"];
346  NSString *className = NSStringFromClass(moduleClass);
347
348  if ([moduleClass isSubclassOfClass:[RCTViewManager class]] && !componentDataByName[className]) {
349    RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass bridge:bridge eventDispatcher:bridge.eventDispatcher];
350    componentDataByName[className] = componentData;
351  }
352}
353
354- (void)assignExportedMethodsKeys:(NSMutableArray<NSMutableDictionary<const NSString *, id> *> *)exportedMethods forModuleName:(const NSString *)moduleName
355{
356  if (!_exportedMethodsKeys[moduleName]) {
357    _exportedMethodsKeys[moduleName] = [NSMutableDictionary dictionary];
358  }
359
360  if (!_exportedMethodsReverseKeys[moduleName]) {
361    _exportedMethodsReverseKeys[moduleName] = [NSMutableDictionary dictionary];
362  }
363
364  for (int i = 0; i < [exportedMethods count]; i++) {
365    NSMutableDictionary<const NSString *, id> *methodInfo = exportedMethods[i];
366
367    if (!methodInfo[(NSString *)methodInfoNameKey] || ![methodInfo[methodInfoNameKey] isKindOfClass:[NSString class]]) {
368      NSString *reason = [NSString stringWithFormat:@"Method info of a method of module %@ has no method name.", moduleName];
369      @throw [NSException exceptionWithName:@"Empty method name in method info" reason:reason userInfo:nil];
370    }
371
372    NSString *methodName = methodInfo[(NSString *)methodInfoNameKey];
373    NSNumber *previousMethodKey = _exportedMethodsKeys[moduleName][methodName];
374    if (previousMethodKey) {
375      methodInfo[methodInfoKeyKey] = previousMethodKey;
376    } else {
377      NSNumber *newKey = @([[_exportedMethodsKeys[moduleName] allValues] count]);
378      methodInfo[methodInfoKeyKey] = newKey;
379      _exportedMethodsKeys[moduleName][methodName] = newKey;
380      _exportedMethodsReverseKeys[moduleName][newKey] = methodName;
381    }
382  }
383}
384
385@end
386