Skip to content

Commit e0a2dbd

Browse files
committed
fix: fire the debugger attached event only when the app is restarted (allow debug + hmr in sidekick)
1 parent 95e9cbb commit e0a2dbd

14 files changed

+102
-50
lines changed

lib/declarations.d.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -473,10 +473,14 @@ interface IGenerateOptions {
473473
collection?: string;
474474
}
475475

476-
interface IDebugInformation extends IPort, Mobile.IDeviceIdentifier {
476+
interface IDebugInformation extends IPort, Mobile.IDeviceIdentifier, IHasHasReconnected {
477477
url: string;
478478
}
479479

480+
interface IHasHasReconnected {
481+
hasReconnected: boolean;
482+
}
483+
480484
interface IPort {
481485
port: Number;
482486
}

lib/definitions/debug.d.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ interface IDebugOptions {
100100
* Defines if the handshake(AppLaunching notification) between CLI and runtime should be executed. The handshake is not needed when CLI retries to attach to the debugger.
101101
*/
102102
skipHandshake?: boolean;
103+
/**
104+
* Forces the debugger attach event to be emitted.
105+
*/
106+
forceDebuggerAttachedEvent?: boolean;
103107
}
104108

105109
/**
@@ -161,5 +165,9 @@ interface IDeviceDebugService extends IPlatform, NodeJS.EventEmitter {
161165
* @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line.
162166
* @returns {Promise<string>} Full url where the frontend client may be connected.
163167
*/
164-
debug(debugData: IAppDebugData, debugOptions: IDebugOptions): Promise<string>;
168+
debug(debugData: IAppDebugData, debugOptions: IDebugOptions): Promise<IDebugResultInfo>;
169+
}
170+
171+
interface IDebugResultInfo extends IHasHasReconnected {
172+
debugUrl: string;
165173
}

