format import fs from 'fs-extra' ; import child_process from 'child_process' ; import { DeviceType } from 'flipper-common' ; import { v1 as uuid } from 'uuid' ; import path from 'path' ; import { exec } from 'promisify-child-process' ; import { getFlipperServerConfig } from '../../FlipperServerConfig' ; export const ERR_NO_IDB_OR_XCODE_AVAILABLE = 'Neither Xcode nor idb available. Cannot provide iOS device functionality.' ; export const ERR_PHYSICAL_DEVICE_LOGS_WITHOUT_IDB = 'Cannot provide logs from a physical device without idb.' ; export interface IOSBridge { startLogListener : ( udid : string , deviceType : DeviceType ) => child_process . ChildProcessWithoutNullStreams ; screenshot : ( serial : string ) => Promise < Buffer > ; navigate : ( serial : string , location : string ) => Promise < void > ; recordVideo : ( serial : string , outputFile : string ) => child_process . ChildProcess ; } async function isAvailable ( idbPath ) { if ( ! idbPath ) { return false ; } return fs . promises . access ( idbPath , fs . constants . X_OK ) . then ( ( _ ) => true ) . catch ( ( _ ) => false ) ; } function getLogExtraArgs ( deviceType ) { if ( deviceType === 'physical' ) { return [ ] ; } else { return [ '--style' , 'json' , '--predicate' , 'senderImagePath contains "Containers"' , '--debug' , '--info' , ] ; } } export function idbStartLogListener ( idbPath , udid , deviceType ) { return child_process . spawn ( idbPath , [ 'log' , '--udid' , udid , '--' , ... getLogExtraArgs ( deviceType ) ] , { env : { PYTHONUNBUFFERED : '1' , } , } ) ; } export function xcrunStartLogListener ( udid , deviceType ) { if ( deviceType === 'physical' ) { throw new Error ( ERR_PHYSICAL_DEVICE_LOGS_WITHOUT_IDB ) ; } const deviceSetPath = process . env . DEVICE_SET_PATH ? [ '--set' , process . env . DEVICE_SET_PATH ] : [ ] ; return child_process . spawn ( 'xcrun' , [ 'simctl' , ... deviceSetPath , 'spawn' , udid , 'log' , 'stream' , ... getLogExtraArgs ( deviceType ) , ] , { } ) ; } function makeTempScreenshotFilePath ( ) { const imageName = uuid ( ) + '.png' ; return path . join ( getFlipperServerConfig ( ) . paths . tempPath , imageName ) ; } async function runScreenshotCommand ( command , imagePath ) { await exec ( command ) ; const buffer = await fs . readFile ( imagePath ) ; await fs . unlink ( imagePath ) ; return buffer ; } export async function xcrunScreenshot ( serial ) { const imagePath = makeTempScreenshotFilePath ( ) ; const command = `xcrun simctl io ${ serial } screenshot ${ imagePath }` ; return runScreenshotCommand ( command , imagePath ) ; } export async function idbScreenshot ( serial ) { const imagePath = makeTempScreenshotFilePath ( ) ; const command = `idb screenshot --udid ${ serial } ${ imagePath }` ; return runScreenshotCommand ( command , imagePath ) ; } export async function xcrunNavigate ( serial , location ) { exec ( `xcrun simctl io ${ serial } launch url "${ location }"` ) ; } export async function idbNavigate ( serial , location ) { exec ( `idb open --udid ${ serial } "${ location }"` ) ; } export function xcrunRecordVideo ( serial , outputFile ) { console . log ( `Starting screen record via xcrun to ${ outputFile }.` ) ; return exec ( `xcrun simctl io ${ serial } recordVideo --codec=h264 --force ${ outputFile }` ) ; } export function idbRecordVideo ( serial , outputFile ) { console . log ( `Starting screen record via idb to ${ outputFile }.` ) ; return exec ( `idb record-video --udid ${ serial } ${ outputFile }` ) ; } export async function makeIOSBridge ( idbPath , isXcodeDetected , isAvailableFn = isAvailable ) { if ( await isAvailableFn ( idbPath ) ) { return { startLogListener : idbStartLogListener . bind ( null , idbPath ) , screenshot : idbScreenshot , navigate : idbNavigate , recordVideo : idbRecordVideo , } ; } if ( isXcodeDetected ) { return { startLogListener : xcrunStartLogListener , screenshot : xcrunScreenshot , navigate : xcrunNavigate , recordVideo : xcrunRecordVideo , } ; } throw new Error ( ERR_NO_IDB_OR_XCODE_AVAILABLE ) ; }