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