Skip to content

Commit b3e50ad

Browse files
Improve debugService public API
When using Chrome DevTools to debug iOS applications, CLI returns a url that points to a specific commit of the dev tools. This way, in case a new version of the dev tools introduces a breaking change, the debugging will still work. However, in some cases (inside Electron app), we cannot use the remote url, so we must use the bundled one. So introduce a new option (only available when requiring CLI as a library), that defines that the returned url will use the bundled dev tools. The default behavior (used in CLI), will still return the url that includes remote url. Also change the definition of `debug` method in the interface, so now the DebugService can safely implement it. Add a new check in the debug service - in case the device's status is not Connected, we are unable to start debug operation. So fail with correct error message in this case. Add JSDocs for all debug related interfaces. Add documentation in PublicAPI.md for the `debugService`. Add debugService to tests of public API.
1 parent a389de1 commit b3e50ad

File tree

7 files changed

+237
-15
lines changed

7 files changed

+237
-15
lines changed

PublicAPI.md

+123-4
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,10 @@ for (let promise of loadExtensionsPromises) {
180180
}
181181
```
182182
183-
## settings
184-
`settings` module provides a way to configure various settings.
183+
## settingsService
184+
`settingsService` module provides a way to configure various settings.
185185
186-
### set
186+
### setSettings
187187
Used to set various settings in order to modify the behavior of some methods.
188188
* Auxiliary interfaces:
189189
```TypeScript
@@ -339,9 +339,128 @@ tns.npm.view(["nativescript"], {}).then(result => {
339339
});
340340
```
341341
342+
## debugService
343+
Provides methods for debugging applications on devices. The service is also event emitter, that raises the following events:
344+
* `connectionError` event - this event is raised when the debug operation cannot start on iOS device. The causes can be:
345+
* Application is not running on the specified iOS Device.
346+
* Application is not built in debug configuration on the specified iOS device.
347+
The event is raised with the following information:
348+
```TypeScript
349+
{
350+
/**
351+
* Device identifier on which the debug process cannot start.
352+
*/
353+
deviceId: string;
354+
355+
/**
356+
* The error message.
357+
*/
358+
message: string;
359+
360+
/**
361+
* Code of the error.
362+
*/
363+
code: number
364+
}
365+
```
366+
367+
* Usage:
368+
```JavaScript
369+
tns.debugService.on("connectionError", errorData => {
370+
console.log(`Unable to start debug operation on device ${errorData.deviceId}. Error is: ${errorData.message}.`);
371+
});
372+
```
373+
374+
### debug
375+
The `debug` method allows starting a debug operation for specified application on a specific device. The method returns a Promise, which is resolved with a url. The url should be opened in Chrome DevTools in order to debug the application.
376+
377+
The returned Promise will be rejected in case any error occurs. It will also be rejected in case:
378+
1. Specified deviceIdentifier is not found in current list of attached devices.
379+
1. The device, specified as deviceIdentifier is connected but not trusted.
380+
1. The specified application is not installed on the device.
381+
1. Trying to debug applications on connected iOS device on Linux.
382+
1. In case the application is not running on the specified device.
383+
1. In case the installed application is not built in debug configuration.
384+
385+
* Definition:
386+
```TypeScript
387+
/**
388+
* Starts debug operation based on the specified debug data.
389+
* @param {IDebugData} debugData Describes information for device and application that will be debugged.
390+
* @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line.
391+
* @returns {Promise<string>} URL that should be opened in Chrome DevTools.
392+
*/
393+
debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string>;
394+
```
395+
396+
The type of arguments that you can pass are described below:
397+
```TypeScript
398+
/**
399+
* Describes information for starting debug process.
400+
*/
401+
interface IDebugData {
402+
/**
403+
* Id of the device on which the debug process will be started.
404+
*/
405+
deviceIdentifier: string;
406+
407+
/**
408+
* Application identifier of the app that it will be debugged.
409+
*/
410+
applicationIdentifier: string;
411+
412+
/**
413+
* Path to .app built for iOS Simulator.
414+
*/
415+
pathToAppPackage?: string;
416+
417+
/**
418+
* The name of the application, for example `MyProject`.
419+
*/
420+
projectName?: string;
421+
422+
/**
423+
* Path to project.
424+
*/
425+
projectDir?: string;
426+
}
427+
428+
/**
429+
* Describes all options that define the behavior of debug.
430+
*/
431+
interface IDebugOptions {
432+
/**
433+
* Defines if bundled Chrome DevTools should be used or specific commit. Valid for iOS only.
434+
*/
435+
useBundledDevTools?: boolean;
436+
}
437+
```
438+
439+
* Usage:
440+
```JavaScript
441+
tns.debugService.on("connectionError", errorData => {
442+
console.log(`Unable to start debug operation on device ${errorData.deviceId}. Error is: ${errorData.message}.`);
443+
});
444+
445+
const debugData = {
446+
deviceIdentifier: "4df18f307d8a8f1b",
447+
applicationIdentifier: "com.telerik.app1",
448+
projectName: "app1",
449+
projectDir: "/Users/myUser/app1"
450+
};
451+
452+
const debugOptions = {
453+
useBundledDevTools: true
454+
};
455+
456+
tns.debugService.debug(debugData, debugOptions)
457+
.then(url => console.log(`Open the following url in Chrome DevTools: ${url}`))
458+
.catch(err => console.log(`Unable to start debug operation, reason: ${err.message}.`));
459+
```
460+
342461
## How to add a new method to Public API
343462
CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification.
344463
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`.
345464
346465
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).
347-
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.
466+
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/commands/debug.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export abstract class DebugPlatformCommand implements ICommand {
3636
await this.$platformService.trackProjectType(this.$projectData);
3737

3838
if (this.$options.start) {
39-
return this.printDebugInformation(await this.debugService.debug(debugData, debugOptions));
39+
return this.printDebugInformation(await this.debugService.debug<string[]>(debugData, debugOptions));
4040
}
4141

4242
const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: this.$options.bundle, release: this.$options.release };
@@ -58,7 +58,7 @@ export abstract class DebugPlatformCommand implements ICommand {
5858
const buildConfig: IBuildConfig = _.merge({ buildForDevice: !deviceAppData.device.isEmulator }, deployOptions);
5959
debugData.pathToAppPackage = this.$platformService.lastOutputPath(this.debugService.platform, buildConfig, projectData);
6060

61-
this.printDebugInformation(await this.debugService.debug(debugData, debugOptions));
61+
this.printDebugInformation(await this.debugService.debug<string[]>(debugData, debugOptions));
6262
};
6363

