Skip to content

Commit 99c9bf5

Browse files
Fix connect ECONNREFUSED error when trying to debug on iOS Sim (#2704)
* 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. * 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. * 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: ``` <app id>: <app id>: <PID> ``` Add unit tests for the method.
1 parent d94a5ec commit 99c9bf5

File tree

9 files changed

+77
-84
lines changed

9 files changed

+77
-84
lines changed

lib/commands/debug.ts

+19-12
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ export abstract class DebugPlatformCommand implements ICommand {
66
constructor(private debugService: IPlatformDebugService,
77
private $devicesService: Mobile.IDevicesService,
88
private $injector: IInjector,
9-
private $logger: ILogger,
109
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
1110
private $config: IConfiguration,
1211
private $usbLiveSyncService: ILiveSyncService,
1312
private $debugDataService: IDebugDataService,
1413
protected $platformService: IPlatformService,
1514
protected $projectData: IProjectData,
1615
protected $options: IOptions,
17-
protected $platformsData: IPlatformsData) {
16+
protected $platformsData: IPlatformsData,
17+
protected $logger: ILogger) {
1818
this.$projectData.initializeProjectData();
1919
}
2020

@@ -31,9 +31,7 @@ export abstract class DebugPlatformCommand implements ICommand {
3131
teamId: this.$options.teamId
3232
};
3333

34-
const buildConfig: IBuildConfig = _.merge({ buildForDevice: this.$options.forDevice }, deployOptions);
35-
36-
const debugData = this.$debugDataService.createDebugData(this.debugService, this.$options, buildConfig);
34+
let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options);
3735

3836
await this.$platformService.trackProjectType(this.$projectData);
3937

@@ -57,6 +55,9 @@ export abstract class DebugPlatformCommand implements ICommand {
5755

5856
await deviceAppData.device.applicationManager.stopApplication(applicationId);
5957

58+
const buildConfig: IBuildConfig = _.merge({ buildForDevice: this.$options.forDevice }, deployOptions);
59+
debugData.pathToAppPackage = this.$platformService.lastOutputPath(this.debugService.platform, buildConfig, projectData);
60+
6061
this.printDebugInformation(await this.debugService.debug(debugData, debugOptions));
6162
};
6263

@@ -80,18 +81,18 @@ export abstract class DebugPlatformCommand implements ICommand {
8081
return true;
8182
}
8283

83-
private printDebugInformation(information: string[]): void {
84+
protected printDebugInformation(information: string[]): void {
8485
_.each(information, i => {
8586
this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${i}${EOL}`.cyan);
8687
});
8788
}
8889
}
8990

9091
export class DebugIOSCommand extends DebugPlatformCommand {
91-
constructor($iOSDebugService: IPlatformDebugService,
92+
constructor(protected $logger: ILogger,
93+
$iOSDebugService: IPlatformDebugService,
9294
$devicesService: Mobile.IDevicesService,
9395
$injector: IInjector,
94-
$logger: ILogger,
9596
$devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
9697
$config: IConfiguration,
9798
$usbLiveSyncService: ILiveSyncService,
@@ -101,22 +102,28 @@ export class DebugIOSCommand extends DebugPlatformCommand {
101102
$projectData: IProjectData,
102103
$platformsData: IPlatformsData,
103104
$iosDeviceOperations: IIOSDeviceOperations) {
104-
super($iOSDebugService, $devicesService, $injector, $logger, $devicePlatformsConstants, $config, $usbLiveSyncService, $debugDataService, $platformService, $projectData, $options, $platformsData);
105+
super($iOSDebugService, $devicesService, $injector, $devicePlatformsConstants, $config, $usbLiveSyncService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger);
105106
$iosDeviceOperations.setShouldDispose(this.$options.justlaunch);
106107
}
107108

108109
public async canExecute(args: string[]): Promise<boolean> {
109110
return await super.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$projectData, this.$platformsData.availablePlatforms.iOS);
110111
}
112+
113+
protected printDebugInformation(information: string[]): void {
114+
if (this.$options.chrome) {
115+
super.printDebugInformation(information);
116+
}
117+
}
111118
}
112119

113120
$injector.registerCommand("debug|ios", DebugIOSCommand);
114121

115122
export class DebugAndroidCommand extends DebugPlatformCommand {
116-
constructor($androidDebugService: IPlatformDebugService,
123+
constructor($logger: ILogger,
124+
$androidDebugService: IPlatformDebugService,
117125
$devicesService: Mobile.IDevicesService,
118126
$injector: IInjector,
119-
$logger: ILogger,
120127
$devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
121128
$config: IConfiguration,
122129
$usbLiveSyncService: ILiveSyncService,
@@ -125,7 +132,7 @@ export class DebugAndroidCommand extends DebugPlatformCommand {
125132
$options: IOptions,
126133
$projectData: IProjectData,
127134
$platformsData: IPlatformsData) {
128-
super($androidDebugService, $devicesService, $injector, $logger, $devicePlatformsConstants, $config, $usbLiveSyncService, $debugDataService, $platformService, $projectData, $options, $platformsData);
135+
super($androidDebugService, $devicesService, $injector, $devicePlatformsConstants, $config, $usbLiveSyncService, $debugDataService, $platformService, $projectData, $options, $platformsData, $logger);
129136
}
130137

131138
public async canExecute(args: string[]): Promise<boolean> {

lib/declarations.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ interface IAndroidToolsInfoData {
288288
}
289289

290290
interface ISocketProxyFactory extends NodeJS.EventEmitter {
291-
createTCPSocketProxy(factory: () => Promise<any>): any;
291+
createTCPSocketProxy(factory: () => Promise<any>): Promise<any>;
292292
createWebSocketProxy(factory: () => Promise<any>): Promise<any>;
293293
}
294294

lib/definitions/debug.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
interface IDebugData {
22
deviceIdentifier: string;
33
applicationIdentifier: string;
4-
pathToAppPackage: string;
4+
pathToAppPackage?: string;
55
projectName?: string;
66
projectDir?: string;
77
}
@@ -17,7 +17,7 @@ interface IDebugOptions {
1717
}
1818

1919
interface IDebugDataService {
20-
createDebugData(debugService: IPlatformDebugService, options: IOptions, buildConfig: IBuildConfig): IDebugData;
20+
createDebugData(projectData: IProjectData, options: IOptions): IDebugData;
2121
}
2222

2323
interface IDebugService extends NodeJS.EventEmitter {

lib/device-sockets/ios/socket-proxy-factory.ts

+30-25
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { CONNECTION_ERROR_EVENT_NAME } from "../../constants";
33
import { PacketStream } from "./packet-stream";
44
import * as net from "net";
55
import * as ws from "ws";
6+
import * as helpers from "../../common/helpers";
67
import temp = require("temp");
78

89
export class SocketProxyFactory extends EventEmitter implements ISocketProxyFactory {
@@ -14,7 +15,9 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact
1415
super();
1516
}
1617

17-
public createTCPSocketProxy(factory: () => Promise<net.Socket>): net.Server {
18+
public async createTCPSocketProxy(factory: () => Promise<net.Socket>): Promise<net.Server> {
19+
const socketFactory = async (callback: (_socket: net.Socket) => void) => helpers.connectEventually(factory, callback);
20+
1821
this.$logger.info("\nSetting up proxy...\nPress Ctrl + C to terminate, or disconnect.\n");
1922

2023
let server = net.createServer({
@@ -31,32 +34,34 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact
3134
}
3235
});
3336

34-
const backendSocket = await factory();
35-
this.$logger.info("Backend socket created.");
36-
37-
backendSocket.on("end", () => {
38-
this.$logger.info("Backend socket closed!");
39-
if (!(this.$config.debugLivesync && this.$options.watch)) {
40-
process.exit(0);
41-
}
42-
});
37+
await socketFactory((backendSocket: net.Socket) => {
38+
this.$logger.info("Backend socket created.");
4339

44-
frontendSocket.on("close", () => {
45-
console.log("frontend socket closed");
46-
if (!(<any>backendSocket).destroyed) {
47-
backendSocket.destroy();
48-
}
40+
backendSocket.on("end", () => {
41+
this.$logger.info("Backend socket closed!");
42+
if (!(this.$config.debugLivesync && this.$options.watch)) {
43+
process.exit(0);
44+
}
45+
});
46+
47+
frontendSocket.on("close", () => {
48+
this.$logger.info("Frontend socket closed");
49+
if (!(<any>backendSocket).destroyed) {
50+
backendSocket.destroy();
51+
}
52+
});
53+
54+
backendSocket.on("close", () => {
55+
this.$logger.info("Backend socket closed");
56+
if (!(<any>frontendSocket).destroyed) {
57+
frontendSocket.destroy();
58+
}
59+
});
60+
61+
backendSocket.pipe(frontendSocket);
62+
frontendSocket.pipe(backendSocket);
63+
frontendSocket.resume();
4964
});
50-
backendSocket.on("close", () => {
51-
console.log("backend socket closed");
52-
if (!(<any>frontendSocket).destroyed) {
53-
frontendSocket.destroy();
54-
}
55-
});
56-
57-
backendSocket.pipe(frontendSocket);
58-
frontendSocket.pipe(backendSocket);
59-
frontendSocket.resume();
6065
});
6166

6267
let socketFileLocation = temp.path({ suffix: ".sock" });

lib/services/android-debug-service.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,8 @@ class AndroidDebugService extends DebugServiceBase implements IPlatformDebugServ
128128
let startDebuggerCommand = ["am", "broadcast", "-a", `\"${packageName}-debug\"`, "--ez", "enable", "true"];
129129
await this.device.adb.executeShellCommand(startDebuggerCommand);
130130

131-
if (debugOptions.chrome) {
132-
let port = await this.getForwardedLocalDebugPortForPackageName(deviceId, packageName);
133-
return `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${port}`;
134-
}
131+
let port = await this.getForwardedLocalDebugPortForPackageName(deviceId, packageName);
132+
return `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:${port}`;
135133
}
136134

137135
private detachDebugger(packageName: string): Promise<void> {

lib/services/debug-data-service.ts

+4-33
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,12 @@
11
export class DebugDataService implements IDebugDataService {
2-
constructor(private $projectData: IProjectData,
3-
private $platformService: IPlatformService,
4-
private $platformsData: IPlatformsData,
5-
private $mobileHelper: Mobile.IMobileHelper) { }
6-
7-
public createDebugData(debugService: IPlatformDebugService, options: IOptions, buildConfig: IBuildConfig): IDebugData {
8-
this.$projectData.initializeProjectData(options.path);
2+
public createDebugData(projectData: IProjectData, options: IOptions): IDebugData {
93
return {
10-
applicationIdentifier: this.$projectData.projectId,
11-
projectDir: this.$projectData.projectDir,
4+
applicationIdentifier: projectData.projectId,
5+
projectDir: projectData.projectDir,
126
deviceIdentifier: options.device,
13-
pathToAppPackage: this.getPathToAppPackage(debugService, options, buildConfig),
14-
projectName: this.$projectData.projectName
7+
projectName: projectData.projectName
158
};
169
}
17-
18-
private getPathToAppPackage(debugService: IPlatformDebugService, options: IOptions, buildConfig: IBuildConfig): string {
19-
if (this.$mobileHelper.isAndroidPlatform(debugService.platform)) {
20-
if (!options.start && !options.emulator) {
21-
const platformData = this.getPlatformData(debugService);
22-
23-
return this.$platformService.getLatestApplicationPackageForDevice(platformData, buildConfig).packageName;
24-
}
25-
} else if (this.$mobileHelper.isiOSPlatform(debugService.platform)) {
26-
if (options.emulator) {
27-
const platformData = this.getPlatformData(debugService);
28-
29-
return this.$platformService.getLatestApplicationPackageForEmulator(platformData, buildConfig).packageName;
30-
}
31-
}
32-
33-
return null;
34-
}
35-
36-
private getPlatformData(debugService: IPlatformDebugService): IPlatformData {
37-
return this.$platformsData.getPlatformData(debugService.platform, this.$projectData);
38-
}
3910
}
4011

4112
$injector.register("debugDataService", DebugDataService);

lib/services/ios-debug-service.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as log4js from "log4js";
55
import { ChildProcess } from "child_process";
66
import { DebugServiceBase } from "./debug-service-base";
77
import { CONNECTION_ERROR_EVENT_NAME } from "../constants";
8+
import { getPidFromiOSSimulatorLogs } from "../common/helpers";
89

910
import byline = require("byline");
1011

@@ -107,7 +108,12 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService
107108
lineStream.on('data', (line: NodeBuffer) => {
108109
let lineText = line.toString();
109110
if (lineText && _.startsWith(lineText, debugData.applicationIdentifier)) {
110-
let pid = _.trimStart(lineText, debugData.applicationIdentifier + ": ");
111+
const pid = getPidFromiOSSimulatorLogs(debugData.applicationIdentifier, lineText);
112+
if (!pid) {
113+
this.$logger.trace(`Line ${lineText} does not contain PID of the application ${debugData.applicationIdentifier}.`);
114+
return;
115+
}
116+
111117
this._lldbProcess = this.$childProcess.spawn("lldb", ["-p", pid]);
112118
if (log4js.levels.TRACE.isGreaterThanOrEqualTo(this.$logger.getLevel())) {
113119
this._lldbProcess.stdout.pipe(process.stdout);
@@ -182,7 +188,7 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService
182188
const commitSHA = "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; // corresponds to 55.0.2883 Chrome version
183189
return `chrome-devtools://devtools/remote/serve_file/@${commitSHA}/inspector.html?experiments=true&ws=localhost:${this._socketProxy.options.port}`;
184190
} else {
185-
this._socketProxy = this.$socketProxyFactory.createTCPSocketProxy(this.getSocketFactory(device));
191+
this._socketProxy = await this.$socketProxyFactory.createTCPSocketProxy(this.getSocketFactory(device));
186192
await this.openAppInspector(this._socketProxy.address(), debugData, debugOptions);
187193
return null;
188194
}

lib/services/test-execution-service.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,9 @@ class TestExecutionService implements ITestExecutionService {
7474
await this.$usbLiveSyncService.liveSync(platform, projectData);
7575

7676
if (this.$options.debugBrk) {
77-
const buildConfig: IBuildConfig = _.merge({ buildForDevice: this.$options.forDevice }, deployOptions);
7877
this.$logger.info('Starting debugger...');
7978
let debugService: IPlatformDebugService = this.$injector.resolve(`${platform}DebugService`);
80-
const debugData: IDebugData = this.$debugDataService.createDebugData(debugService, this.$options, buildConfig);
79+
const debugData = this.getDebugData(platform, projectData, deployOptions);
8180
await debugService.debugStart(debugData, this.$options);
8281
}
8382
resolve();
@@ -143,8 +142,7 @@ class TestExecutionService implements ITestExecutionService {
143142

144143
if (this.$options.debugBrk) {
145144
const debugService = this.getDebugService(platform);
146-
const buildConfig: IBuildConfig = _.merge({ buildForDevice: this.$options.forDevice }, deployOptions);
147-
const debugData = this.$debugDataService.createDebugData(debugService, this.$options, buildConfig);
145+
const debugData = this.getDebugData(platform, projectData, deployOptions);
148146
await debugService.debug(debugData, this.$options);
149147
} else {
150148
await this.$platformService.deployPlatform(platform, appFilesUpdaterOptions, deployOptions, projectData, { provision: this.$options.provision, sdk: this.$options.sdk });
@@ -247,5 +245,13 @@ class TestExecutionService implements ITestExecutionService {
247245

248246
return karmaConfig;
249247
}
248+
249+
private getDebugData(platform: string, projectData: IProjectData, deployOptions: IDeployPlatformOptions): IDebugData {
250+
const buildConfig: IBuildConfig = _.merge({ buildForDevice: this.$options.forDevice }, deployOptions);
251+
let debugData = this.$debugDataService.createDebugData(projectData, this.$options);
252+
debugData.pathToAppPackage = this.$platformService.lastOutputPath(platform, buildConfig, projectData);
253+
254+
return debugData;
255+
}
250256
}
251257
$injector.register('testExecutionService', TestExecutionService);

0 commit comments

Comments
 (0)