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