1 // Copyright 2015-present 650 Industries. All rights reserved.
2 import SafariServices
3 
4 @objc(DevMenuInternalModule)
5 public class DevMenuInternalModule: NSObject, RCTBridgeModule {
6   public static func moduleName() -> String! {
7     return "ExpoDevMenuInternal"
8   }
9 
10   // Module DevMenuInternalModule requires main queue setup since it overrides `constantsToExport`.
11   public static func requiresMainQueueSetup() -> Bool {
12     return true
13   }
14 
15   let manager: DevMenuManager
16 
17   public override init() {
18     self.manager = DevMenuManager.shared
19   }
20 
21   init(manager: DevMenuManager) {
22     self.manager = manager
23   }
24 
25   // MARK: JavaScript API
26 
27   @objc
28   public func constantsToExport() -> [AnyHashable: Any] {
29 #if targetEnvironment(simulator)
30     let doesDeviceSupportKeyCommands = true
31 #else
32     let doesDeviceSupportKeyCommands = false
33 #endif
34     return ["doesDeviceSupportKeyCommands": doesDeviceSupportKeyCommands]
35   }
36 
37   @objc
38   func fetchDataSourceAsync(_ dataSourceId: String?, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
39     guard let dataSourceId = dataSourceId else {
40       return reject("ERR_DEVMENU_DATA_SOURCE_FAILED", "DataSource ID not provided.", nil)
41     }
42 
43     for dataSource in manager.devMenuDataSources {
44       if dataSource.id == dataSourceId {
45         dataSource.fetchData { data in
46           resolve(data.map { $0.serialize() })
47         }
48         return
49       }
50     }
51 
52     return reject("ERR_DEVMENU_DATA_SOURCE_FAILED", "DataSource \(dataSourceId) not founded.", nil)
53   }
54 
55   @objc
56   func dispatchCallableAsync(_ callableId: String?, args: [String: Any]?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
57     guard let callableId = callableId else {
58       return reject("ERR_DEVMENU_ACTION_FAILED", "Callable ID not provided.", nil)
59     }
60     manager.dispatchCallable(withId: callableId, args: args)
61     resolve(nil)
62   }
63 
64   @objc
65   func hideMenu() {
66     manager.hideMenu()
67   }
68 
69   @objc
70   func setOnboardingFinished(_ finished: Bool) {
71     DevMenuPreferences.isOnboardingFinished = finished
72   }
73 
74   @objc
75   func openDevMenuFromReactNative() {
76     guard let rctDevMenu = manager.currentBridge?.devMenu else {
77       return
78     }
79 
80     DispatchQueue.main.async {
81       rctDevMenu.show()
82     }
83   }
84 
85   @objc
86   func onScreenChangeAsync(_ currentScreen: String?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
87     manager.setCurrentScreen(currentScreen)
88     resolve(nil)
89   }
90 }
91