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