format import { Mutex } from 'async-mutex' ; import { exec as unsafeExec , Output } from 'promisify-child-process' ; import { 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' ; import path from 'path' ; const exec = promisify ( child_process . exec ) ; const idbLogLevel = 'DEBUG' ; const operationPrefix = 'iosContainerUtility' ; 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 ; } ; function isAvailable ( idbPath ) { if ( ! idbPath ) { return Promise . resolve ( false ) ; } return promises . access ( idbPath , constants . X_OK ) . then ( ( _ ) => true ) . catch ( ( _ ) => false ) ; } function safeExec ( command ) { return mutex . acquire ( ) . then ( ( release ) => unsafeExec ( command ) . finally ( release ) ) ; } export async function queryTargetsWithoutXcodeDependency ( idbCompanionPath , isPhysicalDeviceEnabled , isAvailableFunc , safeExecFunc ) { if ( await isAvailableFunc ( idbCompanionPath ) ) { return safeExecFunc ( `${ idbCompanionPath } --list 1 --only device` ) . then ( ( { stdout } ) => parseIdbTargets ( stdout ! . toString ( ) ) ) . then ( ( devices ) => { if ( devices . length > 0 && ! isPhysicalDeviceEnabled ) { console . warn ( 'You are trying to connect Physical Device. Please enable the toggle "Enable physical iOS device" from the setting screen.' ) ; } return devices ; } ) . catch ( ( e ) => { console . warn ( 'Failed to query idb_companion --list 1 --only device for physical targets:' , e ) ; return [ ] ; } ) ; } else { console . warn ( `Unable to locate idb_companion in ${ idbCompanionPath }. Try running sudo yum install -y fb-idb` ) ; return [ ] ; } } function parseIdbTargets ( lines ) { const parsedIdbTargets = lines . trim ( ) . split ( '\n' ) . map ( ( line ) => line . trim ( ) ) . filter ( Boolean ) . map ( ( line ) => JSON . parse ( line ) ) . filter ( ( { state } ) => state . toLocaleLowerCase ( ) === 'booted' ) . map < IdbTarget > ( ( { type , target_type , ... rest } ) => ( { type : ( type || target_type ) === 'simulator' ? 'emulator' : 'physical' , ... rest , } ) ) . map < DeviceTarget > ( ( target ) => ( { udid : target . udid , type : target . type as DeviceType , name : target . name , } ) ) ; const dedupedIdbTargets = { } ; for ( const idbTarget of parsedIdbTargets ) { dedupedIdbTargets [ idbTarget . udid ] = dedupedIdbTargets [ idbTarget . udid ] ?? idbTarget ; } return Object . values ( dedupedIdbTargets ) ; } export async function idbListTargets ( idbPath , safeExecFunc = safeExec ) { return safeExecFunc ( `${ idbPath } list-targets --json` ) . then ( ( { stdout } ) => parseIdbTargets ( stdout ! . toString ( ) ) ) . catch ( ( e ) => { console . warn ( 'Failed to query idb for targets:' , e ) ; return [ ] ; } ) ; } async function targets ( idbPath , isPhysicalDeviceEnabled ) { if ( process . platform !== 'darwin' ) { return [ ] ; } const isXcodeInstalled = await isXcodeDetected ( ) ; if ( ! isXcodeInstalled ) { if ( ! isPhysicalDeviceEnabled ) { console . warn ( 'You are trying to connect Physical Device. Please enable the toggle "Enable physical iOS device" from the setting screen.' ) ; } const idbCompanionPath = path . dirname ( idbPath ) + '/idb_companion' ; return queryTargetsWithoutXcodeDependency ( idbCompanionPath , isPhysicalDeviceEnabled , isAvailable , safeExec ) ; } if ( await memoize ( isAvailable ) ( idbPath ) ) { return await idbListTargets ( idbPath ) ; } else { return safeExec ( 'xcrun xctrace list devices' ) . then ( ( { stdout } ) => stdout ! . toString ( ) . split ( '\n' ) . map ( ( line ) => line . trim ( ) ) . filter ( Boolean ) . map ( ( line ) => /(.+) \([^(]+\) \[(.*)\]( \(Simulator\))?/ . exec ( line ) ) . filter ( notNull ) . filter ( ( [ _match , _name , _udid , isSim ] ) => ! isSim ) . map < DeviceTarget > ( ( [ _match , name , udid ] ) => { return { udid , type : 'physical' , name } ; } ) ) . catch ( ( e ) => { console . warn ( 'Failed to query for devices using xctrace:' , e ) ; return [ ] ; } ) ; } } async function push ( udid , src , bundleId , dst , idbPath ) { await memoize ( checkIdbIsInstalled ) ( idbPath ) ; return wrapWithErrorMessage ( reportPlatformFailures ( safeExec ( `${ idbPath } file push --log ${ idbLogLevel } --udid ${ udid } --bundle-id ${ bundleId } '${ src }' '${ dst }'` ) . then ( ( ) => { return ; } ) . catch ( ( e ) => handleMissingIdb ( e , idbPath ) ) , `${ operationPrefix }:push` ) ) ; } async function pull ( udid , src , bundleId , dst , idbPath ) { await memoize ( checkIdbIsInstalled ) ( idbPath ) ; return wrapWithErrorMessage ( reportPlatformFailures ( safeExec ( `${ idbPath } file pull --log ${ idbLogLevel } --udid ${ udid } --bundle-id ${ bundleId } '${ src }' '${ dst }'` ) . then ( ( ) => { return ; } ) . catch ( ( e ) => handleMissingIdb ( e , idbPath ) ) . catch ( ( e ) => handleMissingPermissions ( e ) ) , `${ operationPrefix }:pull` ) ) ; } export async function checkIdbIsInstalled ( idbPath ) { const isInstalled = await isAvailable ( idbPath ) ; if ( ! isInstalled ) { throw new Error ( `idb is required to use iOS devices. Install it with instructions from https://github.com/facebook/idb and set the installation path in Flipper settings.` ) ; } } function handleMissingIdb ( e , idbPath ) { if ( e . message && e . message . includes ( 'sudo: no tty present and no askpass program specified' ) ) { console . warn ( e ) ; throw new Error ( `idb doesn't appear to be installed. Run "${ idbPath } list-targets" to fix this.` ) ; } throw e ; } function handleMissingPermissions ( e ) { if ( e . message && e . message . includes ( 'Command failed' ) && e . message . includes ( 'file pull' ) && e . message . includes ( 'sonar/app.csr' ) ) { console . warn ( e ) ; throw new Error ( 'Cannot connect to iOS application. idb_certificate_pull_failed' + 'Idb lacks permissions to exchange certificates. Did you install a source build ([FB] or enable certificate exchange)? ' + e ) ; } throw e ; } function wrapWithErrorMessage < T > ( p ) { return p . catch ( ( e ) => { console . warn ( e ) ; throw new Error ( "A problem with idb has ocurred. Please run `sudo rm -rf /tmp/idb*` and `sudo yum install -y fb-idb` to update it, if that doesn't fix it, post in Flipper Support." ) ; } ) ; } async function isXcodeDetected ( ) { return exec ( 'xcode-select -p' ) . then ( ( { stdout } ) => { return fs . pathExists ( stdout . trim ( ) ) ; } ) . catch ( ( _ ) => false ) ; } export default { isAvailable , targets , push , pull , isXcodeDetected , } ;