6464
return this.$usbLiveSyncService.liveSync(this.$devicesService.platform, this.$projectData, applicationReloadAction);

lib/common

lib/definitions/debug.d.ts

+94-1
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,124 @@
1+
/**
2+
* Describes information for starting debug process.
3+
*/
14
interface IDebugData {
5+
/**
6+
* Id of the device on which the debug process will be started.
7+
*/
28
deviceIdentifier: string;
9+
10+
/**
11+
* Application identifier of the app that it will be debugged.
12+
*/
313
applicationIdentifier: string;
14+
15+
/**
16+
* Path to .app built for iOS Simulator.
17+
*/
418
pathToAppPackage?: string;
19+
20+
/**
21+
* The name of the application, for example `MyProject`.
22+
*/
523
projectName?: string;
24+
25+
/**
26+
* Path to project.
27+
*/
628
projectDir?: string;
729
}
830

31+
/**
32+
* Describes all options that define the behavior of debug.
33+
*/
934
interface IDebugOptions {
35+
/**
36+
* Defines if Chrome-Dev Tools should be used for debugging.
37+
*/
1038
chrome?: boolean;
39+
40+
/**
41+
* Defines if thе application is already started on device.
42+
*/
1143
start?: boolean;
44+
45+
/**
46+
* Defines if we should stop the currently running debug process.
47+
*/
1248
stop?: boolean;
49+
50+
/**
51+
* Defines if debug process is for emulator (not for real device).
52+
*/
1353
emulator?: boolean;
54+
55+
/**
56+
* Defines if the debug process should break on the first line.
57+
*/
1458
debugBrk?: boolean;
59+
60+
/**
61+
* Defines if the debug process will not have a client attached (i.e. the process will be started, but NativeScript Inspector will not be started and it will not attach to the running debug process).
62+
*/
1563
client?: boolean;
64+
65+
/**
66+
* Defines if the process will watch for further changes in the project and transferrs them to device immediately, resulting in restar of the debug process.
67+
*/
1668
justlaunch?: boolean;
69+
70+
/**
71+
* Defines if bundled Chrome DevTools should be used or specific commit. Valid for iOS only.
72+
*/
73+
useBundledDevTools?: boolean;
1774
}
1875

76+
/**
77+
* Describes methods to create debug data object used by other methods.
78+
*/
1979
interface IDebugDataService {
80+
/**
81+
* Creates the debug data based on specified options.
82+
* @param {IProjectData} projectData The data describing project that will be debugged.
83+
* @param {IOptions} options The options based on which debugData will be created
84+
* @returns {IDebugData} Data describing the required information for starting debug process.
85+
*/
2086
createDebugData(projectData: IProjectData, options: IOptions): IDebugData;
2187
}
2288

