From 45ea8ba783129e3afef8066a05b3abd89e9b9d36 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 26 Jul 2018 11:02:16 +0300 Subject: [PATCH 01/17] fix: stop forcing app restart on debug --- lib/services/livesync/livesync-service.ts | 54 ++++++++++++----------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 1da9030853..e939d5ced2 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -170,6 +170,8 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi notification: msg }); } + + this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOpts, outputPath); } this.emitLivesyncEvent(LiveSyncEvents.liveSyncExecuted, { @@ -183,41 +185,26 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise { const deviceAppData = liveSyncResultInfo.deviceAppData; + debugOptions = debugOptions || {}; const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; await this.$debugService.debugStop(deviceIdentifier); this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); - const applicationId = deviceAppData.appIdentifier; - const attachDebuggerOptions: IAttachDebuggerOptions = { - platform: liveSyncResultInfo.deviceAppData.device.deviceInfo.platform, - isEmulator: liveSyncResultInfo.deviceAppData.device.isEmulator, - projectDir: projectData.projectDir, - deviceIdentifier, - debugOptions, - outputPath - }; - try { - await deviceAppData.device.applicationManager.stopApplication({ appId: applicationId, projectName: projectData.projectName }); - // Now that we've stopped the application we know it isn't started, so set debugOptions.start to false - // so that it doesn't default to true in attachDebugger method - debugOptions = debugOptions || {}; - debugOptions.start = false; - } catch (err) { - this.$logger.trace("Could not stop application during debug livesync. Will try to restart app instead.", err); - if ((err.message || err) === "Could not find developer disk image") { - // Set isFullSync here to true because we are refreshing with debugger - // We want to force a restart instead of accidentally performing LiveEdit or FastSync - liveSyncResultInfo.isFullSync = true; - await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true }); - this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); - return; - } else { - throw err; + if (debugOptions.debugBrk) { + const applicationId = deviceAppData.appIdentifier; + try { + await deviceAppData.device.applicationManager.stopApplication({ appId: applicationId, projectName: projectData.projectName }); + } catch (err) { + this.$logger.trace("Could not stop application during debug livesync. Will try to restart app instead.", err); + this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOptions, outputPath); } + } else { + await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true }); } + debugOptions.start = !debugOptions.debugBrk; const deviceOption = { deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, debugOptions: debugOptions, @@ -226,6 +213,21 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, { projectDir: projectData.projectDir }); } + private handleDeveloperDiskImageError(err: any, liveSyncResultInfo: ILiveSyncResultInfo, projectData: IProjectData, debugOpts: IDebugOptions, outputPath: string) { + if ((err.message || err) === "Could not find developer disk image") { + const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; + const attachDebuggerOptions: IAttachDebuggerOptions = { + platform: liveSyncResultInfo.deviceAppData.device.deviceInfo.platform, + isEmulator: liveSyncResultInfo.deviceAppData.device.isEmulator, + projectDir: projectData.projectDir, + deviceIdentifier, + debugOptions: debugOpts, + outputPath + }; + this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); + } + } + public async attachDebugger(settings: IAttachDebuggerOptions): Promise { // Default values if (settings.debugOptions) { From 1b52128a37d7ec7a7e1e3621ff140360ec1a4599 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Tue, 30 Oct 2018 14:19:29 +0200 Subject: [PATCH 02/17] fix: allow device socket and client proxy socket reuse on iOS --- .vscode/launch.json | 17 ++---- lib/bootstrap.ts | 1 + lib/commands/debug.ts | 13 +++-- lib/common/definitions/mobile.d.ts | 7 ++- lib/common/mobile/ios/device/ios-device.ts | 31 ++++++++--- .../ios/simulator/ios-emulator-services.ts | 4 +- .../ios/simulator/ios-simulator-device.ts | 28 +++++++++- .../mobile/ios-simulator-discovery.ts | 12 ++-- lib/declarations.d.ts | 9 ++- .../ios/socket-proxy-factory.ts | 42 +++++++++++++- .../ios/socket-request-executor.ts | 1 + lib/helpers/bundle-validator-helper.ts | 2 +- lib/helpers/livesync-command-helper.ts | 2 +- lib/services/ios-debug-service.ts | 55 ++++++++++++++----- lib/services/ios-device-socket-service.ts | 28 ++++++++++ .../livesync/ios-device-livesync-service.ts | 16 ++++-- lib/services/livesync/livesync-service.ts | 10 ++-- test/services/ios-debug-service.ts | 5 +- 18 files changed, 217 insertions(+), 66 deletions(-) create mode 100644 lib/services/ios-device-socket-service.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index d70e74e751..dacd2189ab 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,15 +7,17 @@ { "type": "node", "request": "launch", - "cwd": "${workspaceRoot}", + "cwd": "/Users/tachev/Work/Test/testJsHw", "sourceMaps": true, // In case you want to debug child processes started from CLI: // "autoAttachChildProcesses": true, "name": "Launch CLI (Node 6+)", "program": "${workspaceRoot}/lib/nativescript-cli.js", - // example commands - "args": [ "create", "cliapp"] + "args": [ + "debug", + "ios" + ] // "args": [ "test", "android", "--justlaunch"] // "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"] // "args": [ "platform", "remove", "android", "--path", "cliapp"] @@ -38,7 +40,6 @@ "cwd": "${workspaceRoot}", "sourceMaps": true }, - { "type": "node", "runtimeArgs": [ @@ -51,11 +52,8 @@ "sourceMaps": true, // define the arguments that you would like to pass to CLI, for example // "args": [ "build", "android", "--justlaunch" ] - "args": [ - - ] + "args": [] }, - { // in case you want to debug a single test, modify it's code to be `it.only(...` instead of `it(...` "type": "node", @@ -68,7 +66,6 @@ "cwd": "${workspaceRoot}", "sourceMaps": true }, - { "type": "node", "request": "attach", @@ -77,7 +74,6 @@ "port": 9897, "sourceMaps": true }, - { "type": "node", "request": "attach", @@ -87,6 +83,5 @@ "port": 9855, "sourceMaps": true } - ] } \ No newline at end of file diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 3b12c03486..7307ebf617 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -14,6 +14,7 @@ $injector.require("androidPluginBuildService", "./services/android-plugin-build- $injector.require("iOSEntitlementsService", "./services/ios-entitlements-service"); $injector.require("iOSProjectService", "./services/ios-project-service"); $injector.require("iOSProvisionService", "./services/ios-provision-service"); +$injector.require("iOSDeviceSocketService", "./services/ios-device-socket-service"); $injector.require("xCConfigService", "./services/xcconfig-service"); $injector.require("cocoapodsService", "./services/cocoapods-service"); diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 3869b2640b..eddb6bc6ac 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -3,11 +3,13 @@ import { isInteractive } from "../common/helpers"; import { cache } from "../common/decorators"; import { DebugCommandErrors } from "../constants"; import { ValidatePlatformCommandBase } from "./command-base"; +import { LiveSyncCommandHelper } from "../helpers/livesync-command-helper"; export class DebugPlatformCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; constructor(private platform: string, + private $bundleValidatorHelper: IBundleValidatorHelper, private $debugService: IDebugService, protected $devicesService: Mobile.IDevicesService, $platformService: IPlatformService, @@ -21,7 +23,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements private $prompter: IPrompter, private $liveSyncCommandHelper: ILiveSyncCommandHelper, private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { - super($options, $platformsData, $platformService, $projectData); + super($options, $platformsData, $platformService, $projectData); } public async execute(args: string[]): Promise { @@ -36,7 +38,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements const selectedDeviceForDebug = await this.getDeviceForDebug(); - const debugData = this.$debugDataService.createDebugData(this.$projectData, {device: selectedDeviceForDebug.deviceInfo.identifier}); + const debugData = this.$debugDataService.createDebugData(this.$projectData, { device: selectedDeviceForDebug.deviceInfo.identifier }); if (this.$options.start) { await this.$liveSyncService.printDebugInformation(await this.$debugService.debug(debugData, debugOptions)); @@ -118,7 +120,10 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements this.$errors.fail("--release flag is not applicable to this command"); } - const result = await super.canExecuteCommandBase(this.platform, { validateOptions: true, notConfiguredEnvOptions: { hideCloudBuildOption: true }}); + const minSupportedWebpackVersion = this.$options.hmr ? LiveSyncCommandHelper.MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR : null; + this.$bundleValidatorHelper.validate(minSupportedWebpackVersion); + + const result = await super.canExecuteCommandBase(this.platform, { validateOptions: true, notConfiguredEnvOptions: { hideCloudBuildOption: true, hideSyncToPreviewAppOption: true } }); return result; } } @@ -209,7 +214,7 @@ export class DebugAndroidCommand implements ICommand { private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $injector: IInjector, private $projectData: IProjectData) { - this.$projectData.initializeProjectData(); + this.$projectData.initializeProjectData(); } public execute(args: string[]): Promise { diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index 6a25892a7c..397590df1f 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -115,6 +115,11 @@ declare module Mobile { openDeviceLogStream(options?: IiOSLogStreamOptions): Promise; } + interface IiOSDeviceSocketsService { + getSocket(deviceId: string): any; + addSocket(deviceId: string, socket: any): void; + } + interface IAndroidDevice extends IDevice { adb: Mobile.IDeviceAndroidDebugBridge; init(): Promise; @@ -125,7 +130,7 @@ declare module Mobile { getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService; } - interface IiOSSimulator extends IDevice { } + // interface IiOSSimulator extends IDevice { } /** * Describes log stream options diff --git a/lib/common/mobile/ios/device/ios-device.ts b/lib/common/mobile/ios/device/ios-device.ts index 576b8addeb..13c944e23d 100644 --- a/lib/common/mobile/ios/device/ios-device.ts +++ b/lib/common/mobile/ios/device/ios-device.ts @@ -8,8 +8,17 @@ export class IOSDevice implements Mobile.IiOSDevice { public applicationManager: Mobile.IDeviceApplicationManager; public fileSystem: Mobile.IDeviceFileSystem; public deviceInfo: Mobile.IDeviceInfo; + private socket: net.Socket; + + // private static sockets: { [id: string]: net.Socket; } = {}; + + // get socket(): net.Socket { + // return IOSDevice.sockets[this.deviceInfo.identifier]; + // } + // set socket(newSocket: net.Socket) { + // IOSDevice.sockets[this.deviceInfo.identifier] = newSocket; + // } - private _socket: net.Socket; private _deviceLogHandler: (...args: any[]) => void; constructor(private deviceActionInfo: IOSDeviceLib.IDeviceActionInfo, @@ -19,6 +28,7 @@ export class IOSDevice implements Mobile.IiOSDevice { private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $iOSDeviceProductNameMapper: Mobile.IiOSDeviceProductNameMapper, private $iosDeviceOperations: IIOSDeviceOperations, + private $logger: ILogger, private $mobileHelper: Mobile.IMobileHelper) { this.applicationManager = this.$injector.resolve(applicationManagerPath.IOSApplicationManager, { device: this, devicePointer: this.deviceActionInfo }); @@ -74,15 +84,20 @@ export class IOSDevice implements Mobile.IiOSDevice { // This function works only on OSX public async connectToPort(port: number): Promise { + console.log("connectToPort"); const deviceId = this.deviceInfo.identifier; const deviceResponse = _.first((await this.$iosDeviceOperations.connectToPort([{ deviceId: deviceId, port: port }]))[deviceId]); - const socket = new net.Socket(); - socket.connect(deviceResponse.port, deviceResponse.host); - this._socket = socket; + const _socket = new net.Socket(); + _socket.connect(deviceResponse.port, deviceResponse.host); + this.socket = _socket; + _socket.on("close", () => { + this.socket = null; + this.$logger.info("iOS Device socket closed!"); + }); this.$processService.attachToProcessExitSignals(this, this.destroySocket); - return this._socket; + return this.socket; } private getActiveArchitecture(productType: string): string { @@ -108,9 +123,9 @@ export class IOSDevice implements Mobile.IiOSDevice { } private destroySocket() { - if (this._socket) { - this._socket.destroy(); - this._socket = null; + if (this.socket) { + this.socket.destroy(); + this.socket = null; } } } diff --git a/lib/common/mobile/ios/simulator/ios-emulator-services.ts b/lib/common/mobile/ios/simulator/ios-emulator-services.ts index 5a39448f87..a8f83faaa6 100644 --- a/lib/common/mobile/ios/simulator/ios-emulator-services.ts +++ b/lib/common/mobile/ios/simulator/ios-emulator-services.ts @@ -85,7 +85,7 @@ class IosEmulatorServices implements Mobile.IiOSSimulatorService { const output = await this.tryGetiOSSimDevices(); if (output.devices && output.devices.length) { - devices = _(output.devices) + devices = _(output.devices) .map(simDevice => this.convertSimDeviceToDeviceInfo(simDevice)) .sortBy(deviceInfo => deviceInfo.version) .value(); @@ -102,7 +102,7 @@ class IosEmulatorServices implements Mobile.IiOSSimulatorService { return []; } - private async tryGetiOSSimDevices(): Promise<{devices: Mobile.IiSimDevice[], error: string}> { + private async tryGetiOSSimDevices(): Promise<{ devices: Mobile.IiSimDevice[], error: string }> { let devices: Mobile.IiSimDevice[] = []; let error: string = null; diff --git a/lib/common/mobile/ios/simulator/ios-simulator-device.ts b/lib/common/mobile/ios/simulator/ios-simulator-device.ts index 1c7a0469f0..2b56d93ddf 100644 --- a/lib/common/mobile/ios/simulator/ios-simulator-device.ts +++ b/lib/common/mobile/ios/simulator/ios-simulator-device.ts @@ -1,17 +1,41 @@ import * as applicationManagerPath from "./ios-simulator-application-manager"; import * as fileSystemPath from "./ios-simulator-file-system"; import * as constants from "../../../constants"; +import * as net from "net"; import { cache } from "../../../decorators"; -export class IOSSimulator implements Mobile.IiOSSimulator { +export class IOSSimulator implements Mobile.IiOSDevice { private _applicationManager: Mobile.IDeviceApplicationManager; private _fileSystem: Mobile.IDeviceFileSystem; + private socket: net.Socket; + + // private static sockets: { [id: string]: net.Socket; } = {}; + + // get socket(): net.Socket { + // return IOSSimulator.sockets[this.deviceInfo.identifier]; + // } + // set socket(newSocket: net.Socket) { + // IOSSimulator.sockets[this.deviceInfo.identifier] = newSocket; + // } constructor(private simulator: Mobile.IiSimDevice, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $injector: IInjector, private $iOSSimResolver: Mobile.IiOSSimResolver, - private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider) { } + private $iOSEmulatorServices: Mobile.IiOSSimulatorService, + private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider, + private $logger: ILogger) { } + + public async connectToPort(port: number): Promise { + console.log("connectToPort"); + this.socket = await this.$iOSEmulatorServices.connectToPort({ port }); + this.socket.on("close", () => { + this.socket = null; + this.$logger.info("iOS Simulator socket closed!"); + }); + + return this.socket; + } public get deviceInfo(): Mobile.IDeviceInfo { return { diff --git a/lib/common/test/unit-tests/mobile/ios-simulator-discovery.ts b/lib/common/test/unit-tests/mobile/ios-simulator-discovery.ts index 215754ccaf..a60bc34886 100644 --- a/lib/common/test/unit-tests/mobile/ios-simulator-discovery.ts +++ b/lib/common/test/unit-tests/mobile/ios-simulator-discovery.ts @@ -58,20 +58,20 @@ describe("ios-simulator-discovery", () => { let defaultRunningSimulator: any; let expectedDeviceInfo: Mobile.IDeviceInfo = null; - const detectNewSimulatorAttached = async (runningSimulator: any): Promise => { - return new Promise(async (resolve, reject) => { + const detectNewSimulatorAttached = async (runningSimulator: any): Promise => { + return new Promise(async (resolve, reject) => { currentlyRunningSimulators.push(_.cloneDeep(runningSimulator)); - iOSSimulatorDiscovery.once(DeviceDiscoveryEventNames.DEVICE_FOUND, (device: Mobile.IDevice) => { + iOSSimulatorDiscovery.once(DeviceDiscoveryEventNames.DEVICE_FOUND, (device: Mobile.IiOSDevice) => { resolve(device); }); await iOSSimulatorDiscovery.startLookingForDevices(); }); }; - const detectSimulatorDetached = async (simulatorId: string): Promise => { + const detectSimulatorDetached = async (simulatorId: string): Promise => { _.remove(currentlyRunningSimulators, simulator => simulator.id === simulatorId); - return new Promise(async (resolve, reject) => { - iOSSimulatorDiscovery.once(DeviceDiscoveryEventNames.DEVICE_LOST, (device: Mobile.IDevice) => { + return new Promise(async (resolve, reject) => { + iOSSimulatorDiscovery.once(DeviceDiscoveryEventNames.DEVICE_LOST, (device: Mobile.IiOSDevice) => { resolve(device); }); await iOSSimulatorDiscovery.startLookingForDevices(); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index eafdaab489..16fe5cea5d 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -732,8 +732,13 @@ interface IAndroidToolsInfoValidateInput extends IAndroidToolsInfoOptions { } interface ISocketProxyFactory extends NodeJS.EventEmitter { - createTCPSocketProxy(factory: () => Promise): Promise; - createWebSocketProxy(factory: () => Promise, deviceIdentifier: string): Promise; + getTCPSocketProxy(deviceIdentifier: string): any; + addTCPSocketProxy(factory: () => Promise, deviceIdentifier: string): Promise; + + getWebSocketProxy(deviceIdentifier: string): any; + addWebSocketProxy(factory: () => Promise, deviceIdentifier: string): Promise; + + removeAllProxies(): void; } interface IiOSNotification extends NodeJS.EventEmitter { diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/socket-proxy-factory.ts index 86215b9e05..ad39c7b9b5 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/socket-proxy-factory.ts @@ -7,14 +7,32 @@ import * as helpers from "../../common/helpers"; import temp = require("temp"); export class SocketProxyFactory extends EventEmitter implements ISocketProxyFactory { + private deviceWebServers: { [id: string]: ws.Server; } = {}; + private deviceTcpServers: { [id: string]: net.Server; } = {}; + constructor(private $logger: ILogger, private $errors: IErrors, + private $iOSDeviceSocketService: Mobile.IiOSDeviceSocketsService, private $options: IOptions, private $net: INet) { super(); } - public async createTCPSocketProxy(factory: () => Promise): Promise { + public getTCPSocketProxy(deviceIdentifier: string): net.Server { + return this.deviceTcpServers[deviceIdentifier]; + } + + public getWebSocketProxy(deviceIdentifier: string): ws.Server { + return this.deviceWebServers[deviceIdentifier]; + } + + public async addTCPSocketProxy(factory: () => Promise, deviceIdentifier: string): Promise { + const existingServer = this.deviceTcpServers[deviceIdentifier]; + if (existingServer) { + throw new Error(`TCP socket proxy is already running for device '${deviceIdentifier}'`); + } + + // await here? const socketFactory = async (callback: (_socket: net.Socket) => void) => helpers.connectEventually(factory, callback); this.$logger.info("\nSetting up proxy...\nPress Ctrl + C to terminate, or disconnect.\n"); @@ -23,6 +41,8 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact allowHalfOpen: true }); + this.deviceTcpServers[deviceIdentifier] = server; + server.on("connection", async (frontendSocket: net.Socket) => { this.$logger.info("Frontend client connected."); @@ -72,7 +92,12 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact return server; } - public async createWebSocketProxy(factory: () => Promise, deviceIdentifier: string): Promise { + public async addWebSocketProxy(factory: () => Promise, deviceIdentifier: string): Promise { + const existingServer = this.deviceWebServers[deviceIdentifier]; + if (existingServer) { + throw new Error(`Web socket proxy is already running for device '${deviceIdentifier}'`); + } + // NOTE: We will try to provide command line options to select ports, at least on the localhost. const localPort = await this.$net.getAvailablePortInRange(41000); @@ -90,7 +115,8 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact this.$logger.info("Frontend client connected."); let _socket; try { - _socket = await helpers.connectEventuallyUntilTimeout(factory, 10000); + const existingServerSocket = this.$iOSDeviceSocketService.getSocket(deviceIdentifier); + _socket = existingServerSocket || await helpers.connectEventuallyUntilTimeout(factory, 10000); } catch (err) { err.deviceIdentifier = deviceIdentifier; this.$logger.trace(err); @@ -103,6 +129,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact callback(true); } }); + this.deviceWebServers[deviceIdentifier] = server; server.on("connection", (webSocket, req) => { const encoding = "utf16le"; @@ -137,7 +164,10 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact webSocket.on("close", () => { this.$logger.info('Frontend socket closed!'); + packets.destroy(); deviceSocket.destroy(); + // delete this.deviceWebServers[deviceIdentifier]; + // server.close(); if (!this.$options.watch) { process.exit(0); } @@ -146,7 +176,13 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact }); this.$logger.info("Opened localhost " + localPort); + console.log("return new proxy"); return server; } + + public removeAllProxies() { + this.deviceWebServers = {}; + this.deviceTcpServers = {}; + } } $injector.register("socketProxyFactory", SocketProxyFactory); diff --git a/lib/device-sockets/ios/socket-request-executor.ts b/lib/device-sockets/ios/socket-request-executor.ts index 76db790060..10914289ff 100644 --- a/lib/device-sockets/ios/socket-request-executor.ts +++ b/lib/device-sockets/ios/socket-request-executor.ts @@ -7,6 +7,7 @@ export class IOSSocketRequestExecutor implements IiOSSocketRequestExecutor { private $logger: ILogger) { } public async executeAttachRequest(device: Mobile.IiOSDevice, timeout: number, projectId: string): Promise { + console.log("executeAttachRequest"); const deviceIdentifier = device.deviceInfo.identifier; const observeNotificationSockets = [ diff --git a/lib/helpers/bundle-validator-helper.ts b/lib/helpers/bundle-validator-helper.ts index 41336b2846..4eedac652b 100644 --- a/lib/helpers/bundle-validator-helper.ts +++ b/lib/helpers/bundle-validator-helper.ts @@ -20,7 +20,7 @@ export class BundleValidatorHelper extends VersionValidatorHelper implements IBu const bundlerVersionInDependencies = this.$projectData.dependencies && this.$projectData.dependencies[bundlePluginName]; const bundlerVersionInDevDependencies = this.$projectData.devDependencies && this.$projectData.devDependencies[bundlePluginName]; if (!bundlePluginName || (!bundlerVersionInDependencies && !bundlerVersionInDevDependencies)) { - this.$errors.fail(BundleValidatorMessages.MissingBundlePlugin); + this.$errors.failWithoutHelp(BundleValidatorMessages.MissingBundlePlugin); } const currentVersion = bundlerVersionInDependencies || bundlerVersionInDevDependencies; diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index f91463af07..1d64c300e3 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -1,5 +1,5 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { - private static MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR = "0.17.0"; + public static MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR = "0.17.0"; constructor(private $platformService: IPlatformService, private $projectData: IProjectData, diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index e13de3419e..b965545710 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -26,6 +26,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private $errors: IErrors, private $packageInstallationManager: IPackageInstallationManager, private $iOSDebuggerPortService: IIOSDebuggerPortService, + private $iOSDeviceSocketService: Mobile.IiOSDeviceSocketsService, private $iOSNotification: IiOSNotification, private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, private $processService: IProcessService, @@ -42,6 +43,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + if (debugOptions.debugBrk && debugOptions.start) { this.$errors.failWithoutHelp("Expected exactly one of the --debug-brk or --start options."); } @@ -51,7 +53,6 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } await this.startDeviceLogProcess(debugData, debugOptions); - await this.$iOSDebuggerPortService.attachToDebuggerPortFoundEvent(this.device, debugData, debugOptions); if (debugOptions.emulator) { if (debugOptions.start) { @@ -83,6 +84,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS _.forEach(this._sockets, socket => socket.destroy()); this._sockets = []; + this.$socketProxyFactory.removeAllProxies(); if (this._lldbProcess) { this._lldbProcess.stdin.write("process detach\n"); @@ -151,15 +153,21 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS this._lldbProcess.stderr.pipe(process.stderr); this._lldbProcess.stdin.write("process continue\n"); - return this.wireDebuggerClient(debugData, debugOptions); + const debugUrl = await this.wireDebuggerClient(debugData, debugOptions); + return debugUrl; } private async emulatorStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - const result = await this.wireDebuggerClient(debugData, debugOptions); - const attachRequestMessage = this.$iOSNotification.getAttachRequest(debugData.applicationIdentifier, debugData.deviceIdentifier); - const iOSEmulatorService = this.$iOSEmulatorServices; - await iOSEmulatorService.postDarwinNotification(attachRequestMessage, debugData.deviceIdentifier); - return result; + const isServerSocketConnected = !!this.$iOSDeviceSocketService.getSocket(debugData.deviceIdentifier); + const debugUrl = await this.wireDebuggerClient(debugData, debugOptions); + // tODO: !result.wasAlreadyRunning || ?? + if (!isServerSocketConnected) { + console.log("attach debug socket on emulator"); + const attachRequestMessage = this.$iOSNotification.getAttachRequest(debugData.applicationIdentifier, debugData.deviceIdentifier); + const iOSEmulatorService = this.$iOSEmulatorServices; + await iOSEmulatorService.postDarwinNotification(attachRequestMessage, debugData.deviceIdentifier); + } + return debugUrl; } private async deviceDebugBrk(debugData: IDebugData, debugOptions: IDebugOptions): Promise { @@ -190,7 +198,8 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private async debugBrkCore(device: Mobile.IiOSDevice, debugData: IDebugData, debugOptions: IDebugOptions): Promise { await this.$iOSSocketRequestExecutor.executeLaunchRequest(device.deviceInfo.identifier, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, debugData.applicationIdentifier, debugOptions); - return this.wireDebuggerClient(debugData, debugOptions, device); + const debugUrl = await this.wireDebuggerClient(debugData, debugOptions, device); + return debugUrl; } private async deviceStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { @@ -201,25 +210,36 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } private async deviceStartCore(device: Mobile.IiOSDevice, debugData: IDebugData, debugOptions: IDebugOptions): Promise { + const deviceIdentifier = device ? device.deviceInfo.identifier : debugData.deviceIdentifier; + if (!this.$iOSDeviceSocketService.getSocket(deviceIdentifier)) { + console.log("attach debug socket on device"); + await this.$iOSSocketRequestExecutor.executeAttachRequest(device, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, debugData.applicationIdentifier); + } - await this.$iOSSocketRequestExecutor.executeAttachRequest(device, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, debugData.applicationIdentifier); - return this.wireDebuggerClient(debugData, debugOptions, device); + const debugUrl = await this.wireDebuggerClient(debugData, debugOptions, device); + return debugUrl; } private async wireDebuggerClient(debugData: IDebugData, debugOptions: IDebugOptions, device?: Mobile.IiOSDevice): Promise { // the VSCode Ext starts `tns debug ios --no-client` to start/attach to debug sessions // check if --no-client is passed - default to opening a tcp socket (versus Chrome DevTools (websocket)) + const deviceIdentifier = device ? device.deviceInfo.identifier : debugData.deviceIdentifier; if ((debugOptions.inspector || !debugOptions.client) && this.$hostInfo.isDarwin) { - this._socketProxy = await this.$socketProxyFactory.createTCPSocketProxy(this.getSocketFactory(device, debugData, debugOptions)); - await this.openAppInspector(this._socketProxy.address(), debugData, debugOptions); + const existingProxy = this.$socketProxyFactory.getTCPSocketProxy(deviceIdentifier); + this._socketProxy = existingProxy || await this.$socketProxyFactory.addTCPSocketProxy(this.getSocketFactory(device, debugData, debugOptions), deviceIdentifier); + + if (!existingProxy) { + await this.openAppInspector(this._socketProxy.address(), debugData, debugOptions); + } + return null; } else { if (debugOptions.chrome) { this.$logger.info("'--chrome' is the default behavior. Use --inspector to debug iOS applications using the Safari Web Inspector."); } - const deviceIdentifier = device ? device.deviceInfo.identifier : debugData.deviceIdentifier; - this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(device, debugData, debugOptions), deviceIdentifier); + const existingProxy = this.$socketProxyFactory.getWebSocketProxy(deviceIdentifier); + this._socketProxy = existingProxy || await this.$socketProxyFactory.addWebSocketProxy(this.getSocketFactory(device, debugData, debugOptions), deviceIdentifier); return this.getChromeDebugUrl(debugOptions, this._socketProxy.options.port); } } @@ -247,9 +267,14 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS if (!port) { this.$errors.fail("NativeScript debugger was not able to get inspector socket port."); } - const socket = device ? await device.connectToPort(port) : net.connect(port); + + const deviceIdentifier = device ? device.deviceInfo.identifier : debugData.deviceIdentifier; + const socket = this.$iOSDeviceSocketService.getSocket(deviceIdentifier) || + (device ? await device.connectToPort(port) : net.connect(port)); + console.log("port", port); this._sockets.push(socket); pendingExecution = null; + this.$iOSDeviceSocketService.addSocket(deviceIdentifier, socket); return socket; }; pendingExecution = func(); diff --git a/lib/services/ios-device-socket-service.ts b/lib/services/ios-device-socket-service.ts new file mode 100644 index 0000000000..c42aa8cb3a --- /dev/null +++ b/lib/services/ios-device-socket-service.ts @@ -0,0 +1,28 @@ +import * as net from "net"; + +export class IOSDeviceSocketService implements Mobile.IiOSDeviceSocketsService { + private static sockets: { [id: string]: net.Socket; } = {}; + + constructor() { + } + + getSocket(deviceId: string): net.Socket { + return IOSDeviceSocketService.sockets[deviceId]; + } + + addSocket(deviceId: string, socket: any): void { + if (IOSDeviceSocketService.sockets[deviceId] === socket) { + return; + } + + console.log("cached socket"); + IOSDeviceSocketService.sockets[deviceId] = socket; + socket.on("close", () => { + console.log("delete cached socket"); + delete IOSDeviceSocketService.sockets[deviceId]; + }); + } + +} + +$injector.register("iOSDeviceSocketService", IOSDeviceSocketService); \ No newline at end of file diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index abbdeafab4..a52a9742b8 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -13,11 +13,12 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen private $iOSNotification: IiOSNotification, private $iOSEmulatorServices: Mobile.IiOSSimulatorService, private $iOSDebuggerPortService: IIOSDebuggerPortService, + private $iOSDeviceSocketService: Mobile.IiOSDeviceSocketsService, private $logger: ILogger, private $processService: IProcessService, protected $platformsData: IPlatformsData, protected device: Mobile.IiOSDevice) { - super($platformsData, device); + super($platformsData, device); } private async setupSocketIfNeeded(projectData: IProjectData): Promise { @@ -26,11 +27,15 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen } const appId = projectData.projectIdentifiers.ios; - - if (this.device.isEmulator) { + const existingDeviceSocket = this.$iOSDeviceSocketService.getSocket(this.device.deviceInfo.identifier); + if (existingDeviceSocket) { + this.socket = existingDeviceSocket; + console.log("will use existing socket"); + } else if (this.device.isEmulator) { await this.$iOSEmulatorServices.postDarwinNotification(this.$iOSNotification.getAttachRequest(appId, this.device.deviceInfo.identifier), this.device.deviceInfo.identifier); const port = await this.$iOSDebuggerPortService.getPort({ projectDir: projectData.projectDir, deviceId: this.device.deviceInfo.identifier, appId }); - this.socket = await this.$iOSEmulatorServices.connectToPort({ port }); + this.socket = await this.device.connectToPort(port); + console.log("port", port); if (!this.socket) { return false; } @@ -38,8 +43,11 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen await this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, constants.AWAIT_NOTIFICATION_TIMEOUT_SECONDS, appId); const port = await this.$iOSDebuggerPortService.getPort({ projectDir: projectData.projectDir, deviceId: this.device.deviceInfo.identifier, appId }); this.socket = await this.device.connectToPort(port); + console.log("port", port); } + this.$iOSDeviceSocketService.addSocket(this.device.deviceInfo.identifier, this.socket); + this.attachEventHandlers(); return true; diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index e939d5ced2..7ee71b5947 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -188,11 +188,9 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi debugOptions = debugOptions || {}; const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; - await this.$debugService.debugStop(deviceIdentifier); - this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); - - if (debugOptions.debugBrk) { + await this.$debugService.debugStop(deviceIdentifier); + this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); const applicationId = deviceAppData.appIdentifier; try { await deviceAppData.device.applicationManager.stopApplication({ appId: applicationId, projectName: projectData.projectName }); @@ -204,7 +202,10 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true }); } + // we do not stop the application when debugBrk is false, so we need to attach, instead of launch + // if we try to send the launch request, the debugger port will not be printed and the command will timeout debugOptions.start = !debugOptions.debugBrk; + const deviceOption = { deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, debugOptions: debugOptions, @@ -529,6 +530,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi }); await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); + this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); this.emitLivesyncEvent(LiveSyncEvents.liveSyncStarted, { diff --git a/test/services/ios-debug-service.ts b/test/services/ios-debug-service.ts index d6384542be..50fb06daf2 100644 --- a/test/services/ios-debug-service.ts +++ b/test/services/ios-debug-service.ts @@ -15,6 +15,7 @@ class IOSDebugServiceInheritor extends IOSDebugService { $errors: IErrors, $packageInstallationManager: IPackageInstallationManager, $iOSDebuggerPortService: IIOSDebuggerPortService, + $iOSDeviceSocketService: Mobile.IiOSDeviceSocketsService, $iOSNotification: IiOSNotification, $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, $processService: IProcessService, @@ -23,8 +24,8 @@ class IOSDebugServiceInheritor extends IOSDebugService { $projectDataService: IProjectDataService, $deviceLogProvider: Mobile.IDeviceLogProvider) { super({}, $devicesService, $platformService, $iOSEmulatorServices, $childProcess, $hostInfo, $logger, $errors, - $packageInstallationManager, $iOSDebuggerPortService, $iOSNotification, $iOSSocketRequestExecutor, $processService, - $socketProxyFactory, $projectDataService, $deviceLogProvider); + $packageInstallationManager, $iOSDebuggerPortService, $iOSDeviceSocketService, $iOSNotification, $iOSSocketRequestExecutor, + $processService, $socketProxyFactory, $projectDataService, $deviceLogProvider); } public getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { From e111b796345e2bf586758450d914cca84b07ca40 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Fri, 2 Nov 2018 15:18:59 +0200 Subject: [PATCH 03/17] fix: move the ios debug and livesync sockets logic in the iOS devices --- .vscode/launch.json | 5 +- lib/bootstrap.ts | 1 - lib/common/definitions/mobile.d.ts | 5 +- lib/common/mobile/ios/device/ios-device.ts | 56 ++++++++++------- .../ios/simulator/ios-emulator-services.ts | 5 +- .../ios/simulator/ios-simulator-device.ts | 54 +++++++++++----- .../ios/socket-proxy-factory.ts | 62 ++++++++----------- lib/services/ios-debug-service.ts | 56 +++++------------ lib/services/ios-device-socket-service.ts | 28 --------- .../livesync/ios-device-livesync-service.ts | 27 +------- test/services/ios-debug-service.ts | 6 +- 11 files changed, 122 insertions(+), 183 deletions(-) delete mode 100644 lib/services/ios-device-socket-service.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index dacd2189ab..56a47250b9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ { "type": "node", "request": "launch", - "cwd": "/Users/tachev/Work/Test/testJsHw", + "cwd": "/Users/tachev/Work/Test/debugTest", "sourceMaps": true, // In case you want to debug child processes started from CLI: // "autoAttachChildProcesses": true, @@ -16,7 +16,8 @@ // example commands "args": [ "debug", - "ios" + "ios", + "--hmr" ] // "args": [ "test", "android", "--justlaunch"] // "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"] diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 7307ebf617..3b12c03486 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -14,7 +14,6 @@ $injector.require("androidPluginBuildService", "./services/android-plugin-build- $injector.require("iOSEntitlementsService", "./services/ios-entitlements-service"); $injector.require("iOSProjectService", "./services/ios-project-service"); $injector.require("iOSProvisionService", "./services/ios-provision-service"); -$injector.require("iOSDeviceSocketService", "./services/ios-device-socket-service"); $injector.require("xCConfigService", "./services/xcconfig-service"); $injector.require("cocoapodsService", "./services/cocoapods-service"); diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index 397590df1f..172700386b 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -111,7 +111,8 @@ declare module Mobile { } interface IiOSDevice extends IDevice { - connectToPort(port: number): Promise; + getLiveSyncSocket(appId: string, projectDir: string): Promise; + getDebugSocket(appId: string, projectDir: string): Promise; openDeviceLogStream(options?: IiOSLogStreamOptions): Promise; } @@ -130,8 +131,6 @@ declare module Mobile { getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService; } - // interface IiOSSimulator extends IDevice { } - /** * Describes log stream options */ diff --git a/lib/common/mobile/ios/device/ios-device.ts b/lib/common/mobile/ios/device/ios-device.ts index 13c944e23d..4670c91cfb 100644 --- a/lib/common/mobile/ios/device/ios-device.ts +++ b/lib/common/mobile/ios/device/ios-device.ts @@ -1,6 +1,7 @@ import * as applicationManagerPath from "./ios-application-manager"; import * as fileSystemPath from "./ios-device-file-system"; -import * as constants from "../../../constants"; +import * as commonConstants from "../../../constants"; +import * as constants from "../../../../constants"; import * as net from "net"; import { cache } from "../../../decorators"; @@ -10,25 +11,18 @@ export class IOSDevice implements Mobile.IiOSDevice { public deviceInfo: Mobile.IDeviceInfo; private socket: net.Socket; - // private static sockets: { [id: string]: net.Socket; } = {}; - - // get socket(): net.Socket { - // return IOSDevice.sockets[this.deviceInfo.identifier]; - // } - // set socket(newSocket: net.Socket) { - // IOSDevice.sockets[this.deviceInfo.identifier] = newSocket; - // } - private _deviceLogHandler: (...args: any[]) => void; constructor(private deviceActionInfo: IOSDeviceLib.IDeviceActionInfo, + private $errors: IErrors, private $injector: IInjector, + private $iOSDebuggerPortService: IIOSDebuggerPortService, + private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, private $processService: IProcessService, private $deviceLogProvider: Mobile.IDeviceLogProvider, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $iOSDeviceProductNameMapper: Mobile.IiOSDeviceProductNameMapper, private $iosDeviceOperations: IIOSDeviceOperations, - private $logger: ILogger, private $mobileHelper: Mobile.IMobileHelper) { this.applicationManager = this.$injector.resolve(applicationManagerPath.IOSApplicationManager, { device: this, devicePointer: this.deviceActionInfo }); @@ -36,13 +30,13 @@ export class IOSDevice implements Mobile.IiOSDevice { const productType = deviceActionInfo.productType; const isTablet = this.$mobileHelper.isiOSTablet(productType); - const deviceStatus = deviceActionInfo.status || constants.UNREACHABLE_STATUS; + const deviceStatus = deviceActionInfo.status || commonConstants.UNREACHABLE_STATUS; this.deviceInfo = { identifier: deviceActionInfo.deviceId, vendor: "Apple", platform: this.$devicePlatformsConstants.iOS, status: deviceStatus, - errorHelp: deviceStatus === constants.UNREACHABLE_STATUS ? `Device ${deviceActionInfo.deviceId} is ${constants.UNREACHABLE_STATUS}` : null, + errorHelp: deviceStatus === commonConstants.UNREACHABLE_STATUS ? `Device ${deviceActionInfo.deviceId} is ${commonConstants.UNREACHABLE_STATUS}` : null, type: "Device", isTablet: isTablet, displayName: this.$iOSDeviceProductNameMapper.resolveProductName(deviceActionInfo.deviceName) || deviceActionInfo.deviceName, @@ -69,31 +63,45 @@ export class IOSDevice implements Mobile.IiOSDevice { @cache() public async openDeviceLogStream(): Promise { - if (this.deviceInfo.status !== constants.UNREACHABLE_STATUS) { + if (this.deviceInfo.status !== commonConstants.UNREACHABLE_STATUS) { this._deviceLogHandler = this.actionOnDeviceLog.bind(this); - this.$iosDeviceOperations.on(constants.DEVICE_LOG_EVENT_NAME, this._deviceLogHandler); + this.$iosDeviceOperations.on(commonConstants.DEVICE_LOG_EVENT_NAME, this._deviceLogHandler); this.$iosDeviceOperations.startDeviceLog(this.deviceInfo.identifier); } } public detach(): void { if (this._deviceLogHandler) { - this.$iosDeviceOperations.removeListener(constants.DEVICE_LOG_EVENT_NAME, this._deviceLogHandler); + this.$iosDeviceOperations.removeListener(commonConstants.DEVICE_LOG_EVENT_NAME, this._deviceLogHandler); } } - // This function works only on OSX - public async connectToPort(port: number): Promise { - console.log("connectToPort"); + public async getLiveSyncSocket(appId: string, projectDir: string): Promise { + return this.getSocket(appId, projectDir); + } + + public async getDebugSocket(appId: string, projectDir: string): Promise { + return this.getSocket(appId, projectDir); + } + + private async getSocket(appId: string, projectDir: string): Promise { + if (this.socket) { + return this.socket; + } + + await this.$iOSSocketRequestExecutor.executeAttachRequest(this, constants.AWAIT_NOTIFICATION_TIMEOUT_SECONDS, appId); + const port = await this.$iOSDebuggerPortService.getPort({ projectDir, deviceId: this.deviceInfo.identifier, appId }); + if (!port) { + this.$errors.fail("NativeScript debugger was not able to get inspector socket port."); + } + const deviceId = this.deviceInfo.identifier; const deviceResponse = _.first((await this.$iosDeviceOperations.connectToPort([{ deviceId: deviceId, port: port }]))[deviceId]); - const _socket = new net.Socket(); - _socket.connect(deviceResponse.port, deviceResponse.host); - this.socket = _socket; - _socket.on("close", () => { + this.socket = new net.Socket(); + this.socket.connect(deviceResponse.port, deviceResponse.host); + this.socket.on("close", () => { this.socket = null; - this.$logger.info("iOS Device socket closed!"); }); this.$processService.attachToProcessExitSignals(this, this.destroySocket); diff --git a/lib/common/mobile/ios/simulator/ios-emulator-services.ts b/lib/common/mobile/ios/simulator/ios-emulator-services.ts index a8f83faaa6..52004fb940 100644 --- a/lib/common/mobile/ios/simulator/ios-emulator-services.ts +++ b/lib/common/mobile/ios/simulator/ios-emulator-services.ts @@ -1,10 +1,7 @@ import * as net from "net"; -import { connectEventuallyUntilTimeout } from "../../../helpers"; import { APPLE_VENDOR_NAME, DeviceTypes, RUNNING_EMULATOR_STATUS, NOT_RUNNING_EMULATOR_STATUS } from "../../../constants"; class IosEmulatorServices implements Mobile.IiOSSimulatorService { - private static DEFAULT_TIMEOUT = 10000; - constructor(private $logger: ILogger, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $iOSSimResolver: Mobile.IiOSSimResolver, @@ -72,7 +69,7 @@ class IosEmulatorServices implements Mobile.IiOSSimulatorService { public async connectToPort(data: Mobile.IConnectToPortData): Promise { try { - const socket = await connectEventuallyUntilTimeout(async () => net.connect(data.port), data.timeout || IosEmulatorServices.DEFAULT_TIMEOUT); + const socket = net.connect(data.port); return socket; } catch (e) { this.$logger.debug(e); diff --git a/lib/common/mobile/ios/simulator/ios-simulator-device.ts b/lib/common/mobile/ios/simulator/ios-simulator-device.ts index 2b56d93ddf..d8f0dcda8e 100644 --- a/lib/common/mobile/ios/simulator/ios-simulator-device.ts +++ b/lib/common/mobile/ios/simulator/ios-simulator-device.ts @@ -3,36 +3,58 @@ import * as fileSystemPath from "./ios-simulator-file-system"; import * as constants from "../../../constants"; import * as net from "net"; import { cache } from "../../../decorators"; +import * as helpers from "../../../../common/helpers"; export class IOSSimulator implements Mobile.IiOSDevice { + private static socketConnectionTimeout = 30000; private _applicationManager: Mobile.IDeviceApplicationManager; private _fileSystem: Mobile.IDeviceFileSystem; private socket: net.Socket; - // private static sockets: { [id: string]: net.Socket; } = {}; - - // get socket(): net.Socket { - // return IOSSimulator.sockets[this.deviceInfo.identifier]; - // } - // set socket(newSocket: net.Socket) { - // IOSSimulator.sockets[this.deviceInfo.identifier] = newSocket; - // } - constructor(private simulator: Mobile.IiSimDevice, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $errors: IErrors, private $injector: IInjector, + private $iOSDebuggerPortService: IIOSDebuggerPortService, private $iOSSimResolver: Mobile.IiOSSimResolver, private $iOSEmulatorServices: Mobile.IiOSSimulatorService, + private $iOSNotification: IiOSNotification, private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider, private $logger: ILogger) { } - public async connectToPort(port: number): Promise { - console.log("connectToPort"); - this.socket = await this.$iOSEmulatorServices.connectToPort({ port }); - this.socket.on("close", () => { - this.socket = null; - this.$logger.info("iOS Simulator socket closed!"); - }); + public async getLiveSyncSocket(appId: string, projectDir: string): Promise { + return this.getSocket(appId, projectDir); + } + + public async getDebugSocket(appId: string, projectDir: string): Promise { + return this.getSocket(appId, projectDir); + } + + private async getSocket(appId: string, projectDir: string): Promise { + if (this.socket) { + return this.socket; + } + + const attachRequestMessage = this.$iOSNotification.getAttachRequest(appId, this.deviceInfo.identifier); + await this.$iOSEmulatorServices.postDarwinNotification(attachRequestMessage, this.deviceInfo.identifier); + const port = await this.$iOSDebuggerPortService.getPort({ projectDir, deviceId: this.deviceInfo.identifier, appId }); + if (!port) { + this.$errors.fail("NativeScript debugger was not able to get inspector socket port."); + } + + try { + this.socket = await helpers.connectEventuallyUntilTimeout( + async () => { return this.$iOSEmulatorServices.connectToPort({ port }) }, + IOSSimulator.socketConnectionTimeout); + } catch (e) { + this.$logger.warn(e); + } + + if (this.socket) { + this.socket.on("close", () => { + this.socket = null; + }); + } return this.socket; } diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/socket-proxy-factory.ts index ad39c7b9b5..680335231b 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/socket-proxy-factory.ts @@ -3,7 +3,6 @@ import { CONNECTION_ERROR_EVENT_NAME } from "../../constants"; import { PacketStream } from "./packet-stream"; import * as net from "net"; import * as ws from "ws"; -import * as helpers from "../../common/helpers"; import temp = require("temp"); export class SocketProxyFactory extends EventEmitter implements ISocketProxyFactory { @@ -12,7 +11,6 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact constructor(private $logger: ILogger, private $errors: IErrors, - private $iOSDeviceSocketService: Mobile.IiOSDeviceSocketsService, private $options: IOptions, private $net: INet) { super(); @@ -32,9 +30,6 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact throw new Error(`TCP socket proxy is already running for device '${deviceIdentifier}'`); } - // await here? - const socketFactory = async (callback: (_socket: net.Socket) => void) => helpers.connectEventually(factory, callback); - this.$logger.info("\nSetting up proxy...\nPress Ctrl + C to terminate, or disconnect.\n"); const server = net.createServer({ @@ -45,7 +40,6 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact server.on("connection", async (frontendSocket: net.Socket) => { this.$logger.info("Frontend client connected."); - frontendSocket.on("end", () => { this.$logger.info('Frontend socket closed!'); if (!this.$options.watch) { @@ -53,34 +47,33 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact } }); - await socketFactory((backendSocket: net.Socket) => { - this.$logger.info("Backend socket created."); + const backendSocket = await factory(); + this.$logger.info("Backend socket created."); + + backendSocket.on("end", () => { + this.$logger.info("Backend socket closed!"); + if (!this.$options.watch) { + process.exit(0); + } + }); - backendSocket.on("end", () => { - this.$logger.info("Backend socket closed!"); - if (!this.$options.watch) { - process.exit(0); - } - }); - - frontendSocket.on("close", () => { - this.$logger.info("Frontend socket closed"); - if (!(backendSocket).destroyed) { - backendSocket.destroy(); - } - }); - - backendSocket.on("close", () => { - this.$logger.info("Backend socket closed"); - if (!(frontendSocket).destroyed) { - frontendSocket.destroy(); - } - }); - - backendSocket.pipe(frontendSocket); - frontendSocket.pipe(backendSocket); - frontendSocket.resume(); + frontendSocket.on("close", () => { + this.$logger.info("Frontend socket closed"); + if (!(backendSocket).destroyed) { + backendSocket.destroy(); + } }); + + backendSocket.on("close", () => { + this.$logger.info("Backend socket closed"); + if (!(frontendSocket).destroyed) { + frontendSocket.destroy(); + } + }); + + backendSocket.pipe(frontendSocket); + frontendSocket.pipe(backendSocket); + frontendSocket.resume(); }); const socketFileLocation = temp.path({ suffix: ".sock" }); @@ -115,8 +108,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact this.$logger.info("Frontend client connected."); let _socket; try { - const existingServerSocket = this.$iOSDeviceSocketService.getSocket(deviceIdentifier); - _socket = existingServerSocket || await helpers.connectEventuallyUntilTimeout(factory, 10000); + _socket = await factory(); } catch (err) { err.deviceIdentifier = deviceIdentifier; this.$logger.trace(err); @@ -166,8 +158,6 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact this.$logger.info('Frontend socket closed!'); packets.destroy(); deviceSocket.destroy(); - // delete this.deviceWebServers[deviceIdentifier]; - // server.close(); if (!this.$options.watch) { process.exit(0); } diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index b965545710..de09f5cab6 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -25,9 +25,6 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private $logger: ILogger, private $errors: IErrors, private $packageInstallationManager: IPackageInstallationManager, - private $iOSDebuggerPortService: IIOSDebuggerPortService, - private $iOSDeviceSocketService: Mobile.IiOSDeviceSocketsService, - private $iOSNotification: IiOSNotification, private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, private $processService: IProcessService, private $socketProxyFactory: ISocketProxyFactory, @@ -71,7 +68,8 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS public async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); - const action = async (device: Mobile.IiOSDevice) => device.isEmulator ? await this.emulatorDebugBrk(debugData, debugOptions) : await this.debugBrkCore(device, debugData, debugOptions); + // TODO: this.device + const action = async (device: Mobile.IiOSDevice) => device.isEmulator ? await this.emulatorDebugBrk(debugData, debugOptions) : await this.debugBrkCore(debugData, debugOptions); await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); } @@ -158,15 +156,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } private async emulatorStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - const isServerSocketConnected = !!this.$iOSDeviceSocketService.getSocket(debugData.deviceIdentifier); const debugUrl = await this.wireDebuggerClient(debugData, debugOptions); - // tODO: !result.wasAlreadyRunning || ?? - if (!isServerSocketConnected) { - console.log("attach debug socket on emulator"); - const attachRequestMessage = this.$iOSNotification.getAttachRequest(debugData.applicationIdentifier, debugData.deviceIdentifier); - const iOSEmulatorService = this.$iOSEmulatorServices; - await iOSEmulatorService.postDarwinNotification(attachRequestMessage, debugData.deviceIdentifier); - } return debugUrl; } @@ -186,47 +176,42 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS const promisesResults = await Promise.all([ this.$platformService.startApplication(this.platform, runOptions, { appId: debugData.applicationIdentifier, projectName: projectData.projectName }), - this.debugBrkCore(device, debugData, debugOptions) + this.debugBrkCore(debugData, debugOptions) ]); return _.last(promisesResults); }; + // TODO: this.device const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); return deviceActionResult[0].result; } - private async debugBrkCore(device: Mobile.IiOSDevice, debugData: IDebugData, debugOptions: IDebugOptions): Promise { - await this.$iOSSocketRequestExecutor.executeLaunchRequest(device.deviceInfo.identifier, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, debugData.applicationIdentifier, debugOptions); - const debugUrl = await this.wireDebuggerClient(debugData, debugOptions, device); + private async debugBrkCore(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + await this.$iOSSocketRequestExecutor.executeLaunchRequest(this.device.deviceInfo.identifier, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, debugData.applicationIdentifier, debugOptions); + const debugUrl = await this.wireDebuggerClient(debugData, debugOptions); return debugUrl; } private async deviceStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); - const action = async (device: Mobile.IiOSDevice) => device.isEmulator ? await this.emulatorStart(debugData, debugOptions) : await this.deviceStartCore(device, debugData, debugOptions); + const action = async (device: Mobile.IiOSDevice) => device.isEmulator ? await this.emulatorStart(debugData, debugOptions) : await this.deviceStartCore(debugData, debugOptions); const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); return deviceActionResult[0].result; } - private async deviceStartCore(device: Mobile.IiOSDevice, debugData: IDebugData, debugOptions: IDebugOptions): Promise { - const deviceIdentifier = device ? device.deviceInfo.identifier : debugData.deviceIdentifier; - if (!this.$iOSDeviceSocketService.getSocket(deviceIdentifier)) { - console.log("attach debug socket on device"); - await this.$iOSSocketRequestExecutor.executeAttachRequest(device, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, debugData.applicationIdentifier); - } - - const debugUrl = await this.wireDebuggerClient(debugData, debugOptions, device); + private async deviceStartCore(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + const debugUrl = await this.wireDebuggerClient(debugData, debugOptions); return debugUrl; } - private async wireDebuggerClient(debugData: IDebugData, debugOptions: IDebugOptions, device?: Mobile.IiOSDevice): Promise { + private async wireDebuggerClient(debugData: IDebugData, debugOptions: IDebugOptions): Promise { // the VSCode Ext starts `tns debug ios --no-client` to start/attach to debug sessions // check if --no-client is passed - default to opening a tcp socket (versus Chrome DevTools (websocket)) - const deviceIdentifier = device ? device.deviceInfo.identifier : debugData.deviceIdentifier; + const deviceIdentifier = this.device ? this.device.deviceInfo.identifier : debugData.deviceIdentifier; if ((debugOptions.inspector || !debugOptions.client) && this.$hostInfo.isDarwin) { const existingProxy = this.$socketProxyFactory.getTCPSocketProxy(deviceIdentifier); - this._socketProxy = existingProxy || await this.$socketProxyFactory.addTCPSocketProxy(this.getSocketFactory(device, debugData, debugOptions), deviceIdentifier); + this._socketProxy = existingProxy || await this.$socketProxyFactory.addTCPSocketProxy(this.getSocketFactory(debugData, debugOptions), deviceIdentifier); if (!existingProxy) { await this.openAppInspector(this._socketProxy.address(), debugData, debugOptions); @@ -239,7 +224,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } const existingProxy = this.$socketProxyFactory.getWebSocketProxy(deviceIdentifier); - this._socketProxy = existingProxy || await this.$socketProxyFactory.addWebSocketProxy(this.getSocketFactory(device, debugData, debugOptions), deviceIdentifier); + this._socketProxy = existingProxy || await this.$socketProxyFactory.addWebSocketProxy(this.getSocketFactory(debugData, debugOptions), deviceIdentifier); return this.getChromeDebugUrl(debugOptions, this._socketProxy.options.port); } } @@ -258,23 +243,14 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } } - private getSocketFactory(device: Mobile.IiOSDevice, debugData: IDebugData, debugOptions: IDebugOptions): () => Promise { + private getSocketFactory(debugData: IDebugData, debugOptions: IDebugOptions): () => Promise { let pendingExecution: Promise = null; const factory = async () => { if (!pendingExecution) { const func = async () => { - const port = await this.$iOSDebuggerPortService.getPort({ projectDir: debugData.projectDir, deviceId: debugData.deviceIdentifier, appId: debugData.applicationIdentifier }, debugOptions); - if (!port) { - this.$errors.fail("NativeScript debugger was not able to get inspector socket port."); - } - - const deviceIdentifier = device ? device.deviceInfo.identifier : debugData.deviceIdentifier; - const socket = this.$iOSDeviceSocketService.getSocket(deviceIdentifier) || - (device ? await device.connectToPort(port) : net.connect(port)); - console.log("port", port); + const socket = await this.device.getDebugSocket(debugData.applicationIdentifier, debugData.projectDir); this._sockets.push(socket); pendingExecution = null; - this.$iOSDeviceSocketService.addSocket(deviceIdentifier, socket); return socket; }; pendingExecution = func(); diff --git a/lib/services/ios-device-socket-service.ts b/lib/services/ios-device-socket-service.ts deleted file mode 100644 index c42aa8cb3a..0000000000 --- a/lib/services/ios-device-socket-service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as net from "net"; - -export class IOSDeviceSocketService implements Mobile.IiOSDeviceSocketsService { - private static sockets: { [id: string]: net.Socket; } = {}; - - constructor() { - } - - getSocket(deviceId: string): net.Socket { - return IOSDeviceSocketService.sockets[deviceId]; - } - - addSocket(deviceId: string, socket: any): void { - if (IOSDeviceSocketService.sockets[deviceId] === socket) { - return; - } - - console.log("cached socket"); - IOSDeviceSocketService.sockets[deviceId] = socket; - socket.on("close", () => { - console.log("delete cached socket"); - delete IOSDeviceSocketService.sockets[deviceId]; - }); - } - -} - -$injector.register("iOSDeviceSocketService", IOSDeviceSocketService); \ No newline at end of file diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index a52a9742b8..88d20d429a 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -9,11 +9,6 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen private socket: net.Socket; constructor( - private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, - private $iOSNotification: IiOSNotification, - private $iOSEmulatorServices: Mobile.IiOSSimulatorService, - private $iOSDebuggerPortService: IIOSDebuggerPortService, - private $iOSDeviceSocketService: Mobile.IiOSDeviceSocketsService, private $logger: ILogger, private $processService: IProcessService, protected $platformsData: IPlatformsData, @@ -27,27 +22,11 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen } const appId = projectData.projectIdentifiers.ios; - const existingDeviceSocket = this.$iOSDeviceSocketService.getSocket(this.device.deviceInfo.identifier); - if (existingDeviceSocket) { - this.socket = existingDeviceSocket; - console.log("will use existing socket"); - } else if (this.device.isEmulator) { - await this.$iOSEmulatorServices.postDarwinNotification(this.$iOSNotification.getAttachRequest(appId, this.device.deviceInfo.identifier), this.device.deviceInfo.identifier); - const port = await this.$iOSDebuggerPortService.getPort({ projectDir: projectData.projectDir, deviceId: this.device.deviceInfo.identifier, appId }); - this.socket = await this.device.connectToPort(port); - console.log("port", port); - if (!this.socket) { - return false; - } - } else { - await this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, constants.AWAIT_NOTIFICATION_TIMEOUT_SECONDS, appId); - const port = await this.$iOSDebuggerPortService.getPort({ projectDir: projectData.projectDir, deviceId: this.device.deviceInfo.identifier, appId }); - this.socket = await this.device.connectToPort(port); - console.log("port", port); + this.socket = await this.device.getLiveSyncSocket(appId, projectData.projectDir); + if (!this.socket) { + return false; } - this.$iOSDeviceSocketService.addSocket(this.device.deviceInfo.identifier, this.socket); - this.attachEventHandlers(); return true; diff --git a/test/services/ios-debug-service.ts b/test/services/ios-debug-service.ts index 50fb06daf2..53c75925b7 100644 --- a/test/services/ios-debug-service.ts +++ b/test/services/ios-debug-service.ts @@ -14,17 +14,13 @@ class IOSDebugServiceInheritor extends IOSDebugService { $logger: ILogger, $errors: IErrors, $packageInstallationManager: IPackageInstallationManager, - $iOSDebuggerPortService: IIOSDebuggerPortService, - $iOSDeviceSocketService: Mobile.IiOSDeviceSocketsService, - $iOSNotification: IiOSNotification, $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, $processService: IProcessService, $socketProxyFactory: ISocketProxyFactory, - $net: INet, $projectDataService: IProjectDataService, $deviceLogProvider: Mobile.IDeviceLogProvider) { super({}, $devicesService, $platformService, $iOSEmulatorServices, $childProcess, $hostInfo, $logger, $errors, - $packageInstallationManager, $iOSDebuggerPortService, $iOSDeviceSocketService, $iOSNotification, $iOSSocketRequestExecutor, + $packageInstallationManager, $iOSSocketRequestExecutor, $processService, $socketProxyFactory, $projectDataService, $deviceLogProvider); } From 6d6bce792327008fa08856b83fb778c49d526de1 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Wed, 7 Nov 2018 15:03:23 +0200 Subject: [PATCH 04/17] refactor: remove sockets knowledge from the ios-debug-service --- .vscode/launch.json | 2 +- .../ios/socket-proxy-factory.ts | 8 +++ lib/services/ios-debug-service.ts | 52 ++++--------------- 3 files changed, 20 insertions(+), 42 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 56a47250b9..0602e8baec 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ "args": [ "debug", "ios", - "--hmr" + "--debug-brk" ] // "args": [ "test", "android", "--justlaunch"] // "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"] diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/socket-proxy-factory.ts index 680335231b..be6c9f7ba8 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/socket-proxy-factory.ts @@ -171,6 +171,14 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact } public removeAllProxies() { + for (var deviceId in this.deviceWebServers) { + this.deviceWebServers[deviceId].close(); + } + + for (var deviceId in this.deviceTcpServers) { + this.deviceTcpServers[deviceId].close(); + } + this.deviceWebServers = {}; this.deviceTcpServers = {}; } diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index de09f5cab6..78ea92a3f7 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -1,5 +1,4 @@ import * as iOSDevice from "../common/mobile/ios/device/ios-device"; -import * as net from "net"; import * as path from "path"; import * as log4js from "log4js"; import { ChildProcess } from "child_process"; @@ -13,8 +12,6 @@ const inspectorUiDir = "WebInspectorUI/"; export class IOSDebugService extends DebugServiceBase implements IPlatformDebugService { private _lldbProcess: ChildProcess; - private _sockets: net.Socket[] = []; - private _socketProxy: any; constructor(protected device: Mobile.IiOSDevice, protected $devicesService: Mobile.IDevicesService, @@ -74,19 +71,10 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } public async debugStop(): Promise { - if (this._socketProxy) { - this._socketProxy.close(); - this._socketProxy = null; - } - - _.forEach(this._sockets, socket => socket.destroy()); - - this._sockets = []; this.$socketProxyFactory.removeAllProxies(); if (this._lldbProcess) { this._lldbProcess.stdin.write("process detach\n"); - await this.killProcess(this._lldbProcess); this._lldbProcess = undefined; } @@ -210,11 +198,11 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS // check if --no-client is passed - default to opening a tcp socket (versus Chrome DevTools (websocket)) const deviceIdentifier = this.device ? this.device.deviceInfo.identifier : debugData.deviceIdentifier; if ((debugOptions.inspector || !debugOptions.client) && this.$hostInfo.isDarwin) { - const existingProxy = this.$socketProxyFactory.getTCPSocketProxy(deviceIdentifier); - this._socketProxy = existingProxy || await this.$socketProxyFactory.addTCPSocketProxy(this.getSocketFactory(debugData, debugOptions), deviceIdentifier); - - if (!existingProxy) { - await this.openAppInspector(this._socketProxy.address(), debugData, debugOptions); + const existingTcpProxy = this.$socketProxyFactory.getTCPSocketProxy(deviceIdentifier); + const getDeviceSocket = async () => await this.device.getDebugSocket(debugData.applicationIdentifier, debugData.projectDir); + const tcpSocketProxy = existingTcpProxy || await this.$socketProxyFactory.addTCPSocketProxy(getDeviceSocket, deviceIdentifier); + if (!existingTcpProxy) { + await this.openAppInspector(tcpSocketProxy.address(), debugData, debugOptions); } return null; @@ -223,9 +211,11 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS this.$logger.info("'--chrome' is the default behavior. Use --inspector to debug iOS applications using the Safari Web Inspector."); } - const existingProxy = this.$socketProxyFactory.getWebSocketProxy(deviceIdentifier); - this._socketProxy = existingProxy || await this.$socketProxyFactory.addWebSocketProxy(this.getSocketFactory(debugData, debugOptions), deviceIdentifier); - return this.getChromeDebugUrl(debugOptions, this._socketProxy.options.port); + const existingWebProxy = this.$socketProxyFactory.getWebSocketProxy(deviceIdentifier); + const getDeviceSocket = async () => await this.device.getDebugSocket(debugData.applicationIdentifier, debugData.projectDir); + const webSocketProxy = existingWebProxy || await this.$socketProxyFactory.addWebSocketProxy(getDeviceSocket, deviceIdentifier); + + return this.getChromeDebugUrl(debugOptions, webSocketProxy.options.port); } } @@ -242,26 +232,6 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS this.$logger.info("Suppressing debugging client."); } } - - private getSocketFactory(debugData: IDebugData, debugOptions: IDebugOptions): () => Promise { - let pendingExecution: Promise = null; - const factory = async () => { - if (!pendingExecution) { - const func = async () => { - const socket = await this.device.getDebugSocket(debugData.applicationIdentifier, debugData.projectDir); - this._sockets.push(socket); - pendingExecution = null; - return socket; - }; - pendingExecution = func(); - } - - return pendingExecution; - }; - - factory.bind(this); - return factory; - } } -$injector.register("iOSDebugService", IOSDebugService, false); +$injector.register("iOSDebugService", IOSDebugService, false); \ No newline at end of file From 9bea8acd1a790d68c5bf4676507abb6c10eb0e6d Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 8 Nov 2018 11:45:22 +0200 Subject: [PATCH 05/17] refactor: remove most of the emulator specific logic from the ios-debug-service --- lib/common/constants.ts | 3 + lib/common/definitions/mobile.d.ts | 5 - lib/common/mobile/ios/device/ios-device.ts | 12 ++- .../ios/simulator/ios-simulator-device.ts | 3 +- lib/definitions/debug.d.ts | 1 + .../ios/socket-request-executor.ts | 2 +- lib/services/ios-debug-service.ts | 97 +++++-------------- test/services/ios-debug-service.ts | 4 +- 8 files changed, 41 insertions(+), 86 deletions(-) diff --git a/lib/common/constants.ts b/lib/common/constants.ts index 272291a758..f498df37a9 100644 --- a/lib/common/constants.ts +++ b/lib/common/constants.ts @@ -156,4 +156,7 @@ export class AndroidVirtualDevice { static GENYMOTION_DEFAULT_STDERR_STRING = "Logging activities to file"; static UNABLE_TO_START_EMULATOR_MESSAGE = "Cannot run the app in the selected native emulator. Try to restart the adb server by running the `adb kill-server` command in the Command Prompt, or increase the allocated RAM of the virtual device through the Android Virtual Device manager. NativeScript CLI users can try to increase the timeout of the operation by adding the `--timeout` flag."; + } + +export const SOCKET_CONNECTION_TIMEOUT_MS = 30000; diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index 172700386b..5f31af8dc0 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -116,11 +116,6 @@ declare module Mobile { openDeviceLogStream(options?: IiOSLogStreamOptions): Promise; } - interface IiOSDeviceSocketsService { - getSocket(deviceId: string): any; - addSocket(deviceId: string, socket: any): void; - } - interface IAndroidDevice extends IDevice { adb: Mobile.IDeviceAndroidDebugBridge; init(): Promise; diff --git a/lib/common/mobile/ios/device/ios-device.ts b/lib/common/mobile/ios/device/ios-device.ts index 4670c91cfb..61e067bab5 100644 --- a/lib/common/mobile/ios/device/ios-device.ts +++ b/lib/common/mobile/ios/device/ios-device.ts @@ -4,6 +4,7 @@ import * as commonConstants from "../../../constants"; import * as constants from "../../../../constants"; import * as net from "net"; import { cache } from "../../../decorators"; +import * as helpers from "../../../../common/helpers"; export class IOSDevice implements Mobile.IiOSDevice { public applicationManager: Mobile.IDeviceApplicationManager; @@ -96,10 +97,15 @@ export class IOSDevice implements Mobile.IiOSDevice { } const deviceId = this.deviceInfo.identifier; - const deviceResponse = _.first((await this.$iosDeviceOperations.connectToPort([{ deviceId: deviceId, port: port }]))[deviceId]); + this.socket = await helpers.connectEventuallyUntilTimeout( + async () => { + const deviceResponse = _.first((await this.$iosDeviceOperations.connectToPort([{ deviceId: deviceId, port: port }]))[deviceId]); + const _socket = new net.Socket(); + _socket.connect(deviceResponse.port, deviceResponse.host); + return _socket; + }, + commonConstants.SOCKET_CONNECTION_TIMEOUT_MS); - this.socket = new net.Socket(); - this.socket.connect(deviceResponse.port, deviceResponse.host); this.socket.on("close", () => { this.socket = null; }); diff --git a/lib/common/mobile/ios/simulator/ios-simulator-device.ts b/lib/common/mobile/ios/simulator/ios-simulator-device.ts index d8f0dcda8e..7fcb0759aa 100644 --- a/lib/common/mobile/ios/simulator/ios-simulator-device.ts +++ b/lib/common/mobile/ios/simulator/ios-simulator-device.ts @@ -6,7 +6,6 @@ import { cache } from "../../../decorators"; import * as helpers from "../../../../common/helpers"; export class IOSSimulator implements Mobile.IiOSDevice { - private static socketConnectionTimeout = 30000; private _applicationManager: Mobile.IDeviceApplicationManager; private _fileSystem: Mobile.IDeviceFileSystem; private socket: net.Socket; @@ -45,7 +44,7 @@ export class IOSSimulator implements Mobile.IiOSDevice { try { this.socket = await helpers.connectEventuallyUntilTimeout( async () => { return this.$iOSEmulatorServices.connectToPort({ port }) }, - IOSSimulator.socketConnectionTimeout); + constants.SOCKET_CONNECTION_TIMEOUT_MS); } catch (e) { this.$logger.warn(e); } diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 350094fa27..4ddcb86f67 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -37,6 +37,7 @@ interface IDebugOptions { */ stop?: boolean; + // TODO: remove /** * Defines if debug process is for emulator (not for real device). */ diff --git a/lib/device-sockets/ios/socket-request-executor.ts b/lib/device-sockets/ios/socket-request-executor.ts index 10914289ff..7b5e1c3486 100644 --- a/lib/device-sockets/ios/socket-request-executor.ts +++ b/lib/device-sockets/ios/socket-request-executor.ts @@ -19,7 +19,7 @@ export class IOSSocketRequestExecutor implements IiOSSocketRequestExecutor { const observeNotificationPromises = _(observeNotificationSockets) .uniq() .map(s => { - return this.$iOSNotificationService.awaitNotification(deviceIdentifier, +s, timeout); + return this.$iOSNotificationService.awaitNotification(deviceIdentifier, s, timeout); }) .value(); diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index 78ea92a3f7..e185a2dbb9 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -1,10 +1,9 @@ -import * as iOSDevice from "../common/mobile/ios/device/ios-device"; import * as path from "path"; import * as log4js from "log4js"; import { ChildProcess } from "child_process"; import { DebugServiceBase } from "./debug-service-base"; import { IOS_LOG_PREDICATE } from "../common/constants"; -import { CONNECTION_ERROR_EVENT_NAME, AWAIT_NOTIFICATION_TIMEOUT_SECONDS } from "../constants"; +import { CONNECTION_ERROR_EVENT_NAME } from "../constants"; import { getPidFromiOSSimulatorLogs } from "../common/helpers"; const inspectorAppName = "NativeScript Inspector.app"; const inspectorNpmPackageName = "tns-ios-inspector"; @@ -22,7 +21,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private $logger: ILogger, private $errors: IErrors, private $packageInstallationManager: IPackageInstallationManager, - private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, + private $iOSDebuggerPortService: IIOSDebuggerPortService, private $processService: IProcessService, private $socketProxyFactory: ISocketProxyFactory, private $projectDataService: IProjectDataService, @@ -42,32 +41,28 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS this.$errors.failWithoutHelp("Expected exactly one of the --debug-brk or --start options."); } - if (this.$devicesService.isOnlyiOSSimultorRunning() || this.$devicesService.deviceCount === 0) { - debugOptions.emulator = true; - } - await this.startDeviceLogProcess(debugData, debugOptions); + await this.$iOSDebuggerPortService.attachToDebuggerPortFoundEvent(this.device, debugData, debugOptions); - if (debugOptions.emulator) { - if (debugOptions.start) { - return this.emulatorStart(debugData, debugOptions); + if (!debugOptions.start) { // not attach + if (this.device.isEmulator) { + await this.startAppOnSimulator(debugData, debugOptions); } else { - return this.emulatorDebugBrk(debugData, debugOptions); - } - } else { - if (debugOptions.start) { - return this.deviceStart(debugData, debugOptions); - } else { - return this.deviceDebugBrk(debugData, debugOptions); + await this.startAppOnDevice(debugData, debugOptions); } } + + return this.wireDebuggerClient(debugData, debugOptions); } public async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); - // TODO: this.device - const action = async (device: Mobile.IiOSDevice) => device.isEmulator ? await this.emulatorDebugBrk(debugData, debugOptions) : await this.debugBrkCore(debugData, debugOptions); - await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); + if (this.device.isEmulator) { + await this.startAppOnSimulator(debugData, debugOptions); + } else { + await this.startAppOnDevice(debugData, debugOptions); + } + + await this.wireDebuggerClient(debugData, debugOptions); } public async debugStop(): Promise { @@ -117,7 +112,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } } - private async emulatorDebugBrk(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + private async startAppOnSimulator(debugData: IDebugData, debugOptions: IDebugOptions): Promise { const args = debugOptions.debugBrk ? "--nativescript-debug-brk" : "--nativescript-debug-start"; const launchResult = await this.$iOSEmulatorServices.runApplicationOnEmulator(debugData.pathToAppPackage, { waitForDebugger: true, @@ -130,7 +125,6 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS timeout: debugOptions.timeout, sdk: debugOptions.sdk }); - const pid = getPidFromiOSSimulatorLogs(debugData.applicationIdentifier, launchResult); this._lldbProcess = this.$childProcess.spawn("lldb", ["-p", pid]); if (log4js.levels.TRACE.isGreaterThanOrEqualTo(this.$logger.getLevel())) { @@ -138,59 +132,16 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } this._lldbProcess.stderr.pipe(process.stderr); this._lldbProcess.stdin.write("process continue\n"); - - const debugUrl = await this.wireDebuggerClient(debugData, debugOptions); - return debugUrl; } - private async emulatorStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - const debugUrl = await this.wireDebuggerClient(debugData, debugOptions); - return debugUrl; - } - - private async deviceDebugBrk(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); - const projectData = this.$projectDataService.getProjectData(debugData.projectDir); - const action = async (device: iOSDevice.IOSDevice): Promise => { - if (device.isEmulator) { - return await this.emulatorDebugBrk(debugData, debugOptions); - } - - const runOptions: IRunPlatformOptions = { - device: debugData.deviceIdentifier, - emulator: debugOptions.emulator, - justlaunch: debugOptions.justlaunch - }; - - const promisesResults = await Promise.all([ - this.$platformService.startApplication(this.platform, runOptions, { appId: debugData.applicationIdentifier, projectName: projectData.projectName }), - this.debugBrkCore(debugData, debugOptions) - ]); - - return _.last(promisesResults); + private async startAppOnDevice(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + const runOptions: IRunPlatformOptions = { + device: debugData.deviceIdentifier, + emulator: this.device.isEmulator, + justlaunch: debugOptions.justlaunch }; - - // TODO: this.device - const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); - return deviceActionResult[0].result; - } - - private async debugBrkCore(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - await this.$iOSSocketRequestExecutor.executeLaunchRequest(this.device.deviceInfo.identifier, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, AWAIT_NOTIFICATION_TIMEOUT_SECONDS, debugData.applicationIdentifier, debugOptions); - const debugUrl = await this.wireDebuggerClient(debugData, debugOptions); - return debugUrl; - } - - private async deviceStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); - const action = async (device: Mobile.IiOSDevice) => device.isEmulator ? await this.emulatorStart(debugData, debugOptions) : await this.deviceStartCore(debugData, debugOptions); - const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); - return deviceActionResult[0].result; - } - - private async deviceStartCore(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - const debugUrl = await this.wireDebuggerClient(debugData, debugOptions); - return debugUrl; + const projectData = this.$projectDataService.getProjectData(debugData.projectDir); + await this.$platformService.startApplication(this.platform, runOptions, { appId: debugData.applicationIdentifier, projectName: projectData.projectName }); } private async wireDebuggerClient(debugData: IDebugData, debugOptions: IDebugOptions): Promise { diff --git a/test/services/ios-debug-service.ts b/test/services/ios-debug-service.ts index 53c75925b7..8ef0b695a9 100644 --- a/test/services/ios-debug-service.ts +++ b/test/services/ios-debug-service.ts @@ -14,13 +14,13 @@ class IOSDebugServiceInheritor extends IOSDebugService { $logger: ILogger, $errors: IErrors, $packageInstallationManager: IPackageInstallationManager, - $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, + $iOSDebuggerPortService: IIOSDebuggerPortService, $processService: IProcessService, $socketProxyFactory: ISocketProxyFactory, $projectDataService: IProjectDataService, $deviceLogProvider: Mobile.IDeviceLogProvider) { super({}, $devicesService, $platformService, $iOSEmulatorServices, $childProcess, $hostInfo, $logger, $errors, - $packageInstallationManager, $iOSSocketRequestExecutor, + $packageInstallationManager, $iOSDebuggerPortService, $processService, $socketProxyFactory, $projectDataService, $deviceLogProvider); } From 670f1c8c94440072d0e9bbba8a246e72988cc1d0 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 8 Nov 2018 14:54:03 +0200 Subject: [PATCH 06/17] refactor: unify deviceId usage in ios-debug-service --- lib/definitions/debug.d.ts | 1 + lib/services/ios-debug-service.ts | 84 +++++++++++++++++-------------- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 4ddcb86f67..4d225872b2 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -1,3 +1,4 @@ +// TODO: remove IDeviceIdentifier /** * Describes information for starting debug process. */ diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index e185a2dbb9..f1a6a724e6 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -11,6 +11,7 @@ const inspectorUiDir = "WebInspectorUI/"; export class IOSDebugService extends DebugServiceBase implements IPlatformDebugService { private _lldbProcess: ChildProcess; + private deviceIdentifier: string; constructor(protected device: Mobile.IiOSDevice, protected $devicesService: Mobile.IDevicesService, @@ -26,9 +27,11 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private $socketProxyFactory: ISocketProxyFactory, private $projectDataService: IProjectDataService, private $deviceLogProvider: Mobile.IDeviceLogProvider) { + super(device, $devicesService); this.$processService.attachToProcessExitSignals(this, this.debugStop); this.$socketProxyFactory.on(CONNECTION_ERROR_EVENT_NAME, (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e)); + this.deviceIdentifier = this.device.deviceInfo.identifier; } public get platform(): string { @@ -36,15 +39,12 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - - if (debugOptions.debugBrk && debugOptions.start) { - this.$errors.failWithoutHelp("Expected exactly one of the --debug-brk or --start options."); - } + this.validateOptions(debugOptions); await this.startDeviceLogProcess(debugData, debugOptions); await this.$iOSDebuggerPortService.attachToDebuggerPortFoundEvent(this.device, debugData, debugOptions); - if (!debugOptions.start) { // not attach + if (!debugOptions.start) { if (this.device.isEmulator) { await this.startAppOnSimulator(debugData, debugOptions); } else { @@ -67,12 +67,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS public async debugStop(): Promise { this.$socketProxyFactory.removeAllProxies(); - - if (this._lldbProcess) { - this._lldbProcess.stdin.write("process detach\n"); - await this.killProcess(this._lldbProcess); - this._lldbProcess = undefined; - } + await this.stopAppDebuggerOnSimulator(); } protected getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { @@ -83,11 +78,17 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS return chromeDebugUrl; } + private validateOptions(debugOptions: IDebugOptions) { + if (debugOptions.debugBrk && debugOptions.start) { + this.$errors.failWithoutHelp("Expected exactly one of the --debug-brk or --start options."); + } + } + private async startDeviceLogProcess(debugData: IDebugData, debugOptions: IDebugOptions): Promise { if (debugOptions.justlaunch) { // No logs should be printed on console when `--justlaunch` option is passed. // On the other side we need to start log process in order to get debugger port from logs. - this.$deviceLogProvider.muteLogsForDevice(debugData.deviceIdentifier); + this.$deviceLogProvider.muteLogsForDevice(this.deviceIdentifier); } let projectName = debugData.projectName; @@ -97,21 +98,12 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } if (projectName) { - this.$deviceLogProvider.setProjectNameForDevice(debugData.deviceIdentifier, projectName); + this.$deviceLogProvider.setProjectNameForDevice(this.deviceIdentifier, projectName); } await this.device.openDeviceLogStream({ predicate: IOS_LOG_PREDICATE }); } - private async killProcess(childProcess: ChildProcess): Promise { - if (childProcess) { - return new Promise((resolve, reject) => { - childProcess.on("close", resolve); - childProcess.kill(); - }); - } - } - private async startAppOnSimulator(debugData: IDebugData, debugOptions: IDebugOptions): Promise { const args = debugOptions.debugBrk ? "--nativescript-debug-brk" : "--nativescript-debug-start"; const launchResult = await this.$iOSEmulatorServices.runApplicationOnEmulator(debugData.pathToAppPackage, { @@ -120,23 +112,18 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS args: args, appId: debugData.applicationIdentifier, skipInstall: true, - device: debugData.deviceIdentifier, + device: this.deviceIdentifier, justlaunch: debugOptions.justlaunch, timeout: debugOptions.timeout, sdk: debugOptions.sdk }); const pid = getPidFromiOSSimulatorLogs(debugData.applicationIdentifier, launchResult); - this._lldbProcess = this.$childProcess.spawn("lldb", ["-p", pid]); - if (log4js.levels.TRACE.isGreaterThanOrEqualTo(this.$logger.getLevel())) { - this._lldbProcess.stdout.pipe(process.stdout); - } - this._lldbProcess.stderr.pipe(process.stderr); - this._lldbProcess.stdin.write("process continue\n"); + this.startAppDebuggerOnSimulator(pid); } private async startAppOnDevice(debugData: IDebugData, debugOptions: IDebugOptions): Promise { const runOptions: IRunPlatformOptions = { - device: debugData.deviceIdentifier, + device: this.deviceIdentifier, emulator: this.device.isEmulator, justlaunch: debugOptions.justlaunch }; @@ -144,14 +131,37 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS await this.$platformService.startApplication(this.platform, runOptions, { appId: debugData.applicationIdentifier, projectName: projectData.projectName }); } + private startAppDebuggerOnSimulator(pid: string) { + this._lldbProcess = this.$childProcess.spawn("lldb", ["-p", pid]); + if (log4js.levels.TRACE.isGreaterThanOrEqualTo(this.$logger.getLevel())) { + this._lldbProcess.stdout.pipe(process.stdout); + } + this._lldbProcess.stderr.pipe(process.stderr); + this._lldbProcess.stdin.write("process continue\n"); + } + + private async stopAppDebuggerOnSimulator() { + if (this._lldbProcess) { + this._lldbProcess.stdin.write("process detach\n"); + await this.killProcess(this._lldbProcess); + this._lldbProcess = undefined; + } + } + + private async killProcess(childProcess: ChildProcess): Promise { + if (childProcess) { + return new Promise((resolve, reject) => { + childProcess.on("close", resolve); + childProcess.kill(); + }); + } + } + private async wireDebuggerClient(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - // the VSCode Ext starts `tns debug ios --no-client` to start/attach to debug sessions - // check if --no-client is passed - default to opening a tcp socket (versus Chrome DevTools (websocket)) - const deviceIdentifier = this.device ? this.device.deviceInfo.identifier : debugData.deviceIdentifier; if ((debugOptions.inspector || !debugOptions.client) && this.$hostInfo.isDarwin) { - const existingTcpProxy = this.$socketProxyFactory.getTCPSocketProxy(deviceIdentifier); + const existingTcpProxy = this.$socketProxyFactory.getTCPSocketProxy(this.deviceIdentifier); const getDeviceSocket = async () => await this.device.getDebugSocket(debugData.applicationIdentifier, debugData.projectDir); - const tcpSocketProxy = existingTcpProxy || await this.$socketProxyFactory.addTCPSocketProxy(getDeviceSocket, deviceIdentifier); + const tcpSocketProxy = existingTcpProxy || await this.$socketProxyFactory.addTCPSocketProxy(getDeviceSocket, this.deviceIdentifier); if (!existingTcpProxy) { await this.openAppInspector(tcpSocketProxy.address(), debugData, debugOptions); } @@ -162,9 +172,9 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS this.$logger.info("'--chrome' is the default behavior. Use --inspector to debug iOS applications using the Safari Web Inspector."); } - const existingWebProxy = this.$socketProxyFactory.getWebSocketProxy(deviceIdentifier); + const existingWebProxy = this.$socketProxyFactory.getWebSocketProxy(this.deviceIdentifier); const getDeviceSocket = async () => await this.device.getDebugSocket(debugData.applicationIdentifier, debugData.projectDir); - const webSocketProxy = existingWebProxy || await this.$socketProxyFactory.addWebSocketProxy(getDeviceSocket, deviceIdentifier); + const webSocketProxy = existingWebProxy || await this.$socketProxyFactory.addWebSocketProxy(getDeviceSocket, this.deviceIdentifier); return this.getChromeDebugUrl(debugOptions, webSocketProxy.options.port); } From 31d7d4a8a675872e711fe24f200e875426e591ae Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 8 Nov 2018 16:30:35 +0200 Subject: [PATCH 07/17] refactor: keep deviceId in a single place in the device debug services --- lib/definitions/debug.d.ts | 17 +++++++++++------ lib/services/android-debug-service.ts | 18 +++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 4d225872b2..f6806c0208 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -1,8 +1,13 @@ -// TODO: remove IDeviceIdentifier /** * Describes information for starting debug process. */ -interface IDebugData extends Mobile.IDeviceIdentifier, IProjectDir { +interface IDebugData extends IAppDebugData, Mobile.IDeviceIdentifier { +} + +/** + * Describes information application that will be debugged. + */ +interface IAppDebugData extends IProjectDir { /** * Application identifier of the app that it will be debugged. */ @@ -144,11 +149,11 @@ interface IDebugService extends IDebugServiceBase { interface IPlatformDebugService extends IPlatform, NodeJS.EventEmitter { /** * Starts debug operation. - * @param {IDebugData} debugData Describes information for device and application that will be debugged. + * @param {IAppDebugData} debugData Describes information for application that will be debugged. * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. * @returns {Promise} */ - debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise; + debugStart(debugData: IAppDebugData, debugOptions: IDebugOptions): Promise; /** * Stops debug operation. @@ -158,9 +163,9 @@ interface IPlatformDebugService extends IPlatform, NodeJS.EventEmitter { /** * Starts debug operation based on the specified debug data. - * @param {IDebugData} debugData Describes information for device and application that will be debugged. + * @param {IAppDebugData} debugData Describes information for application that will be debugged. * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. * @returns {Promise} Full url where the frontend client may be connected. */ - debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; + debug(debugData: IAppDebugData, debugOptions: IDebugOptions): Promise; } diff --git a/lib/services/android-debug-service.ts b/lib/services/android-debug-service.ts index 0891e72132..9f9b0d82f0 100644 --- a/lib/services/android-debug-service.ts +++ b/lib/services/android-debug-service.ts @@ -4,6 +4,8 @@ import { LiveSyncPaths } from "../common/constants"; export class AndroidDebugService extends DebugServiceBase implements IPlatformDebugService { private _packageName: string; + private deviceIdentifier: string; + public get platform() { return "android"; } @@ -17,7 +19,9 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe private $net: INet, private $projectDataService: IProjectDataService, private $deviceLogProvider: Mobile.IDeviceLogProvider) { + super(device, $devicesService); + this.deviceIdentifier = device.deviceInfo.identifier; } public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { @@ -27,10 +31,10 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe : await this.debugOnDevice(debugData, debugOptions); if (!debugOptions.justlaunch) { - const pid = await this.$androidProcessService.getAppProcessId(debugData.deviceIdentifier, debugData.applicationIdentifier); + const pid = await this.$androidProcessService.getAppProcessId(this.deviceIdentifier, debugData.applicationIdentifier); if (pid) { - this.$deviceLogProvider.setApplicationPidForDevice(debugData.deviceIdentifier, pid); - const device = await this.$devicesService.getDevice(debugData.deviceIdentifier); + this.$deviceLogProvider.setApplicationPidForDevice(this.deviceIdentifier, pid); + const device = await this.$devicesService.getDevice(this.deviceIdentifier); await device.openDeviceLogStream(); } } @@ -39,7 +43,7 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe } public async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); + await this.$devicesService.initialize({ platform: this.platform, deviceId: this.deviceIdentifier }); const projectData = this.$projectDataService.getProjectData(debugData.projectDir); const appData: Mobile.IApplicationData = { appId: debugData.applicationIdentifier, @@ -48,7 +52,7 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe const action = (device: Mobile.IAndroidDevice): Promise => this.debugStartCore(appData, debugOptions); - await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); + await this.$devicesService.execute(action, this.getCanExecuteAction(this.deviceIdentifier)); } public debugStop(): Promise { @@ -109,7 +113,7 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe this.$logger.out("Using ", packageFile); } - await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); + await this.$devicesService.initialize({ platform: this.platform, deviceId: this.deviceIdentifier }); const projectName = this.$projectDataService.getProjectData(debugData.projectDir).projectName; const appData: Mobile.IApplicationData = { @@ -119,7 +123,7 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe const action = (device: Mobile.IAndroidDevice): Promise => this.debugCore(device, packageFile, appData, debugOptions); - const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); + const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(this.deviceIdentifier)); return deviceActionResult[0].result; } From 72ab33713d40a89a9234fa2c5dbcc41ff89fac09 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 8 Nov 2018 17:17:05 +0200 Subject: [PATCH 08/17] refactor: remove the emulator debug option --- lib/definitions/debug.d.ts | 6 ------ lib/services/android-debug-service.ts | 4 ++-- lib/services/debug-service.ts | 4 +--- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index f6806c0208..1cadad5a05 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -43,12 +43,6 @@ interface IDebugOptions { */ stop?: boolean; - // TODO: remove - /** - * Defines if debug process is for emulator (not for real device). - */ - emulator?: boolean; - /** * Defines if the debug process should break on the first line. */ diff --git a/lib/services/android-debug-service.ts b/lib/services/android-debug-service.ts index 9f9b0d82f0..c277d9e21a 100644 --- a/lib/services/android-debug-service.ts +++ b/lib/services/android-debug-service.ts @@ -26,7 +26,7 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { this._packageName = debugData.applicationIdentifier; - const result = debugOptions.emulator + const result = this.device.isEmulator ? await this.debugOnEmulator(debugData, debugOptions) : await this.debugOnDevice(debugData, debugOptions); @@ -108,7 +108,7 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe private async debugOnDevice(debugData: IDebugData, debugOptions: IDebugOptions): Promise { let packageFile = ""; - if (!debugOptions.start && !debugOptions.emulator) { + if (!debugOptions.start && !this.device.isEmulator) { packageFile = debugData.pathToAppPackage; this.$logger.out("Using ", packageFile); } diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index 6e56eabd2d..94a9e5731d 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -57,9 +57,7 @@ export class DebugService extends EventEmitter implements IDebugService { this.$errors.failWithoutHelp("To debug on iOS simulator you need to provide path to the app package."); } - if (this.$hostInfo.isWindows) { - debugOptions.emulator = false; - } else if (!this.$hostInfo.isDarwin) { + if (!this.$hostInfo.isWindows && !this.$hostInfo.isDarwin) { this.$errors.failWithoutHelp(`Debugging on iOS devices is not supported for ${platform()} yet.`); } } From dbd7a0514a1d5078b70d36f200fb612a25865598 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 8 Nov 2018 17:20:47 +0200 Subject: [PATCH 09/17] fix: validate debugBrk + hmr combo as we cannot force restarts and avoid restarts at the same time --- lib/commands/debug.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index eddb6bc6ac..876a94785b 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -120,6 +120,10 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements this.$errors.fail("--release flag is not applicable to this command"); } + if (this.$options.hmr && this.$options.debugBrk) { + this.$errors.fail("Expected exactly one of the --debug-brk and --hmr options"); + } + const minSupportedWebpackVersion = this.$options.hmr ? LiveSyncCommandHelper.MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR : null; this.$bundleValidatorHelper.validate(minSupportedWebpackVersion); From b1f3e76e0457f9b5dba8929d9aca0c0651111630 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 8 Nov 2018 17:26:36 +0200 Subject: [PATCH 10/17] refactor: remove debugging infos --- .vscode/launch.json | 18 +++++++++++------- lib/device-sockets/ios/socket-proxy-factory.ts | 1 - .../ios/socket-request-executor.ts | 1 - 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 0602e8baec..d70e74e751 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,18 +7,15 @@ { "type": "node", "request": "launch", - "cwd": "/Users/tachev/Work/Test/debugTest", + "cwd": "${workspaceRoot}", "sourceMaps": true, // In case you want to debug child processes started from CLI: // "autoAttachChildProcesses": true, "name": "Launch CLI (Node 6+)", "program": "${workspaceRoot}/lib/nativescript-cli.js", + // example commands - "args": [ - "debug", - "ios", - "--debug-brk" - ] + "args": [ "create", "cliapp"] // "args": [ "test", "android", "--justlaunch"] // "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"] // "args": [ "platform", "remove", "android", "--path", "cliapp"] @@ -41,6 +38,7 @@ "cwd": "${workspaceRoot}", "sourceMaps": true }, + { "type": "node", "runtimeArgs": [ @@ -53,8 +51,11 @@ "sourceMaps": true, // define the arguments that you would like to pass to CLI, for example // "args": [ "build", "android", "--justlaunch" ] - "args": [] + "args": [ + + ] }, + { // in case you want to debug a single test, modify it's code to be `it.only(...` instead of `it(...` "type": "node", @@ -67,6 +68,7 @@ "cwd": "${workspaceRoot}", "sourceMaps": true }, + { "type": "node", "request": "attach", @@ -75,6 +77,7 @@ "port": 9897, "sourceMaps": true }, + { "type": "node", "request": "attach", @@ -84,5 +87,6 @@ "port": 9855, "sourceMaps": true } + ] } \ No newline at end of file diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/socket-proxy-factory.ts index be6c9f7ba8..1430403a00 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/socket-proxy-factory.ts @@ -166,7 +166,6 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact }); this.$logger.info("Opened localhost " + localPort); - console.log("return new proxy"); return server; } diff --git a/lib/device-sockets/ios/socket-request-executor.ts b/lib/device-sockets/ios/socket-request-executor.ts index 7b5e1c3486..c3eb4ccf5d 100644 --- a/lib/device-sockets/ios/socket-request-executor.ts +++ b/lib/device-sockets/ios/socket-request-executor.ts @@ -7,7 +7,6 @@ export class IOSSocketRequestExecutor implements IiOSSocketRequestExecutor { private $logger: ILogger) { } public async executeAttachRequest(device: Mobile.IiOSDevice, timeout: number, projectId: string): Promise { - console.log("executeAttachRequest"); const deviceIdentifier = device.deviceInfo.identifier; const observeNotificationSockets = [ From 76d402ec7b62e1b9984461b66e46ebd1c00b9ffc Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 8 Nov 2018 17:48:14 +0200 Subject: [PATCH 11/17] reafactor: fix some error messages --- lib/commands/debug.ts | 2 +- lib/common/mobile/ios/device/ios-device.ts | 2 +- lib/common/mobile/ios/simulator/ios-simulator-device.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 876a94785b..e2cba61edc 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -121,7 +121,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements } if (this.$options.hmr && this.$options.debugBrk) { - this.$errors.fail("Expected exactly one of the --debug-brk and --hmr options"); + this.$errors.fail("--debug-brk and --hmr flags cannot be combined"); } const minSupportedWebpackVersion = this.$options.hmr ? LiveSyncCommandHelper.MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR : null; diff --git a/lib/common/mobile/ios/device/ios-device.ts b/lib/common/mobile/ios/device/ios-device.ts index 61e067bab5..b25116614a 100644 --- a/lib/common/mobile/ios/device/ios-device.ts +++ b/lib/common/mobile/ios/device/ios-device.ts @@ -93,7 +93,7 @@ export class IOSDevice implements Mobile.IiOSDevice { await this.$iOSSocketRequestExecutor.executeAttachRequest(this, constants.AWAIT_NOTIFICATION_TIMEOUT_SECONDS, appId); const port = await this.$iOSDebuggerPortService.getPort({ projectDir, deviceId: this.deviceInfo.identifier, appId }); if (!port) { - this.$errors.fail("NativeScript debugger was not able to get inspector socket port."); + this.$errors.failWithoutHelp("Device socket port cannot be found."); } const deviceId = this.deviceInfo.identifier; diff --git a/lib/common/mobile/ios/simulator/ios-simulator-device.ts b/lib/common/mobile/ios/simulator/ios-simulator-device.ts index 7fcb0759aa..402f99ecfa 100644 --- a/lib/common/mobile/ios/simulator/ios-simulator-device.ts +++ b/lib/common/mobile/ios/simulator/ios-simulator-device.ts @@ -38,7 +38,7 @@ export class IOSSimulator implements Mobile.IiOSDevice { await this.$iOSEmulatorServices.postDarwinNotification(attachRequestMessage, this.deviceInfo.identifier); const port = await this.$iOSDebuggerPortService.getPort({ projectDir, deviceId: this.deviceInfo.identifier, appId }); if (!port) { - this.$errors.fail("NativeScript debugger was not able to get inspector socket port."); + this.$errors.failWithoutHelp("Device socket port cannot be found."); } try { From 04868e258ef2caec6b3d8b5af251d98923f04e29 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 8 Nov 2018 18:48:40 +0200 Subject: [PATCH 12/17] refactor: renamed the debug services in order to follow the livesync ones --- lib/bootstrap.ts | 4 ++-- lib/definitions/debug.d.ts | 2 +- ...-service.ts => android-device-debug-service.ts} | 4 ++-- lib/services/debug-service-base.ts | 2 +- lib/services/debug-service.ts | 14 +++++++------- ...ebug-service.ts => ios-device-debug-service.ts} | 4 ++-- lib/services/test-execution-service.ts | 4 ++-- test/services/android-debug-service.ts | 10 +++++----- test/services/android-project-service.ts | 10 +++++----- test/services/debug-service.ts | 12 ++++++------ test/services/ios-debug-service.ts | 10 +++++----- test/stubs.ts | 2 +- 12 files changed, 39 insertions(+), 39 deletions(-) rename lib/services/{android-debug-service.ts => android-device-debug-service.ts} (97%) rename lib/services/{ios-debug-service.ts => ios-device-debug-service.ts} (97%) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 3b12c03486..737ec8572c 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -29,8 +29,8 @@ $injector.require("preparePlatformNativeService", "./services/prepare-platform-n $injector.require("debugDataService", "./services/debug-data-service"); $injector.requirePublicClass("debugService", "./services/debug-service"); -$injector.require("iOSDebugService", "./services/ios-debug-service"); -$injector.require("androidDebugService", "./services/android-debug-service"); +$injector.require("iOSDeviceDebugService", "./services/ios-device-debug-service"); +$injector.require("androidDeviceDebugService", "./services/android-device-debug-service"); $injector.require("userSettingsService", "./services/user-settings-service"); $injector.requirePublic("analyticsSettingsService", "./services/analytics-settings-service"); diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 1cadad5a05..b75ec84bfb 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -140,7 +140,7 @@ interface IDebugService extends IDebugServiceBase { /** * Describes actions required for debugging on specific platform (Android or iOS). */ -interface IPlatformDebugService extends IPlatform, NodeJS.EventEmitter { +interface IDeviceDebugService extends IPlatform, NodeJS.EventEmitter { /** * Starts debug operation. * @param {IAppDebugData} debugData Describes information for application that will be debugged. diff --git a/lib/services/android-debug-service.ts b/lib/services/android-device-debug-service.ts similarity index 97% rename from lib/services/android-debug-service.ts rename to lib/services/android-device-debug-service.ts index c277d9e21a..2bb6567474 100644 --- a/lib/services/android-debug-service.ts +++ b/lib/services/android-device-debug-service.ts @@ -2,7 +2,7 @@ import { sleep } from "../common/helpers"; import { DebugServiceBase } from "./debug-service-base"; import { LiveSyncPaths } from "../common/constants"; -export class AndroidDebugService extends DebugServiceBase implements IPlatformDebugService { +export class AndroidDeviceDebugService extends DebugServiceBase implements IDeviceDebugService { private _packageName: string; private deviceIdentifier: string; @@ -204,4 +204,4 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe } } -$injector.register("androidDebugService", AndroidDebugService, false); +$injector.register("androidDeviceDebugService", AndroidDeviceDebugService, false); diff --git a/lib/services/debug-service-base.ts b/lib/services/debug-service-base.ts index 054c8f8578..bc8fa901e8 100644 --- a/lib/services/debug-service-base.ts +++ b/lib/services/debug-service-base.ts @@ -1,6 +1,6 @@ import { EventEmitter } from "events"; -export abstract class DebugServiceBase extends EventEmitter implements IPlatformDebugService { +export abstract class DebugServiceBase extends EventEmitter implements IDeviceDebugService { constructor( protected device: Mobile.IDevice, protected $devicesService: Mobile.IDevicesService diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index 94a9e5731d..cafd3e6619 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -6,7 +6,7 @@ import { CONNECTED_STATUS } from "../common/constants"; import { DebugTools, TrackActionNames } from "../constants"; export class DebugService extends EventEmitter implements IDebugService { - private _platformDebugServices: IDictionary; + private _platformDebugServices: IDictionary; constructor(private $devicesService: Mobile.IDevicesService, private $errors: IErrors, private $injector: IInjector, @@ -51,7 +51,7 @@ export class DebugService extends EventEmitter implements IDebugService { this.$errors.failWithoutHelp(`Unsupported device OS: ${device.deviceInfo.platform}. You can debug your applications only on iOS or Android.`); } - // TODO: Consider to move this code to ios-debug-service + // TODO: Consider to move this code to ios-device-debug-service if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { if (device.isEmulator && !debugData.pathToAppPackage && debugOptions.debugBrk) { this.$errors.failWithoutHelp("To debug on iOS simulator you need to provide path to the app package."); @@ -72,13 +72,13 @@ export class DebugService extends EventEmitter implements IDebugService { return debugService.debugStop(); } - protected getDebugService(device: Mobile.IDevice): IPlatformDebugService { + protected getDebugService(device: Mobile.IDevice): IDeviceDebugService { if (!this._platformDebugServices[device.deviceInfo.identifier]) { const platform = device.deviceInfo.platform; if (this.$mobileHelper.isiOSPlatform(platform)) { - this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("iOSDebugService", { device }); + this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("iOSDeviceDebugService", { device }); } else if (this.$mobileHelper.isAndroidPlatform(platform)) { - this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("androidDebugService", { device }); + this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("androidDeviceDebugService", { device }); } else { this.$errors.failWithoutHelp(DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); } @@ -89,12 +89,12 @@ export class DebugService extends EventEmitter implements IDebugService { return this._platformDebugServices[device.deviceInfo.identifier]; } - private getDebugServiceByIdentifier(deviceIdentifier: string): IPlatformDebugService { + private getDebugServiceByIdentifier(deviceIdentifier: string): IDeviceDebugService { const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); return this.getDebugService(device); } - private attachConnectionErrorHandlers(platformDebugService: IPlatformDebugService) { + private attachConnectionErrorHandlers(platformDebugService: IDeviceDebugService) { let connectionErrorHandler = (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e); connectionErrorHandler = connectionErrorHandler.bind(this); platformDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-device-debug-service.ts similarity index 97% rename from lib/services/ios-debug-service.ts rename to lib/services/ios-device-debug-service.ts index f1a6a724e6..d4dabfb06d 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-device-debug-service.ts @@ -9,7 +9,7 @@ const inspectorAppName = "NativeScript Inspector.app"; const inspectorNpmPackageName = "tns-ios-inspector"; const inspectorUiDir = "WebInspectorUI/"; -export class IOSDebugService extends DebugServiceBase implements IPlatformDebugService { +export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDebugService { private _lldbProcess: ChildProcess; private deviceIdentifier: string; @@ -195,4 +195,4 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } } -$injector.register("iOSDebugService", IOSDebugService, false); \ No newline at end of file +$injector.register("iOSDeviceDebugService", IOSDeviceDebugService, false); \ No newline at end of file diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index c1e754ca9a..c081d1d1bd 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -137,7 +137,7 @@ class TestExecutionService implements ITestExecutionService { if (this.$options.debugBrk) { this.$logger.info('Starting debugger...'); - const debugService: IPlatformDebugService = this.$injector.resolve(`${platform}DebugService`); + const debugService: IDeviceDebugService = this.$injector.resolve(`${platform}DebugService`); const debugData = this.getDebugData(platform, projectData, deployOptions); await debugService.debugStart(debugData, this.$options); } @@ -172,7 +172,7 @@ class TestExecutionService implements ITestExecutionService { const karmaConfig = this.getKarmaConfiguration(platform, projectData), // In case you want to debug the unit test runner, add "--inspect-brk=" as a first element in the array of args. - karmaRunner = this.$childProcess.spawn(process.execPath, [ path.join(__dirname, "karma-execution.js")], { stdio: ["inherit", "inherit", "inherit", "ipc"] }), + karmaRunner = this.$childProcess.spawn(process.execPath, [path.join(__dirname, "karma-execution.js")], { stdio: ["inherit", "inherit", "inherit", "ipc"] }), launchKarmaTests = async (karmaData: any) => { this.$logger.trace("## Unit-testing: Parent process received message", karmaData); let port: string; diff --git a/test/services/android-debug-service.ts b/test/services/android-debug-service.ts index 234d8aa26c..f7c7384401 100644 --- a/test/services/android-debug-service.ts +++ b/test/services/android-debug-service.ts @@ -1,11 +1,11 @@ -import { AndroidDebugService } from "../../lib/services/android-debug-service"; +import { AndroidDeviceDebugService } from "../../lib/services/android-device-debug-service"; import { Yok } from "../../lib/common/yok"; import * as stubs from "../stubs"; import { assert } from "chai"; const expectedDevToolsCommitSha = "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; -class AndroidDebugServiceInheritor extends AndroidDebugService { +class AndroidDeviceDebugServiceInheritor extends AndroidDeviceDebugService { constructor(protected $devicesService: Mobile.IDevicesService, $errors: IErrors, $logger: ILogger, @@ -43,7 +43,7 @@ interface IChromeUrlTestCase { scenarioName: string; } -describe("androidDebugService", () => { +describe("androidDeviceDebugService", () => { describe("getChromeDebugUrl", () => { const expectedPort = 12345; const customDevToolsCommit = "customDevToolsCommit"; @@ -156,8 +156,8 @@ describe("androidDebugService", () => { for (const testCase of chromUrlTestCases) { it(`returns correct url when ${testCase.scenarioName}`, () => { const testInjector = createTestInjector(); - const androidDebugService = testInjector.resolve(AndroidDebugServiceInheritor); - const actualChromeUrl = androidDebugService.getChromeDebugUrl(testCase.debugOptions, expectedPort); + const androidDeviceDebugService = testInjector.resolve(AndroidDeviceDebugServiceInheritor); + const actualChromeUrl = androidDeviceDebugService.getChromeDebugUrl(testCase.debugOptions, expectedPort); assert.equal(actualChromeUrl, testCase.expectedChromeUrl); }); } diff --git a/test/services/android-project-service.ts b/test/services/android-project-service.ts index 9c69115a31..2c6abdb18e 100644 --- a/test/services/android-project-service.ts +++ b/test/services/android-project-service.ts @@ -51,7 +51,7 @@ const getDefautlBuildConfig = (): IBuildConfig => { }; }; -describe("androidDebugService", () => { +describe("androidDeviceDebugService", () => { let injector: IInjector; let androidProjectService: IPlatformProjectService; let sandbox: sinon.SinonSandbox = null; @@ -86,7 +86,7 @@ describe("androidDebugService", () => { const buildConfig = getDefautlBuildConfig(); //act - await androidProjectService.buildProject("local/local", projectData, buildConfig, ); + await androidProjectService.buildProject("local/local", projectData, buildConfig); //assert assert.include(childProcess.lastCommandArgs, "assembleRelease"); @@ -98,7 +98,7 @@ describe("androidDebugService", () => { buildConfig.release = false; //act - await androidProjectService.buildProject("local/local", projectData, buildConfig, ); + await androidProjectService.buildProject("local/local", projectData, buildConfig); //assert assert.include(childProcess.lastCommandArgs, "assembleDebug"); @@ -110,7 +110,7 @@ describe("androidDebugService", () => { buildConfig.androidBundle = true; //act - await androidProjectService.buildProject("local/local", projectData, buildConfig, ); + await androidProjectService.buildProject("local/local", projectData, buildConfig); //assert assert.include(childProcess.lastCommandArgs, "bundleRelease"); @@ -123,7 +123,7 @@ describe("androidDebugService", () => { buildConfig.release = false; //act - await androidProjectService.buildProject("local/local", projectData, buildConfig, ); + await androidProjectService.buildProject("local/local", projectData, buildConfig); //assert assert.include(childProcess.lastCommandArgs, "bundleDebug"); diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index 3f14bbc9f0..26767ca24a 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -74,9 +74,9 @@ describe("debugService", () => { } }); - testInjector.register("androidDebugService", PlatformDebugService); + testInjector.register("androidDeviceDebugService", PlatformDebugService); - testInjector.register("iOSDebugService", PlatformDebugService); + testInjector.register("iOSDeviceDebugService", PlatformDebugService); testInjector.register("mobileHelper", { isAndroidPlatform: (platform: string) => { @@ -167,7 +167,7 @@ describe("debugService", () => { const testInjector = getTestInjectorForTestConfiguration(testData); const expectedErrorMessage = "Platform specific error"; - const platformDebugService = testInjector.resolve(`${platform}DebugService`); + const platformDebugService = testInjector.resolve(`${platform}DebugService`); platformDebugService.debug = async (debugData: IDebugData, debugOptions: IDebugOptions): Promise => { throw new Error(expectedErrorMessage); }; @@ -178,11 +178,11 @@ describe("debugService", () => { await assert.isRejected(debugService.debug(debugData, null), expectedErrorMessage); }; - it("androidDebugService's debug method fails", async () => { + it("androidDeviceDebugService's debug method fails", async () => { await assertIsRejectedWhenPlatformDebugServiceFails("android"); }); - it("iOSDebugService's debug method fails", async () => { + it("iOSDeviceDebugService's debug method fails", async () => { await assertIsRejectedWhenPlatformDebugServiceFails("iOS"); }); }); @@ -204,7 +204,7 @@ describe("debugService", () => { await assert.isFulfilled(debugService.debug(debugData, null)); const expectedErrorData = { deviceIdentifier: "deviceId", message: "my message", code: 2048 }; - const platformDebugService = testInjector.resolve(`${platform}DebugService`); + const platformDebugService = testInjector.resolve(`${platform}DebugService`); platformDebugService.emit(CONNECTION_ERROR_EVENT_NAME, expectedErrorData); assert.deepEqual(dataRaisedForConnectionError, expectedErrorData); }); diff --git a/test/services/ios-debug-service.ts b/test/services/ios-debug-service.ts index 8ef0b695a9..5f0a2e4bd7 100644 --- a/test/services/ios-debug-service.ts +++ b/test/services/ios-debug-service.ts @@ -1,11 +1,11 @@ -import { IOSDebugService } from "../../lib/services/ios-debug-service"; +import { IOSDeviceDebugService } from "../../lib/services/ios-device-debug-service"; import { Yok } from "../../lib/common/yok"; import * as stubs from "../stubs"; import { assert } from "chai"; const expectedDevToolsCommitSha = "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; -class IOSDebugServiceInheritor extends IOSDebugService { +class IOSDeviceDebugServiceInheritor extends IOSDeviceDebugService { constructor(protected $devicesService: Mobile.IDevicesService, $platformService: IPlatformService, $iOSEmulatorServices: Mobile.IiOSSimulatorService, @@ -68,7 +68,7 @@ interface IChromeUrlTestCase { scenarioName: string; } -describe("iOSDebugService", () => { +describe("iOSDeviceDebugService", () => { describe("getChromeDebugUrl", () => { const expectedPort = 12345; const customDevToolsCommit = "customDevToolsCommit"; @@ -190,8 +190,8 @@ describe("iOSDebugService", () => { for (const testCase of chromUrlTestCases) { it(`returns correct url when ${testCase.scenarioName}`, () => { const testInjector = createTestInjector(); - const iOSDebugService = testInjector.resolve(IOSDebugServiceInheritor); - const actualChromeUrl = iOSDebugService.getChromeDebugUrl(testCase.debugOptions, expectedPort); + const iOSDeviceDebugService = testInjector.resolve(IOSDeviceDebugServiceInheritor); + const actualChromeUrl = iOSDeviceDebugService.getChromeDebugUrl(testCase.debugOptions, expectedPort); assert.equal(actualChromeUrl, testCase.expectedChromeUrl); }); } diff --git a/test/stubs.ts b/test/stubs.ts index be32216147..851431106f 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -633,7 +633,7 @@ function unexpected(msg: string): Error { return err; } -export class DebugServiceStub extends EventEmitter implements IPlatformDebugService { +export class DebugServiceStub extends EventEmitter implements IDeviceDebugService { public async debug(): Promise { return; } From 15c9db30d0748fd2b1ebcbb0b1e0a58121e37ca2 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Wed, 14 Nov 2018 18:57:47 +0200 Subject: [PATCH 13/17] fix: fix PR comments --- lib/bootstrap.ts | 2 +- lib/common/definitions/mobile.d.ts | 9 +- lib/common/mobile/ios/device/ios-device.ts | 76 ++++----------- lib/common/mobile/ios/ios-device-base.ts | 81 ++++++++++++++++ .../ios/simulator/ios-simulator-device.ts | 96 ++++++------------- .../mobile/ios-simulator-discovery.ts | 11 +++ lib/declarations.d.ts | 10 +- lib/definitions/debug.d.ts | 2 +- .../ios-debugger-port-service.d.ts | 2 +- ...y.ts => app-debug-socket-proxy-factory.ts} | 78 ++++++++------- lib/services/debug-service.ts | 10 +- lib/services/ios-debugger-port-service.ts | 21 ---- lib/services/ios-device-debug-service.ts | 64 +++++++------ .../livesync/ios-device-livesync-service.ts | 8 +- test/debug.ts | 2 + ...ice.ts => android-device-debug-service.ts} | 2 +- test/services/debug-service.ts | 4 +- test/services/ios-debugger-port-service.ts | 4 +- ...service.ts => ios-device-debug-service.ts} | 8 +- 19 files changed, 251 insertions(+), 239 deletions(-) create mode 100644 lib/common/mobile/ios/ios-device-base.ts rename lib/device-sockets/ios/{socket-proxy-factory.ts => app-debug-socket-proxy-factory.ts} (62%) rename test/services/{android-debug-service.ts => android-device-debug-service.ts} (97%) rename test/services/{ios-debug-service.ts => ios-device-debug-service.ts} (95%) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 737ec8572c..10ad6a63f7 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -146,7 +146,7 @@ $injector.requirePublic("previewQrCodeService", "./services/livesync/playground/ $injector.requirePublic("sysInfo", "./sys-info"); $injector.require("iOSNotificationService", "./services/ios-notification-service"); -$injector.require("socketProxyFactory", "./device-sockets/ios/socket-proxy-factory"); +$injector.require("appDebugSocketProxyFactory", "./device-sockets/ios/app-debug-socket-proxy-factory"); $injector.require("iOSNotification", "./device-sockets/ios/notification"); $injector.require("iOSSocketRequestExecutor", "./device-sockets/ios/socket-request-executor"); $injector.require("messages", "./common/messages/messages"); diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index 5f31af8dc0..8336463808 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -111,9 +111,14 @@ declare module Mobile { } interface IiOSDevice extends IDevice { - getLiveSyncSocket(appId: string, projectDir: string): Promise; - getDebugSocket(appId: string, projectDir: string): Promise; + getLiveSyncSocket(appId: string): Promise; + destroyLiveSyncSocket(appId: string): void; + + getDebugSocket(appId: string): Promise; + destroyDebugSocket(appId: string): void; + openDeviceLogStream(options?: IiOSLogStreamOptions): Promise; + destroyAllSockets(): void; } interface IAndroidDevice extends IDevice { diff --git a/lib/common/mobile/ios/device/ios-device.ts b/lib/common/mobile/ios/device/ios-device.ts index b25116614a..f0d3b7a223 100644 --- a/lib/common/mobile/ios/device/ios-device.ts +++ b/lib/common/mobile/ios/device/ios-device.ts @@ -5,30 +5,28 @@ import * as constants from "../../../../constants"; import * as net from "net"; import { cache } from "../../../decorators"; import * as helpers from "../../../../common/helpers"; +import { IOSDeviceBase } from "../ios-device-base"; -export class IOSDevice implements Mobile.IiOSDevice { +export class IOSDevice extends IOSDeviceBase { public applicationManager: Mobile.IDeviceApplicationManager; public fileSystem: Mobile.IDeviceFileSystem; public deviceInfo: Mobile.IDeviceInfo; - private socket: net.Socket; - private _deviceLogHandler: (...args: any[]) => void; constructor(private deviceActionInfo: IOSDeviceLib.IDeviceActionInfo, - private $errors: IErrors, + protected $errors: IErrors, private $injector: IInjector, - private $iOSDebuggerPortService: IIOSDebuggerPortService, + protected $iOSDebuggerPortService: IIOSDebuggerPortService, private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, - private $processService: IProcessService, + protected $processService: IProcessService, private $deviceLogProvider: Mobile.IDeviceLogProvider, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $iOSDeviceProductNameMapper: Mobile.IiOSDeviceProductNameMapper, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper) { - + super(); this.applicationManager = this.$injector.resolve(applicationManagerPath.IOSApplicationManager, { device: this, devicePointer: this.deviceActionInfo }); this.fileSystem = this.$injector.resolve(fileSystemPath.IOSDeviceFileSystem, { device: this, devicePointer: this.deviceActionInfo }); - const productType = deviceActionInfo.productType; const isTablet = this.$mobileHelper.isiOSTablet(productType); const deviceStatus = deviceActionInfo.status || commonConstants.UNREACHABLE_STATUS; @@ -52,16 +50,6 @@ export class IOSDevice implements Mobile.IiOSDevice { return false; } - public getApplicationInfo(applicationIdentifier: string): Promise { - return this.applicationManager.getApplicationInfo(applicationIdentifier); - } - - private actionOnDeviceLog(response: IOSDeviceLib.IDeviceLogData): void { - if (response.deviceId === this.deviceInfo.identifier) { - this.$deviceLogProvider.logData(response.message, this.$devicePlatformsConstants.iOS, this.deviceInfo.identifier); - } - } - @cache() public async openDeviceLogStream(): Promise { if (this.deviceInfo.status !== commonConstants.UNREACHABLE_STATUS) { @@ -71,33 +59,11 @@ export class IOSDevice implements Mobile.IiOSDevice { } } - public detach(): void { - if (this._deviceLogHandler) { - this.$iosDeviceOperations.removeListener(commonConstants.DEVICE_LOG_EVENT_NAME, this._deviceLogHandler); - } - } - - public async getLiveSyncSocket(appId: string, projectDir: string): Promise { - return this.getSocket(appId, projectDir); - } - - public async getDebugSocket(appId: string, projectDir: string): Promise { - return this.getSocket(appId, projectDir); - } - - private async getSocket(appId: string, projectDir: string): Promise { - if (this.socket) { - return this.socket; - } - + protected async getSocketCore(appId: string): Promise { await this.$iOSSocketRequestExecutor.executeAttachRequest(this, constants.AWAIT_NOTIFICATION_TIMEOUT_SECONDS, appId); - const port = await this.$iOSDebuggerPortService.getPort({ projectDir, deviceId: this.deviceInfo.identifier, appId }); - if (!port) { - this.$errors.failWithoutHelp("Device socket port cannot be found."); - } - + const port = await this.getDebuggerPort(appId); const deviceId = this.deviceInfo.identifier; - this.socket = await helpers.connectEventuallyUntilTimeout( + const socket = await helpers.connectEventuallyUntilTimeout( async () => { const deviceResponse = _.first((await this.$iosDeviceOperations.connectToPort([{ deviceId: deviceId, port: port }]))[deviceId]); const _socket = new net.Socket(); @@ -106,12 +72,19 @@ export class IOSDevice implements Mobile.IiOSDevice { }, commonConstants.SOCKET_CONNECTION_TIMEOUT_MS); - this.socket.on("close", () => { - this.socket = null; - }); + return socket; + } - this.$processService.attachToProcessExitSignals(this, this.destroySocket); - return this.socket; + private actionOnDeviceLog(response: IOSDeviceLib.IDeviceLogData): void { + if (response.deviceId === this.deviceInfo.identifier) { + this.$deviceLogProvider.logData(response.message, this.$devicePlatformsConstants.iOS, this.deviceInfo.identifier); + } + } + + public detach(): void { + if (this._deviceLogHandler) { + this.$iosDeviceOperations.removeListener(commonConstants.DEVICE_LOG_EVENT_NAME, this._deviceLogHandler); + } } private getActiveArchitecture(productType: string): string { @@ -135,13 +108,6 @@ export class IOSDevice implements Mobile.IiOSDevice { return activeArchitecture; } - - private destroySocket() { - if (this.socket) { - this.socket.destroy(); - this.socket = null; - } - } } $injector.register("iOSDevice", IOSDevice); diff --git a/lib/common/mobile/ios/ios-device-base.ts b/lib/common/mobile/ios/ios-device-base.ts new file mode 100644 index 0000000000..c2ef1c4602 --- /dev/null +++ b/lib/common/mobile/ios/ios-device-base.ts @@ -0,0 +1,81 @@ +import * as net from "net"; + +export abstract class IOSDeviceBase implements Mobile.IiOSDevice { + private cachedSockets: IDictionary = {}; + protected abstract $errors: IErrors; + protected abstract $iOSDebuggerPortService: IIOSDebuggerPortService; + protected abstract $processService: IProcessService; + abstract deviceInfo: Mobile.IDeviceInfo; + abstract applicationManager: Mobile.IDeviceApplicationManager; + abstract fileSystem: Mobile.IDeviceFileSystem; + abstract isEmulator: boolean; + abstract openDeviceLogStream(): Promise; + + public getApplicationInfo(applicationIdentifier: string): Promise { + return this.applicationManager.getApplicationInfo(applicationIdentifier); + } + + public async getLiveSyncSocket(appId: string): Promise { + return this.getSocket(appId); + } + + public async getDebugSocket(appId: string): Promise { + return this.getSocket(appId); + } + + public async getSocket(appId: string): Promise { + if (this.cachedSockets[appId]) { + return this.cachedSockets[appId]; + } + + this.cachedSockets[appId] = await this.getSocketCore(appId); + + if (this.cachedSockets[appId]) { + this.cachedSockets[appId].on("close", () => { + this.destroySocket(appId); + }); + + this.$processService.attachToProcessExitSignals(this, () => this.destroySocket(appId)); + } + + return this.cachedSockets[appId]; + } + + public destroyLiveSyncSocket(appId: string) { + this.destroySocket(appId); + } + + public destroyDebugSocket(appId: string) { + this.destroySocket(appId); + } + + protected abstract async getSocketCore(appId: string): Promise; + + protected async getDebuggerPort(appId: string): Promise { + const port = await this.$iOSDebuggerPortService.getPort({ deviceId: this.deviceInfo.identifier, appId }); + if (!port) { + this.$errors.failWithoutHelp("Device socket port cannot be found."); + } + + return port; + } + + public destroyAllSockets() { + for (const appId in this.cachedSockets) { + this.destroySocketSafe(this.cachedSockets[appId]); + } + + this.cachedSockets = {}; + } + + private destroySocket(appId: string) { + this.destroySocketSafe(this.cachedSockets[appId]); + this.cachedSockets[appId] = null; + } + + private destroySocketSafe(socket: net.Socket) { + if (socket && !socket.destroyed) { + socket.destroy(); + } + } +} diff --git a/lib/common/mobile/ios/simulator/ios-simulator-device.ts b/lib/common/mobile/ios/simulator/ios-simulator-device.ts index 402f99ecfa..1a50da4617 100644 --- a/lib/common/mobile/ios/simulator/ios-simulator-device.ts +++ b/lib/common/mobile/ios/simulator/ios-simulator-device.ts @@ -4,62 +4,28 @@ import * as constants from "../../../constants"; import * as net from "net"; import { cache } from "../../../decorators"; import * as helpers from "../../../../common/helpers"; +import { IOSDeviceBase } from "../ios-device-base"; -export class IOSSimulator implements Mobile.IiOSDevice { - private _applicationManager: Mobile.IDeviceApplicationManager; - private _fileSystem: Mobile.IDeviceFileSystem; - private socket: net.Socket; +export class IOSSimulator extends IOSDeviceBase implements Mobile.IiOSDevice { + public applicationManager: Mobile.IDeviceApplicationManager; + public fileSystem: Mobile.IDeviceFileSystem; + public deviceInfo: Mobile.IDeviceInfo; constructor(private simulator: Mobile.IiSimDevice, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $errors: IErrors, + protected $errors: IErrors, private $injector: IInjector, - private $iOSDebuggerPortService: IIOSDebuggerPortService, + protected $iOSDebuggerPortService: IIOSDebuggerPortService, private $iOSSimResolver: Mobile.IiOSSimResolver, private $iOSEmulatorServices: Mobile.IiOSSimulatorService, private $iOSNotification: IiOSNotification, private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider, - private $logger: ILogger) { } - - public async getLiveSyncSocket(appId: string, projectDir: string): Promise { - return this.getSocket(appId, projectDir); - } - - public async getDebugSocket(appId: string, projectDir: string): Promise { - return this.getSocket(appId, projectDir); - } - - private async getSocket(appId: string, projectDir: string): Promise { - if (this.socket) { - return this.socket; - } - - const attachRequestMessage = this.$iOSNotification.getAttachRequest(appId, this.deviceInfo.identifier); - await this.$iOSEmulatorServices.postDarwinNotification(attachRequestMessage, this.deviceInfo.identifier); - const port = await this.$iOSDebuggerPortService.getPort({ projectDir, deviceId: this.deviceInfo.identifier, appId }); - if (!port) { - this.$errors.failWithoutHelp("Device socket port cannot be found."); - } - - try { - this.socket = await helpers.connectEventuallyUntilTimeout( - async () => { return this.$iOSEmulatorServices.connectToPort({ port }) }, - constants.SOCKET_CONNECTION_TIMEOUT_MS); - } catch (e) { - this.$logger.warn(e); - } - - if (this.socket) { - this.socket.on("close", () => { - this.socket = null; - }); - } - - return this.socket; - } - - public get deviceInfo(): Mobile.IDeviceInfo { - return { + private $logger: ILogger, + protected $processService: IProcessService) { + super(); + this.applicationManager = this.$injector.resolve(applicationManagerPath.IOSSimulatorApplicationManager, { iosSim: this.$iOSSimResolver.iOSSim, device: this }); + this.fileSystem = this.$injector.resolve(fileSystemPath.IOSSimulatorFileSystem, { iosSim: this.$iOSSimResolver.iOSSim }); + this.deviceInfo = { imageIdentifier: this.simulator.id, identifier: this.simulator.id, displayName: this.simulator.name, @@ -78,30 +44,26 @@ export class IOSSimulator implements Mobile.IiOSDevice { return true; } - public async getApplicationInfo(applicationIdentifier: string): Promise { - return this.applicationManager.getApplicationInfo(applicationIdentifier); - } - - public get applicationManager(): Mobile.IDeviceApplicationManager { - if (!this._applicationManager) { - this._applicationManager = this.$injector.resolve(applicationManagerPath.IOSSimulatorApplicationManager, { iosSim: this.$iOSSimResolver.iOSSim, device: this }); - } - - return this._applicationManager; - } - - public get fileSystem(): Mobile.IDeviceFileSystem { - if (!this._fileSystem) { - this._fileSystem = this.$injector.resolve(fileSystemPath.IOSSimulatorFileSystem, { iosSim: this.$iOSSimResolver.iOSSim }); - } - - return this._fileSystem; - } - @cache() public async openDeviceLogStream(options?: Mobile.IiOSLogStreamOptions): Promise { options = options || {}; options.predicate = options.hasOwnProperty("predicate") ? options.predicate : constants.IOS_LOG_PREDICATE; return this.$iOSSimulatorLogProvider.startLogProcess(this.simulator.id, options); } + + protected async getSocketCore(appId: string): Promise { + let socket: net.Socket; + const attachRequestMessage = this.$iOSNotification.getAttachRequest(appId, this.deviceInfo.identifier); + await this.$iOSEmulatorServices.postDarwinNotification(attachRequestMessage, this.deviceInfo.identifier); + const port = await this.getDebuggerPort(appId); + try { + socket = await helpers.connectEventuallyUntilTimeout( + async () => { return this.$iOSEmulatorServices.connectToPort({ port }); }, + constants.SOCKET_CONNECTION_TIMEOUT_MS); + } catch (e) { + this.$logger.warn(e); + } + + return socket; + } } diff --git a/lib/common/test/unit-tests/mobile/ios-simulator-discovery.ts b/lib/common/test/unit-tests/mobile/ios-simulator-discovery.ts index a60bc34886..406a501fe6 100644 --- a/lib/common/test/unit-tests/mobile/ios-simulator-discovery.ts +++ b/lib/common/test/unit-tests/mobile/ios-simulator-discovery.ts @@ -4,12 +4,18 @@ import { Yok } from "../../../yok"; import { assert } from "chai"; import { DeviceDiscoveryEventNames, CONNECTED_STATUS } from "../../../constants"; import { DevicePlatformsConstants } from "../../../mobile/device-platforms-constants"; +import { ErrorsStub, CommonLoggerStub, HooksServiceStub } from "../stubs"; +import { FileSystemStub } from "../../../../../test/stubs"; let currentlyRunningSimulators: Mobile.IiSimDevice[]; function createTestInjector(): IInjector { const injector = new Yok(); + injector.register("fs", FileSystemStub); + injector.register("plistParser", {}); injector.register("injector", injector); + injector.register("errors", ErrorsStub); + injector.register("iOSDebuggerPortService", {}); injector.register("iOSSimResolver", { iOSSim: { getRunningSimulators: async () => currentlyRunningSimulators @@ -32,6 +38,11 @@ function createTestInjector(): IInjector { injector.register("iOSSimulatorLogProvider", {}); injector.register("deviceLogProvider", {}); injector.register("iOSEmulatorServices", {}); + injector.register("iOSNotification", {}); + injector.register("processService", {}); + injector.register("options", {}); + injector.register("hooksService", HooksServiceStub); + injector.register("logger", CommonLoggerStub); return injector; } diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 16fe5cea5d..b21eeb1acc 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -731,12 +731,12 @@ interface IAndroidToolsInfoValidateInput extends IAndroidToolsInfoOptions { validateTargetSdk: boolean; } -interface ISocketProxyFactory extends NodeJS.EventEmitter { - getTCPSocketProxy(deviceIdentifier: string): any; - addTCPSocketProxy(factory: () => Promise, deviceIdentifier: string): Promise; +interface IAppDebugSocketProxyFactory extends NodeJS.EventEmitter { + getTCPSocketProxy(deviceIdentifier: string, appId: string): any; + addTCPSocketProxy(device: Mobile.IiOSDevice, appId: string): Promise; - getWebSocketProxy(deviceIdentifier: string): any; - addWebSocketProxy(factory: () => Promise, deviceIdentifier: string): Promise; + getWebSocketProxy(deviceIdentifier: string, appId: string): any; + addWebSocketProxy(device: Mobile.IiOSDevice, appId: string): Promise; removeAllProxies(): void; } diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index b75ec84bfb..6c8e4eca67 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -5,7 +5,7 @@ interface IDebugData extends IAppDebugData, Mobile.IDeviceIdentifier { } /** - * Describes information application that will be debugged. + * Describes information for application that will be debugged. */ interface IAppDebugData extends IProjectDir { /** diff --git a/lib/definitions/ios-debugger-port-service.d.ts b/lib/definitions/ios-debugger-port-service.d.ts index ce7527034c..02d1872551 100644 --- a/lib/definitions/ios-debugger-port-service.d.ts +++ b/lib/definitions/ios-debugger-port-service.d.ts @@ -1,4 +1,4 @@ -interface IIOSDebuggerPortInputData extends IProjectDir { +interface IIOSDebuggerPortInputData { deviceId: string; appId: string; } diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts similarity index 62% rename from lib/device-sockets/ios/socket-proxy-factory.ts rename to lib/device-sockets/ios/app-debug-socket-proxy-factory.ts index 1430403a00..4f5f8a3a49 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts @@ -5,9 +5,9 @@ import * as net from "net"; import * as ws from "ws"; import temp = require("temp"); -export class SocketProxyFactory extends EventEmitter implements ISocketProxyFactory { - private deviceWebServers: { [id: string]: ws.Server; } = {}; - private deviceTcpServers: { [id: string]: net.Server; } = {}; +export class AppDebugSocketProxyFactory extends EventEmitter implements IAppDebugSocketProxyFactory { + private deviceWebServers: IDictionary = {}; + private deviceTcpServers: IDictionary = {}; constructor(private $logger: ILogger, private $errors: IErrors, @@ -16,18 +16,19 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact super(); } - public getTCPSocketProxy(deviceIdentifier: string): net.Server { - return this.deviceTcpServers[deviceIdentifier]; + public getTCPSocketProxy(deviceIdentifier: string, appId: string): net.Server { + return this.deviceTcpServers[`${deviceIdentifier}-${appId}`]; } - public getWebSocketProxy(deviceIdentifier: string): ws.Server { - return this.deviceWebServers[deviceIdentifier]; + public getWebSocketProxy(deviceIdentifier: string, appId: string): ws.Server { + return this.deviceWebServers[`${deviceIdentifier}-${appId}`]; } - public async addTCPSocketProxy(factory: () => Promise, deviceIdentifier: string): Promise { - const existingServer = this.deviceTcpServers[deviceIdentifier]; + public async addTCPSocketProxy(device: Mobile.IiOSDevice, appId: string): Promise { + const cacheKey = `${device.deviceInfo.identifier}-${appId}`; + const existingServer = this.deviceTcpServers[cacheKey]; if (existingServer) { - throw new Error(`TCP socket proxy is already running for device '${deviceIdentifier}'`); + this.$errors.failWithoutHelp(`TCP socket proxy is already running for device '${device.deviceInfo.identifier}' and app '${appId}'`); } this.$logger.info("\nSetting up proxy...\nPress Ctrl + C to terminate, or disconnect.\n"); @@ -36,7 +37,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact allowHalfOpen: true }); - this.deviceTcpServers[deviceIdentifier] = server; + this.deviceTcpServers[cacheKey] = server; server.on("connection", async (frontendSocket: net.Socket) => { this.$logger.info("Frontend client connected."); @@ -47,10 +48,10 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact } }); - const backendSocket = await factory(); + const appDebugSocket = await device.getDebugSocket(appId); this.$logger.info("Backend socket created."); - backendSocket.on("end", () => { + appDebugSocket.on("end", () => { this.$logger.info("Backend socket closed!"); if (!this.$options.watch) { process.exit(0); @@ -59,20 +60,15 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact frontendSocket.on("close", () => { this.$logger.info("Frontend socket closed"); - if (!(backendSocket).destroyed) { - backendSocket.destroy(); - } + device.destroyDebugSocket(appId); }); - backendSocket.on("close", () => { + appDebugSocket.on("close", () => { this.$logger.info("Backend socket closed"); - if (!(frontendSocket).destroyed) { - frontendSocket.destroy(); - } }); - backendSocket.pipe(frontendSocket); - frontendSocket.pipe(backendSocket); + appDebugSocket.pipe(frontendSocket); + frontendSocket.pipe(appDebugSocket); frontendSocket.resume(); }); @@ -85,10 +81,11 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact return server; } - public async addWebSocketProxy(factory: () => Promise, deviceIdentifier: string): Promise { - const existingServer = this.deviceWebServers[deviceIdentifier]; + public async addWebSocketProxy(device: Mobile.IiOSDevice, appId: string): Promise { + const cacheKey = `${device.deviceInfo.identifier}-${appId}`; + const existingServer = this.deviceWebServers[cacheKey]; if (existingServer) { - throw new Error(`Web socket proxy is already running for device '${deviceIdentifier}'`); + this.$errors.failWithoutHelp(`Web socket proxy is already running for device '${device.deviceInfo.identifier}' and app '${appId}'`); } // NOTE: We will try to provide command line options to select ports, at least on the localhost. @@ -106,28 +103,28 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact host: "localhost", verifyClient: async (info: any, callback: Function) => { this.$logger.info("Frontend client connected."); - let _socket; + let appDebugSocket; try { - _socket = await factory(); + appDebugSocket = await device.getDebugSocket(appId); } catch (err) { - err.deviceIdentifier = deviceIdentifier; + err.deviceIdentifier = device.deviceInfo.identifier; this.$logger.trace(err); this.emit(CONNECTION_ERROR_EVENT_NAME, err); - this.$errors.failWithoutHelp(`Cannot connect to device socket. The error message is ${err.message}`); + this.$errors.failWithoutHelp(`Cannot connect to device socket.The error message is ${err.message} `); } this.$logger.info("Backend socket created."); - info.req["__deviceSocket"] = _socket; + info.req["__deviceSocket"] = appDebugSocket; callback(true); } }); - this.deviceWebServers[deviceIdentifier] = server; + this.deviceWebServers[cacheKey] = server; server.on("connection", (webSocket, req) => { const encoding = "utf16le"; - const deviceSocket: net.Socket = (req)["__deviceSocket"]; + const appDebugSocket: net.Socket = (req)["__deviceSocket"]; const packets = new PacketStream(); - deviceSocket.pipe(packets); + appDebugSocket.pipe(packets); packets.on("data", (buffer: Buffer) => { webSocket.send(buffer.toString(encoding)); @@ -137,7 +134,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact this.$logger.trace("Error on debugger websocket", err); }); - deviceSocket.on("error", err => { + appDebugSocket.on("error", err => { this.$logger.trace("Error on debugger deviceSocket", err); }); @@ -146,10 +143,10 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact const payload = Buffer.allocUnsafe(length + 4); payload.writeInt32BE(length, 0); payload.write(message, 4, length, encoding); - deviceSocket.write(payload); + appDebugSocket.write(payload); }); - deviceSocket.on("close", () => { + appDebugSocket.on("close", () => { this.$logger.info("Backend socket closed!"); webSocket.close(); }); @@ -157,7 +154,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact webSocket.on("close", () => { this.$logger.info('Frontend socket closed!'); packets.destroy(); - deviceSocket.destroy(); + device.destroyDebugSocket(appId); if (!this.$options.watch) { process.exit(0); } @@ -170,11 +167,12 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact } public removeAllProxies() { - for (var deviceId in this.deviceWebServers) { + let deviceId; + for (deviceId in this.deviceWebServers) { this.deviceWebServers[deviceId].close(); } - for (var deviceId in this.deviceTcpServers) { + for (deviceId in this.deviceTcpServers) { this.deviceTcpServers[deviceId].close(); } @@ -182,4 +180,4 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact this.deviceTcpServers = {}; } } -$injector.register("socketProxyFactory", SocketProxyFactory); +$injector.register("appDebugSocketProxyFactory", AppDebugSocketProxyFactory); diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index cafd3e6619..d9ae030dc5 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -46,7 +46,7 @@ export class DebugService extends EventEmitter implements IDebugService { // After we find a way to check on iOS we should use it here. let result: string; - const debugService = this.getDebugService(device); + const debugService = this.getDeviceDebugService(device); if (!debugService) { this.$errors.failWithoutHelp(`Unsupported device OS: ${device.deviceInfo.platform}. You can debug your applications only on iOS or Android.`); } @@ -68,11 +68,11 @@ export class DebugService extends EventEmitter implements IDebugService { } public debugStop(deviceIdentifier: string): Promise { - const debugService = this.getDebugServiceByIdentifier(deviceIdentifier); + const debugService = this.getDeviceDebugServiceByIdentifier(deviceIdentifier); return debugService.debugStop(); } - protected getDebugService(device: Mobile.IDevice): IDeviceDebugService { + protected getDeviceDebugService(device: Mobile.IDevice): IDeviceDebugService { if (!this._platformDebugServices[device.deviceInfo.identifier]) { const platform = device.deviceInfo.platform; if (this.$mobileHelper.isiOSPlatform(platform)) { @@ -89,9 +89,9 @@ export class DebugService extends EventEmitter implements IDebugService { return this._platformDebugServices[device.deviceInfo.identifier]; } - private getDebugServiceByIdentifier(deviceIdentifier: string): IDeviceDebugService { + private getDeviceDebugServiceByIdentifier(deviceIdentifier: string): IDeviceDebugService { const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); - return this.getDebugService(device); + return this.getDeviceDebugService(device); } private attachConnectionErrorHandlers(platformDebugService: IDeviceDebugService) { diff --git a/lib/services/ios-debugger-port-service.ts b/lib/services/ios-debugger-port-service.ts index 8950c3ca81..4af4adcb69 100644 --- a/lib/services/ios-debugger-port-service.ts +++ b/lib/services/ios-debugger-port-service.ts @@ -1,28 +1,18 @@ import { DEBUGGER_PORT_FOUND_EVENT_NAME, ATTACH_REQUEST_EVENT_NAME } from "../common/constants"; import { cache } from "../common/decorators"; -import * as semver from "semver"; export class IOSDebuggerPortService implements IIOSDebuggerPortService { public static DEBUG_PORT_LOG_REGEX = /NativeScript debugger has opened inspector socket on port (\d+?) for (.*)[.]/; private mapDebuggerPortData: IDictionary = {}; - private static DEFAULT_PORT = 18181; - private static MIN_REQUIRED_FRAMEWORK_VERSION = "4.0.1"; private static DEFAULT_TIMEOUT_IN_SECONDS = 10; constructor(private $logParserService: ILogParserService, - private $iOSProjectService: IPlatformProjectService, private $iOSNotification: IiOSNotification, - private $projectDataService: IProjectDataService, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $logger: ILogger) { } public getPort(data: IIOSDebuggerPortInputData, debugOptions?: IDebugOptions): Promise { return new Promise((resolve, reject) => { - if (!this.canStartLookingForDebuggerPort(data)) { - resolve(IOSDebuggerPortService.DEFAULT_PORT); - return; - } - const key = `${data.deviceId}${data.appId}`; const timeout = this.getTimeout(debugOptions); let retryCount = Math.max(timeout * 1000 / 500, 10); @@ -41,21 +31,10 @@ export class IOSDebuggerPortService implements IIOSDebuggerPortService { } public async attachToDebuggerPortFoundEvent(device: Mobile.IDevice, data: IProjectDir, debugOptions: IDebugOptions): Promise { - const projectData = this.$projectDataService.getProjectData(data && data.projectDir); - if (!this.canStartLookingForDebuggerPort(projectData)) { - return; - } - this.attachToDebuggerPortFoundEventCore(); this.attachToAttachRequestEvent(device, debugOptions); } - private canStartLookingForDebuggerPort(data: IProjectDir): boolean { - const projectData = this.$projectDataService.getProjectData(data && data.projectDir); - const frameworkVersion = this.$iOSProjectService.getFrameworkVersion(projectData); - return !frameworkVersion || !semver.valid(frameworkVersion) || semver.gt(frameworkVersion, IOSDebuggerPortService.MIN_REQUIRED_FRAMEWORK_VERSION); - } - @cache() private attachToDebuggerPortFoundEventCore(): void { this.$logParserService.addParseRule({ diff --git a/lib/services/ios-device-debug-service.ts b/lib/services/ios-device-debug-service.ts index d4dabfb06d..bb9c68f053 100644 --- a/lib/services/ios-device-debug-service.ts +++ b/lib/services/ios-device-debug-service.ts @@ -24,13 +24,13 @@ export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDe private $packageInstallationManager: IPackageInstallationManager, private $iOSDebuggerPortService: IIOSDebuggerPortService, private $processService: IProcessService, - private $socketProxyFactory: ISocketProxyFactory, + private $appDebugSocketProxyFactory: IAppDebugSocketProxyFactory, private $projectDataService: IProjectDataService, private $deviceLogProvider: Mobile.IDeviceLogProvider) { super(device, $devicesService); this.$processService.attachToProcessExitSignals(this, this.debugStop); - this.$socketProxyFactory.on(CONNECTION_ERROR_EVENT_NAME, (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e)); + this.$appDebugSocketProxyFactory.on(CONNECTION_ERROR_EVENT_NAME, (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e)); this.deviceIdentifier = this.device.deviceInfo.identifier; } @@ -45,28 +45,19 @@ export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDe await this.$iOSDebuggerPortService.attachToDebuggerPortFoundEvent(this.device, debugData, debugOptions); if (!debugOptions.start) { - if (this.device.isEmulator) { - await this.startAppOnSimulator(debugData, debugOptions); - } else { - await this.startAppOnDevice(debugData, debugOptions); - } + await this.startApp(debugData, debugOptions); } return this.wireDebuggerClient(debugData, debugOptions); } public async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - if (this.device.isEmulator) { - await this.startAppOnSimulator(debugData, debugOptions); - } else { - await this.startAppOnDevice(debugData, debugOptions); - } - + await this.startApp(debugData, debugOptions); await this.wireDebuggerClient(debugData, debugOptions); } public async debugStop(): Promise { - this.$socketProxyFactory.removeAllProxies(); + this.$appDebugSocketProxyFactory.removeAllProxies(); await this.stopAppDebuggerOnSimulator(); } @@ -78,6 +69,14 @@ export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDe return chromeDebugUrl; } + private async startApp(debugData: IDebugData, debugOptions: IDebugOptions) { + if (this.device.isEmulator) { + await this.startAppOnSimulator(debugData, debugOptions); + } else { + await this.startAppOnDevice(debugData, debugOptions); + } + } + private validateOptions(debugOptions: IDebugOptions) { if (debugOptions.debugBrk && debugOptions.start) { this.$errors.failWithoutHelp("Expected exactly one of the --debug-brk or --start options."); @@ -159,25 +158,30 @@ export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDe private async wireDebuggerClient(debugData: IDebugData, debugOptions: IDebugOptions): Promise { if ((debugOptions.inspector || !debugOptions.client) && this.$hostInfo.isDarwin) { - const existingTcpProxy = this.$socketProxyFactory.getTCPSocketProxy(this.deviceIdentifier); - const getDeviceSocket = async () => await this.device.getDebugSocket(debugData.applicationIdentifier, debugData.projectDir); - const tcpSocketProxy = existingTcpProxy || await this.$socketProxyFactory.addTCPSocketProxy(getDeviceSocket, this.deviceIdentifier); - if (!existingTcpProxy) { - await this.openAppInspector(tcpSocketProxy.address(), debugData, debugOptions); - } - - return null; + return await this.setupTcpAppDebugProxy(debugData, debugOptions); } else { - if (debugOptions.chrome) { - this.$logger.info("'--chrome' is the default behavior. Use --inspector to debug iOS applications using the Safari Web Inspector."); - } + return await this.setupWebAppDebugProxy(debugOptions, debugData); + } + } - const existingWebProxy = this.$socketProxyFactory.getWebSocketProxy(this.deviceIdentifier); - const getDeviceSocket = async () => await this.device.getDebugSocket(debugData.applicationIdentifier, debugData.projectDir); - const webSocketProxy = existingWebProxy || await this.$socketProxyFactory.addWebSocketProxy(getDeviceSocket, this.deviceIdentifier); + private async setupWebAppDebugProxy(debugOptions: IDebugOptions, debugData: IDebugData): Promise { + if (debugOptions.chrome) { + this.$logger.info("'--chrome' is the default behavior. Use --inspector to debug iOS applications using the Safari Web Inspector."); + } + const existingWebProxy = this.$appDebugSocketProxyFactory.getWebSocketProxy(this.deviceIdentifier, debugData.applicationIdentifier); + const webSocketProxy = existingWebProxy || await this.$appDebugSocketProxyFactory.addWebSocketProxy(this.device, debugData.applicationIdentifier); + + return this.getChromeDebugUrl(debugOptions, webSocketProxy.options.port); + } - return this.getChromeDebugUrl(debugOptions, webSocketProxy.options.port); + private async setupTcpAppDebugProxy(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + const existingTcpProxy = this.$appDebugSocketProxyFactory.getTCPSocketProxy(this.deviceIdentifier, debugData.applicationIdentifier); + const tcpSocketProxy = existingTcpProxy || await this.$appDebugSocketProxyFactory.addTCPSocketProxy(this.device, debugData.applicationIdentifier); + if (!existingTcpProxy) { + await this.openAppInspector(tcpSocketProxy.address(), debugData, debugOptions); } + + return null; } private async openAppInspector(fileDescriptor: string, debugData: IDebugData, debugOptions: IDebugOptions): Promise { @@ -195,4 +199,4 @@ export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDe } } -$injector.register("iOSDeviceDebugService", IOSDeviceDebugService, false); \ No newline at end of file +$injector.register("iOSDeviceDebugService", IOSDeviceDebugService, false); diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 88d20d429a..62e428c15d 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -17,12 +17,13 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen } private async setupSocketIfNeeded(projectData: IProjectData): Promise { + // TODO: persist the sockets per app in order to support LiveSync on multiple apps on the same device if (this.socket) { return true; } const appId = projectData.projectIdentifiers.ios; - this.socket = await this.device.getLiveSyncSocket(appId, projectData.projectDir); + this.socket = await this.device.getLiveSyncSocket(appId); if (!this.socket) { return false; } @@ -132,7 +133,10 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen private destroySocket(): void { if (this.socket) { - this.socket.destroy(); + // we do not support LiveSync on multiple apps on the same device + // in order to do that, we should cache the socket per app + // and destroy just the current app socket when possible + this.device.destroyAllSockets(); this.socket = null; } } diff --git a/test/debug.ts b/test/debug.ts index be29e08028..ae180e5c6c 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -2,6 +2,7 @@ import * as stubs from "./stubs"; import * as yok from "../lib/common/yok"; import { DebugAndroidCommand, DebugPlatformCommand } from "../lib/commands/debug"; import { assert } from "chai"; +import { BundleValidatorHelper } from "../lib/helpers/bundle-validator-helper"; import { Configuration, StaticConfig } from "../lib/config"; import { Options } from "../lib/options"; import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants"; @@ -30,6 +31,7 @@ function createTestInjector(): IInjector { testInjector.register('errors', stubs.ErrorsStub); testInjector.register('hostInfo', {}); testInjector.register("androidBundleValidatorHelper", stubs.AndroidBundleValidatorHelper); + testInjector.register("bundleValidatorHelper", BundleValidatorHelper); testInjector.register("analyticsService", { trackException: async (): Promise => undefined, checkConsent: async (): Promise => undefined, diff --git a/test/services/android-debug-service.ts b/test/services/android-device-debug-service.ts similarity index 97% rename from test/services/android-debug-service.ts rename to test/services/android-device-debug-service.ts index f7c7384401..3edf69f536 100644 --- a/test/services/android-debug-service.ts +++ b/test/services/android-device-debug-service.ts @@ -14,7 +14,7 @@ class AndroidDeviceDebugServiceInheritor extends AndroidDeviceDebugService { $net: INet, $projectDataService: IProjectDataService, $deviceLogProvider: Mobile.IDeviceLogProvider) { - super({}, $devicesService, $errors, $logger, $androidDeviceDiscovery, $androidProcessService, $net, $projectDataService, $deviceLogProvider); + super({ deviceInfo: { identifier: "123" } }, $devicesService, $errors, $logger, $androidDeviceDiscovery, $androidProcessService, $net, $projectDataService, $deviceLogProvider); } public getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index 26767ca24a..93d5958203 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -167,7 +167,7 @@ describe("debugService", () => { const testInjector = getTestInjectorForTestConfiguration(testData); const expectedErrorMessage = "Platform specific error"; - const platformDebugService = testInjector.resolve(`${platform}DebugService`); + const platformDebugService = testInjector.resolve(`${platform}DeviceDebugService`); platformDebugService.debug = async (debugData: IDebugData, debugOptions: IDebugOptions): Promise => { throw new Error(expectedErrorMessage); }; @@ -204,7 +204,7 @@ describe("debugService", () => { await assert.isFulfilled(debugService.debug(debugData, null)); const expectedErrorData = { deviceIdentifier: "deviceId", message: "my message", code: 2048 }; - const platformDebugService = testInjector.resolve(`${platform}DebugService`); + const platformDebugService = testInjector.resolve(`${platform}DeviceDebugService`); platformDebugService.emit(CONNECTION_ERROR_EVENT_NAME, expectedErrorData); assert.deepEqual(dataRaisedForConnectionError, expectedErrorData); }); diff --git a/test/services/ios-debugger-port-service.ts b/test/services/ios-debugger-port-service.ts index 6864307077..2f12ac2cdc 100644 --- a/test/services/ios-debugger-port-service.ts +++ b/test/services/ios-debugger-port-service.ts @@ -159,7 +159,7 @@ describe("iOSDebuggerPortService", () => { emitDeviceLog(getDebuggerPortMessage(testCase.emittedPort)); } - const promise = iOSDebuggerPortService.getPort({ deviceId: deviceId, appId: appId, projectDir: mockProjectDirObj.projectDir }); + const promise = iOSDebuggerPortService.getPort({ deviceId: deviceId, appId: appId }); clock.tick(20000); const port = await promise; assert.deepEqual(port, testCase.emittedPort); @@ -173,7 +173,7 @@ describe("iOSDebuggerPortService", () => { emitDeviceLog(getMultilineDebuggerPortMessage(testCase.emittedPort)); } - const promise = iOSDebuggerPortService.getPort({ deviceId: deviceId, appId: appId, projectDir: mockProjectDirObj.projectDir }); + const promise = iOSDebuggerPortService.getPort({ deviceId: deviceId, appId: appId }); clock.tick(20000); const port = await promise; assert.deepEqual(port, testCase.emittedPort); diff --git a/test/services/ios-debug-service.ts b/test/services/ios-device-debug-service.ts similarity index 95% rename from test/services/ios-debug-service.ts rename to test/services/ios-device-debug-service.ts index 5f0a2e4bd7..4f70e24c25 100644 --- a/test/services/ios-debug-service.ts +++ b/test/services/ios-device-debug-service.ts @@ -16,12 +16,12 @@ class IOSDeviceDebugServiceInheritor extends IOSDeviceDebugService { $packageInstallationManager: IPackageInstallationManager, $iOSDebuggerPortService: IIOSDebuggerPortService, $processService: IProcessService, - $socketProxyFactory: ISocketProxyFactory, + $appDebugSocketProxyFactory: IAppDebugSocketProxyFactory, $projectDataService: IProjectDataService, $deviceLogProvider: Mobile.IDeviceLogProvider) { - super({}, $devicesService, $platformService, $iOSEmulatorServices, $childProcess, $hostInfo, $logger, $errors, + super({ deviceInfo: { identifier: "123" } }, $devicesService, $platformService, $iOSEmulatorServices, $childProcess, $hostInfo, $logger, $errors, $packageInstallationManager, $iOSDebuggerPortService, - $processService, $socketProxyFactory, $projectDataService, $deviceLogProvider); + $processService, $appDebugSocketProxyFactory, $projectDataService, $deviceLogProvider); } public getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { @@ -46,7 +46,7 @@ const createTestInjector = (): IInjector => { attachToProcessExitSignals: (context: any, callback: () => void): void => undefined }); - testInjector.register("socketProxyFactory", { + testInjector.register("appDebugSocketProxyFactory", { on: (event: string | symbol, listener: Function): any => undefined }); From 4b6d169ef50d4f354f1102df78e8b8d0e83442c4 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Wed, 28 Nov 2018 17:04:26 +0200 Subject: [PATCH 14/17] fix: stop printing the debug port before ensuring a running app --- lib/services/android-device-debug-service.ts | 33 +++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/lib/services/android-device-debug-service.ts b/lib/services/android-device-debug-service.ts index 2bb6567474..3b5d2b1bf7 100644 --- a/lib/services/android-device-debug-service.ts +++ b/lib/services/android-device-debug-service.ts @@ -76,11 +76,11 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi } private async removePortForwarding(packageName?: string): Promise { - const port = await this.getForwardedLocalDebugPortForPackageName(this.device.deviceInfo.identifier, packageName || this._packageName); + const port = await this.getForwardedDebugPort(this.device.deviceInfo.identifier, packageName || this._packageName); return this.device.adb.executeCommand(["forward", "--remove", `tcp:${port}`]); } - private async getForwardedLocalDebugPortForPackageName(deviceId: string, packageName: string): Promise { + private async getForwardedDebugPort(deviceId: string, packageName: string): Promise { let port = -1; const forwardsResult = await this.device.adb.executeCommand(["forward", "--list"]); @@ -128,38 +128,33 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi } private async debugCore(device: Mobile.IAndroidDevice, packageFile: string, appData: Mobile.IApplicationData, debugOptions: IDebugOptions): Promise { - await this.printDebugPort(device.deviceInfo.identifier, appData.appId); - - if (debugOptions.start) { - return await this.attachDebugger(device.deviceInfo.identifier, appData.appId, debugOptions); - } else if (debugOptions.stop) { + if (debugOptions.stop) { await this.removePortForwarding(); return null; - } else { + } + + if (!debugOptions.start) { await this.debugStartCore(appData, debugOptions); - return await this.attachDebugger(device.deviceInfo.identifier, appData.appId, debugOptions); } + + await this.validateRunningApp(device.deviceInfo.identifier, appData.appId); + const debugPort = await this.getForwardedDebugPort(device.deviceInfo.identifier, appData.appId); + await this.printDebugPort(device.deviceInfo.identifier, debugPort); + + return this.getChromeDebugUrl(debugOptions, debugPort); } - private async printDebugPort(deviceId: string, packageName: string): Promise { - const port = await this.getForwardedLocalDebugPortForPackageName(deviceId, packageName); + private async printDebugPort(deviceId: string, port: number): Promise { this.$logger.info("device: " + deviceId + " debug port: " + port + "\n"); } - private async attachDebugger(deviceId: string, packageName: string, debugOptions: IDebugOptions): Promise { + private async validateRunningApp(deviceId: string, packageName: string): Promise { if (!(await this.isAppRunning(packageName, deviceId))) { this.$errors.failWithoutHelp(`The application ${packageName} does not appear to be running on ${deviceId} or is not built with debugging enabled.`); } - - const port = await this.getForwardedLocalDebugPortForPackageName(deviceId, packageName); - - return this.getChromeDebugUrl(debugOptions, port); } private async debugStartCore(appData: Mobile.IApplicationData, debugOptions: IDebugOptions): Promise { - // Arguments passed to executeShellCommand must be in array ([]), but it turned out adb shell "arg with intervals" still works correctly. - // As we need to redirect output of a command on the device, keep using only one argument. - // We could rewrite this with two calls - touch and rm -f , but -f flag is not available on old Android, so rm call will fail when file does not exist. await this.device.applicationManager.stopApplication(appData); if (debugOptions.debugBrk) { From 63b519271488ce04480b303aad20b4ad88dd7dab Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Thu, 29 Nov 2018 18:17:42 +0200 Subject: [PATCH 15/17] fix: destroy the inspector socket on debug socket close in order to avoid infinite wait on ctrl+c --- lib/device-sockets/ios/app-debug-socket-proxy-factory.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts b/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts index 4f5f8a3a49..9e929de2ff 100644 --- a/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts +++ b/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts @@ -65,6 +65,7 @@ export class AppDebugSocketProxyFactory extends EventEmitter implements IAppDebu appDebugSocket.on("close", () => { this.$logger.info("Backend socket closed"); + frontendSocket.destroy(); }); appDebugSocket.pipe(frontendSocket); From 3f0ea54cf4b7641948dd524d48691bbe2394a50b Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Mon, 3 Dec 2018 16:08:42 +0200 Subject: [PATCH 16/17] fix: close the Inspector proxy on runtime socket close in allow creating the proxy on app restart --- lib/device-sockets/ios/app-debug-socket-proxy-factory.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts b/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts index 9e929de2ff..b12118b887 100644 --- a/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts +++ b/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts @@ -66,6 +66,8 @@ export class AppDebugSocketProxyFactory extends EventEmitter implements IAppDebu appDebugSocket.on("close", () => { this.$logger.info("Backend socket closed"); frontendSocket.destroy(); + server.close(); + delete this.deviceTcpServers[cacheKey]; }); appDebugSocket.pipe(frontendSocket); From 4bd9408791182664fe2476df42cae83bd02d5d02 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Mon, 3 Dec 2018 16:10:01 +0200 Subject: [PATCH 17/17] fix: close the inspector app on Inspector proxy `close` in order to create a new one for the next proxy --- lib/services/ios-device-debug-service.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/services/ios-device-debug-service.ts b/lib/services/ios-device-debug-service.ts index bb9c68f053..72166245b6 100644 --- a/lib/services/ios-device-debug-service.ts +++ b/lib/services/ios-device-debug-service.ts @@ -178,23 +178,30 @@ export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDe const existingTcpProxy = this.$appDebugSocketProxyFactory.getTCPSocketProxy(this.deviceIdentifier, debugData.applicationIdentifier); const tcpSocketProxy = existingTcpProxy || await this.$appDebugSocketProxyFactory.addTCPSocketProxy(this.device, debugData.applicationIdentifier); if (!existingTcpProxy) { - await this.openAppInspector(tcpSocketProxy.address(), debugData, debugOptions); + const inspectorProcess = await this.openAppInspector(tcpSocketProxy.address(), debugData, debugOptions); + if (inspectorProcess) { + tcpSocketProxy.on("close", async () => { + await this.killProcess(inspectorProcess); + }); + } } return null; } - private async openAppInspector(fileDescriptor: string, debugData: IDebugData, debugOptions: IDebugOptions): Promise { + private async openAppInspector(fileDescriptor: string, debugData: IDebugData, debugOptions: IDebugOptions): Promise { if (debugOptions.client) { const inspectorPath = await this.$packageInstallationManager.getInspectorFromCache(inspectorNpmPackageName, debugData.projectDir); const inspectorSourceLocation = path.join(inspectorPath, inspectorUiDir, "Main.html"); - const inspectorApplicationPath = path.join(inspectorPath, inspectorAppName); + const inspectorApplicationPath = path.join(inspectorPath, inspectorAppName, "Contents", "MacOS", inspectorAppName, "Contents", "MacOS", "NativeScript Inspector"); - const cmd = `open -a '${inspectorApplicationPath}' --args '${inspectorSourceLocation}' '${debugData.projectName}' '${fileDescriptor}'`; - await this.$childProcess.exec(cmd); + const inspectorProcess: ChildProcess = this.$childProcess.spawn(inspectorApplicationPath, [inspectorSourceLocation, debugData.projectName, fileDescriptor]); + inspectorProcess.on("error", (e: Error) => this.$logger.trace(e)); + return inspectorProcess; } else { this.$logger.info("Suppressing debugging client."); + return null; } } }