1 // Copyright 2022-present 650 Industries. All rights reserved.
2 
3 import React
4 import Foundation
5 
6 /**
7  The classic bridge module that is responsible for:
8  - Creating and owning the `AppContext` when the Expo modules architecture is automatically initialized
9    by React Native (as opposed to native unit tests, where React Native is not used at all).
10  - Installing the host object to the runtime.
11  */
12 @objc(ExpoBridgeModule)
13 public final class ExpoBridgeModule: NSObject, RCTBridgeModule {
14   @objc
15   public let appContext: AppContext
16 
17   /**
18    The initializer that is used by React Native when it loads bridge modules.
19    In this scenario, we create an `AppContext` that manages the
20    architecture of Expo modules and the app itself.
21    */
22   override init() {
23     appContext = AppContext()
24     super.init()
25   }
26 
27   deinit {
28     NotificationCenter.default.removeObserver(self)
29   }
30 
31   // MARK: - RCTBridgeModule
32 
33   public static func moduleName() -> String! {
34     return "Expo"
35   }
36 
37   public static func requiresMainQueueSetup() -> Bool {
38     return true
39   }
40 
41   public var bridge: RCTBridge! {
42     didSet {
43       appContext.reactBridge = bridge
44 
45       let attachRuntime = { [weak self] in
46         guard let self = self, let bridge = self.appContext.reactBridge else {
47           return
48         }
49         self.appContext._runtime = EXJavaScriptRuntimeManager.runtime(fromBridge: bridge)
50       }
51 
52       if bridge.responds(to: Selector(("runtime"))) {
53         // Getting the `runtime` on a different thread than JS is considered to be dangerous.
54         // However, we just checking if it exists. We don't do anything with it.
55         let result = bridge.perform(Selector(("runtime")))
56         if result == nil {
57           // When exporting expo modules using `extraModulesForBridge` (e.g. in Expo Go),
58           // the runtime won't be initiated before the bridge didSet method is called.
59           // Therefore, we need to wait for the main thread to complete its initialization by dispatching on it first.
60           bridge.dispatchBlock({ [weak self] in
61             guard let self = self, let bridge = self.appContext.reactBridge else {
62               return
63             }
64             bridge.dispatchBlock(attachRuntime, queue: RCTJSThread)
65           }, queue: DispatchQueue.main)
66           return
67         }
68       }
69 
70       bridge.dispatchBlock(attachRuntime, queue: RCTJSThread)
71     }
72   }
73 
74   /**
75    This should be called inside EXNativeModulesProxy.setBridge()
76    */
77   @objc
78   public func legacyProxyDidSetBridge(legacyModulesProxy: LegacyNativeModulesProxy,
79                                       legacyModuleRegistry: EXModuleRegistry) {
80     appContext.legacyModuleRegistry = legacyModuleRegistry
81     appContext.legacyModulesProxy = legacyModulesProxy
82 
83     // we need to register all the modules after the legacy module registry is set
84     // otherwise legacy modules (e.g. permissions) won't be available in OnCreate { }
85     appContext.useModulesProvider("ExpoModulesProvider")
86     appContext.moduleRegistry.register(moduleType: NativeModulesProxyModule.self)
87   }
88 }
89