import Debug from 'debug' ; import { readFileSync } from 'fs' ; import { boolish } from 'getenv' ; import * as path from 'path' ; import { AFC_STATUS , AFCError , ClientManager , LockdowndClient , OnInstallProgressCallback , UsbmuxdClient , } from './native-run/ios/lib' ; import type { DeviceValues , IPLookupResult } from './native-run/ios/lib' ; import { getDeveloperDiskImagePath } from './native-run/ios/utils/xcode' ; import { onBeforeExit , wait } from './native-run/utils/process' ; const EXPO_USE_APPLE_DEVICE = boolish ( 'EXPO_USE_APPLE_DEVICE' , false ) ; const debug = Debug ( 'expo:xdl:ios:utils:device' ) ; export function isEnabled ( ) { return EXPO_USE_APPLE_DEVICE ; } export async function getConnectedDevices ( ) { const usbmuxClient = new UsbmuxdClient ( UsbmuxdClient . connectUsbmuxdSocket ( ) ) ; const usbmuxDevices = await usbmuxClient . getDevices ( ) ; usbmuxClient . socket . end ( ) ; return Promise . all ( usbmuxDevices . map ( async ( d ) : Promise < DeviceValues > => { const socket = await new UsbmuxdClient ( UsbmuxdClient . connectUsbmuxdSocket ( ) ) . connect ( d , 62078 ) ; const device = await new LockdowndClient ( socket ) . getAllValues ( ) ; socket . end ( ) ; return device ; } ) ) ; } export async function runOnDevice ( { udid , appPath , bundleId , waitForApp , deltaPath , onProgress , } ) { const clientManager = await ClientManager . create ( udid ) ; try { await mountDeveloperDiskImage ( clientManager ) ; const packageName = path . basename ( appPath ) ; const destPackagePath = path . join ( 'PublicStaging' , packageName ) ; await uploadApp ( clientManager , appPath , destPackagePath ) ; const installer = await clientManager . getInstallationProxyClient ( ) ; await installer . installApp ( destPackagePath , bundleId , { ApplicationsType : 'Any' , CFBundleIdentifier : bundleId , CloseOnInvalidate : '1' , InvalidateOnDetach : '1' , IsUserInitiated : '1' , PreferWifi : '0' , PackageType : 'Developer' , ShadowParentKey : deltaPath } , onProgress ) ; const { [ bundleId ] : appInfo } = await installer . lookupApp ( [ bundleId ] ) ; await wait ( 200 ) ; const debugServerClient = await launchApp ( clientManager , { appInfo , detach : ! waitForApp } ) ; if ( waitForApp ) { onBeforeExit ( async ( ) => { debugServerClient . halt ( ) ; await wait ( 64 ) ; } ) ; debug ( `Waiting for app to close...\n` ) ; const result = await debugServerClient . continue ( ) ; if ( result !== 'W00' ) { await debugServerClient . kill ( ) ; } } } finally { clientManager . end ( ) ; } } async function mountDeveloperDiskImage ( clientManager ) { const imageMounter = await clientManager . getMobileImageMounterClient ( ) ; if ( ! ( await imageMounter . lookupImage ( ) ) . ImageSignature ) { const version = await ( await clientManager . getLockdowndClient ( ) ) . getValue ( 'ProductVersion' ) ; const developerDiskImagePath = await getDeveloperDiskImagePath ( version ) ; const developerDiskImageSig = readFileSync ( `${ developerDiskImagePath }.signature` ) ; await imageMounter . uploadImage ( developerDiskImagePath , developerDiskImageSig ) ; await imageMounter . mountImage ( developerDiskImagePath , developerDiskImageSig ) ; } } async function uploadApp ( clientManager , srcPath , destinationPath ) { const afcClient = await clientManager . getAFCClient ( ) ; try { await afcClient . getFileInfo ( 'PublicStaging' ) ; } catch ( err ) { if ( err instanceof AFCError && err . status === AFC_STATUS . OBJECT_NOT_FOUND ) { await afcClient . makeDirectory ( 'PublicStaging' ) ; } else { throw err ; } } await afcClient . uploadDirectory ( srcPath , destinationPath ) ; } async function launchApp ( clientManager , { appInfo , detach } ) { let tries = 0 ; while ( tries < 3 ) { const debugServerClient = await clientManager . getDebugserverClient ( ) ; await debugServerClient . setMaxPacketSize ( 1024 ) ; await debugServerClient . setWorkingDir ( appInfo . Container ) ; await debugServerClient . launchApp ( appInfo . Path , appInfo . CFBundleExecutable ) ; const result = await debugServerClient . checkLaunchSuccess ( ) ; if ( result === 'OK' ) { if ( detach ) { const res = await debugServerClient . sendCommand ( 'D' , [ ] ) ; debug ( 'Disconnect from debug server request:' , res ) ; if ( res !== 'OK' ) { console . warn ( 'Something went wrong while attempting to disconnect from iOS debug server, you may need to reopen the app manually.' ) ; } } return debugServerClient ; } else if ( result === 'EBusy' || result === 'ENotFound' ) { debug ( 'Device busy or app not found, trying to launch again in .5s...' ) ; tries ++ ; debugServerClient . socket . end ( ) ; await wait ( 500 ) ; } else { throw new Error ( `There was an error launching app: ${ result }` ) ; } } throw new Error ( 'Unable to launch app, number of tries exceeded' ) ; }