1 // Copyright 2015-present 650 Industries. All rights reserved. 2 3 @objc 4 class DevMenuAppInstance: DevMenuBaseAppInstance, RCTBridgeDelegate { 5 static private var CloseEventName = "closeDevMenu" 6 static private var OpenEventName = "openDevMenu" 7 8 private let manager: DevMenuManager 9 10 var bridge: RCTBridge? 11 12 init(manager: DevMenuManager) { 13 self.manager = manager 14 15 super.init() 16 17 self.bridge = DevMenuRCTBridge.init(delegate: self, launchOptions: nil) 18 fixChromeDevTools() 19 } 20 21 init(manager: DevMenuManager, bridge: RCTBridge) { 22 self.manager = manager 23 24 super.init() 25 26 self.bridge = bridge 27 fixChromeDevTools() 28 } 29 30 private func fixChromeDevTools() { 31 // Hermes inspector will use latest executed script for Chrome DevTools Protocol. 32 // It will be EXDevMenuApp.ios.js in our case. 33 // To let Hermes aware target bundle, we try to reload here as a workaround solution. 34 // See https://github.com/facebook/react-native/blob/ec614c16b331bf3f793fda5780fa273d181a8492/ReactCommon/hermes/inspector/Inspector.cpp#L291 35 if let appBridge = manager.currentBridge { 36 appBridge.requestReload() 37 } 38 } 39 40 /** 41 Sends an event to JS triggering the animation that collapses the dev menu. 42 */ 43 public func sendCloseEvent() { 44 bridge?.enqueueJSCall("RCTDeviceEventEmitter.emit", args: [DevMenuAppInstance.CloseEventName]) 45 } 46 47 public func sendOpenEvent() { 48 bridge?.enqueueJSCall("RCTDeviceEventEmitter.emit", args: [DevMenuAppInstance.OpenEventName]) 49 } 50 51 // MARK: RCTBridgeDelegate 52 53 func sourceURL(for bridge: RCTBridge!) -> URL! { 54 #if DEBUG 55 if let packagerHost = jsPackagerHost() { 56 return RCTBundleURLProvider.jsBundleURL(forBundleRoot: "index", packagerHost: packagerHost, enableDev: true, enableMinification: false) 57 } 58 #endif 59 return jsSourceUrl() 60 } 61 62 func extraModules(for bridge: RCTBridge!) -> [RCTBridgeModule]! { 63 var modules: [RCTBridgeModule] = [DevMenuInternalModule(manager: manager)] 64 modules.append(contentsOf: DevMenuVendoredModulesUtils.vendoredModules()) 65 modules.append(DevMenuLoadingView.init()) 66 return modules 67 } 68 69 func bridge(_ bridge: RCTBridge!, didNotFindModule moduleName: String!) -> Bool { 70 return moduleName == "DevMenu" 71 } 72 73 // MARK: private 74 75 private func jsSourceUrl() -> URL? { 76 return DevMenuUtils.resourcesBundle()?.url(forResource: "EXDevMenuApp.ios", withExtension: "js") 77 } 78 79 private func jsPackagerHost() -> String? { 80 // Return `nil` if resource doesn't exist in the bundle. 81 guard let packagerHostPath = DevMenuUtils.resourcesBundle()?.path(forResource: "dev-menu-packager-host", ofType: nil) else { 82 return nil 83 } 84 // Return `nil` if the content is not a valid URL. 85 guard let content = try? String(contentsOfFile: packagerHostPath, encoding: String.Encoding.utf8).trimmingCharacters(in: CharacterSet.newlines), 86 let url = URL(string: content) else { 87 return nil 88 } 89 return url.absoluteString 90 } 91 } 92