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