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