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