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   @objc
27   public func constantsToExport() -> [AnyHashable: Any] {
28 #if targetEnvironment(simulator)
29     let doesDeviceSupportKeyCommands = true
30 #else
31     let doesDeviceSupportKeyCommands = false
32 #endif
33     return [
34       "doesDeviceSupportKeyCommands": doesDeviceSupportKeyCommands,
35     ]
36   }
37 
38   @objc
39   func fetchDataSourceAsync(_ dataSourceId: String?, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
40     guard let dataSourceId = dataSourceId else {
41       return reject("ERR_DEVMENU_DATA_SOURCE_FAILED", "DataSource ID not provided.", nil)
42     }
43 
44     for dataSource in manager.devMenuDataSources {
45       if dataSource.id == dataSourceId {
46         dataSource.fetchData { data in
47           resolve(data.map { $0.serialize() })
48         }
49         return
50       }
51     }
52 
53     return reject("ERR_DEVMENU_DATA_SOURCE_FAILED", "DataSource \(dataSourceId) not founded.", nil)
54   }
55 
56   @objc
57   func dispatchCallableAsync(_ callableId: String?, args: [String: Any]?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
58     guard let callableId = callableId else {
59       return reject("ERR_DEVMENU_ACTION_FAILED", "Callable ID not provided.", nil)
60     }
61     manager.dispatchCallable(withId: callableId, args: args)
62     resolve(nil)
63   }
64 
65   @objc
66   func hideMenu() {
67     manager.hideMenu()
68   }
69 
70   @objc
71   func setOnboardingFinished(_ finished: Bool) {
72     DevMenuPreferences.isOnboardingFinished = finished
73   }
74 
75   @objc
76   func openDevMenuFromReactNative() {
77     guard let rctDevMenu = manager.currentBridge?.devMenu else {
78       return
79     }
80 
81     DispatchQueue.main.async {
82       rctDevMenu.show()
83     }
84   }
85 
86   @objc
87   func onScreenChangeAsync(_ currentScreen: String?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
88     manager.setCurrentScreen(currentScreen)
89     resolve(nil)
90   }
91 }
92