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 appContext = AppContext(legacyModulesProxy: legacyModulesProxy, legacyModuleRegistry: legacyModuleRegistry)
59 
60     self.appContext = appContext
61     self.legacyModuleRegistry = legacyModuleRegistry
62     self.legacyModulesProxy = legacyModulesProxy
63 
64     let modules: [Any] = [
65       EXAppState(),
66       EXDisabledDevLoadingView(),
67       EXStatusBarManager(),
68 
69       // Adding EXNativeModulesProxy with the custom moduleRegistry.
70       legacyModulesProxy,
71 
72       // Adding the way to access the module registry from RCTBridgeModules.
73       EXModuleRegistryHolderReactModule(moduleRegistry: legacyModuleRegistry),
74 
75       // When ExpoBridgeModule is initialized by RN, it automatically creates the app context.
76       // In Expo Go, it has to use already created app context.
77       ExpoBridgeModule(appContext: appContext)
78     ]
79 
80     // Register additional Expo modules, specific to Expo Go.
81     registerExpoModules()
82 
83     return modules + super.extraModules(forBridge: bridge)
84   }
85 
86   /**
87    Registers Expo modules that are not generated in ``ExpoModulesProvider``, but are necessary for Expo Go apps.
88    */
89   private func registerExpoModules() {
90     guard let appContext else {
91       log.error("Unable to register Expo modules, the app context is unavailable")
92       return
93     }
94     appContext.moduleRegistry.register(module: ExpoGoModule(appContext: appContext, manifest: manifest))
95   }
96 }
97 
98 /**
99  Configures some React Native global options.
100  */
101 private func configureReact(
102   enableTurboModules: Bool,
103   fatalHandler: @escaping (Error?) -> Void,
104   logFunction: @escaping RCTLogFunction,
105   logThreshold: RCTLogLevel
106 ) {
107   RCTEnableTurboModule(enableTurboModules)
108   RCTSetFatalHandler(fatalHandler)
109   RCTSetLogThreshold(logThreshold)
110   RCTSetLogFunction(logFunction)
111 }
112 
113 /**
114  Creates a module registry for legacy Expo modules.
115  */
116 private func createLegacyModuleRegistry(params: [AnyHashable: Any], manifest: Manifest) -> EXModuleRegistry {
117   guard let singletonModules = params["singletonModules"] as? Set<AnyHashable> else {
118     fatalError("Singleton modules param cannot be cast to Set<AnyHashable>")
119   }
120   let moduleRegistryProvider = ModuleRegistryProvider(singletonModules: singletonModules)
121   let moduleRegistryAdapter = EXScopedModuleRegistryAdapter(moduleRegistryProvider: moduleRegistryProvider)
122 
123   moduleRegistryProvider.moduleRegistryDelegate = EXScopedModuleRegistryDelegate(params: params)
124 
125   return moduleRegistryAdapter.moduleRegistry(
126     forParams: params,
127     forExperienceStableLegacyId: manifest.stableLegacyId(),
128     scopeKey: manifest.scopeKey(),
129     manifest: manifest,
130     withKernelServices: params["services"] as? [AnyHashable: Any]
131   )
132 }
133