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