From 2b9bd70e3ac95244db3a3cd57a587709ea6fcd92 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Mon, 19 Nov 2018 14:29:16 +0200 Subject: [PATCH 01/21] fix: plugin create doesn't always clean up folder if execution fails --- lib/commands/plugin/create-plugin.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/commands/plugin/create-plugin.ts b/lib/commands/plugin/create-plugin.ts index 423f0c815d..d83cb29da1 100644 --- a/lib/commands/plugin/create-plugin.ts +++ b/lib/commands/plugin/create-plugin.ts @@ -22,8 +22,17 @@ export class CreatePluginCommand implements ICommand { const selectedPath = path.resolve(pathToProject || "."); const projectDir = path.join(selectedPath, pluginRepoName); - await this.downloadPackage(selectedTemplate, projectDir); - await this.setupSeed(projectDir, pluginRepoName); + // Must be out of try catch block, because will throw error if folder alredy exists and we don't want to delete it. + this.ensurePackageDir(projectDir); + + try { + await this.downloadPackage(selectedTemplate, projectDir); + await this.setupSeed(projectDir, pluginRepoName); + } catch (err) { + // The call to this.ensurePackageDir() above will throw error if folder alredy exists, so it is safe to delete here. + this.$fs.deleteDirectory(projectDir); + throw err; + } this.$logger.printMarkdown("Solution for `%s` was successfully created.", pluginRepoName); } @@ -66,13 +75,15 @@ export class CreatePluginCommand implements ICommand { } } - private async downloadPackage(selectedTemplate: string, projectDir: string): Promise { + private ensurePackageDir(projectDir: string): void { this.$fs.createDirectory(projectDir); if (this.$fs.exists(projectDir) && !this.$fs.isEmptyDir(projectDir)) { this.$errors.fail("Path already exists and is not empty %s", projectDir); } + } + private async downloadPackage(selectedTemplate: string, projectDir: string): Promise { if (selectedTemplate) { this.$logger.printMarkdown("Make sure your custom template is compatible with the Plugin Seed at https://github.com/NativeScript/nativescript-plugin-seed/"); } else { @@ -84,9 +95,6 @@ export class CreatePluginCommand implements ICommand { try { spinner.start(); await this.$pacoteService.extractPackage(packageToInstall, projectDir); - } catch (err) { - this.$fs.deleteDirectory(projectDir); - throw err; } finally { spinner.stop(); } From 57b1631cf4e989f2056831a380cff51de41e9ed9 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Mon, 19 Nov 2018 18:13:45 +0200 Subject: [PATCH 02/21] test: add tests for plugin dir removal --- lib/commands/plugin/create-plugin.ts | 3 +- test/plugin-create.ts | 65 +++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/lib/commands/plugin/create-plugin.ts b/lib/commands/plugin/create-plugin.ts index d83cb29da1..3eb4baa121 100644 --- a/lib/commands/plugin/create-plugin.ts +++ b/lib/commands/plugin/create-plugin.ts @@ -5,6 +5,7 @@ export class CreatePluginCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; public userMessage = "What is your GitHub username?\n(will be used to update the Github URLs in the plugin's package.json)"; public nameMessage = "What will be the name of your plugin?\n(use lowercase characters and dashes only)"; + public pathAlreadyExistsMessageTemplate = "Path already exists and is not empty %s"; constructor(private $options: IOptions, private $errors: IErrors, private $terminalSpinnerService: ITerminalSpinnerService, @@ -79,7 +80,7 @@ export class CreatePluginCommand implements ICommand { this.$fs.createDirectory(projectDir); if (this.$fs.exists(projectDir) && !this.$fs.isEmptyDir(projectDir)) { - this.$errors.fail("Path already exists and is not empty %s", projectDir); + this.$errors.fail(this.pathAlreadyExistsMessageTemplate, projectDir); } } diff --git a/test/plugin-create.ts b/test/plugin-create.ts index c6ab5b6f6b..a59e42f5a5 100644 --- a/test/plugin-create.ts +++ b/test/plugin-create.ts @@ -3,6 +3,11 @@ import * as stubs from "./stubs"; import { CreatePluginCommand } from "../lib/commands/plugin/create-plugin"; import { assert } from "chai"; import helpers = require("../lib/common/helpers"); +import * as sinon from "sinon"; +import temp = require("temp"); +import * as path from "path"; +import * as util from "util"; +temp.track(); interface IPacoteOutput { packageName: string; @@ -10,7 +15,8 @@ interface IPacoteOutput { } const originalIsInteractive = helpers.isInteractive; -const dummyArgs = ["dummyProjectName"]; +const dummyProjectName = "dummyProjectName"; +const dummyArgs = [dummyProjectName]; const dummyUser = "devUsername"; const dummyName = "devPlugin"; const dummyPacote: IPacoteOutput = { packageName: "", destinationDirectory: "" }; @@ -142,5 +148,62 @@ describe("Plugin create command tests", () => { options.pluginName = dummyName; await createPluginCommand.execute(dummyArgs); }); + + describe("when fails", () => { + let sandbox: sinon.SinonSandbox; + let fsSpy: sinon.SinonSpy; + let projectPath: string; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + const workingPath = temp.mkdirSync("test_plugin"); + options.path = workingPath; + projectPath = path.join(workingPath, dummyProjectName); + const fsService = testInjector.resolve("fs"); + fsSpy = sandbox.spy(fsService, "deleteDirectory"); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("downloadPackage, should remove projectDir", async () => { + const errorMessage = "Test fail"; + const pacoteService = testInjector.resolve("pacoteService"); + sandbox.stub(pacoteService, "extractPackage").callsFake(() => { + return Promise.reject(new Error(errorMessage)); + }); + + const executePromise = createPluginCommand.execute(dummyArgs); + + await assert.isRejected(executePromise, errorMessage); + assert(fsSpy.calledWith(projectPath)); + }); + + it("setupSeed, should remove projectDir", async () => { + const errorMessage = "Test fail"; + const npmService = testInjector.resolve("npm"); + sandbox.stub(npmService, "install").callsFake(() => { + return Promise.reject(new Error(errorMessage)); + }); + + const executePromise = createPluginCommand.execute(dummyArgs); + + await assert.isRejected(executePromise, errorMessage); + assert(fsSpy.calledWith(projectPath)); + }); + + it("ensurePachageDir should not remove projectDir", async () => { + const fsService = testInjector.resolve("fs"); + sandbox.stub(fsService, "isEmptyDir").callsFake(() => { + return false; + }); + + const executePromise = createPluginCommand.execute(dummyArgs); + + await assert.isRejected(executePromise, util.format(createPluginCommand.pathAlreadyExistsMessageTemplate, projectPath)); + assert(fsSpy.notCalled); + }); + }); }); }); From 655687c2da4e67e13039224e600011c2bdfdef9d Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 19 Nov 2018 08:13:25 +0200 Subject: [PATCH 03/21] fix(preview-api): raise deviceLost event after timeout of 5 seconds In case when some `.js` file is changed, preview app is restarted on device e.g preview app is stopped and started again. When the preview app is stopped, Pubnub reports the device as lost and when preview app is started, Pubnub reports the device as found. With this fix, we want to delay the emitting of deviceLost event. This way we'll give a chance to find the device before reporting it as lost. --- .../devices/preview-devices-service.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/services/livesync/playground/devices/preview-devices-service.ts b/lib/services/livesync/playground/devices/preview-devices-service.ts index 93429b789e..d8401161f2 100644 --- a/lib/services/livesync/playground/devices/preview-devices-service.ts +++ b/lib/services/livesync/playground/devices/preview-devices-service.ts @@ -4,6 +4,7 @@ import { DeviceDiscoveryEventNames, DEVICE_LOG_EVENT_NAME } from "../../../../co export class PreviewDevicesService extends EventEmitter implements IPreviewDevicesService { private connectedDevices: Device[] = []; + private deviceTimers: IDictionary = {}; constructor(private $previewAppLogProvider: IPreviewAppLogProvider, private $previewAppPluginsService: IPreviewAppPluginsService) { @@ -23,7 +24,7 @@ export class PreviewDevicesService extends EventEmitter implements IPreviewDevic _(this.connectedDevices) .reject(d => _.find(devices, device => d.id === device.id)) - .each(device => this.raiseDeviceLost(device)); + .each(device => this.raiseDeviceLostAfterTimeout(device)); } public getDeviceById(id: string): Device { @@ -45,6 +46,10 @@ export class PreviewDevicesService extends EventEmitter implements IPreviewDevic } private raiseDeviceFound(device: Device) { + if (this.deviceTimers[device.id]) { + clearTimeout(this.deviceTimers[device.id]); + } + this.emit(DeviceDiscoveryEventNames.DEVICE_FOUND, device); this.connectedDevices.push(device); } @@ -53,5 +58,15 @@ export class PreviewDevicesService extends EventEmitter implements IPreviewDevic this.emit(DeviceDiscoveryEventNames.DEVICE_LOST, device); _.remove(this.connectedDevices, d => d.id === device.id); } + + private raiseDeviceLostAfterTimeout(device: Device) { + if (!this.deviceTimers[device.id]) { + const timeoutId = setTimeout(() => { + this.raiseDeviceLost(device); + clearTimeout(timeoutId); + }, 5 * 1000); + this.deviceTimers[device.id] = timeoutId; + } + } } $injector.register("previewDevicesService", PreviewDevicesService); From 9acce5472f67806767e13e353ba4289cd6755e58 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 20 Nov 2018 09:35:25 +0200 Subject: [PATCH 04/21] chore: fix unit tests --- test/services/preview-devices-service.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/services/preview-devices-service.ts b/test/services/preview-devices-service.ts index ccc9339a66..afd0b95b87 100644 --- a/test/services/preview-devices-service.ts +++ b/test/services/preview-devices-service.ts @@ -4,6 +4,7 @@ import { Device } from "nativescript-preview-sdk"; import { assert } from "chai"; import { DeviceDiscoveryEventNames } from "../../lib/common/constants"; import { LoggerStub, ErrorsStub } from "../stubs"; +import * as sinon from "sinon"; let foundDevices: Device[] = []; let lostDevices: Device[] = []; @@ -40,6 +41,7 @@ function resetDevices() { describe("PreviewDevicesService", () => { describe("onDevicesPresence", () => { let previewDevicesService: IPreviewDevicesService = null; + let clock: sinon.SinonFakeTimers = null; beforeEach(() => { const injector = createTestInjector(); previewDevicesService = injector.resolve("previewDevicesService"); @@ -49,11 +51,13 @@ describe("PreviewDevicesService", () => { previewDevicesService.on(DeviceDiscoveryEventNames.DEVICE_LOST, device => { lostDevices.push(device); }); + clock = sinon.useFakeTimers(); }); afterEach(() => { previewDevicesService.removeAllListeners(); resetDevices(); + clock.restore(); }); it("should add new device", () => { @@ -101,6 +105,7 @@ describe("PreviewDevicesService", () => { resetDevices(); previewDevicesService.updateConnectedDevices([]); + clock.tick(5000); assert.deepEqual(foundDevices, []); assert.deepEqual(lostDevices, [device1]); @@ -116,6 +121,7 @@ describe("PreviewDevicesService", () => { resetDevices(); previewDevicesService.updateConnectedDevices([device2]); + clock.tick(5000); assert.deepEqual(previewDevicesService.getConnectedDevices(), [device2]); assert.deepEqual(foundDevices, [device2]); From 1d288789eac88d3d9576e881b7b2835867233bb3 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 20 Nov 2018 22:29:25 +0200 Subject: [PATCH 05/21] chore: fix PR comments --- .../playground/devices/preview-devices-service.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/services/livesync/playground/devices/preview-devices-service.ts b/lib/services/livesync/playground/devices/preview-devices-service.ts index d8401161f2..6be8c2627b 100644 --- a/lib/services/livesync/playground/devices/preview-devices-service.ts +++ b/lib/services/livesync/playground/devices/preview-devices-service.ts @@ -4,7 +4,7 @@ import { DeviceDiscoveryEventNames, DEVICE_LOG_EVENT_NAME } from "../../../../co export class PreviewDevicesService extends EventEmitter implements IPreviewDevicesService { private connectedDevices: Device[] = []; - private deviceTimers: IDictionary = {}; + private deviceLostTimers: IDictionary = {}; constructor(private $previewAppLogProvider: IPreviewAppLogProvider, private $previewAppPluginsService: IPreviewAppPluginsService) { @@ -46,8 +46,8 @@ export class PreviewDevicesService extends EventEmitter implements IPreviewDevic } private raiseDeviceFound(device: Device) { - if (this.deviceTimers[device.id]) { - clearTimeout(this.deviceTimers[device.id]); + if (this.deviceLostTimers[device.id]) { + clearTimeout(this.deviceLostTimers[device.id]); } this.emit(DeviceDiscoveryEventNames.DEVICE_FOUND, device); @@ -60,12 +60,12 @@ export class PreviewDevicesService extends EventEmitter implements IPreviewDevic } private raiseDeviceLostAfterTimeout(device: Device) { - if (!this.deviceTimers[device.id]) { + if (!this.deviceLostTimers[device.id]) { const timeoutId = setTimeout(() => { this.raiseDeviceLost(device); clearTimeout(timeoutId); }, 5 * 1000); - this.deviceTimers[device.id] = timeoutId; + this.deviceLostTimers[device.id] = timeoutId; } } } From edc348657462ad56702b50d8a6f25d3d3ce409ab Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 20 Nov 2018 22:29:41 +0200 Subject: [PATCH 06/21] chore: add additional tests --- test/services/preview-devices-service.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test/services/preview-devices-service.ts b/test/services/preview-devices-service.ts index afd0b95b87..6ae78312b4 100644 --- a/test/services/preview-devices-service.ts +++ b/test/services/preview-devices-service.ts @@ -39,7 +39,7 @@ function resetDevices() { } describe("PreviewDevicesService", () => { - describe("onDevicesPresence", () => { + describe("getConnectedDevices", () => { let previewDevicesService: IPreviewDevicesService = null; let clock: sinon.SinonFakeTimers = null; beforeEach(() => { @@ -127,5 +127,24 @@ describe("PreviewDevicesService", () => { assert.deepEqual(foundDevices, [device2]); assert.deepEqual(lostDevices, [device1]); }); + it("shouldn't emit deviceFound or deviceLost when preview app is restarted on device", () => { + const device1 = createDevice("device1"); + + previewDevicesService.updateConnectedDevices([device1]); + + assert.deepEqual(previewDevicesService.getConnectedDevices(), [device1]); + assert.deepEqual(foundDevices, [device1]); + assert.deepEqual(lostDevices, []); + resetDevices(); + + // preview app is restarted + previewDevicesService.updateConnectedDevices([]); + clock.tick(500); + previewDevicesService.updateConnectedDevices([device1]); + + assert.deepEqual(foundDevices, []); + assert.deepEqual(lostDevices, []); + assert.deepEqual(previewDevicesService.getConnectedDevices(), [device1]); + }); }); }); From bdaf3ea71e4efb5cc320e7e7ae100b7b65a30772 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 20 Nov 2018 22:36:35 +0200 Subject: [PATCH 07/21] fix: reset devices list when stopLiveSync method is called When stopLiveSync method is called, we want to reset devices list and receive deviceLost event for all connected devices. --- lib/services/livesync/playground/preview-app-livesync-service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts index 1b8de93f86..02dd1394df 100644 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -72,6 +72,7 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService { public async stopLiveSync(): Promise { this.$previewSdkService.stop(); + this.$previewDevicesService.updateConnectedDevices([]); } private async initializePreviewForDevice(data: IPreviewAppLiveSyncData, device: Device): Promise { From 3bfa2de563f33286711086a72235b7c41c41392e Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 21 Nov 2018 15:30:08 +0200 Subject: [PATCH 08/21] fix: reset deviceLostTimer after clearing the device's timeout --- .../livesync/playground/devices/preview-devices-service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/services/livesync/playground/devices/preview-devices-service.ts b/lib/services/livesync/playground/devices/preview-devices-service.ts index 6be8c2627b..66f674572e 100644 --- a/lib/services/livesync/playground/devices/preview-devices-service.ts +++ b/lib/services/livesync/playground/devices/preview-devices-service.ts @@ -48,6 +48,7 @@ export class PreviewDevicesService extends EventEmitter implements IPreviewDevic private raiseDeviceFound(device: Device) { if (this.deviceLostTimers[device.id]) { clearTimeout(this.deviceLostTimers[device.id]); + this.deviceLostTimers[device.id] = null; } this.emit(DeviceDiscoveryEventNames.DEVICE_FOUND, device); From 83577edd22cf8c59134d0381e1fda89fa4624817 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 22 Nov 2018 09:25:49 +0200 Subject: [PATCH 09/21] feat(preview-api): emit previewAppLiveSyncError when some error is thrown while livesyncing to preview app --- lib/definitions/preview-app-livesync.d.ts | 2 +- lib/services/livesync/livesync-service.ts | 7 +++++++ .../playground/preview-app-constants.ts | 4 ++++ .../preview-app-livesync-service.ts | 19 ++++++++++++++----- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/lib/definitions/preview-app-livesync.d.ts b/lib/definitions/preview-app-livesync.d.ts index bac20cb7b0..cc17e6959b 100644 --- a/lib/definitions/preview-app-livesync.d.ts +++ b/lib/definitions/preview-app-livesync.d.ts @@ -2,7 +2,7 @@ import { FilePayload, Device, FilesPayload } from "nativescript-preview-sdk"; import { EventEmitter } from "events"; declare global { - interface IPreviewAppLiveSyncService { + interface IPreviewAppLiveSyncService extends EventEmitter { initialize(data: IPreviewAppLiveSyncData): void; syncFiles(data: IPreviewAppLiveSyncData, filesToSync: string[], filesToRemove: string[]): Promise; stopLiveSync(): Promise; diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index ecac15210b..9f9b6283a9 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -7,6 +7,7 @@ import { PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEED import { DeviceTypes, DeviceDiscoveryEventNames, HmrConstants } from "../../common/constants"; import { cache } from "../../common/decorators"; import * as constants from "../../constants"; +import { PreviewAppLiveSyncEvents } from "./playground/preview-app-constants"; const deviceDescriptorPrimaryKey = "identifier"; @@ -14,6 +15,7 @@ const LiveSyncEvents = { liveSyncStopped: "liveSyncStopped", // In case we name it error, EventEmitter expects instance of Error to be raised and will also raise uncaught exception in case there's no handler liveSyncError: "liveSyncError", + previewAppLiveSyncError: PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, liveSyncExecuted: "liveSyncExecuted", liveSyncStarted: "liveSyncStarted", liveSyncNotification: "notify" @@ -54,6 +56,10 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi } public async liveSyncToPreviewApp(data: IPreviewAppLiveSyncData): Promise { + this.$previewAppLiveSyncService.on(LiveSyncEvents.previewAppLiveSyncError, liveSyncData => { + this.emit(LiveSyncEvents.previewAppLiveSyncError, liveSyncData); + }); + await this.liveSync([], { syncToPreviewApp: true, projectDir: data.projectDir, @@ -102,6 +108,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi if (liveSyncProcessInfo.syncToPreviewApp) { await this.$previewAppLiveSyncService.stopLiveSync(); + this.$previewAppLiveSyncService.removeAllListeners(); } // Kill typescript watcher diff --git a/lib/services/livesync/playground/preview-app-constants.ts b/lib/services/livesync/playground/preview-app-constants.ts index 49afc03626..fd8ac000e6 100644 --- a/lib/services/livesync/playground/preview-app-constants.ts +++ b/lib/services/livesync/playground/preview-app-constants.ts @@ -18,3 +18,7 @@ export class PluginComparisonMessages { public static LOCAL_PLUGIN_WITH_DIFFERENCE_IN_MAJOR_VERSION = "Local plugin %s differs in major version from plugin in preview app. The local plugin has version %s and the plugin in preview app has version %s. Some features might not work as expected."; public static LOCAL_PLUGIN_WITH_GREATHER_MINOR_VERSION = "Local plugin %s differs in minor version from plugin in preview app. The local plugin has version %s and the plugin in preview app has version %s. Some features might not work as expected."; } + +export class PreviewAppLiveSyncEvents { + public static PREVIEW_APP_LIVE_SYNC_ERROR = "previewAppLiveSyncError"; +} diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts index 02dd1394df..4f6dbe8655 100644 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -1,8 +1,9 @@ import * as path from "path"; import { FilePayload, Device, FilesPayload } from "nativescript-preview-sdk"; -import { PreviewSdkEventNames } from "./preview-app-constants"; +import { PreviewSdkEventNames, PreviewAppLiveSyncEvents } from "./preview-app-constants"; import { APP_FOLDER_NAME, APP_RESOURCES_FOLDER_NAME, TNS_MODULES_FOLDER_NAME } from "../../../constants"; import { HmrConstants } from "../../../common/constants"; +import { EventEmitter } from "events"; const isTextOrBinary = require('istextorbinary'); interface ISyncFilesOptions { @@ -14,7 +15,7 @@ interface ISyncFilesOptions { deviceId?: string; } -export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService { +export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewAppLiveSyncService { private excludedFileExtensions = [".ts", ".sass", ".scss", ".less"]; private excludedFiles = [".DS_Store"]; private deviceInitializationPromise: IDictionary> = {}; @@ -31,7 +32,9 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService { private $previewDevicesService: IPreviewDevicesService, private $projectFilesManager: IProjectFilesManager, private $hmrStatusService: IHmrStatusService, - private $projectFilesProvider: IProjectFilesProvider) { } + private $projectFilesProvider: IProjectFilesProvider) { + super(); + } public async initialize(data: IPreviewAppLiveSyncData): Promise { await this.$previewSdkService.initialize(async (device: Device) => { @@ -159,8 +162,14 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService { } return payloads; - } catch (err) { - this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${err}, ${JSON.stringify(err, null, 2)}.`); + } catch (error) { + this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${error}, ${JSON.stringify(error, null, 2)}.`); + this.emit(PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, { + error, + data, + platform, + deviceId: opts.deviceId + }); } } From f0d2f8a4a757899afea5ac6c1e2b513e03756437 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 20 Nov 2018 10:56:12 +0200 Subject: [PATCH 10/21] docs: add docs for `deviceFound`, `deviceLost` and `deviceLogData` events in `PublicAPI.md` --- PublicAPI.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/PublicAPI.md b/PublicAPI.md index c3851816be..0c26026bfc 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -36,6 +36,7 @@ const tns = require("nativescript"); * [debug](#debug) * [liveSyncService](#livesyncservice) * [liveSync](#livesync) + * [liveSyncToPreviewApp](#livesynctopreviewapp) * [stopLiveSync](#stopLiveSync) * [enableDebugging](#enableDebugging) * [attachDebugger](#attachDebugger) @@ -58,6 +59,12 @@ const tns = require("nativescript"); * [startEmulator](#startemulator) * [deviceEmitter](#deviceemitter) * [events](#deviceemitterevents) +* [previewDevicesService](#previewdevicesservice) + * [deviceFound](#devicefound) + * [deviceLost](#devicelost) + * [deviceLog](#devicelog) +* [previewQrCodeService](#previewqrcodeservice) + * [getPlaygroundAppQrCode](#getplaygroundappqrCode) ## Module projectService @@ -1341,6 +1348,35 @@ tns.deviceEmitter.on("emulatorImageLost", (emulatorImageInfo) => { ``` `emulatorImageInfo` is of type [Moble.IDeviceInfo](https://github.com/telerik/mobile-cli-lib/blob/61cdaaaf7533394afbbe84dd4eee355072ade2de/definitions/mobile.d.ts#L9-L86). +## previewDevicesService +The `previewDevicesService` module allows interaction with preview devices. You can get a list of the connected preview devices and logs from specified device. + +### previewDevicesEmitterEvents + +* `deviceFound` - Raised when the QR code is scanned with any device. The callback function will receive one argument - `device`. +Sample usage: +```JavaScript +tns.previewDevicesService.on("deviceFound", device => { + console.log("Attached device with identifier: " + device.id); +}); +``` + +* `deviceLost` - Raised when the Preview app is stopped on a specified device. The callback function will receive one argument - `device`. +Sample usage: +```JavaScript +tns.previewDevicesService.on("deviceLost", device => { + console.log("Detached device with identifier: " + device.id); +}); +``` + +* `deviceLog` - Raised when the app deployed in Preview app reports any information. The event is raised for any device that reports data. The callback function has two arguments - `device` and `message`.

+Sample usage: +```JavaScript +tns.previewDevicesService.on("deviceLogData", (device, message) => { + console.log("Device " + device.id + " reports: " + message); +}); +``` + ## How to add a new method to Public API 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. 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`. From 5b09642099d65dbbc6bb8d661ef2dad26fde04f0 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 21 Nov 2018 09:43:55 +0200 Subject: [PATCH 11/21] docs: add docs for `liveSyncToPreviewApp` method in `PublicAPI.md` --- PublicAPI.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/PublicAPI.md b/PublicAPI.md index 0c26026bfc..27f00c1cb9 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -775,6 +775,38 @@ tns.liveSyncService.liveSync([ androidDeviceDescriptor, iOSDeviceDescriptor ], l }); ``` + + + + + +### liveSyncToPreviewApp +Starts a LiveSync operation to the Preview app. After scanning the QR code with the scanner provided in the NativeScript Playground app, the app will be launched on a device through the Preview app. Additionally, any changes made to the project will be automatically synchronized with the deployed app. + +* Definition +```TypeScript +/** + * Starts LiveSync operation by producting a QR code and starting watcher. + * @param {IPreviewAppLiveSyncData} liveSyncData Describes the LiveSync operation - for which project directory is the operation and other settings. + * @returns {Promise} + */ +liveSyncToPreviewApp(liveSyncData: IPreviewAppLiveSyncData): Promise; +``` + +* Usage: +```JavaScript +const liveSyncData = { + projectDir, + bundle: false, + useHotModuleReload: false, + env: { } +}; +tns.liveSyncService.liveSyncToPreviewApp(liveSyncData) + .then(qrCodeImageData => { + console.log("The qrCodeImageData is: " + qrCodeImageData); + }); +``` + ### stopLiveSync Stops LiveSync operation. In case deviceIdentifires are passed, the operation will be stopped only for these devices. From 2eba8302da9526a80cf0be8846226473cfe3ff65 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 21 Nov 2018 10:21:04 +0200 Subject: [PATCH 12/21] docs: add docs for previewQrCodeService in `PublicApi.md` --- PublicAPI.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/PublicAPI.md b/PublicAPI.md index 27f00c1cb9..855f543593 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -64,7 +64,7 @@ const tns = require("nativescript"); * [deviceLost](#devicelost) * [deviceLog](#devicelog) * [previewQrCodeService](#previewqrcodeservice) - * [getPlaygroundAppQrCode](#getplaygroundappqrCode) + * [getPlaygroundAppQrCode](#getplaygroundappqrcode) ## Module projectService @@ -1409,6 +1409,21 @@ tns.previewDevicesService.on("deviceLogData", (device, message) => { }); ``` +## previewQrCodeService +The `previewQrCodeService` exposes methods for getting information about the QR of the Playground app and deployed app in Preview app. + +### getPlaygroundAppQrCode +Returns information used to generate the QR code of the Playground app. + +* Usage: +```TypeScript +tns.previewQrCodeService.getPlaygroundAppQrCode() + .then(result => { + console.log("QR code data for iOS platform: " + result.ios); + console.log("QR code data for Android platform: " + result.android); + }); +``` + ## How to add a new method to Public API 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. 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`. From 384df58c08e442c7657617ac5942b5979eba0787 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 22 Nov 2018 09:43:02 +0200 Subject: [PATCH 13/21] chore: remove unneeded empty lines --- PublicAPI.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 855f543593..e962a3c00a 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -775,11 +775,6 @@ tns.liveSyncService.liveSync([ androidDeviceDescriptor, iOSDeviceDescriptor ], l }); ``` - - - - - ### liveSyncToPreviewApp Starts a LiveSync operation to the Preview app. After scanning the QR code with the scanner provided in the NativeScript Playground app, the app will be launched on a device through the Preview app. Additionally, any changes made to the project will be automatically synchronized with the deployed app. From 91f9b8f27494b2eb21ebce7c46582fa685762e8b Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 27 Nov 2018 14:33:53 +0200 Subject: [PATCH 14/21] fix: execute correctly local build after cloud build from sidekick Reset prepare info when native platform status is not alreadyPrepared in order to ensure the project will be prepared and all properties of this._prepareInfo will be correctly populated. --- lib/services/project-changes-service.ts | 2 +- test/project-changes-service.ts | 46 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index ac3be05546..5ee4b39c08 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -171,7 +171,7 @@ export class ProjectChangesService implements IProjectChangesService { public setNativePlatformStatus(platform: string, projectData: IProjectData, addedPlatform: IAddedNativePlatform): void { this._prepareInfo = this._prepareInfo || this.getPrepareInfo(platform, projectData); - if (this._prepareInfo) { + if (this._prepareInfo && addedPlatform.nativePlatformStatus === NativePlatformStatus.alreadyPrepared) { this._prepareInfo.nativePlatformStatus = addedPlatform.nativePlatformStatus; } else { this._prepareInfo = { diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index 47eae49639..b42b5d318e 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -187,5 +187,51 @@ describe("Project Changes Service Tests", () => { assert.deepEqual(actualPrepareInfo, { nativePlatformStatus: Constants.NativePlatformStatus.requiresPrepare }); } }); + + it(`shouldn't reset prepare info when native platform status is ${Constants.NativePlatformStatus.alreadyPrepared} and there is existing prepare info`, async () => { + for (const platform of ["ios", "android"]) { + await serviceTest.projectChangesService.checkForChanges({ + platform, + projectData: serviceTest.projectData, + projectChangesOptions: { + bundle: false, + release: false, + provision: undefined, + teamId: undefined, + useHotModuleReload: false + } + }); + serviceTest.projectChangesService.savePrepareInfo(platform, serviceTest.projectData); + const prepareInfo = serviceTest.projectChangesService.getPrepareInfo(platform, serviceTest.projectData); + + serviceTest.projectChangesService.setNativePlatformStatus(platform, serviceTest.projectData, { nativePlatformStatus: Constants.NativePlatformStatus.alreadyPrepared }); + + const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(platform, serviceTest.projectData); + prepareInfo.nativePlatformStatus = Constants.NativePlatformStatus.alreadyPrepared; + assert.deepEqual(actualPrepareInfo, prepareInfo); + } + }); + + _.each([Constants.NativePlatformStatus.requiresPlatformAdd, Constants.NativePlatformStatus.requiresPrepare], nativePlatformStatus => { + it(`should reset prepare info when native platform status is ${nativePlatformStatus} and there is existing prepare info`, async () => { + for (const platform of ["ios", "android"]) { + await serviceTest.projectChangesService.checkForChanges({ + platform, + projectData: serviceTest.projectData, + projectChangesOptions: { + bundle: false, + release: false, + provision: undefined, + teamId: undefined, + useHotModuleReload: false + } + }); + serviceTest.projectChangesService.setNativePlatformStatus(platform, serviceTest.projectData, { nativePlatformStatus: nativePlatformStatus }); + + const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(platform, serviceTest.projectData); + assert.deepEqual(actualPrepareInfo, { nativePlatformStatus: nativePlatformStatus }); + } + }); + }); }); }); From c3e295031c64e0f3b15ad17afc6b134ce0034aa6 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 29 Nov 2018 14:19:02 +0200 Subject: [PATCH 15/21] chore: changelog for 5.0.2 release --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16cafb03d2..2f8b728359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ NativeScript CLI Changelog ================ +5.0.2 (2018, November 29) +== +### Implemented +* [Implemented #4167](https://github.com/NativeScript/nativescript-cli/pull/4167): API: Expose previewAppLiveSyncError event when some error is thrown while livesyncing to preview app + +### Fixed +* [Fixed #3962](https://github.com/NativeScript/nativescript-cli/issues/3962): If command 'tns plugin create .. ' failed , directory with plugin repository name must be deleted +* [Fixed #4053](https://github.com/NativeScript/nativescript-cli/issues/4053): Update Nativescript cli setup scripts to use android sdk 28 +* [Fixed #4077](https://github.com/NativeScript/nativescript-cli/issues/4077): Platform add with framework path and custom version breaks run with "--bundle" +* [Fixed #4129](https://github.com/NativeScript/nativescript-cli/issues/4129): tns preview doesn't sync changes when download 2 Playground projects +* [Fixed #4135](https://github.com/NativeScript/nativescript-cli/issues/4135): Too many TypeScript "Watching for file changes" messages in console during build +* [Fixed #4158](https://github.com/NativeScript/nativescript-cli/pull/4158): API: reset devices list when stopLiveSync method is called +* [Fixed #4161](https://github.com/NativeScript/nativescript-cli/pull/4161): API: raise deviceLost event after timeout of 5 seconds + + 5.0.1 (2018, November 14) == ### Implemented From 6e280c4ac31278894e6141474045c2c2a76c74d5 Mon Sep 17 00:00:00 2001 From: TsvetanMilanov Date: Thu, 29 Nov 2018 16:48:38 +0200 Subject: [PATCH 16/21] Retry sporadically stuck requests/responses Sometimes we have sporadically stuck http requests/responses and in those cases the CLI is just waiting and not doing anything. If we retry the requests, they will pass. That's why we need to add a retry logic in the http client. --- lib/common/http-client.ts | 79 ++++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/lib/common/http-client.ts b/lib/common/http-client.ts index fa191f1922..25f8e524e2 100644 --- a/lib/common/http-client.ts +++ b/lib/common/http-client.ts @@ -9,13 +9,36 @@ import * as request from "request"; export class HttpClient implements Server.IHttpClient { private defaultUserAgent: string; private static STATUS_CODE_REGEX = /statuscode=(\d+)/i; + private static STUCK_REQUEST_ERROR_MESSAGE = "The request can't receive any response."; + private static STUCK_RESPONSE_ERROR_MESSAGE = "Can't receive all parts of the response."; + private static STUCK_REQUEST_TIMEOUT = 60000; + // We receive multiple response packets every ms but we don't need to be very aggressive here. + private static STUCK_RESPONSE_CHECK_INTERVAL = 10000; constructor(private $config: Config.IConfig, private $logger: ILogger, private $proxyService: IProxyService, private $staticConfig: Config.IStaticConfig) { } - async httpRequest(options: any, proxySettings?: IProxySettings): Promise { + public async httpRequest(options: any, proxySettings?: IProxySettings): Promise { + try { + const result = await this.httpRequestCore(options, proxySettings); + return result; + } catch (err) { + if (err.message === HttpClient.STUCK_REQUEST_ERROR_MESSAGE || err.message === HttpClient.STUCK_RESPONSE_ERROR_MESSAGE) { + // Retry the request immediately because there are at least 10 seconds between the two requests. + // We have to retry only once the sporadically stuck requests/responses. + // We can add exponential backoff retry here if we decide that we need to workaround bigger network issues on the client side. + this.$logger.warn("%s Retrying request to %s...", err.message, options.url || options); + const retryResult = await this.httpRequestCore(options, proxySettings); + return retryResult; + } + + throw err; + } + } + + private async httpRequestCore(options: any, proxySettings?: IProxySettings): Promise { if (_.isString(options)) { options = { url: options, @@ -73,6 +96,10 @@ export class HttpClient implements Server.IHttpClient { const result = new Promise((resolve, reject) => { let timerId: number; + let stuckRequestTimerId: number; + let stuckResponseIntervalId: NodeJS.Timer; + let hasResponse = false; + const timers: number[] = []; const promiseActions: IPromiseActions = { resolve, @@ -82,8 +109,9 @@ export class HttpClient implements Server.IHttpClient { if (options.timeout) { timerId = setTimeout(() => { - this.setResponseResult(promiseActions, timerId, { err: new Error(`Request to ${unmodifiedOptions.url} timed out.`) }, ); + this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err: new Error(`Request to ${unmodifiedOptions.url} timed out.`) }); }, options.timeout); + timers.push(timerId); delete options.timeout; } @@ -95,6 +123,16 @@ export class HttpClient implements Server.IHttpClient { this.$logger.trace("httpRequest: %s", util.inspect(options)); const requestObj = request(options); + stuckRequestTimerId = setTimeout(() => { + clearTimeout(stuckRequestTimerId); + stuckRequestTimerId = null; + if (!hasResponse) { + requestObj.abort(); + this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err: new Error(HttpClient.STUCK_REQUEST_ERROR_MESSAGE) }); + } + }, options.timeout || HttpClient.STUCK_REQUEST_TIMEOUT); + timers.push(stuckRequestTimerId); + requestObj .on("error", (err: IHttpRequestError) => { this.$logger.trace("An error occurred while sending the request:", err); @@ -107,15 +145,29 @@ export class HttpClient implements Server.IHttpClient { const errorMessage = this.getErrorMessage(errorMessageStatusCode, null); err.proxyAuthenticationRequired = errorMessageStatusCode === HttpStatusCodes.PROXY_AUTHENTICATION_REQUIRED; err.message = errorMessage || err.message; - this.setResponseResult(promiseActions, timerId, { err }); + this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err }); }) .on("response", (response: Server.IRequestResponseData) => { + hasResponse = true; + let lastChunkTimestamp = Date.now(); + stuckResponseIntervalId = setInterval(() => { + if (Date.now() - lastChunkTimestamp > HttpClient.STUCK_RESPONSE_CHECK_INTERVAL) { + if ((response).destroy) { + (response).destroy(); + } + + this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err: new Error(HttpClient.STUCK_RESPONSE_ERROR_MESSAGE) }); + } + }, HttpClient.STUCK_RESPONSE_CHECK_INTERVAL); const successful = helpers.isRequestSuccessful(response); if (!successful) { pipeTo = undefined; } let responseStream = response; + responseStream.on("data", (chunk: string) => { + lastChunkTimestamp = Date.now(); + }); switch (response.headers["content-encoding"]) { case "gzip": responseStream = responseStream.pipe(zlib.createGunzip()); @@ -128,7 +180,7 @@ export class HttpClient implements Server.IHttpClient { if (pipeTo) { pipeTo.on("finish", () => { this.$logger.trace("httpRequest: Piping done. code = %d", response.statusCode.toString()); - this.setResponseResult(promiseActions, timerId, { response }); + this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { response }); }); responseStream.pipe(pipeTo); @@ -144,13 +196,13 @@ export class HttpClient implements Server.IHttpClient { const responseBody = data.join(""); if (successful) { - this.setResponseResult(promiseActions, timerId, { body: responseBody, response }); + this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { body: responseBody, response }); } else { const errorMessage = this.getErrorMessage(response.statusCode, responseBody); const err: any = new Error(errorMessage); err.response = response; err.body = responseBody; - this.setResponseResult(promiseActions, timerId, { err }); + this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err }); } }); } @@ -181,10 +233,17 @@ export class HttpClient implements Server.IHttpClient { return response; } - private setResponseResult(result: IPromiseActions, timerId: number, resultData: { response?: Server.IRequestResponseData, body?: string, err?: Error }): void { - if (timerId) { - clearTimeout(timerId); - timerId = null; + private setResponseResult(result: IPromiseActions, timers: number[], stuckResponseIntervalId: NodeJS.Timer, resultData: { response?: Server.IRequestResponseData, body?: string, err?: Error }): void { + timers.forEach(t => { + if (t) { + clearTimeout(t); + t = null; + } + }); + + if (stuckResponseIntervalId) { + clearInterval(stuckResponseIntervalId); + stuckResponseIntervalId = null; } if (!result.isResolved()) { From d1162f5b164beca31f75b3b43f3ecd49fa6a39ae Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 30 Nov 2018 12:32:36 +0200 Subject: [PATCH 17/21] fix: update nativescript-preview-sdk version to 0.3.1 and cli's version to 5.0.3 --- npm-shrinkwrap.json | 140 ++++++++++++++++++++++---------------------- package.json | 4 +- 2 files changed, 72 insertions(+), 72 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 1e8264df55..eed4abb517 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "5.0.2", + "version": "5.0.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -85,7 +85,7 @@ }, "@types/events": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", "dev": true }, @@ -128,7 +128,7 @@ }, "@types/ora": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@types/ora/-/ora-1.3.3.tgz", + "resolved": "http://registry.npmjs.org/@types/ora/-/ora-1.3.3.tgz", "integrity": "sha512-XaSVRyCfnGq1xGlb6iuoxnomMXPIlZnvIIkKiGNMTCeVOg7G1Si+FA9N1lPrykPEfiRHwbuZXuTCSoYcHyjcdg==", "dev": true, "requires": { @@ -161,7 +161,7 @@ }, "@types/rx": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/rx/-/rx-4.1.1.tgz", + "resolved": "http://registry.npmjs.org/@types/rx/-/rx-4.1.1.tgz", "integrity": "sha1-WY/JSla67ZdfGUV04PVy/Y5iekg=", "dev": true, "requires": { @@ -423,7 +423,7 @@ }, "ansi-escapes": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" }, "ansi-regex": { @@ -542,9 +542,9 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "ast-types": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.5.tgz", - "integrity": "sha512-oJjo+5e7/vEc2FBK8gUalV0pba4L3VdBIs2EKhOLHLcOd2FgQIVQN9xb0eZ9IjEWyAL7vq6fGJxOvVvdCHNyMw==" + "version": "0.11.7", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.7.tgz", + "integrity": "sha512-2mP3TwtkY/aTv5X3ZsMpNAbOnyoC/aMJwJSoaELPkHId0nSQgFcnU4dRW3isxiz7+zBexk0ym3WNVjMiQBnJSw==" }, "async": { "version": "1.2.1", @@ -690,7 +690,7 @@ }, "bignumber.js": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", + "resolved": "http://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", "integrity": "sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg=" }, "binary": { @@ -724,7 +724,7 @@ }, "body-parser": { "version": "1.14.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", + "resolved": "http://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", "integrity": "sha1-EBXLH+LEQ4WCWVgdtTMy+NDPUPk=", "dev": true, "requires": { @@ -773,7 +773,7 @@ }, "ms": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz", "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", "dev": true }, @@ -977,7 +977,7 @@ }, "callsites": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "resolved": "http://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", "dev": true }, @@ -988,7 +988,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { @@ -1149,7 +1149,7 @@ "dependencies": { "colors": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz", "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" }, "lodash": { @@ -1257,7 +1257,7 @@ }, "colors": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz", "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" }, "combined-stream": { @@ -1392,7 +1392,7 @@ }, "d": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { @@ -1414,7 +1414,7 @@ }, "date-format": { "version": "0.0.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-0.0.0.tgz", + "resolved": "http://registry.npmjs.org/date-format/-/date-format-0.0.0.tgz", "integrity": "sha1-CSBoY6sHDrRZrOpVQsvYVrEZZrM=" }, "dateformat": { @@ -1715,7 +1715,7 @@ }, "es6-promisify": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "requires": { "es6-promise": "^4.0.3" @@ -1850,7 +1850,7 @@ "dependencies": { "ansi-escapes": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", "dev": true }, @@ -1962,7 +1962,7 @@ }, "espree": { "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz", "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", "dev": true, "requires": { @@ -2229,7 +2229,7 @@ }, "fast-deep-equal": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, "fast-json-stable-stringify": { @@ -2282,7 +2282,7 @@ }, "file-type": { "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=" }, "file-uri-to-path": { @@ -2372,9 +2372,9 @@ } }, "follow-redirects": { - "version": "1.5.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.8.tgz", - "integrity": "sha512-sy1mXPmv7kLAMKW/8XofG7o9T+6gAjzdZK4AJF6ryqQYUa/hnzgiypoeUecZ53x7XiqKNEpNqLtS97MshW2nxg==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", "requires": { "debug": "=3.1.0" }, @@ -3001,7 +3001,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } @@ -3265,7 +3265,7 @@ }, "grunt-cli": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", "dev": true, "requires": { @@ -3740,7 +3740,7 @@ }, "hoek": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "resolved": "http://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" }, "hooker": { @@ -4079,7 +4079,7 @@ "dependencies": { "colors": { "version": "0.6.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "resolved": "http://registry.npmjs.org/colors/-/colors-0.6.2.tgz", "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" }, "is-fullwidth-code-point": { @@ -4102,7 +4102,7 @@ }, "shelljs": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.0.tgz", + "resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.7.0.tgz", "integrity": "sha1-P28uSWXOxWX2X/OGHWRPh5KBpXY=", "requires": { "glob": "^7.0.0", @@ -4512,7 +4512,7 @@ }, "source-map": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", "dev": true, "optional": true, @@ -5050,13 +5050,13 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, "meow": { "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { @@ -5276,7 +5276,7 @@ }, "ms": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz", "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", "dev": true }, @@ -5331,9 +5331,9 @@ "optional": true }, "nanoid": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.2.6.tgz", - "integrity": "sha512-um9vXiM407BaRbBNa0aKPzFBSD2fDbVmmA9TzCWWlxZvEBzTbixM7ss6GDS4G/cNMYeZSNFx5SzAgWoG1uHU9g==" + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.3.4.tgz", + "integrity": "sha512-4ug4BsuHxiVHoRUe1ud6rUFT3WUMmjXt1W0quL0CviZQANdan7D8kqN5/maw53hmAApY/jfzMRkC57BNNs60ZQ==" }, "nanomatch": { "version": "1.2.13", @@ -5383,9 +5383,9 @@ } }, "nativescript-preview-sdk": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/nativescript-preview-sdk/-/nativescript-preview-sdk-0.3.0.tgz", - "integrity": "sha512-HZFT/eT/4HQcZ7Y20zrwpnEXf3OvOSL8o75A+faKBAox34Y/rfGsZQd/flDfJWrNd79OwQXuKSCV8vn9hJD9Ng==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/nativescript-preview-sdk/-/nativescript-preview-sdk-0.3.1.tgz", + "integrity": "sha512-8xKl150/LJk2YvmbDgCKJHOd1pFwYf/2s5PtXXF/D0fw6DpwGgFNBe26IaTHNiZJ2U0w9n4o2L9YSrHRk7kjqQ==", "requires": { "@types/axios": "0.14.0", "@types/pubnub": "4.0.2", @@ -5416,7 +5416,7 @@ }, "next-tick": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, @@ -5724,7 +5724,7 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { @@ -5737,7 +5737,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { @@ -5770,9 +5770,9 @@ }, "dependencies": { "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { "ms": "^2.1.1" } @@ -5961,7 +5961,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { @@ -6036,7 +6036,7 @@ }, "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" }, "pinkie": { @@ -6094,7 +6094,7 @@ }, "xmlbuilder": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.2.1.tgz", + "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.2.1.tgz", "integrity": "sha1-kyZDDxMNh0NdTECGZDqikm4QWjI=", "requires": { "lodash-node": "~2.4.1" @@ -6167,7 +6167,7 @@ }, "progress": { "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", "dev": true }, @@ -6209,9 +6209,9 @@ }, "dependencies": { "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { "ms": "^2.1.1" } @@ -6580,7 +6580,7 @@ }, "require-uncached": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { @@ -6668,7 +6668,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { "ret": "~0.1.10" @@ -6733,7 +6733,7 @@ }, "shelljs": { "version": "0.7.6", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", + "resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", "integrity": "sha1-N5zM+1a5HIYB5HkzVutTgpJN6a0=", "requires": { "glob": "^7.0.0", @@ -6872,7 +6872,7 @@ }, "slice-ansi": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "dev": true }, @@ -7037,7 +7037,7 @@ }, "source-map": { "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" }, "source-map-resolve": { @@ -7320,9 +7320,9 @@ }, "dependencies": { "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { "ms": "^2.1.1" } @@ -7344,9 +7344,9 @@ }, "dependencies": { "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { "ms": "^2.1.1" } @@ -7502,7 +7502,7 @@ }, "ms": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz", "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", "dev": true }, @@ -7710,7 +7710,7 @@ }, "underscore.string": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz", + "resolved": "http://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz", "integrity": "sha1-gGmSYzZl1eX8tNsfs6hi62jp5to=", "dev": true }, @@ -8016,7 +8016,7 @@ }, "ws": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.0.tgz", + "resolved": "http://registry.npmjs.org/ws/-/ws-5.1.0.tgz", "integrity": "sha512-7KU/qkUXtJW9aa5WRKlo0puE1ejEoAgDb0D/Pt+lWpTkKF7Kp+MqFOtwNFwnuiYeeDpFjp0qyMniE84OjKIEqQ==", "requires": { "async-limiter": "~1.0.0" @@ -8057,14 +8057,14 @@ "dependencies": { "xmlbuilder": { "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" } } }, "xmlbuilder": { "version": "8.2.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=" }, "xmldom": { @@ -8138,7 +8138,7 @@ }, "yargs-parser": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "resolved": "http://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", "requires": { "camelcase": "^3.0.0" diff --git a/package.json b/package.json index c9fe354a3d..9a2511334e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "5.0.2", + "version": "5.0.3", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { @@ -56,7 +56,7 @@ "mkdirp": "0.5.1", "mute-stream": "0.0.5", "nativescript-doctor": "1.6.0", - "nativescript-preview-sdk": "0.3.0", + "nativescript-preview-sdk": "0.3.1", "open": "0.0.5", "ora": "2.0.0", "osenv": "0.1.3", From 1242a7b64bd36ca70b0a1b447f082bf1ee31d9eb Mon Sep 17 00:00:00 2001 From: TsvetanMilanov Date: Fri, 30 Nov 2018 13:17:25 +0200 Subject: [PATCH 18/21] Cleanup the timers and intervals in the http-client when the process is killed --- lib/common/http-client.ts | 71 ++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/lib/common/http-client.ts b/lib/common/http-client.ts index 25f8e524e2..a9e660c00f 100644 --- a/lib/common/http-client.ts +++ b/lib/common/http-client.ts @@ -7,7 +7,6 @@ import { HttpStatusCodes } from "./constants"; import * as request from "request"; export class HttpClient implements Server.IHttpClient { - private defaultUserAgent: string; private static STATUS_CODE_REGEX = /statuscode=(\d+)/i; private static STUCK_REQUEST_ERROR_MESSAGE = "The request can't receive any response."; private static STUCK_RESPONSE_ERROR_MESSAGE = "Can't receive all parts of the response."; @@ -15,10 +14,19 @@ export class HttpClient implements Server.IHttpClient { // We receive multiple response packets every ms but we don't need to be very aggressive here. private static STUCK_RESPONSE_CHECK_INTERVAL = 10000; + private defaultUserAgent: string; + private cleanupData: ICleanupRequestData[]; + constructor(private $config: Config.IConfig, private $logger: ILogger, + private $processService: IProcessService, private $proxyService: IProxyService, - private $staticConfig: Config.IStaticConfig) { } + private $staticConfig: Config.IStaticConfig) { + this.cleanupData = []; + this.$processService.attachToProcessExitSignals(this, () => { + this.cleanupData.forEach(d => this.cleanupAfterRequest(d)); + }); + } public async httpRequest(options: any, proxySettings?: IProxySettings): Promise { try { @@ -97,9 +105,9 @@ export class HttpClient implements Server.IHttpClient { const result = new Promise((resolve, reject) => { let timerId: number; let stuckRequestTimerId: number; - let stuckResponseIntervalId: NodeJS.Timer; let hasResponse = false; - const timers: number[] = []; + const cleanupRequestData: ICleanupRequestData = { timers: [], stuckResponseIntervalId: null }; + this.cleanupData.push(cleanupRequestData); const promiseActions: IPromiseActions = { resolve, @@ -109,9 +117,9 @@ export class HttpClient implements Server.IHttpClient { if (options.timeout) { timerId = setTimeout(() => { - this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err: new Error(`Request to ${unmodifiedOptions.url} timed out.`) }); + this.setResponseResult(promiseActions, cleanupRequestData, { err: new Error(`Request to ${unmodifiedOptions.url} timed out.`) }); }, options.timeout); - timers.push(timerId); + cleanupRequestData.timers.push(timerId); delete options.timeout; } @@ -128,10 +136,10 @@ export class HttpClient implements Server.IHttpClient { stuckRequestTimerId = null; if (!hasResponse) { requestObj.abort(); - this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err: new Error(HttpClient.STUCK_REQUEST_ERROR_MESSAGE) }); + this.setResponseResult(promiseActions, cleanupRequestData, { err: new Error(HttpClient.STUCK_REQUEST_ERROR_MESSAGE) }); } }, options.timeout || HttpClient.STUCK_REQUEST_TIMEOUT); - timers.push(stuckRequestTimerId); + cleanupRequestData.timers.push(stuckRequestTimerId); requestObj .on("error", (err: IHttpRequestError) => { @@ -145,18 +153,18 @@ export class HttpClient implements Server.IHttpClient { const errorMessage = this.getErrorMessage(errorMessageStatusCode, null); err.proxyAuthenticationRequired = errorMessageStatusCode === HttpStatusCodes.PROXY_AUTHENTICATION_REQUIRED; err.message = errorMessage || err.message; - this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err }); + this.setResponseResult(promiseActions, cleanupRequestData, { err }); }) .on("response", (response: Server.IRequestResponseData) => { hasResponse = true; let lastChunkTimestamp = Date.now(); - stuckResponseIntervalId = setInterval(() => { + cleanupRequestData.stuckResponseIntervalId = setInterval(() => { if (Date.now() - lastChunkTimestamp > HttpClient.STUCK_RESPONSE_CHECK_INTERVAL) { if ((response).destroy) { (response).destroy(); } - this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err: new Error(HttpClient.STUCK_RESPONSE_ERROR_MESSAGE) }); + this.setResponseResult(promiseActions, cleanupRequestData, { err: new Error(HttpClient.STUCK_RESPONSE_ERROR_MESSAGE) }); } }, HttpClient.STUCK_RESPONSE_CHECK_INTERVAL); const successful = helpers.isRequestSuccessful(response); @@ -180,7 +188,7 @@ export class HttpClient implements Server.IHttpClient { if (pipeTo) { pipeTo.on("finish", () => { this.$logger.trace("httpRequest: Piping done. code = %d", response.statusCode.toString()); - this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { response }); + this.setResponseResult(promiseActions, cleanupRequestData, { response }); }); responseStream.pipe(pipeTo); @@ -196,13 +204,13 @@ export class HttpClient implements Server.IHttpClient { const responseBody = data.join(""); if (successful) { - this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { body: responseBody, response }); + this.setResponseResult(promiseActions, cleanupRequestData, { body: responseBody, response }); } else { const errorMessage = this.getErrorMessage(response.statusCode, responseBody); const err: any = new Error(errorMessage); err.response = response; err.body = responseBody; - this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err }); + this.setResponseResult(promiseActions, cleanupRequestData, { err }); } }); } @@ -233,19 +241,8 @@ export class HttpClient implements Server.IHttpClient { return response; } - private setResponseResult(result: IPromiseActions, timers: number[], stuckResponseIntervalId: NodeJS.Timer, resultData: { response?: Server.IRequestResponseData, body?: string, err?: Error }): void { - timers.forEach(t => { - if (t) { - clearTimeout(t); - t = null; - } - }); - - if (stuckResponseIntervalId) { - clearInterval(stuckResponseIntervalId); - stuckResponseIntervalId = null; - } - + private setResponseResult(result: IPromiseActions, cleanupRequestData: ICleanupRequestData, resultData: { response?: Server.IRequestResponseData, body?: string, err?: Error }): void { + this.cleanupAfterRequest(cleanupRequestData); if (!result.isResolved()) { result.isResolved = () => true; if (resultData.err) { @@ -317,5 +314,25 @@ export class HttpClient implements Server.IHttpClient { this.$logger.trace("Using proxy: %s", options.proxy); } } + + private cleanupAfterRequest(data: ICleanupRequestData): void { + data.timers.forEach(t => { + if (t) { + clearTimeout(t); + t = null; + } + }); + + if (data.stuckResponseIntervalId) { + clearInterval(data.stuckResponseIntervalId); + data.stuckResponseIntervalId = null; + } + } } + +interface ICleanupRequestData { + timers: number[]; + stuckResponseIntervalId: NodeJS.Timer; +} + $injector.register("httpClient", HttpClient); From 96098149225fa008cc76ed64cc9b7f84cddf43ff Mon Sep 17 00:00:00 2001 From: TsvetanMilanov Date: Fri, 30 Nov 2018 16:06:53 +0200 Subject: [PATCH 19/21] Cleanup the req/res streams --- lib/common/declarations.d.ts | 4 +++- lib/common/http-client.ts | 28 +++++++++++++++++++--------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/common/declarations.d.ts b/lib/common/declarations.d.ts index 06bd4e3061..2ac51950f1 100644 --- a/lib/common/declarations.d.ts +++ b/lib/common/declarations.d.ts @@ -163,8 +163,10 @@ declare module Server { interface IRequestResponseData { statusCode: number; headers: { [index: string]: any }; + complete: boolean; pipe(destination: any, options?: { end?: boolean; }): IRequestResponseData; on(event: string, listener: Function): void; + destroy(error?: Error): void; } } @@ -758,7 +760,7 @@ interface IAnalyticsSettingsService { * Gets information for projects that are exported from playground * @param projectDir Project directory path */ - getPlaygroundInfo(projectDir?: string): Promise; + getPlaygroundInfo(projectDir?: string): Promise; } /** diff --git a/lib/common/http-client.ts b/lib/common/http-client.ts index a9e660c00f..cb30d342b2 100644 --- a/lib/common/http-client.ts +++ b/lib/common/http-client.ts @@ -24,7 +24,9 @@ export class HttpClient implements Server.IHttpClient { private $staticConfig: Config.IStaticConfig) { this.cleanupData = []; this.$processService.attachToProcessExitSignals(this, () => { - this.cleanupData.forEach(d => this.cleanupAfterRequest(d)); + this.cleanupData.forEach(d => { + this.cleanupAfterRequest(d); + }); }); } @@ -106,7 +108,7 @@ export class HttpClient implements Server.IHttpClient { let timerId: number; let stuckRequestTimerId: number; let hasResponse = false; - const cleanupRequestData: ICleanupRequestData = { timers: [], stuckResponseIntervalId: null }; + const cleanupRequestData: ICleanupRequestData = Object.create({ timers: [] }); this.cleanupData.push(cleanupRequestData); const promiseActions: IPromiseActions = { @@ -130,12 +132,12 @@ export class HttpClient implements Server.IHttpClient { this.$logger.trace("httpRequest: %s", util.inspect(options)); const requestObj = request(options); + cleanupRequestData.req = requestObj; stuckRequestTimerId = setTimeout(() => { clearTimeout(stuckRequestTimerId); stuckRequestTimerId = null; if (!hasResponse) { - requestObj.abort(); this.setResponseResult(promiseActions, cleanupRequestData, { err: new Error(HttpClient.STUCK_REQUEST_ERROR_MESSAGE) }); } }, options.timeout || HttpClient.STUCK_REQUEST_TIMEOUT); @@ -156,14 +158,11 @@ export class HttpClient implements Server.IHttpClient { this.setResponseResult(promiseActions, cleanupRequestData, { err }); }) .on("response", (response: Server.IRequestResponseData) => { + cleanupRequestData.res = response; hasResponse = true; let lastChunkTimestamp = Date.now(); cleanupRequestData.stuckResponseIntervalId = setInterval(() => { if (Date.now() - lastChunkTimestamp > HttpClient.STUCK_RESPONSE_CHECK_INTERVAL) { - if ((response).destroy) { - (response).destroy(); - } - this.setResponseResult(promiseActions, cleanupRequestData, { err: new Error(HttpClient.STUCK_RESPONSE_ERROR_MESSAGE) }); } }, HttpClient.STUCK_RESPONSE_CHECK_INTERVAL); @@ -245,8 +244,8 @@ export class HttpClient implements Server.IHttpClient { this.cleanupAfterRequest(cleanupRequestData); if (!result.isResolved()) { result.isResolved = () => true; - if (resultData.err) { - return result.reject(resultData.err); + if (resultData.err || !resultData.response.complete) { + return result.reject(resultData.err || new Error("Request canceled")); } const finalResult: any = resultData; @@ -327,12 +326,23 @@ export class HttpClient implements Server.IHttpClient { clearInterval(data.stuckResponseIntervalId); data.stuckResponseIntervalId = null; } + + if (data.req) { + data.req.abort(); + } + + if (data.res) { + data.res.destroy(); + } } + } interface ICleanupRequestData { timers: number[]; stuckResponseIntervalId: NodeJS.Timer; + req: request.Request; + res: Server.IRequestResponseData; } $injector.register("httpClient", HttpClient); From 9da376bf0ef94138e6a31f38155faa6b1bdf69dc Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 4 Dec 2018 08:51:37 +0200 Subject: [PATCH 20/21] chore: add changelog for 5.0.3 release --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f8b728359..796e029bcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ NativeScript CLI Changelog ================ +5.0.3 (2018, December 4) +== +### Fixed +* [Fixed #4186](https://github.com/NativeScript/nativescript-cli/issues/4186): Fix stuck http requests/responses +* [Fixed #4189](https://github.com/NativeScript/nativescript-cli/pull/4189): API: Fix "Cannot read property 'removeListener' of undefined" error on second stop of livesync to preview app + 5.0.2 (2018, November 29) == From 687181467b17a74883493ec8be2ee549de215467 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 5 Dec 2018 14:25:35 +0200 Subject: [PATCH 21/21] chore: fix unit test --- test/plugin-create.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/plugin-create.ts b/test/plugin-create.ts index 191e27ccd7..60f037006d 100644 --- a/test/plugin-create.ts +++ b/test/plugin-create.ts @@ -182,8 +182,8 @@ describe("Plugin create command tests", () => { it("setupSeed, should remove projectDir", async () => { const errorMessage = "Test fail"; - const npmService = testInjector.resolve("npm"); - sandbox.stub(npmService, "install").callsFake(() => { + const packageManagerService = testInjector.resolve("packageManager"); + sandbox.stub(packageManagerService, "install").callsFake(() => { return Promise.reject(new Error(errorMessage)); });