diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index 82c43369ca..3fa23012fa 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -149,7 +149,7 @@ declare module Mobile { muted?: boolean; } - interface IDeviceAppData extends IPlatform { + interface IDeviceAppData extends IPlatform, IConnectTimeoutOption { appIdentifier: string; device: Mobile.IDevice; getDeviceProjectRootPath(): Promise; diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 6bf3a8a72e..d6619dadda 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -343,7 +343,14 @@ interface IShouldSkipEmitLiveSyncNotification { interface IAttachDebuggerOptions extends IDebuggingAdditionalOptions, IEnableDebuggingDeviceOptions, IIsEmulator, IPlatform, IOptionalOutputPath { } -interface ILiveSyncWatchInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption { +interface IConnectTimeoutOption { + /** + * Time to wait for successful connection. Defaults to 30000 miliseconds. + */ + connectTimeout?: number; +} + +interface ILiveSyncWatchInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption { filesToRemove: string[]; filesToSync: string[]; isReinstalled: boolean; @@ -362,7 +369,7 @@ interface ILiveSyncResultInfo extends IHasUseHotModuleReloadOption { interface IAndroidLiveSyncResultInfo extends ILiveSyncResultInfo, IAndroidLivesyncSyncOperationResult { } -interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption { +interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption { device: Mobile.IDevice; watch: boolean; syncAllFiles: boolean; @@ -510,7 +517,7 @@ interface IDoSyncOperationOptions { operationId?: string } -interface IAndroidLivesyncToolConfiguration { +interface IAndroidLivesyncToolConfiguration extends IConnectTimeoutOption { /** * The application identifier. */ @@ -531,10 +538,6 @@ interface IAndroidLivesyncToolConfiguration { * If provider will call it when an error occurs. */ errorHandler?: any; - /** - * Time to wait for successful connection. Defaults to 30000 miliseconds. - */ - connectTimeout?: number; } interface IAndroidLivesyncSyncOperationResult { diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index f68e911bbc..43cd7f7fd1 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -23,16 +23,23 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe private $fs: IFileSystem, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $filesHashService: IFilesHashService) { - super($injector, $platformsData, $filesHashService, $logger, device); - this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool); + super($injector, $platformsData, $filesHashService, $logger, device); + this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool); } public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise { - const pathToLiveSyncFile = temp.path({ prefix: "livesync" }); - this.$fs.writeFile(pathToLiveSyncFile, ""); - await this.device.fileSystem.putFile(pathToLiveSyncFile, this.getPathToLiveSyncFileOnDevice(deviceAppData.appIdentifier), deviceAppData.appIdentifier); - await this.device.applicationManager.startApplication({ appId: deviceAppData.appIdentifier, projectName: this.data.projectName, justLaunch: true }); - await this.connectLivesyncTool(this.data.projectIdentifiers.android); + if (!this.livesyncTool.hasConnection()) { + try { + const pathToLiveSyncFile = temp.path({ prefix: "livesync" }); + this.$fs.writeFile(pathToLiveSyncFile, ""); + await this.device.fileSystem.putFile(pathToLiveSyncFile, this.getPathToLiveSyncFileOnDevice(deviceAppData.appIdentifier), deviceAppData.appIdentifier); + await this.device.applicationManager.startApplication({ appId: deviceAppData.appIdentifier, projectName: this.data.projectName, justLaunch: true }); + await this.connectLivesyncTool(this.data.projectIdentifiers.android, deviceAppData.connectTimeout); + } catch (err) { + await this.device.fileSystem.deleteFile(this.getPathToLiveSyncFileOnDevice(deviceAppData.appIdentifier), deviceAppData.appIdentifier); + throw err; + } + } } private getPathToLiveSyncFileOnDevice(appIdentifier: string): string { @@ -59,7 +66,7 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe if (liveSyncInfo.modifiedFilesData.length) { const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo, liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform); - const doSyncPromise = this.livesyncTool.sendDoSyncOperation({ doRefresh: canExecuteFastSync, operationId}); + const doSyncPromise = this.livesyncTool.sendDoSyncOperation({ doRefresh: canExecuteFastSync, operationId }); const syncInterval: NodeJS.Timer = setInterval(() => { if (this.livesyncTool.isOperationInProgress(operationId)) { @@ -114,14 +121,15 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe await this.livesyncTool.sendDirectory(projectFilesPath); } - private async connectLivesyncTool(appIdentifier: string) { + private async connectLivesyncTool(appIdentifier: string, connectTimeout?: number) { const platformData = this.$platformsData.getPlatformData(this.$devicePlatformsConstants.Android, this.data); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); if (!this.livesyncTool.hasConnection()) { await this.livesyncTool.connect({ appIdentifier, deviceIdentifier: this.device.deviceInfo.identifier, - appPlatformsPath: projectFilesPath + appPlatformsPath: projectFilesPath, + connectTimeout }); } } diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 063de63e42..4ad472d760 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -594,7 +594,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi let filesToRemove: string[] = []; let timeoutTimer: NodeJS.Timer; - const startSyncFilesTimeout = (platform?: string) => { + const startSyncFilesTimeout = (platform?: string, opts?: { calledFromHook: boolean }) => { timeoutTimer = setTimeout(async () => { if (platform && liveSyncData.bundle) { filesToSync = filesToSyncMap[platform]; @@ -636,6 +636,52 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; const deviceBuildInfoDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + const settings: ILiveSyncWatchInfo = { + liveSyncDeviceInfo: deviceBuildInfoDescriptor, + projectData, + filesToRemove: currentFilesToRemove, + filesToSync: currentFilesToSync, + isReinstalled: false, + syncAllFiles: liveSyncData.watchAllFiles, + hmrData: currentHmrData, + useHotModuleReload: liveSyncData.useHotModuleReload, + force: liveSyncData.force, + connectTimeout: 1000 + }; + + const service = this.getLiveSyncService(device.deviceInfo.platform); + + const watchAction = async (watchInfo: ILiveSyncWatchInfo): Promise => { + let liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo); + + await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); + + // If didRecover is true, this means we were in ErrorActivity and fallback files were already transfered and app will be restarted. + if (!liveSyncResultInfo.didRecover && liveSyncData.useHotModuleReload && currentHmrData.hash) { + const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, currentHmrData.hash); + if (status === HmrConstants.HMR_ERROR_STATUS) { + watchInfo.filesToSync = currentHmrData.fallbackFiles[device.deviceInfo.platform]; + liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo); + // We want to force a restart of the application. + liveSyncResultInfo.isFullSync = true; + await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); + } + } + + this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); + }; + + if (liveSyncData.useHotModuleReload && opts && opts.calledFromHook) { + try { + this.$logger.trace("Try executing watch action without any preparation of files."); + await watchAction(settings); + this.$logger.trace("Successfully executed watch action without any preparation of files."); + return; + } catch (err) { + this.$logger.trace(`Error while trying to execute fast sync. Now we'll check the state of the app and we'll try to resurrect from the error. The error is: ${err}`); + } + } + const appInstalledOnDeviceResult = await this.ensureLatestAppPackageIsInstalledOnDevice({ device, preparedPlatforms, @@ -653,41 +699,15 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi skipModulesNativeCheck: !liveSyncData.watchAllFiles }, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare }); + settings.isReinstalled = appInstalledOnDeviceResult.appInstalled; + settings.connectTimeout = null; + if (liveSyncData.useHotModuleReload && appInstalledOnDeviceResult.appInstalled) { const additionalFilesToSync = currentHmrData && currentHmrData.fallbackFiles && currentHmrData.fallbackFiles[device.deviceInfo.platform]; _.each(additionalFilesToSync, fileToSync => currentFilesToSync.push(fileToSync)); } - const service = this.getLiveSyncService(device.deviceInfo.platform); - const settings: ILiveSyncWatchInfo = { - liveSyncDeviceInfo: deviceBuildInfoDescriptor, - projectData, - filesToRemove: currentFilesToRemove, - filesToSync: currentFilesToSync, - isReinstalled: appInstalledOnDeviceResult.appInstalled, - syncAllFiles: liveSyncData.watchAllFiles, - hmrData: currentHmrData, - useHotModuleReload: liveSyncData.useHotModuleReload, - force: liveSyncData.force - }; - - let liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); - - await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); - - //If didRecover is true, this means we were in ErrorActivity and fallback files were already transfered and app will be restarted. - if (!liveSyncResultInfo.didRecover && liveSyncData.useHotModuleReload && currentHmrData.hash) { - const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, currentHmrData.hash); - if (status === HmrConstants.HMR_ERROR_STATUS) { - settings.filesToSync = currentHmrData.fallbackFiles[device.deviceInfo.platform]; - liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); - //We want to force a restart of the application. - liveSyncResultInfo.isFullSync = true; - await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); - } - } - - this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); + await watchAction(settings); }, (device: Mobile.IDevice) => { const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; @@ -715,7 +735,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi }); } } - }, 250); + }, liveSyncData.useHotModuleReload ? 0 : 250); this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; }; @@ -738,11 +758,12 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi hmrData, filesToRemove, startSyncFilesTimeout: async (platform: string) => { + const opts = { calledFromHook: true }; if (platform) { - await startSyncFilesTimeout(platform); + await startSyncFilesTimeout(platform, opts); } else { // This code is added for backwards compatibility with old versions of nativescript-dev-webpack plugin. - await startSyncFilesTimeout(); + await startSyncFilesTimeout(null, opts); } } } @@ -823,7 +844,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi } } - public emitLivesyncEvent (event: string, livesyncData: ILiveSyncEventData): boolean { + public emitLivesyncEvent(event: string, livesyncData: ILiveSyncEventData): boolean { this.$logger.trace(`Will emit event ${event} with data`, livesyncData); return this.emit(event, livesyncData); } diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index 95c5c3d08a..fc08a776a5 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -135,7 +135,8 @@ export abstract class PlatformLiveSyncServiceBase { platform: syncInfo.device.deviceInfo.platform, getDeviceProjectRootPath: () => this.$devicePathProvider.getDeviceProjectRootPath(syncInfo.device, deviceProjectRootOptions), deviceSyncZipPath: this.$devicePathProvider.getDeviceSyncZipPath(syncInfo.device), - isLiveSyncSupported: async () => true + isLiveSyncSupported: async () => true, + connectTimeout: syncInfo.connectTimeout }; }