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 loadFontsAsync(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 69 manager.loadFonts() 70 resolve(nil) 71 } 72 73 @objc 74 func hideMenu() { 75 manager.hideMenu() 76 } 77 78 @objc 79 func closeMenu() { 80 manager.closeMenu() 81 } 82 83 @objc 84 func setOnboardingFinished(_ finished: Bool) { 85 DevMenuPreferences.isOnboardingFinished = finished 86 } 87 88 @objc 89 func openDevMenuFromReactNative() { 90 guard let rctDevMenu = manager.currentBridge?.devMenu else { 91 return 92 } 93 94 DispatchQueue.main.async { 95 rctDevMenu.show() 96 } 97 } 98 99 @objc 100 func onScreenChangeAsync(_ currentScreen: String?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 101 manager.setCurrentScreen(currentScreen) 102 resolve(nil) 103 } 104 105 @objc 106 func fireCallback(_ name: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 107 guard let callback = manager.registeredCallbacks.first(where: { $0.name == name }) else { 108 return reject("ERR_DEVMENU_ACTION_FAILED", "\(name) is not a registered callback", nil) 109 } 110 111 manager.sendEventToDelegateBridge("registeredCallbackFired", data: name) 112 if callback.shouldCollapse { 113 closeMenu() 114 } 115 return resolve(nil) 116 } 117 } 118