From 88825dab40e66c8c153a654c4a227e27ea053b86 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 7 Apr 2017 18:27:54 +0300 Subject: [PATCH 1/3] Fix connect ECONNREFUSED error when trying to debug on iOS Sim When trying to debug on iOS Simulator (via NativeScript Inspector), we often receive `Error: connect ECONNREFUSED 127.0.0.1:18181` error. There are two different problems. First one is incorrect identifying of the application's PID - we are using `_.trimStart` method totally incorrectly and in case your application identifier has numbers in it, they'll also be replaced in the search PID string. For example in case your app id is `org.nativescript.app10`, and the real PID of the running application is 18129, the current usage of `trimStart` method will give you the PID 8129. Fix this by applying regular expression and find the correct PID. The second problem is that there's some delay between opening the NativeScript Inspector and the debugged application on the device. In many cases the first time when we try to connect, we receive the error ECONNREFUSED. However in the previous implementation, we were trying to connect multiple times. Get back the old code and adjust it to work with Promises instead of the old sync execution. --- lib/common | 2 +- lib/declarations.d.ts | 2 +- .../ios/socket-proxy-factory.ts | 54 ++++++++++--------- lib/services/ios-debug-service.ts | 11 +++- 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/lib/common b/lib/common index f30ac23b0f..165693ed6f 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit f30ac23b0f8f0e6309157f2ac0c421b19719b43a +Subproject commit 165693ed6f926e7b7be44976d6d94d24608a7c69 diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index e048ed66ae..9746e88b14 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -288,7 +288,7 @@ interface IAndroidToolsInfoData { } interface ISocketProxyFactory extends NodeJS.EventEmitter { - createTCPSocketProxy(factory: () => Promise): any; + createTCPSocketProxy(factory: () => Promise): Promise; createWebSocketProxy(factory: () => Promise): Promise; } diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/socket-proxy-factory.ts index fea0e5a41c..3a78b6de28 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/socket-proxy-factory.ts @@ -3,6 +3,7 @@ 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 { @@ -14,7 +15,9 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact super(); } - public createTCPSocketProxy(factory: () => Promise): net.Server { + public async createTCPSocketProxy(factory: () => Promise): Promise { + 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"); let server = net.createServer({ @@ -31,32 +34,33 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact } }); - const backendSocket = await factory(); - this.$logger.info("Backend socket created."); - - backendSocket.on("end", () => { - this.$logger.info("Backend socket closed!"); - if (!(this.$config.debugLivesync && this.$options.watch)) { - process.exit(0); - } - }); + await socketFactory((backendSocket: net.Socket) => { + this.$logger.info("Backend socket created."); - frontendSocket.on("close", () => { - console.log("frontend socket closed"); - if (!(backendSocket).destroyed) { - backendSocket.destroy(); - } + backendSocket.on("end", () => { + this.$logger.info("Backend socket closed!"); + if (!(this.$config.debugLivesync && this.$options.watch)) { + process.exit(0); + } + }); + + frontendSocket.on("close", () => { + console.log("frontend socket closed"); + if (!(backendSocket).destroyed) { + backendSocket.destroy(); + } + }); + backendSocket.on("close", () => { + console.log("backend socket closed"); + if (!(frontendSocket).destroyed) { + frontendSocket.destroy(); + } + }); + + backendSocket.pipe(frontendSocket); + frontendSocket.pipe(backendSocket); + frontendSocket.resume(); }); - backendSocket.on("close", () => { - console.log("backend socket closed"); - if (!(frontendSocket).destroyed) { - frontendSocket.destroy(); - } - }); - - backendSocket.pipe(frontendSocket); - frontendSocket.pipe(backendSocket); - frontendSocket.resume(); }); let socketFileLocation = temp.path({ suffix: ".sock" }); diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index 0dc16e1518..8cbf99087f 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -103,11 +103,18 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService let lineStream = byline(child_process.stdout); this._childProcess = child_process; + const pidRegExp = new RegExp(`${debugData.applicationIdentifier}:\\s?(\\d+)`); lineStream.on('data', (line: NodeBuffer) => { let lineText = line.toString(); if (lineText && _.startsWith(lineText, debugData.applicationIdentifier)) { - let pid = _.trimStart(lineText, debugData.applicationIdentifier + ": "); + const pidMatch = lineText.match(pidRegExp); + + if (!pidMatch) { + this.$logger.trace(`Line ${lineText} does not contain the searched pattern: ${pidRegExp}.`); + } + + const pid = pidMatch[1]; this._lldbProcess = this.$childProcess.spawn("lldb", ["-p", pid]); if (log4js.levels.TRACE.isGreaterThanOrEqualTo(this.$logger.getLevel())) { this._lldbProcess.stdout.pipe(process.stdout); @@ -182,7 +189,7 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService const commitSHA = "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; // corresponds to 55.0.2883 Chrome version return `chrome-devtools://devtools/remote/serve_file/@${commitSHA}/inspector.html?experiments=true&ws=localhost:${this._socketProxy.options.port}`; } else { - this._socketProxy = this.$socketProxyFactory.createTCPSocketProxy(this.getSocketFactory(device)); + this._socketProxy = await this.$socketProxyFactory.createTCPSocketProxy(this.getSocketFactory(device)); await this.openAppInspector(this._socketProxy.address(), debugData, debugOptions); return null; } From 8180c705b7a35cbcd1dd586e07a53470587c7c79 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 10 Apr 2017 13:06:07 +0300 Subject: [PATCH 2/3] Fix debug command - `tns debug ios` prints message that you have to open `null` url in Chrome - when you do not pass `--chrome` to this command, it will use NativeScript inspector, so hide the incorrect message. - debugData is constructed too early in the command - in case the application has not been built before calling `tns debug ...`, construction of debugData will fail as CLI will not be able to find the latest built package. In order to fix this make the `pathToAppPackage` not mandatory (we do not need it for debug commands when `--start` is passed) and populate it after successful deploy of the application. This way it will have correct value. Delete most of the DebugDataService as most of the methods are not needed with these changes. - remove the check if `--chrome` is passed in order to print the url when `tns debug android` is used. The check was incorrect (it should check the value of `options.client`), but in fact we do not need it - the idea of the check was to suppress starting of Node Inspector, however we do not start it anymore no matter of the passed flags. So remove the incorrect check. --- lib/commands/debug.ts | 31 ++++++++++------ lib/common | 2 +- lib/definitions/debug.d.ts | 4 +- .../ios/socket-proxy-factory.ts | 5 ++- lib/services/android-debug-service.ts | 6 +-- lib/services/debug-data-service.ts | 37 ++----------------- lib/services/ios-debug-service.ts | 1 + lib/services/test-execution-service.ts | 14 +++++-- 8 files changed, 42 insertions(+), 58 deletions(-) diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 30e446366c..1620567f9f 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -6,7 +6,6 @@ export abstract class DebugPlatformCommand implements ICommand { constructor(private debugService: IPlatformDebugService, private $devicesService: Mobile.IDevicesService, private $injector: IInjector, - private $logger: ILogger, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $config: IConfiguration, private $usbLiveSyncService: ILiveSyncService, @@ -14,7 +13,8 @@ export abstract class DebugPlatformCommand implements ICommand { protected $platformService: IPlatformService, protected $projectData: IProjectData, protected $options: IOptions, - protected $platformsData: IPlatformsData) { + protected $platformsData: IPlatformsData, + protected $logger: ILogger) { this.$projectData.initializeProjectData(); } @@ -31,9 +31,7 @@ export abstract class DebugPlatformCommand implements ICommand { teamId: this.$options.teamId }; - const buildConfig: IBuildConfig = _.merge({ buildForDevice: this.$options.forDevice }, deployOptions); - - const debugData = this.$debugDataService.createDebugData(this.debugService, this.$options, buildConfig); + let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); await this.$platformService.trackProjectType(this.$projectData); @@ -57,6 +55,9 @@ export abstract class DebugPlatformCommand implements ICommand { await deviceAppData.device.applicationManager.stopApplication(applicationId); + const buildConfig: IBuildConfig = _.merge({ buildForDevice: this.$options.forDevice }, deployOptions); + debugData.pathToAppPackage = this.$platformService.lastOutputPath(this.debugService.platform, buildConfig, projectData); + this.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); }; @@ -80,7 +81,7 @@ export abstract class DebugPlatformCommand implements ICommand { return true; } - private printDebugInformation(information: string[]): void { + protected printDebugInformation(information: string[]): void { _.each(information, i => { this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${i}${EOL}`.cyan); }); @@ -88,10 +89,10 @@ export abstract class DebugPlatformCommand implements ICommand { } export class DebugIOSCommand extends DebugPlatformCommand { - constructor($iOSDebugService: IPlatformDebugService, + constructor(protected $logger: ILogger, + $iOSDebugService: IPlatformDebugService, $devicesService: Mobile.IDevicesService, $injector: IInjector, - $logger: ILogger, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $config: IConfiguration, $usbLiveSyncService: ILiveSyncService, @@ -101,22 +102,28 @@ export class DebugIOSCommand extends DebugPlatformCommand { $projectData: IProjectData, $platformsData: IPlatformsData, $iosDeviceOperations: IIOSDeviceOperations) { - super($iOSDebugService, $devicesService, $injector, $logger, $devicePlatformsConstants, $config, $usbLiveSyncService, $debugDataService, $platformService, $projectData, $options, $platformsData); + super($iOSDebugService, $devicesService, $injector, $devicePlatformsConstants, $config, $usbLiveSyncService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger); $iosDeviceOperations.setShouldDispose(this.$options.justlaunch); } public async canExecute(args: string[]): Promise { return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS); } + + protected printDebugInformation(information: string[]): void { + if (this.$options.chrome) { + super.printDebugInformation(information); + } + } } $injector.registerCommand("debug|ios", DebugIOSCommand); export class DebugAndroidCommand extends DebugPlatformCommand { - constructor($androidDebugService: IPlatformDebugService, + constructor($logger: ILogger, + $androidDebugService: IPlatformDebugService, $devicesService: Mobile.IDevicesService, $injector: IInjector, - $logger: ILogger, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $config: IConfiguration, $usbLiveSyncService: ILiveSyncService, @@ -125,7 +132,7 @@ export class DebugAndroidCommand extends DebugPlatformCommand { $options: IOptions, $projectData: IProjectData, $platformsData: IPlatformsData) { - super($androidDebugService, $devicesService, $injector, $logger, $devicePlatformsConstants, $config, $usbLiveSyncService, $debugDataService, $platformService, $projectData, $options, $platformsData); + super($androidDebugService, $devicesService, $injector, $devicePlatformsConstants, $config, $usbLiveSyncService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger); } public async canExecute(args: string[]): Promise { diff --git a/lib/common b/lib/common index 165693ed6f..57a37dcdd1 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 165693ed6f926e7b7be44976d6d94d24608a7c69 +Subproject commit 57a37dcdd1421889a29aee0df5a43552d3920351 diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 9da884109f..752b2ad762 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -1,7 +1,7 @@ interface IDebugData { deviceIdentifier: string; applicationIdentifier: string; - pathToAppPackage: string; + pathToAppPackage?: string; projectName?: string; projectDir?: string; } @@ -17,7 +17,7 @@ interface IDebugOptions { } interface IDebugDataService { - createDebugData(debugService: IPlatformDebugService, options: IOptions, buildConfig: IBuildConfig): IDebugData; + createDebugData(projectData: IProjectData, options: IOptions): IDebugData; } interface IDebugService extends NodeJS.EventEmitter { diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/socket-proxy-factory.ts index 3a78b6de28..45f32abf8b 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/socket-proxy-factory.ts @@ -45,13 +45,14 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact }); frontendSocket.on("close", () => { - console.log("frontend socket closed"); + this.$logger.info("Frontend socket closed"); if (!(backendSocket).destroyed) { backendSocket.destroy(); } }); + backendSocket.on("close", () => { - console.log("backend socket closed"); + this.$logger.info("Backend socket closed"); if (!(frontendSocket).destroyed) { frontendSocket.destroy(); } diff --git a/lib/services/android-debug-service.ts b/lib/services/android-debug-service.ts index cb6cb442e0..01cb77b9b1 100644 --- a/lib/services/android-debug-service.ts +++ b/lib/services/android-debug-service.ts @@ -128,10 +128,8 @@ class AndroidDebugService extends DebugServiceBase implements IPlatformDebugServ let startDebuggerCommand = ["am", "broadcast", "-a", `\"${packageName}-debug\"`, "--ez", "enable", "true"]; await this.device.adb.executeShellCommand(startDebuggerCommand); - if (debugOptions.chrome) { - let port = await this.getForwardedLocalDebugPortForPackageName(deviceId, packageName); - return `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${port}`; - } + let port = await this.getForwardedLocalDebugPortForPackageName(deviceId, packageName); + return `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${port}`; } private detachDebugger(packageName: string): Promise { diff --git a/lib/services/debug-data-service.ts b/lib/services/debug-data-service.ts index ebd9c0a0f3..038e2b6091 100644 --- a/lib/services/debug-data-service.ts +++ b/lib/services/debug-data-service.ts @@ -1,41 +1,12 @@ export class DebugDataService implements IDebugDataService { - constructor(private $projectData: IProjectData, - private $platformService: IPlatformService, - private $platformsData: IPlatformsData, - private $mobileHelper: Mobile.IMobileHelper) { } - - public createDebugData(debugService: IPlatformDebugService, options: IOptions, buildConfig: IBuildConfig): IDebugData { - this.$projectData.initializeProjectData(options.path); + public createDebugData(projectData: IProjectData, options: IOptions): IDebugData { return { - applicationIdentifier: this.$projectData.projectId, - projectDir: this.$projectData.projectDir, + applicationIdentifier: projectData.projectId, + projectDir: projectData.projectDir, deviceIdentifier: options.device, - pathToAppPackage: this.getPathToAppPackage(debugService, options, buildConfig), - projectName: this.$projectData.projectName + projectName: projectData.projectName }; } - - private getPathToAppPackage(debugService: IPlatformDebugService, options: IOptions, buildConfig: IBuildConfig): string { - if (this.$mobileHelper.isAndroidPlatform(debugService.platform)) { - if (!options.start && !options.emulator) { - const platformData = this.getPlatformData(debugService); - - return this.$platformService.getLatestApplicationPackageForDevice(platformData, buildConfig).packageName; - } - } else if (this.$mobileHelper.isiOSPlatform(debugService.platform)) { - if (options.emulator) { - const platformData = this.getPlatformData(debugService); - - return this.$platformService.getLatestApplicationPackageForEmulator(platformData, buildConfig).packageName; - } - } - - return null; - } - - private getPlatformData(debugService: IPlatformDebugService): IPlatformData { - return this.$platformsData.getPlatformData(debugService.platform, this.$projectData); - } } $injector.register("debugDataService", DebugDataService); diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index 8cbf99087f..0a4975f898 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -112,6 +112,7 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService if (!pidMatch) { this.$logger.trace(`Line ${lineText} does not contain the searched pattern: ${pidRegExp}.`); + return; } const pid = pidMatch[1]; diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 446215f5b9..a547c224e4 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -74,10 +74,9 @@ class TestExecutionService implements ITestExecutionService { await this.$usbLiveSyncService.liveSync(platform, projectData); if (this.$options.debugBrk) { - const buildConfig: IBuildConfig = _.merge({ buildForDevice: this.$options.forDevice }, deployOptions); this.$logger.info('Starting debugger...'); let debugService: IPlatformDebugService = this.$injector.resolve(`${platform}DebugService`); - const debugData: IDebugData = this.$debugDataService.createDebugData(debugService, this.$options, buildConfig); + const debugData = this.getDebugData(platform, projectData, deployOptions); await debugService.debugStart(debugData, this.$options); } resolve(); @@ -143,8 +142,7 @@ class TestExecutionService implements ITestExecutionService { if (this.$options.debugBrk) { const debugService = this.getDebugService(platform); - const buildConfig: IBuildConfig = _.merge({ buildForDevice: this.$options.forDevice }, deployOptions); - const debugData = this.$debugDataService.createDebugData(debugService, this.$options, buildConfig); + const debugData = this.getDebugData(platform, projectData, deployOptions); await debugService.debug(debugData, this.$options); } else { await this.$platformService.deployPlatform(platform, appFilesUpdaterOptions, deployOptions, projectData, { provision: this.$options.provision, sdk: this.$options.sdk }); @@ -247,5 +245,13 @@ class TestExecutionService implements ITestExecutionService { return karmaConfig; } + + private getDebugData(platform: string, projectData: IProjectData, deployOptions: IDeployPlatformOptions): IDebugData { + const buildConfig: IBuildConfig = _.merge({ buildForDevice: this.$options.forDevice }, deployOptions); + let debugData = this.$debugDataService.createDebugData(projectData, this.$options); + debugData.pathToAppPackage = this.$platformService.lastOutputPath(platform, buildConfig, projectData); + + return debugData; + } } $injector.register('testExecutionService', TestExecutionService); From df7b887a84582389520fa57b49b6f7b947d734ca Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 10 Apr 2017 23:07:40 +0300 Subject: [PATCH 3/3] Move logic for getting PID of application from iOS Sim to common Move the logic for getting PID of application started on iOS Simulator to mobile-cli-lib. Add new helper method to get the process ID of an application from iOS Simulator Logs. Whenever we start an application on iOS Simulator, in its logs we can find the PID of the process in the following format: ``` : : ``` Add unit tests for the method. --- lib/common | 2 +- lib/services/ios-debug-service.ts | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/common b/lib/common index 57a37dcdd1..3aefd62e53 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 57a37dcdd1421889a29aee0df5a43552d3920351 +Subproject commit 3aefd62e53bccf12c00814d3e954929dabe4c8ba diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index 0a4975f898..22b01dffaf 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -5,6 +5,7 @@ import * as log4js from "log4js"; import { ChildProcess } from "child_process"; import { DebugServiceBase } from "./debug-service-base"; import { CONNECTION_ERROR_EVENT_NAME } from "../constants"; +import { getPidFromiOSSimulatorLogs } from "../common/helpers"; import byline = require("byline"); @@ -103,19 +104,16 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService let lineStream = byline(child_process.stdout); this._childProcess = child_process; - const pidRegExp = new RegExp(`${debugData.applicationIdentifier}:\\s?(\\d+)`); lineStream.on('data', (line: NodeBuffer) => { let lineText = line.toString(); if (lineText && _.startsWith(lineText, debugData.applicationIdentifier)) { - const pidMatch = lineText.match(pidRegExp); - - if (!pidMatch) { - this.$logger.trace(`Line ${lineText} does not contain the searched pattern: ${pidRegExp}.`); + const pid = getPidFromiOSSimulatorLogs(debugData.applicationIdentifier, lineText); + if (!pid) { + this.$logger.trace(`Line ${lineText} does not contain PID of the application ${debugData.applicationIdentifier}.`); return; } - const pid = pidMatch[1]; this._lldbProcess = this.$childProcess.spawn("lldb", ["-p", pid]); if (log4js.levels.TRACE.isGreaterThanOrEqualTo(this.$logger.getLevel())) { this._lldbProcess.stdout.pipe(process.stdout);