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