From 49ec244277bbad0deece6afb00877242a13871b4 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 8 Sep 2017 15:48:27 +0300 Subject: [PATCH 1/4] Fix UnhandledRejection error when failed to start application on iOS during debug In case we are unable to start application on iOS device just before attaching a debugger, CLI will raise unhandled rejection. The problem is that we want to start two actions in parallel - start of application and attaching a debugger. We do not await the start application and when an error is raised in it, Unhandled Rejection is raised. In order to prevent this, use Promise.all - this way in case any of the operations fail, we'll receive it as error in the same method. --- lib/services/ios-debug-service.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index a7adaf468e..08bed47627 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -160,7 +160,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private async deviceDebugBrk(debugData: IDebugData, debugOptions: IDebugOptions): Promise { await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); - const action = async (device: iOSDevice.IOSDevice) => { + const action = async (device: iOSDevice.IOSDevice): Promise => { if (device.isEmulator) { return await this.emulatorDebugBrk(debugData, debugOptions); } @@ -170,14 +170,13 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS emulator: debugOptions.emulator, justlaunch: debugOptions.justlaunch }; - // we intentionally do not wait on this here, because if we did, we'd miss the AppLaunching notification - const startApplicationAction = this.$platformService.startApplication(this.platform, runOptions, debugData.applicationIdentifier); - const result = await this.debugBrkCore(device, debugData, debugOptions); + const promisesResults = await Promise.all([ + this.$platformService.startApplication(this.platform, runOptions, debugData.applicationIdentifier), + this.debugBrkCore(device, debugData, debugOptions) + ]); - await startApplicationAction; - - return result; + return _.last(promisesResults); }; const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); From 2d5449a121f405963dc1b5adb923fc5e166c0819 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 8 Sep 2017 15:58:35 +0300 Subject: [PATCH 2/4] Fix attaching multiple times on deviceLost event In LiveSync service we attach to deviceLost event in order to stop LiveSync operation in case device is detached. However we are attaching it on every change, so at some point CLI prints warning: `Warning: Possible EventEmitter memory leak detected. 11 deviceLost listeners added. Use emitter.setMaxListeners() to increase limit ` Fix this by attaching only once. --- lib/services/livesync/livesync-service.ts | 25 ++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 941f396f07..8a91a15b84 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -4,7 +4,9 @@ import { EOL } from "os"; import { EventEmitter } from "events"; import { hook } from "../../common/helpers"; import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME, DEBUGGER_DETACHED_EVENT_NAME, TrackActionNames } from "../../constants"; -import { FileExtensions, DeviceTypes } from "../../common/constants"; +import { FileExtensions, DeviceTypes, DeviceDiscoveryEventNames } from "../../common/constants"; +import { cache } from "../../common/decorators"; + const deviceDescriptorPrimaryKey = "identifier"; const LiveSyncEvents = { @@ -473,6 +475,8 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi // Execute the action only on the deviceDescriptors passed to initialSync. // In case where we add deviceDescriptors to already running application, we've already executed initialSync for them. await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); + + this.attachDeviceLostHandler(); } private getDefaultLatestAppPackageInstalledSettings(): ILatestAppPackageInstalledSettings { @@ -632,13 +636,24 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi this.stopLiveSync(projectDir); }); }); - - this.$devicesService.on("deviceLost", async (device: Mobile.IDevice) => { - await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier]); - }); } } + @cache() + private attachDeviceLostHandler(): void { + this.$devicesService.on(DeviceDiscoveryEventNames.DEVICE_LOST, async (device: Mobile.IDevice) => { + this.$logger.trace(`Received ${DeviceDiscoveryEventNames.DEVICE_LOST} event in LiveSync service for ${device.deviceInfo.identifier}. Will stop LiveSync operation for this device.`); + + for (const projectDir in this.liveSyncProcessesInfo) { + try { + await this.stopLiveSync(projectDir, [device.deviceInfo.identifier]); + } catch (err) { + this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err); + } + } + }); + } + private async addActionToChain(projectDir: string, action: () => Promise): Promise { const liveSyncInfo = this.liveSyncProcessesInfo[projectDir]; if (liveSyncInfo) { From 3ddd19b0ebac5d3081b1f725558021db309ed0d7 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 8 Sep 2017 16:02:16 +0300 Subject: [PATCH 3/4] Fix application is not started on iOS Simulator every other time In case you are using iOS Simulator with version < 10, every second LiveSync operation does not start the app on device. The problem is that calling `simctl terminate` is not killing the app immediately. However the action is executed, so we call start application. After its been called, the terminate succeeds and kills the app. So the app is not running. When a change is applied we detect app is not running and just start it. So it works. Next change calls terminate and again the app seems like it has not been started. The fix is in ios-sim-portable, so update its version. --- npm-shrinkwrap.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 86732bf537..ac17b802aa 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -83,7 +83,7 @@ "@types/universal-analytics": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/universal-analytics/-/universal-analytics-0.4.1.tgz", - "integrity": "sha512-AZSPpDUEZ4mAgO9geHc62dp/xCLmBJ1yIpbgTq5W/cWcVQsxmU/FyKwYKHXk2hnT9TAmYVFFdAijMrCdYjuHsA==", + "integrity": "sha1-7mESGwqJiwvqXuskcgCJjg+o8Jw=", "dev": true }, "abbrev": { @@ -2945,9 +2945,9 @@ } }, "ios-sim-portable": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-3.1.1.tgz", - "integrity": "sha1-AmL3x3N6ZnfyAI48rem2KMEIN6c=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-3.1.2.tgz", + "integrity": "sha1-Fq6f+F2gUE0K+4gyj73sCQafFG0=", "requires": { "bplist-parser": "https://github.com/telerik/node-bplist-parser/tarball/master", "colors": "0.6.2", @@ -5186,7 +5186,7 @@ "universal-analytics": { "version": "0.4.15", "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.15.tgz", - "integrity": "sha512-9Dt6WBWsHsmv74G+N/rmEgi6KFZxVvQXkVhr0disegeUryybQAUQwMD1l5EtqaOu+hSOGbhL/hPPQYisZIqPRw==", + "integrity": "sha1-SrxhsVn/52W+FE4Ht7c54O57iKs=", "requires": { "async": "1.2.1", "request": "2.81.0", diff --git a/package.json b/package.json index ec9320ba40..9de808b468 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "inquirer": "0.9.0", "ios-device-lib": "0.4.9", "ios-mobileprovision-finder": "1.0.10", - "ios-sim-portable": "3.1.1", + "ios-sim-portable": "3.1.2", "lockfile": "1.0.3", "lodash": "4.13.1", "log4js": "1.0.1", From f913529a5d1f244c8e744878278ce4d5b4425d20 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 8 Sep 2017 17:03:49 +0300 Subject: [PATCH 4/4] Fix executing of deploy/start app on device, which is not passed to the method In PlatformService we create a canExecute method that does not work correctly in cases when CLI is used as a library. The problem is that in this case methods like `deployPlatform` and `startApplication` receive the device on which to execute the action through their arguments. However, the canExecute method relies on the `devicesService.getDeviceByDeviceOption()` method, which works with the `$options.device`. The last is never set in cases when CLI is used as a library. So the canExecute is changed and now it will work only in case it receives deviceIdentifier as argument. This requires changes in `startApplication` and `deployPlatform` methods, so they will send the deviceIdentifier to canExecute method. --- lib/services/platform-service.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 268c2c2095..22895ae564 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -587,6 +587,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { await this.trackActionForPlatform({ action: constants.TrackActionNames.Deploy, platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); }; + if (deployOptions.device) { + const device = await this.$devicesService.getDevice(deployOptions.device); + deployOptions.device = device.deviceInfo.identifier; + } + await this.$devicesService.execute(action, this.getCanExecuteAction(platform, deployOptions)); } @@ -599,6 +604,12 @@ export class PlatformService extends EventEmitter implements IPlatformService { }; await this.$devicesService.initialize({ platform: platform, deviceId: runOptions.device }); + + if (runOptions.device) { + const device = await this.$devicesService.getDevice(runOptions.device); + runOptions.device = device.deviceInfo.identifier; + } + await this.$devicesService.execute(action, this.getCanExecuteAction(platform, runOptions)); } @@ -715,10 +726,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { private getCanExecuteAction(platform: string, options: IDeviceEmulator): any { const canExecute = (currentDevice: Mobile.IDevice): boolean => { if (options.device && currentDevice && currentDevice.deviceInfo) { - const device = this.$devicesService.getDeviceByDeviceOption(); - if (device && device.deviceInfo) { - return currentDevice.deviceInfo.identifier === device.deviceInfo.identifier; - } + return currentDevice.deviceInfo.identifier === options.device; } if (this.$mobileHelper.isiOSPlatform(platform) && this.$hostInfo.isDarwin) {