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 convenience init() {
23     self.init(appContext: AppContext())
24   }
25 
26   public init(appContext: AppContext) {
27     self.appContext = appContext
28     super.init()
29   }
30 
31   deinit {
32     NotificationCenter.default.removeObserver(self)
33   }
34 
35   // MARK: - RCTBridgeModule
36 
37   public static func moduleName() -> String! {
38     return "Expo"
39   }
40 
41   public static func requiresMainQueueSetup() -> Bool {
42     return true
43   }
44 
45   public var bridge: RCTBridge! {
46     didSet {
47       appContext.reactBridge = bridge
48 
49       let attachRuntime = { [weak self] in
50         guard let self = self, let bridge = self.appContext.reactBridge else {
51           return
52         }
53         self.appContext._runtime = EXJavaScriptRuntimeManager.runtime(fromBridge: bridge)
54       }
55 
56       if bridge.responds(to: Selector(("runtime"))) {
57         // Getting the `runtime` on a different thread than JS is considered to be dangerous.
58         // However, we just checking if it exists. We don't do anything with it.
59         let result = bridge.perform(Selector(("runtime")))
60         if result == nil {
61           // When exporting expo modules using `extraModulesForBridge` (e.g. in Expo Go),
62           // the runtime won't be initiated before the bridge didSet method is called.
63           // Therefore, we need to wait for the main thread to complete its initialization by dispatching on it first.
64           bridge.dispatchBlock({ [weak self] in
65             guard let self = self, let bridge = self.appContext.reactBridge else {
66               return
67             }
68             bridge.dispatchBlock(attachRuntime, queue: RCTJSThread)
69           }, queue: DispatchQueue.main)
70           return
71         }
72       }
73 
74       bridge.dispatchBlock(attachRuntime, queue: RCTJSThread)
75     }
76   }
77 
78   /**
79    This should be called inside EXNativeModulesProxy.setBridge()
80    */
81   @objc
82   public func legacyProxyDidSetBridge(legacyModulesProxy: LegacyNativeModulesProxy,
83                                       legacyModuleRegistry: EXModuleRegistry) {
84     appContext.legacyModuleRegistry = legacyModuleRegistry
85     appContext.legacyModulesProxy = legacyModulesProxy
86 
87     // we need to register all the modules after the legacy module registry is set
88     // otherwise legacy modules (e.g. permissions) won't be available in OnCreate { }
89     appContext.useModulesProvider("ExpoModulesProvider")
90     appContext.moduleRegistry.register(moduleType: NativeModulesProxyModule.self)
91   }
92 }
93