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     appContext?.moduleRegistry.register(moduleType: ExpoGoModule.self)
91   }
92 }
93 
94 /**
95  Configures some React Native global options.
96  */
97 private func configureReact(
98   enableTurboModules: Bool,
99   fatalHandler: @escaping (Error?) -> Void,
100   logFunction: @escaping RCTLogFunction,
101   logThreshold: RCTLogLevel
102 ) {
103   RCTEnableTurboModule(enableTurboModules)
104   RCTSetFatalHandler(fatalHandler)
105   RCTSetLogThreshold(logThreshold)
106   RCTSetLogFunction(logFunction)
107 }
108 
109 /**
110  Creates a module registry for legacy Expo modules.
111  */
112 private func createLegacyModuleRegistry(params: [AnyHashable: Any], manifest: Manifest) -> EXModuleRegistry {
113   guard let singletonModules = params["singletonModules"] as? Set<AnyHashable> else {
114     fatalError("Singleton modules param cannot be cast to Set<AnyHashable>")
115   }
116   let moduleRegistryProvider = ModuleRegistryProvider(singletonModules: singletonModules)
117   let moduleRegistryAdapter = EXScopedModuleRegistryAdapter(moduleRegistryProvider: moduleRegistryProvider)
118 
119   moduleRegistryProvider.moduleRegistryDelegate = EXScopedModuleRegistryDelegate(params: params)
120 
121   return moduleRegistryAdapter.moduleRegistry(
122     forParams: params,
123     forExperienceStableLegacyId: manifest.stableLegacyId(),
124     scopeKey: manifest.scopeKey(),
125     manifest: manifest,
126     withKernelServices: params["services"] as? [AnyHashable: Any]
127   )
128 }
129