1 // Copyright 2023-present 650 Industries. All rights reserved. 2 3 import React 4 import ExpoModulesCore 5 import EXManifests 6 7 /** 8 Manages the React Native app opened in Expo Go. As opposed to ``EXReactAppManager`` and ``EXHomeAppManager``, 9 this manager is versioned and used by them under the hood like an adapter. 10 */ 11 @objc(EXVersionManager) 12 final class VersionManager: EXVersionManagerObjC { 13 var appContext: AppContext? 14 var legacyModulesProxy: LegacyNativeModulesProxy? 15 var legacyModuleRegistry: EXModuleRegistry? 16 17 let params: [AnyHashable: Any] 18 19 let manifest: Manifest 20 21 @objc 22 override init( 23 params: [AnyHashable: Any], 24 manifest: Manifest, 25 fatalHandler: @escaping (Error?) -> Void, 26 logFunction: @escaping RCTLogFunction, 27 logThreshold: RCTLogLevel 28 ) { 29 self.params = params 30 self.manifest = manifest 31 32 configureReact( 33 enableTurboModules: manifest.experiments()?["turboModules"] as? Bool ?? false, 34 fatalHandler: fatalHandler, 35 logFunction: logFunction, 36 logThreshold: logThreshold 37 ) 38 super.init(params: params, manifest: manifest, fatalHandler: fatalHandler, logFunction: logFunction, logThreshold: logThreshold) 39 } 40 41 /** 42 Invalidates the app context when the bridge is about to be rebuilt. 43 */ 44 override func invalidate() { 45 appContext = nil 46 super.invalidate() 47 } 48 49 /** 50 Returns a list of bridge modules to register when the bridge initializes. 51 */ 52 @objc 53 override func extraModules(forBridge bridge: Any) -> [Any] { 54 // Ideally if we don't initialize the app context here, but unfortunately there is no better place in bridge lifecycle 55 // that would work well for us (especially properly invalidating existing app context on reload). 56 let legacyModuleRegistry = createLegacyModuleRegistry(params: params, manifest: manifest) 57 let legacyModulesProxy = LegacyNativeModulesProxy(customModuleRegistry: legacyModuleRegistry) 58 let config = createAppContextConfig() 59 let appContext = AppContext(legacyModulesProxy: legacyModulesProxy, legacyModuleRegistry: legacyModuleRegistry, config: config) 60 61 self.appContext = appContext 62 self.legacyModuleRegistry = legacyModuleRegistry 63 self.legacyModulesProxy = legacyModulesProxy 64 65 let modules: [Any] = [ 66 EXAppState(), 67 EXDisabledDevLoadingView(), 68 EXStatusBarManager(), 69 70 // Adding EXNativeModulesProxy with the custom moduleRegistry. 71 legacyModulesProxy, 72 73 // Adding the way to access the module registry from RCTBridgeModules. 74 EXModuleRegistryHolderReactModule(moduleRegistry: legacyModuleRegistry), 75 76 // When ExpoBridgeModule is initialized by RN, it automatically creates the app context. 77 // In Expo Go, it has to use already created app context. 78 ExpoBridgeModule(appContext: appContext) 79 ] 80 81 // Register additional Expo modules, specific to Expo Go. 82 registerExpoModules() 83 84 return modules + super.extraModules(forBridge: bridge) 85 } 86 87 /** 88 Registers Expo modules that are not generated in ``ExpoModulesProvider``, but are necessary for Expo Go apps. 89 */ 90 private func registerExpoModules() { 91 guard let appContext else { 92 log.error("Unable to register Expo modules, the app context is unavailable") 93 return 94 } 95 appContext.moduleRegistry.register(module: ExpoGoModule(appContext: appContext, manifest: manifest)) 96 } 97 98 private func createAppContextConfig() -> AppContextConfig { 99 guard let fileSystemDirectories = params["fileSystemDirectories"] as? [AnyHashable: Any] else { 100 fatalError("Missing file system directories in the params") 101 } 102 guard let documentDirectory = fileSystemDirectories["documentDirectory"] as? String else { 103 fatalError("Missing document directory param") 104 } 105 guard let cacheDirectory = fileSystemDirectories["cachesDirectory"] as? String else { 106 fatalError("Missing caches directory param") 107 } 108 return AppContextConfig( 109 documentDirectory: URL(fileURLWithPath: documentDirectory), 110 cacheDirectory: URL(fileURLWithPath: cacheDirectory) 111 ) 112 } 113 } 114 115 /** 116 Configures some React Native global options. 117 */ 118 private func configureReact( 119 enableTurboModules: Bool, 120 fatalHandler: @escaping (Error?) -> Void, 121 logFunction: @escaping RCTLogFunction, 122 logThreshold: RCTLogLevel 123 ) { 124 RCTEnableTurboModule(enableTurboModules) 125 RCTSetFatalHandler(fatalHandler) 126 RCTSetLogThreshold(logThreshold) 127 RCTSetLogFunction(logFunction) 128 } 129 130 /** 131 Creates a module registry for legacy Expo modules. 132 */ 133 private func createLegacyModuleRegistry(params: [AnyHashable: Any], manifest: Manifest) -> EXModuleRegistry { 134 guard let singletonModules = params["singletonModules"] as? Set<AnyHashable> else { 135 fatalError("Singleton modules param cannot be cast to Set<AnyHashable>") 136 } 137 let moduleRegistryProvider = ModuleRegistryProvider(singletonModules: singletonModules) 138 let moduleRegistryAdapter = EXScopedModuleRegistryAdapter(moduleRegistryProvider: moduleRegistryProvider) 139 140 moduleRegistryProvider.moduleRegistryDelegate = EXScopedModuleRegistryDelegate(params: params) 141 142 return moduleRegistryAdapter.moduleRegistry( 143 forParams: params, 144 forExperienceStableLegacyId: manifest.stableLegacyId(), 145 scopeKey: manifest.scopeKey(), 146 manifest: manifest, 147 withKernelServices: params["services"] as? [AnyHashable: Any] 148 ) 149 } 150