From 3e8f94cedaca357e540135c9a979a1a4e552ad8e Mon Sep 17 00:00:00 2001 From: Lorenzo Blasa Date: Wed, 30 Aug 2023 04:24:05 -0700 Subject: [PATCH] iOS get devices/targets/simulators cleanup Summary: ^ Reviewed By: passy Differential Revision: D48781211 fbshipit-source-id: 71133c07d15ca6a380d85e582d55cbdb192b5a19 --- desktop/flipper-common/src/PluginDetails.tsx | 13 +- desktop/flipper-common/src/server-types.tsx | 20 ++- .../src/FlipperServerImpl.tsx | 2 +- .../src/devices/ios/IOSBridge.tsx | 88 ++-------- .../devices/ios/__tests__/iOSDevice.node.tsx | 8 +- .../devices/ios/iOSCertificateProvider.tsx | 1 + .../src/devices/ios/iOSContainerUtility.tsx | 163 ++++++++++-------- .../src/devices/ios/iOSDeviceManager.tsx | 43 +++-- .../appinspect/LaunchEmulator.tsx | 12 +- 9 files changed, 170 insertions(+), 180 deletions(-) diff --git a/desktop/flipper-common/src/PluginDetails.tsx b/desktop/flipper-common/src/PluginDetails.tsx index 41443c475..c7a400e9c 100644 --- a/desktop/flipper-common/src/PluginDetails.tsx +++ b/desktop/flipper-common/src/PluginDetails.tsx @@ -7,6 +7,8 @@ * @format */ +import {DeviceType, OS} from './server-types'; + export interface PluginDetails { name: string; specVersion: number; @@ -57,17 +59,6 @@ export interface SupportedApp { readonly type?: DeviceType; } -export type OS = - | 'iOS' - | 'Android' - | 'Metro' - | 'Windows' - | 'MacOS' - | 'Browser' - | 'Linux'; - -export type DeviceType = 'emulator' | 'physical' | 'dummy'; - export type PluginType = 'client' | 'device'; export type DeviceSpec = 'KaiOS'; diff --git a/desktop/flipper-common/src/server-types.tsx b/desktop/flipper-common/src/server-types.tsx index 55d7f821a..b8fd38c89 100644 --- a/desktop/flipper-common/src/server-types.tsx +++ b/desktop/flipper-common/src/server-types.tsx @@ -10,11 +10,9 @@ import {FlipperDoctor} from './doctor'; import { DeviceSpec, - DeviceType, DownloadablePluginDetails, InstalledPluginDetails, MarketplacePluginDetails, - OS as PluginOS, UpdatablePluginDetails, } from './PluginDetails'; import {ServerAddOnStartDetails} from './ServerAddOn'; @@ -39,7 +37,7 @@ export type FlipperServerState = | 'error' | 'closed'; -export type DeviceOS = PluginOS; +export type DeviceOS = OS; export type DeviceDescription = { readonly os: DeviceOS; @@ -172,12 +170,22 @@ export type FlipperServerEvents = { 'server-log': LoggerInfo; }; -export type IOSDeviceParams = { +export type OS = + | 'iOS' + | 'Android' + | 'Metro' + | 'Windows' + | 'MacOS' + | 'Browser' + | 'Linux'; + +export type DeviceType = 'physical' | 'emulator' | 'dummy'; + +export type DeviceTarget = { udid: string; type: DeviceType; name: string; osVersion?: string; - deviceTypeIdentifier?: string; state?: string; }; @@ -298,7 +306,7 @@ export type FlipperServerCommands = { 'android-get-emulators': () => Promise; 'android-launch-emulator': (name: string, coldboot: boolean) => Promise; 'android-adb-kill': () => Promise; - 'ios-get-simulators': (bootedOnly: boolean) => Promise; + 'ios-get-simulators': (bootedOnly: boolean) => Promise; 'ios-launch-simulator': (udid: string) => Promise; 'ios-idb-kill': () => Promise; 'persist-settings': (settings: Settings) => Promise; diff --git a/desktop/flipper-server-core/src/FlipperServerImpl.tsx b/desktop/flipper-server-core/src/FlipperServerImpl.tsx index 23f41af14..c3f7615c6 100644 --- a/desktop/flipper-server-core/src/FlipperServerImpl.tsx +++ b/desktop/flipper-server-core/src/FlipperServerImpl.tsx @@ -496,7 +496,7 @@ export class FlipperServerImpl implements FlipperServer { }, 'ios-launch-simulator': async (udid) => { assertNotNull(this.ios); - return this.ios.simctlBridge.launchSimulator(udid); + return this.ios.launchSimulator(udid); }, 'ios-idb-kill': async () => { assertNotNull(this.ios); diff --git a/desktop/flipper-server-core/src/devices/ios/IOSBridge.tsx b/desktop/flipper-server-core/src/devices/ios/IOSBridge.tsx index 4bb7c98bc..51f5230f7 100644 --- a/desktop/flipper-server-core/src/devices/ios/IOSBridge.tsx +++ b/desktop/flipper-server-core/src/devices/ios/IOSBridge.tsx @@ -8,10 +8,14 @@ */ import fs from 'fs-extra'; -import iosUtil from './iOSContainerUtility'; +import iosUtil, { + getDeviceSetPath, + isIdbAvailable, + queryTargetsWithXcode, +} from './iOSContainerUtility'; import child_process from 'child_process'; -import type {IOSDeviceParams} from 'flipper-common'; +import type {DeviceTarget} from 'flipper-common'; import {DeviceType, uuid} from 'flipper-common'; import path from 'path'; import {ChildProcessPromise, exec, execFile} from 'promisify-child-process'; @@ -42,16 +46,6 @@ interface IOSInstalledAppDescriptor { debuggableStatus: boolean; } -function getOSVersionFromXCRunOutput(s: string): string | undefined { - // E.g. 'com.apple.CoreSimulator.SimRuntime.iOS-16-1' - const match = s.match( - /com\.apple\.CoreSimulator\.SimRuntime\.iOS-(\d+)-(\d+)/, - ); - if (match) { - return `${match[1]}.${match[2]}`; - } -} - export interface IOSBridge { startLogListener: ( udid: string, @@ -63,7 +57,7 @@ export interface IOSBridge { serial: string, outputFile: string, ) => child_process.ChildProcess; - getActiveDevices: (bootedOnly: boolean) => Promise>; + getActiveDevices: (bootedOnly: boolean) => Promise>; installApp: ( serial: string, ipaPath: string, @@ -77,6 +71,7 @@ export interface IOSBridge { bundleId: string, dst: string, ) => Promise; + launchSimulator(udid: string): Promise; } export class IDBBridge implements IOSBridge { @@ -84,6 +79,10 @@ export class IDBBridge implements IOSBridge { private idbPath: string, private enablePhysicalDevices: boolean, ) {} + async launchSimulator(udid: string): Promise { + await this._execIdb(`boot --udid ${udid}`); + await execFile('open', ['-a', 'simulator']); + } async getInstalledApps(serial: string): Promise { const {stdout} = await this._execIdb(`list-apps --udid ${serial}`); @@ -150,9 +149,9 @@ export class IDBBridge implements IOSBridge { await this._execIdb(`install ${ipaPath} --udid ${serial}`); } - async getActiveDevices(_bootedOnly: boolean): Promise { + async getActiveDevices(bootedOnly: boolean): Promise { return iosUtil - .targets(this.idbPath, this.enablePhysicalDevices) + .targets(this.idbPath, this.enablePhysicalDevices, bootedOnly) .catch((e) => { console.warn('Failed to get active iOS devices:', e.message); return []; @@ -285,32 +284,11 @@ export class SimctlBridge implements IOSBridge { ); } - async getActiveDevices(bootedOnly: boolean): Promise> { - return execFile('xcrun', [ - 'simctl', - ...getDeviceSetPath(), - 'list', - 'devices', - '--json', - ]) - .then(({stdout}) => JSON.parse(stdout!.toString()).devices) - .then((simulatorDevices: {[key: string]: Array}) => - Object.keys(simulatorDevices).flatMap((key: string) => - simulatorDevices[key] - .filter( - (simulator: iOSSimulatorDevice) => - (!bootedOnly || simulator.state === 'Booted') && - isSimulatorAvailable(simulator), - ) - .map((simulator: iOSSimulatorDevice) => { - return { - ...simulator, - type: 'emulator', - osVersion: getOSVersionFromXCRunOutput(key), - } as IOSDeviceParams; - }), - ), - ); + async getActiveDevices(bootedOnly: boolean): Promise> { + const devices = await queryTargetsWithXcode(); + return devices.filter( + (target) => !bootedOnly || (bootedOnly && target.state === 'booted'), + ); } async launchSimulator(udid: string): Promise { @@ -319,27 +297,6 @@ export class SimctlBridge implements IOSBridge { } } -function isSimulatorAvailable(simulator: iOSSimulatorDevice): boolean { - // For some users "availability" is set, for others it's "isAvailable" - // It's not clear which key is set, so we are checking both. - // We've also seen isAvailable return "YES" and true, depending on version. - return ( - simulator.availability === '(available)' || - simulator.isAvailable === 'YES' || - simulator.isAvailable === true - ); -} - -async function isIdbAvailable(idbPath: string): Promise { - if (!idbPath) { - return false; - } - return fs.promises - .access(idbPath, fs.constants.X_OK) - .then((_) => true) - .catch((_) => false); -} - function getLogExtraArgs(deviceType: DeviceType) { if (deviceType === 'physical') { return [ @@ -364,7 +321,6 @@ function makeTempScreenshotFilePath() { } async function unzip(filePath: string, destination: string): Promise { - // TODO: probably shouldn't involve shelling out. await exec(`unzip -qq -o ${filePath} -d ${destination}`); if (!(await fs.pathExists(path.join(destination, 'Payload')))) { throw new Error( @@ -379,12 +335,6 @@ async function readScreenshotIntoBuffer(imagePath: string): Promise { return buffer; } -export function getDeviceSetPath() { - return process.env.DEVICE_SET_PATH - ? ['--set', process.env.DEVICE_SET_PATH] - : []; -} - export async function makeIOSBridge( idbPath: string, isXcodeDetected: boolean, diff --git a/desktop/flipper-server-core/src/devices/ios/__tests__/iOSDevice.node.tsx b/desktop/flipper-server-core/src/devices/ios/__tests__/iOSDevice.node.tsx index 9311657af..6db146a50 100644 --- a/desktop/flipper-server-core/src/devices/ios/__tests__/iOSDevice.node.tsx +++ b/desktop/flipper-server-core/src/devices/ios/__tests__/iOSDevice.node.tsx @@ -14,12 +14,12 @@ import { getFlipperServerConfig, setFlipperServerConfig, } from '../../../FlipperServerConfig'; -import {IOSDeviceParams} from 'flipper-common'; +import {DeviceTarget} from 'flipper-common'; let fakeSimctlBridge: any; let fakeIDBBridge: any; let fakeFlipperServer: any; -const fakeDevices: IOSDeviceParams[] = [ +const fakeDevices: DeviceTarget[] = [ { udid: 'luke', type: 'emulator', @@ -122,7 +122,7 @@ test('test queryDevices when simctl used', async () => { fakeFlipperServer, getFlipperServerConfig().settings, ); - ios.simctlBridge = fakeSimctlBridge; + ios.ctlBridge = fakeSimctlBridge; await ios.queryDevices(fakeSimctlBridge); @@ -145,7 +145,7 @@ test('test queryDevices when idb used', async () => { fakeFlipperServer, getFlipperServerConfig().settings, ); - ios.simctlBridge = fakeSimctlBridge; + ios.ctlBridge = fakeSimctlBridge; await ios.queryDevices(fakeIDBBridge); diff --git a/desktop/flipper-server-core/src/devices/ios/iOSCertificateProvider.tsx b/desktop/flipper-server-core/src/devices/ios/iOSCertificateProvider.tsx index f3b9d8a58..5e8b01c4a 100644 --- a/desktop/flipper-server-core/src/devices/ios/iOSCertificateProvider.tsx +++ b/desktop/flipper-server-core/src/devices/ios/iOSCertificateProvider.tsx @@ -48,6 +48,7 @@ export default class iOSCertificateProvider extends CertificateProvider { const targets = await iosUtil.targets( this.idbConfig.idbPath, this.idbConfig.enablePhysicalIOS, + true, clientQuery, ); if (targets.length === 0) { diff --git a/desktop/flipper-server-core/src/devices/ios/iOSContainerUtility.tsx b/desktop/flipper-server-core/src/devices/ios/iOSContainerUtility.tsx index 56a8f559c..fb9a3a9bb 100644 --- a/desktop/flipper-server-core/src/devices/ios/iOSContainerUtility.tsx +++ b/desktop/flipper-server-core/src/devices/ios/iOSContainerUtility.tsx @@ -8,11 +8,10 @@ */ import {Mutex} from 'async-mutex'; -import {exec as unsafeExec, Output} from 'promisify-child-process'; -import {reportPlatformFailures} from 'flipper-common'; +import {exec as unsafeExec, Output, execFile} from 'promisify-child-process'; +import {DeviceTarget, DeviceType, reportPlatformFailures} from 'flipper-common'; import {promises, constants} from 'fs'; import memoize from 'lodash.memoize'; -import {notNull} from '../../utils/typeUtils'; import {promisify} from 'util'; import child_process from 'child_process'; import fs from 'fs-extra'; @@ -25,6 +24,25 @@ export type IdbConfig = { enablePhysicalIOS: boolean; }; +export type IdbTarget = { + udid: string; + type: string; + name: string; + os_version: string; + architecture: string; + state?: string; + target_type?: string | DeviceType; +}; + +export type XcodeTarget = { + state: 'Booted' | 'Shutdown' | 'Shutting Down'; + availability?: string; + isAvailable?: 'YES' | 'NO' | true | false; + name: string; + osVersion?: string; + udid: string; +}; + // Use debug to get helpful logs when idb fails const IDB_LOG_LEVEL = 'DEBUG'; const LOG_TAG = 'iOSContainerUtility'; @@ -32,30 +50,11 @@ const CMD_RECORD_THROTTLE_COUNT = 10; const mutex = new Mutex(); -type IdbTarget = { - name: string; - udid: string; - state: 'Booted' | 'Shutdown'; - type: string | DeviceType; - target_type?: string | DeviceType; - os_version: string; - architecture: string; -}; - -export type DeviceType = 'physical' | 'emulator'; - -export type DeviceTarget = { - udid: string; - type: DeviceType; - name: string; - osVersion?: string; -}; - let idbDeviceListing = 0; let idbCompanionDeviceListing = 0; let xcodeDeviceListing = 0; -async function isAvailable(idbPath: string): Promise { +export async function isIdbAvailable(idbPath: string): Promise { if (!idbPath) { return false; } @@ -74,16 +73,50 @@ async function safeExec( return await unsafeExec(command).finally(release); } -async function queryTargetsWithXcode( - context: any, +export function getDeviceSetPath() { + return process.env.DEVICE_SET_PATH + ? ['--set', process.env.DEVICE_SET_PATH] + : []; +} + +export function isSimulatorAvailable(simulator: XcodeTarget): boolean { + // For some users "availability" is set, for others it's "isAvailable" + // It's not clear which key is set, so we are checking both. + // We've also seen isAvailable return "YES" and true, depending on version. + return ( + simulator.availability === '(available)' || + simulator.isAvailable === 'YES' || + simulator.isAvailable === true + ); +} + +function getOSVersionFromXCRunOutput(s: string): string | undefined { + // E.g. 'com.apple.CoreSimulator.SimRuntime.iOS-16-1' + const match = s.match( + /com\.apple\.CoreSimulator\.SimRuntime\.iOS-(\d+)-(\d+)/, + ); + if (match) { + return `${match[1]}.${match[2]}`; + } +} + +export async function queryTargetsWithXcode( + context?: any, ): Promise> { - const cmd = 'xcrun xctrace list devices'; + const cmd = 'xcrun simctl list devices --json'; const description = 'Query available devices with Xcode'; const troubleshoot = `Xcode command line tools are not installed. Run 'xcode-select --install' from terminal.`; try { - const {stdout} = await safeExec(cmd); + const {stdout} = await execFile('xcrun', [ + 'simctl', + ...getDeviceSetPath(), + 'list', + 'devices', + '--json', + ]); + if (!stdout) { recorder.event('cmd', { cmd, @@ -105,17 +138,22 @@ async function queryTargetsWithXcode( }); } - return stdout - .toString() - .split('\n') - .map((line) => line.trim()) - .filter(Boolean) - .map((line) => /(.+) \([^(]+\) \[(.*)\]( \(Simulator\))?/.exec(line)) - .filter(notNull) - .filter(([_match, _name, _udid, isSim]) => !isSim) - .map(([_match, name, udid]) => { - return {udid, type: 'physical', name}; - }); + const devices = JSON.parse(stdout.toString()).devices as { + [key: string]: Array; + }; + + return Object.keys(devices).flatMap((key: string) => + devices[key] + .filter((simulator: XcodeTarget) => isSimulatorAvailable(simulator)) + .map((simulator: XcodeTarget) => { + return { + ...simulator, + type: 'emulator', + state: simulator.state.toLowerCase(), + osVersion: getOSVersionFromXCRunOutput(key), + } as DeviceTarget; + }), + ); } catch (e) { recorder.event('cmd', { cmd, @@ -176,7 +214,7 @@ async function queryTargetsWithIdb( } } -async function queryTargetsWithIdbCompanion( +async function _queryTargetsWithIdbCompanion( idbCompanionPath: string, isPhysicalDeviceEnabled: boolean, context: any, @@ -187,7 +225,7 @@ async function queryTargetsWithIdbCompanion( const troubleshoot = `Unable to locate idb_companion in '${idbCompanionPath}'. Try running sudo yum install -y fb-idb`; - if (await isAvailable(idbCompanionPath)) { + if (await isIdbAvailable(idbCompanionPath)) { try { const {stdout} = await safeExec(cmd); if (!stdout) { @@ -244,9 +282,6 @@ async function queryTargetsWithIdbCompanion( function parseIdbTarget(line: string): DeviceTarget | undefined { const parsed: IdbTarget = JSON.parse(line); - if (parsed.state.toLocaleLowerCase() !== 'booted') { - return; - } return { udid: parsed.udid, type: @@ -255,6 +290,7 @@ function parseIdbTarget(line: string): DeviceTarget | undefined { : ('physical' as DeviceType), name: parsed.name, osVersion: parsed.os_version, + state: parsed.state?.toLocaleLowerCase(), }; } @@ -331,45 +367,36 @@ async function idbDescribeTarget( async function targets( idbPath: string, isPhysicalDeviceEnabled: boolean, + bootedOnly: boolean = false, context?: any, ): Promise> { if (process.platform !== 'darwin') { return []; } + const bootedFilter = (targets: DeviceTarget[] | undefined) => { + return targets + ? targets.filter( + (target) => !bootedOnly || (bootedOnly && target.state === 'booted'), + ) + : []; + }; + // If companion is started by some external process and its path // is provided to Flipper via IDB_COMPANION environment variable, // use that instead and do not query other devices. // See stack of D36315576 for details if (process.env.IDB_COMPANION) { const target = await idbDescribeTarget(idbPath, context); - return target ? [target] : []; + return bootedFilter(target ? [target] : []); } - const isXcodeInstalled = await isXcodeDetected(); - if (!isXcodeInstalled) { - if (!isPhysicalDeviceEnabled) { - recorder.rawError( - 'You are trying to connect a physical device. Please enable the toggle "Enable physical iOS device" from the setting screen.', - ); - } - const idbCompanionPath = path.dirname(idbPath) + '/idb_companion'; - return queryTargetsWithIdbCompanion( - idbCompanionPath, - isPhysicalDeviceEnabled, - context, - ); - } - - // Not all users have idb installed because you can still use - // Flipper with Simulators without it. - // But idb is MUCH more CPU efficient than xcrun, so - // when installed, use it. This still holds true - // with the move from instruments to xcrun. - if (await memoize(isAvailable)(idbPath)) { - return await queryTargetsWithIdb(idbPath, context); + if (await memoize(isIdbAvailable)(idbPath)) { + const targets = await queryTargetsWithIdb(idbPath, context); + return bootedFilter(targets); } else { - return queryTargetsWithXcode(context); + const targets = await queryTargetsWithXcode(context); + return bootedFilter(targets); } } @@ -459,7 +486,7 @@ async function pull( } async function checkIdbIsInstalled(idbPath: string): Promise { - const isInstalled = await isAvailable(idbPath); + const isInstalled = await isIdbAvailable(idbPath); if (!isInstalled) { throw new Error( `idb is required to use iOS devices. Install it with instructions diff --git a/desktop/flipper-server-core/src/devices/ios/iOSDeviceManager.tsx b/desktop/flipper-server-core/src/devices/ios/iOSDeviceManager.tsx index 68c9cc840..d9458649b 100644 --- a/desktop/flipper-server-core/src/devices/ios/iOSDeviceManager.tsx +++ b/desktop/flipper-server-core/src/devices/ios/iOSDeviceManager.tsx @@ -8,7 +8,7 @@ */ import {ChildProcess} from 'child_process'; -import type {IOSDeviceParams} from 'flipper-common'; +import type {DeviceTarget} from 'flipper-common'; import path from 'path'; import childProcess from 'child_process'; import {exec} from 'promisify-child-process'; @@ -18,7 +18,6 @@ import { ERR_NO_IDB_OR_XCODE_AVAILABLE, IOSBridge, makeIOSBridge, - SimctlBridge, } from './IOSBridge'; import {FlipperServerImpl} from '../../FlipperServerImpl'; import {getFlipperServerConfig} from '../../FlipperServerConfig'; @@ -34,7 +33,7 @@ export class IOSDeviceManager { 'MacOS', 'PortForwardingMacApp', ); - simctlBridge: SimctlBridge = new SimctlBridge(); + ctlBridge: IOSBridge | undefined; readonly certificateProvider: iOSCertificateProvider; @@ -102,7 +101,7 @@ export class IOSDeviceManager { return this.processDevices(bridge, devices); } - private processDevices(bridge: IOSBridge, activeDevices: IOSDeviceParams[]) { + private processDevices(bridge: IOSBridge, activeDevices: DeviceTarget[]) { const currentDeviceIDs = new Set( this.flipperServer .getDevices() @@ -134,9 +133,23 @@ export class IOSDeviceManager { }); } + async getBridge(): Promise { + if (this.ctlBridge !== undefined) { + return this.ctlBridge; + } + + const isDetected = await iosUtil.isXcodeDetected(); + this.ctlBridge = await makeIOSBridge( + this.idbConfig.idbPath, + isDetected, + this.idbConfig.enablePhysicalIOS, + ); + + return this.ctlBridge; + } + public async watchIOSDevices() { try { - const isDetected = await iosUtil.isXcodeDetected(); if (this.idbConfig.enablePhysicalIOS) { this.startDevicePortForwarders(); } @@ -144,11 +157,7 @@ export class IOSDeviceManager { // Check for version mismatch now for immediate error handling. await this.checkXcodeVersionMismatch(); // Awaiting the promise here to trigger immediate error handling. - const bridge = await makeIOSBridge( - this.idbConfig.idbPath, - isDetected, - this.idbConfig.enablePhysicalIOS, - ); + const bridge = await this.getBridge(); await this.queryDevicesForever(bridge); } catch (err) { // This case is expected if both Xcode and idb are missing. @@ -166,9 +175,10 @@ export class IOSDeviceManager { } } - async getSimulators(bootedOnly: boolean): Promise> { + async getSimulators(bootedOnly: boolean): Promise> { try { - return await this.simctlBridge.getActiveDevices(bootedOnly); + const bridge = await this.getBridge(); + return await bridge.getActiveDevices(bootedOnly); } catch (e) { console.warn('Failed to query simulators:', e); if (e.message.includes('Xcode license agreements')) { @@ -183,6 +193,15 @@ export class IOSDeviceManager { } } + async launchSimulator(udid: string) { + try { + const bridge = await this.getBridge(); + await bridge.launchSimulator(udid); + } catch (e) { + console.warn('Failed to launch simulator:', e); + } + } + private async queryDevicesForever(bridge: IOSBridge) { try { await this.queryDevices(bridge); diff --git a/desktop/flipper-ui-core/src/sandy-chrome/appinspect/LaunchEmulator.tsx b/desktop/flipper-ui-core/src/sandy-chrome/appinspect/LaunchEmulator.tsx index bf52d69ee..f149ca6d2 100644 --- a/desktop/flipper-ui-core/src/sandy-chrome/appinspect/LaunchEmulator.tsx +++ b/desktop/flipper-ui-core/src/sandy-chrome/appinspect/LaunchEmulator.tsx @@ -26,7 +26,7 @@ import { theme, } from 'flipper-plugin'; import {Provider} from 'react-redux'; -import {IOSDeviceParams} from 'flipper-common'; +import {DeviceTarget} from 'flipper-common'; import {getRenderHostInstance} from 'flipper-frontend-core'; import SettingsSheet from '../../chrome/SettingsSheet'; import {Link} from '../../ui'; @@ -88,7 +88,7 @@ export const LaunchEmulatorDialog = withTrackingScope( (state) => state.settingsState.enableAndroid, ); - const [iosEmulators, setIosEmulators] = useState([]); + const [iosEmulators, setIosEmulators] = useState([]); const [androidEmulators, setAndroidEmulators] = useState([]); const [waitingForIos, setWaitingForIos] = useState(iosEnabled); const [waitingForAndroid, setWaitingForAndroid] = useState(androidEnabled); @@ -113,13 +113,7 @@ export const LaunchEmulatorDialog = withTrackingScope( .flipperServer.exec('ios-get-simulators', false) .then((emulators) => { setWaitingForIos(false); - setIosEmulators( - emulators.filter( - (device) => - device.state === 'Shutdown' && - device.deviceTypeIdentifier?.match(/iPhone|iPad/i), - ), - ); + setIosEmulators(emulators); }) .catch((e) => { console.warn('Failed to find simulators', e);