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   }
21 
22   init(manager: DevMenuManager, bridge: RCTBridge) {
23     self.manager = manager
24 
25     super.init()
26 
27     self.bridge = bridge
28   }
29 
30   /**
31    Sends an event to JS triggering the animation that collapses the dev menu.
32    */
33   public func sendCloseEvent() {
34     bridge?.enqueueJSCall("RCTDeviceEventEmitter.emit", args: [DevMenuAppInstance.CloseEventName])
35   }
36 
37   public func sendOpenEvent() {
38     bridge?.enqueueJSCall("RCTDeviceEventEmitter.emit", args: [DevMenuAppInstance.OpenEventName])
39   }
40 
41   // MARK: RCTBridgeDelegate
42 
43   func sourceURL(for bridge: RCTBridge!) -> URL! {
44     #if DEBUG
45     if let packagerHost = jsPackagerHost() {
46       return RCTBundleURLProvider.jsBundleURL(forBundleRoot: "index", packagerHost: packagerHost, enableDev: true, enableMinification: false)
47     }
48     #endif
49     return jsSourceUrl()
50   }
51 
52   func extraModules(for bridge: RCTBridge!) -> [RCTBridgeModule]! {
53     var modules: [RCTBridgeModule] = [DevMenuInternalModule(manager: manager)]
54     modules.append(contentsOf: DevMenuVendoredModulesUtils.vendoredModules(bridge, addReanimated2: true))
55     modules.append(DevMenuLoadingView.init())
56     modules.append(DevMenuRCTDevSettings.init())
57     return modules
58   }
59 
60   func bridge(_ bridge: RCTBridge!, didNotFindModule moduleName: String!) -> Bool {
61     return moduleName == "DevMenu"
62   }
63 
64   // MARK: private
65 
66   private func jsSourceUrl() -> URL? {
67     return DevMenuUtils.resourcesBundle()?.url(forResource: "EXDevMenuApp.ios", withExtension: "js")
68   }
69 
70   private func jsPackagerHost() -> String? {
71     // Return `nil` if resource doesn't exist in the bundle.
72     guard let packagerHostPath = DevMenuUtils.resourcesBundle()?.path(forResource: "dev-menu-packager-host", ofType: nil) else {
73       return nil
74     }
75     // Return `nil` if the content is not a valid URL.
76     guard let content = try? String(contentsOfFile: packagerHostPath, encoding: String.Encoding.utf8).trimmingCharacters(in: CharacterSet.newlines),
77       let url = URL(string: content) else {
78       return nil
79     }
80     return url.absoluteString
81   }
82 }
83