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 deferredRootView: EXDevLauncherDeferredRCTRootView?
15   private var rootViewModuleName: String?
16   private var rootViewInitialProperties: [AnyHashable : Any]?
17   static var shouldEnableAutoSetup: Bool = {
18     // if someone else has set this explicitly, use that value
19     if !enableAutoSetup {
20       return false
21     }
22 
23     if !EXAppDefines.APP_DEBUG {
24       return false
25     }
26 
27     // Backwards compatibility -- if the main AppDelegate has already set up expo-dev-launcher,
28     // we just skip in this case.
29     if EXDevLauncherController.sharedInstance().isStarted {
30       return false
31     }
32 
33     return true
34   }()
35 
36   public override func createBridge(reactDelegate: ExpoReactDelegate, bridgeDelegate: RCTBridgeDelegate, launchOptions: [AnyHashable : Any]?) -> RCTBridge? {
37     if !ExpoDevLauncherReactDelegateHandler.shouldEnableAutoSetup {
38       return nil
39     }
40 
41     // DevLauncherController will handle dev menu configuration, so dev menu auto-setup is not needed
42     ExpoDevMenuReactDelegateHandler.enableAutoSetup = false
43 
44     self.reactDelegate = reactDelegate
45     self.bridgeDelegate = EXRCTBridgeDelegateInterceptor(bridgeDelegate: bridgeDelegate, interceptor: self)
46 
47     EXDevLauncherController.sharedInstance().autoSetupPrepare(self, launchOptions: launchOptions)
48     if let sharedController = UpdatesControllerRegistry.sharedInstance.controller {
49       // for some reason the swift compiler and bridge are having issues here
50       EXDevLauncherController.sharedInstance().updatesInterface = sharedController
51     }
52     // swiftlint:disable force_unwrapping
53     return EXDevLauncherDeferredRCTBridge(delegate: self.bridgeDelegate!, launchOptions: launchOptions)
54     // swiftlint:enable force_unwrapping
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   // swiftlint:disable implicitly_unwrapped_optional
71   public func sourceURL(for bridge: RCTBridge!) -> URL! {
72     return EXDevLauncherController.sharedInstance().sourceUrl()
73   }
74   // swiftlint:enable implicitly_unwrapped_optional
75 
76   // MARK: EXDevelopmentClientControllerDelegate implementations
77 
78   public func devLauncherController(_ developmentClientController: EXDevLauncherController, didStartWithSuccess success: Bool) {
79     let bridge = RCTBridge(delegate: self.bridgeDelegate, launchOptions: developmentClientController.getLaunchOptions())
80     developmentClientController.appBridge = bridge
81 
82     // swiftlint:disable force_unwrapping
83     let rootView = RCTRootView(bridge: bridge!, moduleName: self.rootViewModuleName!, initialProperties: self.rootViewInitialProperties)
84     // swiftlint:enable force_unwrapping
85     rootView.backgroundColor = self.deferredRootView?.backgroundColor ?? UIColor.white
86     let window = getWindow()
87 
88     // NOTE: this order of assignment seems to actually have an effect on behaviour
89     // direct assignment of window.rootViewController.view = rootView does not work
90     guard let rootViewController = self.reactDelegate?.createRootViewController() else {
91       fatalError("Invalid rootViewController returned from ExpoReactDelegate")
92     }
93     rootViewController.view = rootView
94     window.rootViewController = rootViewController
95     window.makeKeyAndVisible()
96 
97     // it is purposeful that we don't clean up saved properties here, because we may initialize
98     // several React instances over a single app lifetime and we want them all to have the same
99     // initial properties
100   }
101 
102   // MARK: Internals
103 
104   private func getWindow() -> UIWindow {
105     var window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
106     if (window == nil) {
107       window = UIApplication.shared.delegate?.window ?? nil
108     }
109     guard let window = window else {
110       fatalError("Cannot find the current window.")
111     }
112     return window
113   }
114 }
115