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