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 bridgeDelegateHandler: DevClientAppDelegate?
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 
createBridgenull36   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.bridgeDelegateHandler = EXRCTAppDelegateInterceptor(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     return EXDevLauncherDeferredRCTBridge(delegate: self.bridgeDelegateHandler, launchOptions: launchOptions)
53   }
54 
createRootViewnull55   public override func createRootView(reactDelegate: ExpoReactDelegate, bridge: RCTBridge, moduleName: String, initialProperties: [AnyHashable : Any]?) -> RCTRootView? {
56     if !ExpoDevLauncherReactDelegateHandler.shouldEnableAutoSetup {
57       return nil
58     }
59 
60     self.rootViewModuleName = moduleName
61     self.rootViewInitialProperties = initialProperties
62     self.deferredRootView = EXDevLauncherDeferredRCTRootView(bridge: bridge, moduleName: moduleName, initialProperties: initialProperties)
63     return self.deferredRootView
64   }
65 
66   // MARK: RCTBridgeDelegate implementations
67 
68   // swiftlint:disable implicitly_unwrapped_optional
sourceURLnull69   public func sourceURL(for bridge: RCTBridge!) -> URL! {
70     return EXDevLauncherController.sharedInstance().sourceUrl()
71   }
72   // swiftlint:enable implicitly_unwrapped_optional
73 
74   // MARK: EXDevelopmentClientControllerDelegate implementations
75 
devLauncherControllernull76   public func devLauncherController(_ developmentClientController: EXDevLauncherController, didStartWithSuccess success: Bool) {
77     guard let bridgeDelegateHandler = self.bridgeDelegateHandler else {
78       fatalError("bridgeDelegateHandler is not initialized")
79     }
80     let bridge = bridgeDelegateHandler.createBridgeAndSetAdapter(launchOptions: developmentClientController.getLaunchOptions())
81     developmentClientController.appBridge = bridge
82 
83     guard let rootView = bridgeDelegateHandler.createRootView(
84       with: bridge,
85       // swiftlint:disable:next force_unwrapping
86       moduleName: self.rootViewModuleName!,
87       initProps: self.rootViewInitialProperties
88     ) else {
89       return
90     }
91     rootView.backgroundColor = self.deferredRootView?.backgroundColor ?? UIColor.white
92     let window = getWindow()
93 
94     // NOTE: this order of assignment seems to actually have an effect on behaviour
95     // direct assignment of window.rootViewController.view = rootView does not work
96     guard let rootViewController = self.reactDelegate?.createRootViewController() else {
97       fatalError("Invalid rootViewController returned from ExpoReactDelegate")
98     }
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 
getWindownull110   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     guard let window = window else {
116       fatalError("Cannot find the current window.")
117     }
118     return window
119   }
120 }
121