1 // Copyright 2015-present 650 Industries. All rights reserved. 2 import SafariServices 3 4 @objc(DevMenuInternalModule) 5 public class DevMenuInternalModule: NSObject, RCTBridgeModule { 6 @objc 7 var redirectResolve: RCTPromiseResolveBlock? 8 @objc 9 var redirectReject: RCTPromiseRejectBlock? 10 @objc 11 var authSession: SFAuthenticationSession? 12 13 public static func moduleName() -> String! { 14 return "ExpoDevMenuInternal" 15 } 16 17 // Module DevMenuInternalModule requires main queue setup since it overrides `constantsToExport`. 18 public static func requiresMainQueueSetup() -> Bool { 19 return true; 20 } 21 22 private static var fontsWereLoaded = false; 23 private static let sessionKey = "expo-dev-menu.session" 24 private static let userLoginEvent = "expo.dev-menu.user-login" 25 private static let userLogoutEvent = "expo.dev-menu.user-logout" 26 27 let manager: DevMenuManager 28 29 init(manager: DevMenuManager) { 30 self.manager = manager 31 } 32 33 // MARK: JavaScript API 34 35 @objc 36 func constantsToExport() -> [String : Any] { 37 #if targetEnvironment(simulator) 38 let doesDeviceSupportKeyCommands = true 39 #else 40 let doesDeviceSupportKeyCommands = false 41 #endif 42 return ["doesDeviceSupportKeyCommands": doesDeviceSupportKeyCommands] 43 } 44 45 @objc 46 func loadFontsAsync(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 47 if (DevMenuInternalModule.fontsWereLoaded) { 48 resolve(nil); 49 return; 50 } 51 52 let fonts = ["MaterialCommunityIcons", "Ionicons"] 53 for font in fonts { 54 guard let path = DevMenuUtils.resourcesBundle()?.path(forResource: font, ofType: "ttf") else { 55 reject("ERR_DEVMENU_CANNOT_FIND_FONT", "Font file for '\(font)' doesn't exist.", nil); 56 return; 57 } 58 guard let data = FileManager.default.contents(atPath: path) else { 59 reject("ERR_DEVMENU_CANNOT_OPEN_FONT_FILE", "Could not open '\(path)'.", nil); 60 return; 61 } 62 63 guard let provider = CGDataProvider(data: data as CFData) else { 64 reject("ERR_DEVMENU_CANNOT_CREATE_FONT_PROVIDER", "Could not create font provider for '\(font)'.", nil); 65 return; 66 } 67 guard let cgFont = CGFont(provider) else { 68 reject("ERR_DEVMENU_CANNOT_CREATE_FONT", "Could not create font for '\(font)'.", nil); 69 return; 70 } 71 72 var error: Unmanaged<CFError>? 73 if !CTFontManagerRegisterGraphicsFont(cgFont, &error) { 74 reject("ERR_DEVMENU_CANNOT_ADD_FONT", "Could not create font from loaded data for '\(font)'. '\(error.debugDescription)'.", nil) 75 return 76 } 77 } 78 79 DevMenuInternalModule.fontsWereLoaded = true 80 resolve(nil) 81 } 82 83 @objc 84 func dispatchActionAsync(_ actionId: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 85 if actionId == nil { 86 return reject("ERR_DEVMENU_ACTION_FAILED", "Action ID not provided.", nil) 87 } 88 manager.dispatchAction(withId: actionId) 89 resolve(nil) 90 } 91 92 @objc 93 func hideMenu() { 94 manager.hideMenu() 95 } 96 97 @objc 98 func setOnboardingFinished(_ finished: Bool) { 99 DevMenuSettings.isOnboardingFinished = finished 100 } 101 102 @objc 103 func getSettingsAsync(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 104 resolve(DevMenuSettings.serialize()) 105 } 106 107 @objc 108 func setSettingsAsync(_ dict: [String: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 109 if let motionGestureEnabled = dict["motionGestureEnabled"] as? Bool { 110 DevMenuSettings.motionGestureEnabled = motionGestureEnabled 111 } 112 if let touchGestureEnabled = dict["touchGestureEnabled"] as? Bool { 113 DevMenuSettings.touchGestureEnabled = touchGestureEnabled 114 } 115 if let keyCommandsEnabled = dict["keyCommandsEnabled"] as? Bool { 116 DevMenuSettings.keyCommandsEnabled = keyCommandsEnabled 117 } 118 if let showsAtLaunch = dict["showsAtLaunch"] as? Bool { 119 DevMenuSettings.showsAtLaunch = showsAtLaunch 120 } 121 } 122 123 @objc 124 func openDevMenuFromReactNative() { 125 guard let rctDevMenu = manager.session?.bridge.devMenu else { 126 return 127 } 128 129 DispatchQueue.main.async { 130 rctDevMenu.show() 131 } 132 } 133 134 @objc 135 func onScreenChangeAsync(_ currentScreen: String?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 136 manager.setCurrentScreen(currentScreen) 137 resolve(nil) 138 } 139 140 @objc 141 func setSessionAsync(_ session: Dictionary<String, Any>?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 142 do { 143 try manager.expoSessionDelegate.setSessionAsync(session) 144 resolve(nil) 145 } catch let error { 146 reject("ERR_DEVMENU_CANNOT_SAVE_SESSION", error.localizedDescription, error); 147 } 148 } 149 150 @objc 151 func restoreSessionAsync(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { 152 resolve(manager.expoSessionDelegate.restoreSession()) 153 } 154 } 155