"use strict" ; import * as child_process from "child_process" ; import * as fs from "fs" ; import * as path from "path" ; import * as pl from "plist" ; import * as xcode from "xcode" ; import { delay } from "../utils/extensionHelper" ; import { ChildProcess } from "../common/node/childProcess" ; import * as nls from "vscode-nls" ; nls . config ( { messageFormat : nls . MessageFormat . bundle , bundleFormat : nls . BundleFormat . standalone } ) ( ) ; const localize = nls . loadMessageBundle ( ) ; export class CordovaIosDeviceLauncher { private static nativeDebuggerProxyInstance : child_process . ChildProcess ; private static webDebuggerProxyInstance : child_process . ChildProcess ; public static cleanup ( ) { if ( CordovaIosDeviceLauncher . nativeDebuggerProxyInstance ) { CordovaIosDeviceLauncher . nativeDebuggerProxyInstance . kill ( "SIGHUP" ) ; CordovaIosDeviceLauncher . nativeDebuggerProxyInstance = null ; } if ( CordovaIosDeviceLauncher . webDebuggerProxyInstance ) { CordovaIosDeviceLauncher . webDebuggerProxyInstance . kill ( ) ; CordovaIosDeviceLauncher . webDebuggerProxyInstance = null ; } } public static getBundleIdentifier ( projectRoot ) { return fs . promises . readdir ( path . join ( projectRoot , "platforms" , "ios" ) ) . then ( ( files ) => { let xcodeprojfiles = files . filter ( ( file ) => /\.xcodeproj$/ . test ( file ) ) ; if ( xcodeprojfiles . length === 0 ) { throw new Error ( localize ( "UnableToFindXCodeProjFile" , "Unable to find xcodeproj file" ) ) ; } let xcodeprojfile = xcodeprojfiles [ 0 ] ; let projectName = /^(.*)\.xcodeproj/ . exec ( xcodeprojfile ) [ 1 ] ; let filepath = path . join ( projectRoot , "platforms" , "ios" , projectName , projectName + "-Info.plist" ) ; let plist = pl . parse ( fs . readFileSync ( filepath , "utf8" ) ) ; if ( plist . CFBundleIdentifier === "$(PRODUCT_BUNDLE_IDENTIFIER)" ) { return this . getBundleIdentifierFromPbxproj ( path . join ( projectRoot , "platforms" , "ios" , xcodeprojfile ) ) ; } else { return plist . CFBundleIdentifier ; } } ) ; } public static startDebugProxy ( proxyPort ) { if ( CordovaIosDeviceLauncher . nativeDebuggerProxyInstance ) { CordovaIosDeviceLauncher . nativeDebuggerProxyInstance . kill ( "SIGHUP" ) ; CordovaIosDeviceLauncher . nativeDebuggerProxyInstance = null ; } return CordovaIosDeviceLauncher . mountDeveloperImage ( ) . then ( ( ) => { return new Promise ( ( resolve , reject ) => { CordovaIosDeviceLauncher . nativeDebuggerProxyInstance = child_process . spawn ( "idevicedebugserverproxy" , [ proxyPort . toString ( ) ] ) ; CordovaIosDeviceLauncher . nativeDebuggerProxyInstance . on ( "error" , function ( err ) : void { reject ( err ) ; } ) ; return delay ( 200 ) . then ( ( ) => resolve ( CordovaIosDeviceLauncher . nativeDebuggerProxyInstance ) ) ; } ) ; } ) ; } public static startWebkitDebugProxy ( proxyPort , proxyRangeStart , proxyRangeEnd ) { if ( CordovaIosDeviceLauncher . webDebuggerProxyInstance ) { CordovaIosDeviceLauncher . webDebuggerProxyInstance . kill ( ) ; CordovaIosDeviceLauncher . webDebuggerProxyInstance = null ; } return new Promise ( ( resolve , reject ) => { let portRange = `null:${ proxyPort },:${ proxyRangeStart }-${ proxyRangeEnd }` ; CordovaIosDeviceLauncher . webDebuggerProxyInstance = child_process . spawn ( "ios_webkit_debug_proxy" , [ "-c" , portRange ] ) ; CordovaIosDeviceLauncher . webDebuggerProxyInstance . on ( "error" , function ( ) { reject ( new Error ( localize ( "UnableToStartIosWebkitDebugProxy" , "Unable to start ios_webkit_debug_proxy." ) ) ) ; } ) ; return delay ( 250 ) . then ( ( ) => resolve ( void 0 ) ) ; } ) ; } public static getPathOnDevice ( packageId ) { const cp = new ChildProcess ( ) ; return cp . execToString ( "ideviceinstaller -l -o xml > /tmp/$$.ideviceinstaller && echo /tmp/$$.ideviceinstaller" ) . catch ( function ( err ) : any { if ( err . code === "ENOENT" ) { throw new Error ( localize ( "UnableToStartiDeviceInstaller" , "Unable to find ideviceinstaller." ) ) ; } throw err ; } ) . then ( ( stdout ) => { let filename = stdout . trim ( ) ; if ( ! /^\/tmp\/[0-9]+\.ideviceinstaller$/ . test ( filename ) ) { throw new Error ( localize ( "UnableToListInstalledApplicationsOnDevice" , "Unable to list installed applications on device" ) ) ; } let list = pl . parse ( fs . readFileSync ( filename , "utf8" ) ) ; fs . unlinkSync ( filename ) ; for ( let i = 0 ; i < list . length ; ++ i ) { if ( list [ i ] . CFBundleIdentifier === packageId ) { let path = list [ i ] . Path ; return path ; } } throw new Error ( localize ( "ApplicationNotInstalledOnTheDevice" , "Application not installed on the device" ) ) ; } ) ; } public static encodePath ( packagePath ) { return packagePath . split ( "" ) . map ( ( c ) => c . charCodeAt ( 0 ) . toString ( 16 ) ) . join ( "" ) . toUpperCase ( ) ; } private static getBundleIdentifierFromPbxproj ( xcodeprojFilePath ) { const pbxprojFilePath = path . join ( xcodeprojFilePath , "project.pbxproj" ) ; const pbxproj = xcode . project ( pbxprojFilePath ) . parseSync ( ) ; const target = pbxproj . getFirstTarget ( ) ; const configListUUID = target . firstTarget . buildConfigurationList ; const configListsMap = pbxproj . pbxXCConfigurationList ( ) ; const targetConfigs = configListsMap [ configListUUID ] . buildConfigurations ; const targetConfigUUID = targetConfigs [ 0 ] . value ; const allConfigs = pbxproj . pbxXCBuildConfigurationSection ( ) ; const bundleId = allConfigs [ targetConfigUUID ] . buildSettings . PRODUCT_BUNDLE_IDENTIFIER ; return Promise . resolve ( bundleId ) ; } private static mountDeveloperImage ( ) { return CordovaIosDeviceLauncher . getDiskImage ( ) . then ( ( path ) => { let imagemounter = child_process . spawn ( "ideviceimagemounter" , [ path , path + ".signature" ] ) ; return new Promise ( ( resolve , reject ) => { let stdout = "" ; imagemounter . stdout . on ( "data" , function ( data ) : void { stdout += data . toString ( ) ; } ) ; imagemounter . on ( "close" , function ( code ) : void { if ( code !== 0 ) { if ( stdout . indexOf ( "Error:" ) !== - 1 ) { resolve ( ) ; } else if ( stdout . indexOf ( "No device found, is it plugged in?" ) !== - 1 ) { reject ( localize ( "UnableToFindDevice" , "Unable to find device. Is the device plugged in?" ) ) ; } reject ( localize ( "UnableToMountDeveloperDiskImage" , "Unable to mount developer disk image." ) ) ; } else { resolve ( ) ; } } ) ; imagemounter . on ( "error" , function ( err ) : void { reject ( err ) ; } ) ; } ) ; } ) ; } private static getDiskImage ( ) { const cp = new ChildProcess ( ) ; let versionInfo = cp . execToString ( "ideviceinfo -s -k ProductVersion" ) . then ( stdout => { return /^(\d+\.\d+)(?:\.\d+)?$/gm . exec ( stdout . trim ( ) ) [ 1 ] ; } ) . catch ( err => { throw new Error ( localize ( "UnableToGetDeviceOSVersion" , "Unable to get device OS version. Details: {0}" , err . message ) ) ; } ) ; let pathInfo = cp . execToString ( "xcrun -sdk iphoneos --show-sdk-platform-path" ) . then ( stdout => { let sdkpath = stdout . trim ( ) ; return sdkpath ; } ) ; return Promise . all ( [ versionInfo , pathInfo ] ) . then ( ( [ version , sdkpath ] ) => { let find = child_process . spawn ( "find" , [ sdkpath , "-path" , "*" + version + "*" , "-name" , "DeveloperDiskImage.dmg" ] ) ; return new Promise < string > ( ( resolve , reject ) => { find . stdout . on ( "data" , function ( data ) : void { let dataStr = data . toString ( ) ; let path = dataStr . split ( "\n" ) [ 0 ] . trim ( ) ; if ( ! path ) { reject ( localize ( "UnableToFindDeveloperDiskImage" , "Unable to find developer disk image." ) ) ; } else { resolve ( path ) ; } } ) ; find . on ( "close" , function ( ) : void { reject ( localize ( "UnableToFindDeveloperDiskImage" , "Unable to find developer disk image." ) ) ; } ) ; } ) ; } ) ; } }