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