89+
/**
90+
* Describes methods for debug operation.
91+
*/
2392
interface IDebugService extends NodeJS.EventEmitter {
24-
debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string[]>;
93+
/**
94+
* Starts debug operation based on the specified debug data.
95+
* @param {IDebugData} debugData Describes information for device and application that will be debugged.
96+
* @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line.
97+
* @returns {Promise<T>} Array of URLs that can be used for debugging or a string representing a single url that can be used for debugging.
98+
*/
99+
debug<T>(debugData: IDebugData, debugOptions: IDebugOptions): Promise<T>;
25100
}
26101

102+
/**
103+
* Describes actions required for debugging on specific platform (Android or iOS).
104+
*/
27105
interface IPlatformDebugService extends IDebugService {
106+
/**
107+
* Starts debug operation.
108+
* @param {IDebugData} debugData Describes information for device and application that will be debugged.
109+
* @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line.
110+
* @returns {Promise<void>}
111+
*/
28112
debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise<void>;
113+
114+
/**
115+
* Stops debug operation.
116+
* @returns {Promise<void>}
117+
*/
29118
debugStop(): Promise<void>
119+
120+
/**
121+
* Mobile platform of the device - Android or iOS.
122+
*/
30123
platform: string;
31124
}

lib/services/debug-service.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { platform } from "os";
22
import { EventEmitter } from "events";
33
import { CONNECTION_ERROR_EVENT_NAME } from "../constants";
4+
import { CONNECTED_STATUS } from "../common/constants";
45

5-
// This service can't implement IDebugService because
6-
// the debug method returns only one result.
7-
class DebugService extends EventEmitter {
6+
class DebugService extends EventEmitter implements IDebugService {
87
constructor(private $devicesService: Mobile.IDevicesService,
98
private $androidDebugService: IPlatformDebugService,
109
private $iOSDebugService: IPlatformDebugService,
@@ -23,6 +22,10 @@ class DebugService extends EventEmitter {
2322
this.$errors.failWithoutHelp(`Can't find device with identifier ${debugData.deviceIdentifier}`);
2423
}
2524

25+
if (device.deviceInfo.status !== CONNECTED_STATUS) {
26+
this.$errors.failWithoutHelp(`The device with identifier ${debugData.deviceIdentifier} is unreachable. Make sure it is Trusted and try again.`);
27+
}
28+
2629
if (!(await device.applicationManager.isApplicationInstalled(debugData.applicationIdentifier))) {
2730
this.$errors.failWithoutHelp(`The application ${debugData.applicationIdentifier} is not installed on device with identifier ${debugData.deviceIdentifier}.`);
2831
}
@@ -51,9 +54,9 @@ class DebugService extends EventEmitter {
5154
this.$errors.failWithoutHelp(`Debugging on iOS devices is not supported for ${platform()} yet.`);
5255
}
5356

54-
result = await debugService.debug(debugData, debugOptions);
57+
result = await debugService.debug<string[]>(debugData, debugOptions);
5558
} else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) {
56-
result = await debugService.debug(debugData, debugOptions);
59+
result = await debugService.debug<string[]>(debugData, debugOptions);
5760
}
5861

5962
return _.first(result);

lib/services/ios-debug-service.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,13 @@ class IOSDebugService extends DebugServiceBase implements IPlatformDebugService
197197
this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(device));
198198

199199
const commitSHA = "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; // corresponds to 55.0.2883 Chrome version
200-
return `chrome-devtools://devtools/remote/serve_file/@${commitSHA}/inspector.html?experiments=true&ws=localhost:${this._socketProxy.options.port}`;
200+
let chromeDevToolsPrefix = `chrome-devtools://devtools/remote/serve_file/@${commitSHA}`;
201+
202+
if (debugOptions.useBundledDevTools) {
203+
chromeDevToolsPrefix = "chrome-devtools://devtools/bundled";
204+
}
205+
206+
return `${chromeDevToolsPrefix}/inspector.html?experiments=true&ws=localhost:${this._socketProxy.options.port}`;
201207
} else {
202208
this._socketProxy = await this.$socketProxyFactory.createTCPSocketProxy(this.getSocketFactory(device));
203209
await this.openAppInspector(this._socketProxy.address(), debugData, debugOptions);

test/nativescript-cli-lib.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ describe("nativescript-cli-lib", () => {
1919
localBuildService: ["build"],
2020
deviceLogProvider: null,
2121
npm: ["install", "uninstall", "view", "search"],
22-
extensibilityService: ["loadExtensions", "getInstalledExtensions", "installExtension", "uninstallExtension"]
22+
extensibilityService: ["loadExtensions", "getInstalledExtensions", "installExtension", "uninstallExtension"],
23+
debugService: ["debug"]
2324
};
2425

2526
const pathToEntryPoint = path.join(__dirname, "..", "lib", "nativescript-cli-lib.js").replace(/\\/g, "\\\\");

0 commit comments

Comments
 (0)