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    */
invalidatenull44   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
extraModulesnull53   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    */
registerExpoModulesnull90   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 
createAppContextConfignull98   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  */
createLegacyModuleRegistrynull133 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