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 let sharedController = UpdatesControllerRegistry.sharedInstance.controller {
51       // for some reason the swift compiler and bridge are having issues here
52       EXDevLauncherController.sharedInstance().updatesInterface = sharedController
53     }
54     return EXDevLauncherDeferredRCTBridge(delegate: self.bridgeDelegate!, launchOptions: self.launchOptions)
55   }
56 
57   public override func createRootView(reactDelegate: ExpoReactDelegate, bridge: RCTBridge, moduleName: String, initialProperties: [AnyHashable : Any]?) -> RCTRootView? {
58     if !ExpoDevLauncherReactDelegateHandler.shouldEnableAutoSetup {
59       return nil
60     }
61 
62     self.rootViewModuleName = moduleName
63     self.rootViewInitialProperties = initialProperties
64     self.deferredRootView = EXDevLauncherDeferredRCTRootView(bridge: bridge, moduleName: moduleName, initialProperties: initialProperties)
65     return self.deferredRootView
66   }
67 
68   // MARK: RCTBridgeDelegate implementations
69 
70   public func sourceURL(for bridge: RCTBridge!) -> URL! {
71     return EXDevLauncherController.sharedInstance().sourceUrl()
72   }
73 
74   // MARK: EXDevelopmentClientControllerDelegate implementations
75 
76   public func devLauncherController(_ developmentClientController: EXDevLauncherController, didStartWithSuccess success: Bool) {
77     var launchOptions: [AnyHashable: Any] = [:]
78 
79     if let initialLaunchOptions = self.launchOptions {
80       for (key, value) in initialLaunchOptions {
81         launchOptions[key] = value
82       }
83     }
84 
85     for (key, value) in developmentClientController.getLaunchOptions() {
86       launchOptions[key] = value
87     }
88 
89     let bridge = RCTBridge(delegate: self.bridgeDelegate, launchOptions: launchOptions)
90     developmentClientController.appBridge = bridge
91 
92     let rootView = RCTRootView(bridge: bridge!, moduleName: self.rootViewModuleName!, initialProperties: self.rootViewInitialProperties)
93     rootView.backgroundColor = self.deferredRootView?.backgroundColor ?? UIColor.white
94     let window = getWindow()
95 
96     // NOTE: this order of assignment seems to actually have an effect on behaviour
97     // direct assignment of window.rootViewController.view = rootView does not work
98     let rootViewController = self.reactDelegate?.createRootViewController()
99     rootViewController!.view = rootView
100     window.rootViewController = rootViewController
101     window.makeKeyAndVisible()
102 
103     // it is purposeful that we don't clean up saved properties here, because we may initialize
104     // several React instances over a single app lifetime and we want them all to have the same
105     // initial properties
106   }
107 
108   // MARK: Internals
109 
110   private func getWindow() -> UIWindow {
111     var window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
112     if (window == nil) {
113       window = UIApplication.shared.delegate?.window ?? nil
114     }
115     if (window == nil) {
116       fatalError("Cannot find the current window.")
117     }
118     return window!
119   }
120 }
121