1 // Copyright 2018-present 650 Industries. All rights reserved.
2 
3 import ExpoModulesCore
4 import EXDevMenu
5 import EXUpdatesInterface
6 
7 @objc
8 public class ExpoDevLauncherReactDelegateHandler: ExpoReactDelegateHandler, RCTBridgeDelegate, EXDevLauncherControllerDelegate {
9   @objc
10   public static var enableAutoSetup: Bool = true
11 
12   private weak var reactDelegate: ExpoReactDelegate?
13   private var bridgeDelegate: RCTBridgeDelegate?
14   private var launchOptions: [AnyHashable : Any]?
15   private var deferredRootView: EXDevLauncherDeferredRCTRootView?
16   private var rootViewModuleName: String?
17   private var rootViewInitialProperties: [AnyHashable : Any]?
18   static var shouldEnableAutoSetup: Bool = {
19     // if someone else has set this explicitly, use that value
20     if !enableAutoSetup {
21       return false
22     }
23 
24     if !EXAppDefines.APP_DEBUG {
25       return false
26     }
27 
28     // Backwards compatibility -- if the main AppDelegate has already set up expo-dev-launcher,
29     // we just skip in this case.
30     if EXDevLauncherController.sharedInstance().isStarted {
31       return false
32     }
33 
34     return true
35   }()
36 
37   public override func createBridge(reactDelegate: ExpoReactDelegate, bridgeDelegate: RCTBridgeDelegate, launchOptions: [AnyHashable : Any]?) -> RCTBridge? {
38     if !ExpoDevLauncherReactDelegateHandler.shouldEnableAutoSetup {
39       return nil
40     }
41 
42     // DevLauncherController will handle dev menu configuration, so dev menu auto-setup is not needed
43     ExpoDevMenuReactDelegateHandler.enableAutoSetup = false
44 
45     self.reactDelegate = reactDelegate
46     self.bridgeDelegate = EXRCTBridgeDelegateInterceptor(bridgeDelegate: bridgeDelegate, interceptor: self)
47     self.launchOptions = launchOptions
48 
49     EXDevLauncherController.sharedInstance().autoSetupPrepare(self, launchOptions: launchOptions)
50     if (EXUpdatesControllerRegistry.sharedInstance().controller != nil) {
51       EXDevLauncherController.sharedInstance().updatesInterface = EXUpdatesControllerRegistry.sharedInstance().controller
52     }
53     return EXDevLauncherDeferredRCTBridge(delegate: self.bridgeDelegate!, launchOptions: self.launchOptions)
54   }
55 
56   public override func createRootView(reactDelegate: ExpoReactDelegate, bridge: RCTBridge, moduleName: String, initialProperties: [AnyHashable : Any]?) -> RCTRootView? {
57     if !ExpoDevLauncherReactDelegateHandler.shouldEnableAutoSetup {
58       return nil
59     }
60 
61     self.rootViewModuleName = moduleName
62     self.rootViewInitialProperties = initialProperties
63     self.deferredRootView = EXDevLauncherDeferredRCTRootView(bridge: bridge, moduleName: moduleName, initialProperties: initialProperties)
64     return self.deferredRootView
65   }
66 
67   // MARK: RCTBridgeDelegate implementations
68 
69   public func sourceURL(for bridge: RCTBridge!) -> URL! {
70     return EXDevLauncherController.sharedInstance().sourceUrl()
71   }
72 
73   // MARK: EXDevelopmentClientControllerDelegate implementations
74 
75   public func devLauncherController(_ developmentClientController: EXDevLauncherController, didStartWithSuccess success: Bool) {
76     var launchOptions: [AnyHashable: Any] = [:]
77 
78     if let initialLaunchOptions = self.launchOptions {
79       for (key, value) in initialLaunchOptions {
80         launchOptions[key] = value
81       }
82     }
83 
84     for (key, value) in developmentClientController.getLaunchOptions() {
85       launchOptions[key] = value
86     }
87 
88     let bridge = RCTBridge(delegate: self.bridgeDelegate, launchOptions: launchOptions)
89     developmentClientController.appBridge = bridge
90 
91     let rootView = RCTRootView(bridge: bridge!, moduleName: self.rootViewModuleName!, initialProperties: self.rootViewInitialProperties)
92     rootView.backgroundColor = self.deferredRootView?.backgroundColor ?? UIColor.white
93     let window = getWindow()
94 
95     // NOTE: this order of assignment seems to actually have an effect on behaviour
96     // direct assignment of window.rootViewController.view = rootView does not work
97     let rootViewController = self.reactDelegate?.createRootViewController()
98     rootViewController!.view = rootView
99     window.rootViewController = rootViewController
100     window.makeKeyAndVisible()
101 
102     // it is purposeful that we don't clean up saved properties here, because we may initialize
103     // several React instances over a single app lifetime and we want them all to have the same
104     // initial properties
105   }
106 
107   // MARK: Internals
108 
109   private func getWindow() -> UIWindow {
110     var window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
111     if (window == nil) {
112       window = UIApplication.shared.delegate?.window ?? nil
113     }
114     if (window == nil) {
115       fatalError("Cannot find the current window.")
116     }
117     return window!
118   }
119 }
120