lib/definitions/livesync.d.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ interface ILiveSyncEventData {
188188
applicationIdentifier?: string,
189189
projectDir: string,
190190
syncedFiles?: string[],
191-
error? : Error,
191+
error?: Error,
192192
notification?: string,
193193
isFullSync?: boolean
194194
}
@@ -390,11 +390,18 @@ interface ITransferFilesOptions {
390390
interface IPlatformLiveSyncService {
391391
fullSync(syncInfo: IFullSyncInfo): Promise<ILiveSyncResultInfo>;
392392
liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise<ILiveSyncResultInfo>;
393-
refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<void>;
393+
refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<IRefreshApplicationInfo>;
394394
prepareForLiveSync(device: Mobile.IDevice, data: IProjectDir, liveSyncInfo: ILiveSyncInfo, debugOptions: IDebugOptions): Promise<void>;
395395
getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService;
396396
}
397397

398+
interface IHasDidRestart {
399+
didRestart: boolean;
400+
}
401+
402+
interface IRefreshApplicationInfo extends IHasDidRestart {
403+
}
404+
398405
interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase {
399406
/**
400407
* Refreshes the application's content on a device
@@ -405,7 +412,7 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase
405412
* @return {Promise<void>}
406413
*/
407414
refreshApplication(projectData: IProjectData,
408-
liveSyncInfo: ILiveSyncResultInfo): Promise<void>;
415+
liveSyncInfo: ILiveSyncResultInfo): Promise<IRefreshApplicationInfo>;
409416

410417
/**
411418
* Removes specified files from a connected device

lib/services/android-device-debug-service.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi
2424
this.deviceIdentifier = device.deviceInfo.identifier;
2525
}
2626

27-
public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string> {
27+
public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<IDebugResultInfo> {
2828
this._packageName = debugData.applicationIdentifier;
2929
const result = this.device.isEmulator
3030
? await this.debugOnEmulator(debugData, debugOptions)
@@ -59,7 +59,7 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi
5959
return this.removePortForwarding();
6060
}
6161

62-
private async debugOnEmulator(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string> {
62+
private async debugOnEmulator(debugData: IDebugData, debugOptions: IDebugOptions): Promise<IDebugResultInfo> {
6363
// Assure we've detected the emulator as device
6464
// For example in case deployOnEmulator had stated new emulator instance
6565
// we need some time to detect it. Let's force detection.
@@ -97,7 +97,7 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi
9797
return this.device.adb.executeCommand(["forward", `tcp:${local}`, `localabstract:${remote}`]);
9898
}
9999

100-
private async debugOnDevice(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string> {
100+
private async debugOnDevice(debugData: IDebugData, debugOptions: IDebugOptions): Promise<IDebugResultInfo> {
101101
let packageFile = "";
102102

103103
if (!debugOptions.start && !this.device.isEmulator) {
@@ -113,27 +113,32 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi
113113
projectName
114114
};
115115

116-
const action = (device: Mobile.IAndroidDevice): Promise<string> => this.debugCore(device, packageFile, appData, debugOptions);
116+
const action = (device: Mobile.IAndroidDevice): Promise<IDebugResultInfo> => this.debugCore(device, packageFile, appData, debugOptions);
117117

118118
const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(this.deviceIdentifier));
119119
return deviceActionResult[0].result;
120120
}
121121

122-
private async debugCore(device: Mobile.IAndroidDevice, packageFile: string, appData: Mobile.IApplicationData, debugOptions: IDebugOptions): Promise<string> {
122+
private async debugCore(device: Mobile.IAndroidDevice, packageFile: string, appData: Mobile.IApplicationData, debugOptions: IDebugOptions): Promise<IDebugResultInfo> {
123+
const result: IDebugResultInfo = { hasReconnected: false, debugUrl: null };
124+
123125
if (debugOptions.stop) {
124126
await this.removePortForwarding();
125-
return null;
127+
return result;
126128
}
127129

128130
if (!debugOptions.start) {
129131
await this.debugStartCore(appData, debugOptions);
132+
result.hasReconnected = true;
130133
}
131134

132135
await this.validateRunningApp(device.deviceInfo.identifier, appData.appId);
133136
const debugPort = await this.getForwardedDebugPort(device.deviceInfo.identifier, appData.appId);
134137
await this.printDebugPort(device.deviceInfo.identifier, debugPort);
135138

136-
return this.getChromeDebugUrl(debugOptions, debugPort);
139+
result.debugUrl = this.getChromeDebugUrl(debugOptions, debugPort);
140+
141+
return result;
137142
}
138143

139144
private async printDebugPort(deviceId: string, port: number): Promise<void> {

lib/services/debug-service-base.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export abstract class DebugServiceBase extends EventEmitter implements IDeviceDe
1010

1111
public abstract get platform(): string;
1212

13-
public abstract async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string>;
13+
public abstract async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<IDebugResultInfo>;
1414

1515
public abstract async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise<void>;
1616

lib/services/debug-service.ts

+8-13
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,6 @@ export class DebugService extends EventEmitter implements IDebugService {
4040
}
4141

4242
const debugOptions: IDebugOptions = _.cloneDeep(options);
43-
44-
// TODO: Check if app is running.
45-
// For now we can only check if app is running on Android.
46-
// After we find a way to check on iOS we should use it here.
47-
let result: string;
48-
4943
const debugService = this.getDeviceDebugService(device);
5044
if (!debugService) {
5145
this.$errors.failWithoutHelp(`Unsupported device OS: ${device.deviceInfo.platform}. You can debug your applications only on iOS or Android.`);
@@ -62,9 +56,9 @@ export class DebugService extends EventEmitter implements IDebugService {
6256
}
6357
}
6458

65-
result = await debugService.debug(debugData, debugOptions);
59+
const debugResultInfo = await debugService.debug(debugData, debugOptions);
6660

67-
return this.getDebugInformation(result, device.deviceInfo.identifier);
61+
return this.getDebugInformation(debugResultInfo, device.deviceInfo.identifier);
6862
}
6963

7064
public debugStop(deviceIdentifier: string): Promise<void> {
@@ -100,16 +94,17 @@ export class DebugService extends EventEmitter implements IDebugService {
10094
platformDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler);
10195
}
10296

103-
private getDebugInformation(fullUrl: string, deviceIdentifier: string): IDebugInformation {
97+
private getDebugInformation(debugResultInfo: IDebugResultInfo, deviceIdentifier: string): IDebugInformation {
10498
const debugInfo: IDebugInformation = {
105-
url: fullUrl,
99+
url: debugResultInfo.debugUrl,
106100
port: 0,
107-
deviceIdentifier
101+
deviceIdentifier,
102+
hasReconnected: debugResultInfo.hasReconnected
108103
};
109104

110-
if (fullUrl) {
105+
if (debugResultInfo.debugUrl) {
111106
const parseQueryString = true;
112-
const wsQueryParam = <string>parse(fullUrl, parseQueryString).query.ws;
107+
const wsQueryParam = <string>parse(debugResultInfo.debugUrl, parseQueryString).query.ws;
113108
const hostPortSplit = wsQueryParam && wsQueryParam.split(":");
114109
debugInfo.port = hostPortSplit && +hostPortSplit[1];
115110
}

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,21 @@ export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDe
3838
return "ios";
3939
}
4040

41-
public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string> {
41+
public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<IDebugResultInfo> {
42+
const result: IDebugResultInfo = { hasReconnected: false, debugUrl: null };
4243
this.validateOptions(debugOptions);
4344

4445
await this.startDeviceLogProcess(debugData, debugOptions);
4546
await this.$iOSDebuggerPortService.attachToDebuggerPortFoundEvent(this.device, debugData, debugOptions);
4647

4748
if (!debugOptions.start) {
4849
await this.startApp(debugData, debugOptions);
50+
result.hasReconnected = true;
4951
}
5052

51-
return this.wireDebuggerClient(debugData, debugOptions);
53+
result.debugUrl = await this.wireDebuggerClient(debugData, debugOptions);
54+
55+
return result;
5256
}
5357

5458
public async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise<void> {

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

+7-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBa
1515
protected device: Mobile.IAndroidDevice,
1616
$filesHashService: IFilesHashService,
1717
$logger: ILogger) {
18-
super($injector, $platformsData, $filesHashService, $logger, device);
18+
super($injector, $platformsData, $filesHashService, $logger, device);
1919
}
2020

2121
public async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<void> {
@@ -26,7 +26,8 @@ export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBa
2626
await this.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath);
2727
}
2828

29-
public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<void> {
29+
public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<IRefreshApplicationInfo> {
30+
const result: IRefreshApplicationInfo = { didRestart: false };
3031
const deviceAppData = liveSyncInfo.deviceAppData;
3132
const localToDevicePaths = liveSyncInfo.modifiedFilesData;
3233
const deviceProjectRootDirname = await this.$devicePathProvider.getDeviceProjectRootPath(liveSyncInfo.deviceAppData.device, {
@@ -48,8 +49,11 @@ export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBa
4849
(localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(liveSyncInfo, localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform));
4950

5051
if (!canExecuteFastSync) {
51-
return this.restartApplication(deviceAppData, projectData.projectName);
52+
this.restartApplication(deviceAppData, projectData.projectName);
53+
result.didRestart = true;
5254
}
55+
56+
return result;
5357
}
5458

5559
private async cleanLivesyncDirectories(deviceAppData: Mobile.IDeviceAppData): Promise<void> {

lib/services/livesync/android-device-livesync-sockets-service.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe
9292
return result;
9393
}
9494

95-
public async refreshApplication(projectData: IProjectData, liveSyncInfo: IAndroidLiveSyncResultInfo) {
95+
public async refreshApplication(projectData: IProjectData, liveSyncInfo: IAndroidLiveSyncResultInfo): Promise<IRefreshApplicationInfo> {
96+
const result: IRefreshApplicationInfo = { didRestart: false };
9697
const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo, liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform);
9798
if (!canExecuteFastSync || !liveSyncInfo.didRefresh) {
9899
await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName });
@@ -103,7 +104,11 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe
103104
this.$logger.trace("Failed to connect after app restart.");
104105
}
105106
}
107+
108+
result.didRestart = true;
106109
}
110+
111+
return result;
107112
}
108113

109114
public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise<void> {

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

+9-3
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen
3737
await Promise.all(_.map(localToDevicePaths, localToDevicePathData => this.device.fileSystem.deleteFile(localToDevicePathData.getDevicePath(), deviceAppData.appIdentifier)));
3838
}
3939

40-
public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<void> {
40+
public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<IRefreshApplicationInfo> {
41+
const result: IRefreshApplicationInfo = { didRestart: false };
4142
const deviceAppData = liveSyncInfo.deviceAppData;
4243
const localToDevicePaths = liveSyncInfo.modifiedFilesData;
4344
if (liveSyncInfo.isFullSync) {
4445
await this.restartApplication(deviceAppData, projectData.projectName);
45-
return;
46+
result.didRestart = true;
47+
return result;
4648
}
4749

4850
let scriptRelatedFiles: Mobile.ILocalToDevicePathData[] = [];
@@ -54,14 +56,18 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen
5456

5557
if (!canExecuteFastSync) {
5658
await this.restartApplication(deviceAppData, projectData.projectName);
57-
return;
59+
result.didRestart = true;
60+
return result;
5861
}
5962

6063
if (await this.setupSocketIfNeeded(projectData)) {
6164
await this.reloadPage(otherFiles);
6265
} else {
6366
await this.restartApplication(deviceAppData, projectData.projectName);
67+
result.didRestart = true;
6468
}
69+
70+
return result;
6571
}
6672

6773
private async restartApplication(deviceAppData: Mobile.IDeviceAppData, projectName: string): Promise<void> {

lib/services/livesync/livesync-service.ts

+17-8
Original file line numberDiff line numberDiff line change
@@ -156,20 +156,21 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
156156
}
157157
}
158158

159-
private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise<void | IDebugInformation> {
159+
private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise<IRefreshApplicationInfo | IDebugInformation> {
160160
const deviceDescriptor = this.getDeviceDescriptor(liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, projectData.projectDir);
161161

162162
return deviceDescriptor && deviceDescriptor.debugggingEnabled ?
163163
this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, debugOpts, outputPath) :
164164
this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOpts, outputPath);
165165
}
166166

167-
private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IShouldSkipEmitLiveSyncNotification): Promise<void> {
167+
private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IShouldSkipEmitLiveSyncNotification): Promise<IRefreshApplicationInfo> {
168+
let result: IRefreshApplicationInfo = { didRestart: false };
168169
const platform = liveSyncResultInfo.deviceAppData.platform;
169170
const platformLiveSyncService = this.getLiveSyncService(platform);
170171
const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()];
171172
try {
172-
await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo);
173+
result = await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo);
173174
} catch (err) {
174175
this.$logger.info(`Error while trying to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err.message || err}`);
175176
const msg = `Unable to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`;
@@ -193,12 +194,14 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
193194
deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier,
194195
isFullSync: liveSyncResultInfo.isFullSync
195196
});
197+
198+
return result;
196199
}
197200

198201
private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise<IDebugInformation> {
202+
let didRestart = false;
199203
const deviceAppData = liveSyncResultInfo.deviceAppData;
200204
debugOptions = debugOptions || {};
201-
202205
const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier;
203206
if (debugOptions.debugBrk) {
204207
await this.$debugService.debugStop(deviceIdentifier);
@@ -211,13 +214,15 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
211214
this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOptions, outputPath);
212215
}
213216
} else {
214-
await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true });
217+
const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true });
218+
didRestart = refreshInfo.didRestart;
215219
}
216220

217221
// we do not stop the application when debugBrk is false, so we need to attach, instead of launch
218222
// if we try to send the launch request, the debugger port will not be printed and the command will timeout
219223
debugOptions.start = !debugOptions.debugBrk;
220224

225+
debugOptions.forceDebuggerAttachedEvent = didRestart;
221226
const deviceOption = {
222227
deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier,
223228
debugOptions: debugOptions,
@@ -269,13 +274,17 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
269274
debugData.pathToAppPackage = this.$platformService.lastOutputPath(settings.platform, buildConfig, projectData, settings.outputPath);
270275

271276
const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions);
272-
const result = this.printDebugInformation(debugInfo);
277+
const fireDebuggerAttachedEvent = settings.debugOptions.forceDebuggerAttachedEvent || debugInfo.hasReconnected;
278+
const result = this.printDebugInformation(debugInfo, fireDebuggerAttachedEvent);
273279
return result;
274280
}
275281

276-
public printDebugInformation(debugInformation: IDebugInformation): IDebugInformation {
282+
public printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent: boolean = true): IDebugInformation {
277283
if (!!debugInformation.url) {
278-
this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation);
284+
if (fireDebuggerAttachedEvent) {
285+
this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation);
286+
}
287+
279288
this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${debugInformation.url}${EOL}`.cyan);
280289
}
281290

0 commit comments

Comments
 (0)