Skip to content

Commit 1658629

Browse files
Fix livesync + debug on iOS Simulator (#3334)
When `tns debug ios` is used, each change of application's code will trigger restart of app and Chrome DevTools will be disconnected. CLI prints the "new" URL and in case you copy it and paste it immediately in the browser, you'll receive empty DevTools. The problem is that the URL is printed before application is launched successfully on simulator. In order to resolve this issue, try connection on port 18181 - the connection will be successfull only when the application is running. Wait 10 seconds to connect and throw error in case we are unable to connect for this time. Also fix incorrect behavior of connectionError event - the thrown error should contain the deviceIdentifier, but it was missing.
1 parent b0d4b66 commit 1658629

File tree

8 files changed

+46
-34
lines changed

8 files changed

+46
-34
lines changed

PublicAPI.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ Provides methods for debugging applications on devices. The service is also even
424424
* Usage:
425425
```JavaScript
426426
tns.debugService.on("connectionError", errorData => {
427-
console.log(`Unable to start debug operation on device ${errorData.deviceId}. Error is: ${errorData.message}.`);
427+
console.log(`Unable to start debug operation on device ${errorData.deviceIdentifier}. Error is: ${errorData.message}.`);
428428
});
429429
```
430430
@@ -522,7 +522,7 @@ interface IDebugOptions {
522522
* Usage:
523523
```JavaScript
524524
tns.debugService.on("connectionError", errorData => {
525-
console.log(`Unable to start debug operation on device ${errorData.deviceId}. Error is: ${errorData.message}.`);
525+
console.log(`Unable to start debug operation on device ${errorData.deviceIdentifier}. Error is: ${errorData.message}.`);
526526
});
527527

528528
const debugData = {
@@ -903,4 +903,4 @@ CLI is designed as command line tool and when it is used as a library, it does n
903903
For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`.
904904
905905
More information how to add a method to public API is available [here](https://github.com/telerik/mobile-cli-lib#how-to-make-a-method-public).
906-
After that add each method that you've exposed to the tests in `tests/nativescript-cli-lib.ts` file. There you'll find an object describing each publicly available module and the methods that you can call.
906+
After that add each method that you've exposed to the tests in `tests/nativescript-cli-lib.ts` file. There you'll find an object describing each publicly available module and the methods that you can call.

lib/declarations.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ interface IAndroidToolsInfoData {
544544

545545
interface ISocketProxyFactory extends NodeJS.EventEmitter {
546546
createTCPSocketProxy(factory: () => Promise<any>): Promise<any>;
547-
createWebSocketProxy(factory: () => Promise<any>): Promise<any>;
547+
createWebSocketProxy(factory: () => Promise<any>, deviceIdentifier: string): Promise<any>;
548548
}
549549

550550
interface IiOSNotification {

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact
7272
return server;
7373
}
7474

75-
public async createWebSocketProxy(factory: () => Promise<net.Socket>): Promise<ws.Server> {
75+
public async createWebSocketProxy(factory: () => Promise<net.Socket>, deviceIdentifier: string): Promise<ws.Server> {
7676
// NOTE: We will try to provide command line options to select ports, at least on the localhost.
7777
const localPort = await this.$net.getAvailablePortInRange(41000);
7878

@@ -92,6 +92,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact
9292
try {
9393
_socket = await factory();
9494
} catch (err) {
95+
err.deviceIdentifier = deviceIdentifier;
9596
this.$logger.trace(err);
9697
this.emit(CONNECTION_ERROR_EVENT_NAME, err);
9798
this.$errors.failWithoutHelp("Cannot connect to device socket.");

lib/services/ios-debug-service.ts

+35-21
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS
2323
constructor(protected device: Mobile.IDevice,
2424
protected $devicesService: Mobile.IDevicesService,
2525
private $platformService: IPlatformService,
26-
private $iOSEmulatorServices: Mobile.IEmulatorPlatformServices,
26+
private $iOSEmulatorServices: Mobile.IiOSSimulatorService,
2727
private $childProcess: IChildProcess,
2828
private $hostInfo: IHostInfo,
2929
private $logger: ILogger,
@@ -125,24 +125,27 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS
125125
const lineStream = byline(child_process.stdout);
126126
this._childProcess = child_process;
127127

128-
lineStream.on('data', (line: NodeBuffer) => {
129-
const lineText = line.toString();
130-
if (lineText && _.startsWith(lineText, debugData.applicationIdentifier)) {
131-
const pid = getPidFromiOSSimulatorLogs(debugData.applicationIdentifier, lineText);
132-
if (!pid) {
133-
this.$logger.trace(`Line ${lineText} does not contain PID of the application ${debugData.applicationIdentifier}.`);
134-
return;
128+
await new Promise((resolve: () => void, reject) => {
129+
lineStream.on('data', (line: NodeBuffer) => {
130+
const lineText = line.toString();
131+
if (lineText && _.startsWith(lineText, debugData.applicationIdentifier)) {
132+
const pid = getPidFromiOSSimulatorLogs(debugData.applicationIdentifier, lineText);
133+
if (!pid) {
134+
this.$logger.trace(`Line ${lineText} does not contain PID of the application ${debugData.applicationIdentifier}.`);
135+
return;
136+
}
137+
138+
this._lldbProcess = this.$childProcess.spawn("lldb", ["-p", pid]);
139+
if (log4js.levels.TRACE.isGreaterThanOrEqualTo(this.$logger.getLevel())) {
140+
this._lldbProcess.stdout.pipe(process.stdout);
141+
}
142+
this._lldbProcess.stderr.pipe(process.stderr);
143+
this._lldbProcess.stdin.write("process continue\n");
144+
this.connectToApplicationOnEmulator(debugData.deviceIdentifier).then(resolve, reject);
145+
} else {
146+
process.stdout.write(line + "\n");
135147
}
136-
137-
this._lldbProcess = this.$childProcess.spawn("lldb", ["-p", pid]);
138-
if (log4js.levels.TRACE.isGreaterThanOrEqualTo(this.$logger.getLevel())) {
139-
this._lldbProcess.stdout.pipe(process.stdout);
140-
}
141-
this._lldbProcess.stderr.pipe(process.stderr);
142-
this._lldbProcess.stdin.write("process continue\n");
143-
} else {
144-
process.stdout.write(line + "\n");
145-
}
148+
});
146149
});
147150

148151
return this.wireDebuggerClient(debugData, debugOptions);
@@ -153,11 +156,21 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS
153156

154157
const attachRequestMessage = this.$iOSNotification.getAttachRequest(debugData.applicationIdentifier);
155158

156-
const iOSEmulator = <Mobile.IiOSSimulatorService>this.$iOSEmulatorServices;
157-
await iOSEmulator.postDarwinNotification(attachRequestMessage);
159+
const iOSEmulatorService = <Mobile.IiOSSimulatorService>this.$iOSEmulatorServices;
160+
await iOSEmulatorService.postDarwinNotification(attachRequestMessage);
161+
await this.connectToApplicationOnEmulator(debugData.deviceIdentifier);
158162
return result;
159163
}
160164

165+
private async connectToApplicationOnEmulator(deviceIdentifier: string): Promise<void> {
166+
const socket = await this.$iOSEmulatorServices.connectToPort({ port: inspectorBackendPort });
167+
if (!socket) {
168+
const error = <Mobile.IDeviceError>new Error("Unable to connect to application. Ensure application is running on simulator.");
169+
error.deviceIdentifier = deviceIdentifier;
170+
throw error;
171+
}
172+
}
173+
161174
private async deviceDebugBrk(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string> {
162175
await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier });
163176
const action = async (device: iOSDevice.IOSDevice): Promise<string> => {
@@ -212,7 +225,8 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS
212225
this.$logger.info("'--chrome' is the default behavior. Use --inspector to debug iOS applications using the Safari Web Inspector.");
213226
}
214227

215-
this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(device));
228+
const deviceIdentifier = device ? device.deviceInfo.identifier : debugData.deviceIdentifier;
229+
this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(device), deviceIdentifier);
216230
return this.getChromeDebugUrl(debugOptions, this._socketProxy.options.port);
217231
}
218232
}

lib/services/livesync/ios-device-livesync-service.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as helpers from "../../common/helpers";
21
import * as constants from "../../constants";
32
import * as minimatch from "minimatch";
43
import * as net from "net";
@@ -30,10 +29,8 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen
3029

3130
if (this.device.isEmulator) {
3231
await this.$iOSEmulatorServices.postDarwinNotification(this.$iOSNotification.getAttachRequest(projectId));
33-
try {
34-
this.socket = await helpers.connectEventuallyUntilTimeout(() => net.connect(IOSDeviceLiveSyncService.BACKEND_PORT), 5000);
35-
} catch (e) {
36-
this.$logger.debug(e);
32+
this.socket = await this.$iOSEmulatorServices.connectToPort({ port: IOSDeviceLiveSyncService.BACKEND_PORT });
33+
if (!this.socket) {
3734
return false;
3835
}
3936
} else {

test/services/debug-service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ describe("debugService", () => {
203203
const debugData = getDebugData();
204204
await assert.isFulfilled(debugService.debug(debugData, null));
205205

206-
const expectedErrorData = { deviceId: "deviceId", message: "my message", code: 2048 };
206+
const expectedErrorData = { deviceIdentifier: "deviceId", message: "my message", code: 2048 };
207207
const platformDebugService = testInjector.resolve<IPlatformDebugService>(`${platform}DebugService`);
208208
platformDebugService.emit(CONNECTION_ERROR_EVENT_NAME, expectedErrorData);
209209
assert.deepEqual(dataRaisedForConnectionError, expectedErrorData);

test/services/ios-debug-service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const expectedDevToolsCommitSha = "02e6bde1bbe34e43b309d4ef774b1168d25fd024";
88
class IOSDebugServiceInheritor extends IOSDebugService {
99
constructor(protected $devicesService: Mobile.IDevicesService,
1010
$platformService: IPlatformService,
11-
$iOSEmulatorServices: Mobile.IEmulatorPlatformServices,
11+
$iOSEmulatorServices: Mobile.IiOSSimulatorService,
1212
$childProcess: IChildProcess,
1313
$hostInfo: IHostInfo,
1414
$logger: ILogger,

0 commit comments

Comments
 (0)