diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index f3bf388a30..7d8a4af6ec 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -19,17 +19,11 @@ export class PreviewCommand implements ICommand { this.$logger.info(message); }); - await this.$liveSyncService.liveSync([], { - syncToPreviewApp: true, - projectDir: this.$projectData.projectDir, - skipWatcher: !this.$options.watch, - watchAllFiles: this.$options.syncAllFiles, - clean: this.$options.clean, + await this.$liveSyncService.liveSyncToPreviewApp({ bundle: !!this.$options.bundle, - release: this.$options.release, - env: this.$options.env, - timeout: this.$options.timeout, - useHotModuleReload: this.$options.hmr + useHotModuleReload: this.$options.hmr, + projectDir: this.$projectData.projectDir, + env: this.$options.env }); await this.$previewQrCodeService.printLiveSyncQrCode({ useHotModuleReload: this.$options.hmr, link: this.$options.link }); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 3bff6085fd..5f948e84b6 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -473,10 +473,14 @@ interface IGenerateOptions { collection?: string; } -interface IDebugInformation extends IPort, Mobile.IDeviceIdentifier { +interface IDebugInformation extends IPort, Mobile.IDeviceIdentifier, IHasHasReconnected { url: string; } +interface IHasHasReconnected { + hasReconnected: boolean; +} + interface IPort { port: Number; } @@ -735,8 +739,7 @@ interface IAppDebugSocketProxyFactory extends NodeJS.EventEmitter { getTCPSocketProxy(deviceIdentifier: string, appId: string): any; addTCPSocketProxy(device: Mobile.IiOSDevice, appId: string): Promise; - getWebSocketProxy(deviceIdentifier: string, appId: string): any; - addWebSocketProxy(device: Mobile.IiOSDevice, appId: string): Promise; + ensureWebSocketProxy(device: Mobile.IiOSDevice, appId: string): Promise; removeAllProxies(): void; } diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 6c8e4eca67..48bbba18f8 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -100,6 +100,10 @@ interface IDebugOptions { * Defines if the handshake(AppLaunching notification) between CLI and runtime should be executed. The handshake is not needed when CLI retries to attach to the debugger. */ skipHandshake?: boolean; + /** + * Forces the debugger attach event to be emitted. + */ + forceDebuggerAttachedEvent?: boolean; } /** @@ -161,5 +165,9 @@ interface IDeviceDebugService extends IPlatform, NodeJS.EventEmitter { * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. * @returns {Promise} Full url where the frontend client may be connected. */ - debug(debugData: IAppDebugData, debugOptions: IDebugOptions): Promise; + debug(debugData: IAppDebugData, debugOptions: IDebugOptions): Promise; +} + +interface IDebugResultInfo extends IHasHasReconnected { + debugUrl: string; } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 36d153d452..212474a508 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -188,7 +188,7 @@ interface ILiveSyncEventData { applicationIdentifier?: string, projectDir: string, syncedFiles?: string[], - error? : Error, + error?: Error, notification?: string, isFullSync?: boolean } @@ -242,6 +242,13 @@ interface ILiveSyncService { */ liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise; + /** + * Starts LiveSync operation to Preview app. + * @param {IPreviewAppLiveSyncData} data Describes information about the current operation. + * @returns {Promise} Data of the QR code that should be used to start the LiveSync operation. + */ + liveSyncToPreviewApp(data: IPreviewAppLiveSyncData): Promise; + /** * Stops LiveSync operation for specified directory. * @param {string} projectDir The directory for which to stop the operation. @@ -330,11 +337,9 @@ interface IEnableDebuggingDeviceOptions extends Mobile.IDeviceIdentifier, IOptio /** * Describes settings passed to livesync service in order to control event emitting during refresh application. */ -interface IShouldSkipEmitLiveSyncNotification { - /** - * Whether to skip emitting an event during refresh. Default is false. - */ +interface IRefreshApplicationSettings { shouldSkipEmitLiveSyncNotification: boolean; + shouldCheckDeveloperDiscImage: boolean; } /** @@ -390,11 +395,18 @@ interface ITransferFilesOptions { interface IPlatformLiveSyncService { fullSync(syncInfo: IFullSyncInfo): Promise; liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise; - refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; + refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; prepareForLiveSync(device: Mobile.IDevice, data: IProjectDir, liveSyncInfo: ILiveSyncInfo, debugOptions: IDebugOptions): Promise; getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService; } +interface IHasDidRestart { + didRestart: boolean; +} + +interface IRefreshApplicationInfo extends IHasDidRestart { +} + interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase { /** * Refreshes the application's content on a device @@ -405,7 +417,7 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase * @return {Promise} */ refreshApplication(projectData: IProjectData, - liveSyncInfo: ILiveSyncResultInfo): Promise; + liveSyncInfo: ILiveSyncResultInfo): Promise; /** * Removes specified files from a connected device diff --git a/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts b/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts index 7d932301f0..18448d876a 100644 --- a/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts +++ b/lib/device-sockets/ios/app-debug-socket-proxy-factory.ts @@ -20,10 +20,6 @@ export class AppDebugSocketProxyFactory extends EventEmitter implements IAppDebu return this.deviceTcpServers[`${deviceIdentifier}-${appId}`]; } - public getWebSocketProxy(deviceIdentifier: string, appId: string): ws.Server { - return this.deviceWebServers[`${deviceIdentifier}-${appId}`]; - } - public async addTCPSocketProxy(device: Mobile.IiOSDevice, appId: string): Promise { const cacheKey = `${device.deviceInfo.identifier}-${appId}`; const existingServer = this.deviceTcpServers[cacheKey]; @@ -84,7 +80,17 @@ export class AppDebugSocketProxyFactory extends EventEmitter implements IAppDebu return server; } - public async addWebSocketProxy(device: Mobile.IiOSDevice, appId: string): Promise { + public async ensureWebSocketProxy(device: Mobile.IiOSDevice, appId: string): Promise { + const existingWebProxy = this.deviceWebServers[`${device.deviceInfo.identifier}-${appId}`]; + const result = existingWebProxy || await this.addWebSocketProxy(device, appId); + + // TODO: do not remove till VSCode waits for this message in order to reattach + this.$logger.info("Opened localhost " + result.options.port); + + return result; + } + + private async addWebSocketProxy(device: Mobile.IiOSDevice, appId: string): Promise { const cacheKey = `${device.deviceInfo.identifier}-${appId}`; const existingServer = this.deviceWebServers[cacheKey]; if (existingServer) { @@ -104,21 +110,23 @@ export class AppDebugSocketProxyFactory extends EventEmitter implements IAppDebu const server = new ws.Server({ port: localPort, host: "localhost", - verifyClient: async (info: any, callback: Function) => { + verifyClient: async (info: any, callback: (res: boolean, code?: number, message?: string) => void) => { + let acceptHandshake = true; this.$logger.info("Frontend client connected."); let appDebugSocket; try { appDebugSocket = await device.getDebugSocket(appId); + this.$logger.info("Backend socket created."); + info.req["__deviceSocket"] = appDebugSocket; } catch (err) { err.deviceIdentifier = device.deviceInfo.identifier; this.$logger.trace(err); this.emit(CONNECTION_ERROR_EVENT_NAME, err); - this.$errors.failWithoutHelp(`Cannot connect to device socket.The error message is ${err.message} `); + acceptHandshake = false; + this.$logger.warn(`Cannot connect to device socket. The error message is '${err.message}'. Try starting the application manually.`); } - this.$logger.info("Backend socket created."); - info.req["__deviceSocket"] = appDebugSocket; - callback(true); + callback(acceptHandshake); } }); this.deviceWebServers[cacheKey] = server; @@ -152,12 +160,11 @@ export class AppDebugSocketProxyFactory extends EventEmitter implements IAppDebu appDebugSocket.on("close", () => { this.$logger.info("Backend socket closed!"); webSocket.close(); - server.close(); - delete this.deviceWebServers[cacheKey]; }); webSocket.on("close", () => { this.$logger.info('Frontend socket closed!'); + appDebugSocket.unpipe(packets); packets.destroy(); device.destroyDebugSocket(appId); if (!this.$options.watch) { @@ -167,7 +174,6 @@ export class AppDebugSocketProxyFactory extends EventEmitter implements IAppDebu }); - this.$logger.info("Opened localhost " + localPort); return server; } diff --git a/lib/services/android-device-debug-service.ts b/lib/services/android-device-debug-service.ts index ac63e81278..110946dbef 100644 --- a/lib/services/android-device-debug-service.ts +++ b/lib/services/android-device-debug-service.ts @@ -24,7 +24,7 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi this.deviceIdentifier = device.deviceInfo.identifier; } - public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { this._packageName = debugData.applicationIdentifier; const result = this.device.isEmulator ? await this.debugOnEmulator(debugData, debugOptions) @@ -59,7 +59,7 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi return this.removePortForwarding(); } - private async debugOnEmulator(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + private async debugOnEmulator(debugData: IDebugData, debugOptions: IDebugOptions): Promise { // Assure we've detected the emulator as device // For example in case deployOnEmulator had stated new emulator instance // we need some time to detect it. Let's force detection. @@ -97,7 +97,7 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi return this.device.adb.executeCommand(["forward", `tcp:${local}`, `localabstract:${remote}`]); } - private async debugOnDevice(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + private async debugOnDevice(debugData: IDebugData, debugOptions: IDebugOptions): Promise { let packageFile = ""; if (!debugOptions.start && !this.device.isEmulator) { @@ -113,27 +113,32 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi projectName }; - const action = (device: Mobile.IAndroidDevice): Promise => this.debugCore(device, packageFile, appData, debugOptions); + const action = (device: Mobile.IAndroidDevice): Promise => this.debugCore(device, packageFile, appData, debugOptions); const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(this.deviceIdentifier)); return deviceActionResult[0].result; } - private async debugCore(device: Mobile.IAndroidDevice, packageFile: string, appData: Mobile.IApplicationData, debugOptions: IDebugOptions): Promise { + private async debugCore(device: Mobile.IAndroidDevice, packageFile: string, appData: Mobile.IApplicationData, debugOptions: IDebugOptions): Promise { + const result: IDebugResultInfo = { hasReconnected: false, debugUrl: null }; + if (debugOptions.stop) { await this.removePortForwarding(); - return null; + return result; } if (!debugOptions.start) { await this.debugStartCore(appData, debugOptions); + result.hasReconnected = true; } await this.validateRunningApp(device.deviceInfo.identifier, appData.appId); const debugPort = await this.getForwardedDebugPort(device.deviceInfo.identifier, appData.appId); await this.printDebugPort(device.deviceInfo.identifier, debugPort); - return this.getChromeDebugUrl(debugOptions, debugPort); + result.debugUrl = this.getChromeDebugUrl(debugOptions, debugPort); + + return result; } private async printDebugPort(deviceId: string, port: number): Promise { diff --git a/lib/services/debug-service-base.ts b/lib/services/debug-service-base.ts index 765a2a33ea..230fbb3d78 100644 --- a/lib/services/debug-service-base.ts +++ b/lib/services/debug-service-base.ts @@ -10,7 +10,7 @@ export abstract class DebugServiceBase extends EventEmitter implements IDeviceDe public abstract get platform(): string; - public abstract async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; + public abstract async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; public abstract async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise; diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index d9ae030dc5..0b5186ca72 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -40,12 +40,6 @@ export class DebugService extends EventEmitter implements IDebugService { } const debugOptions: IDebugOptions = _.cloneDeep(options); - - // TODO: Check if app is running. - // For now we can only check if app is running on Android. - // After we find a way to check on iOS we should use it here. - let result: string; - const debugService = this.getDeviceDebugService(device); if (!debugService) { this.$errors.failWithoutHelp(`Unsupported device OS: ${device.deviceInfo.platform}. You can debug your applications only on iOS or Android.`); @@ -62,9 +56,9 @@ export class DebugService extends EventEmitter implements IDebugService { } } - result = await debugService.debug(debugData, debugOptions); + const debugResultInfo = await debugService.debug(debugData, debugOptions); - return this.getDebugInformation(result, device.deviceInfo.identifier); + return this.getDebugInformation(debugResultInfo, device.deviceInfo.identifier); } public debugStop(deviceIdentifier: string): Promise { @@ -100,16 +94,17 @@ export class DebugService extends EventEmitter implements IDebugService { platformDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); } - private getDebugInformation(fullUrl: string, deviceIdentifier: string): IDebugInformation { + private getDebugInformation(debugResultInfo: IDebugResultInfo, deviceIdentifier: string): IDebugInformation { const debugInfo: IDebugInformation = { - url: fullUrl, + url: debugResultInfo.debugUrl, port: 0, - deviceIdentifier + deviceIdentifier, + hasReconnected: debugResultInfo.hasReconnected }; - if (fullUrl) { + if (debugResultInfo.debugUrl) { const parseQueryString = true; - const wsQueryParam = parse(fullUrl, parseQueryString).query.ws; + const wsQueryParam = parse(debugResultInfo.debugUrl, parseQueryString).query.ws; const hostPortSplit = wsQueryParam && wsQueryParam.split(":"); debugInfo.port = hostPortSplit && +hostPortSplit[1]; } diff --git a/lib/services/ios-device-debug-service.ts b/lib/services/ios-device-debug-service.ts index fca655f481..ed711ff184 100644 --- a/lib/services/ios-device-debug-service.ts +++ b/lib/services/ios-device-debug-service.ts @@ -38,7 +38,8 @@ export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDe return "ios"; } - public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + const result: IDebugResultInfo = { hasReconnected: false, debugUrl: null }; this.validateOptions(debugOptions); await this.startDeviceLogProcess(debugData, debugOptions); @@ -46,9 +47,12 @@ export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDe if (!debugOptions.start) { await this.startApp(debugData, debugOptions); + result.hasReconnected = true; } - return this.wireDebuggerClient(debugData, debugOptions); + result.debugUrl = await this.wireDebuggerClient(debugData, debugOptions); + + return result; } public async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { @@ -160,8 +164,7 @@ export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDe if (debugOptions.chrome) { this.$logger.info("'--chrome' is the default behavior. Use --inspector to debug iOS applications using the Safari Web Inspector."); } - const existingWebProxy = this.$appDebugSocketProxyFactory.getWebSocketProxy(this.deviceIdentifier, debugData.applicationIdentifier); - const webSocketProxy = existingWebProxy || await this.$appDebugSocketProxyFactory.addWebSocketProxy(this.device, debugData.applicationIdentifier); + const webSocketProxy = await this.$appDebugSocketProxyFactory.ensureWebSocketProxy(this.device, debugData.applicationIdentifier); return this.getChromeDebugUrl(debugOptions, webSocketProxy.options.port); } diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index 8c3d779d34..07345a146b 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -15,7 +15,7 @@ export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBa protected device: Mobile.IAndroidDevice, $filesHashService: IFilesHashService, $logger: ILogger) { - super($injector, $platformsData, $filesHashService, $logger, device); + super($injector, $platformsData, $filesHashService, $logger, device); } public async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { @@ -26,7 +26,8 @@ export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBa await this.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); } - public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { + public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { + const result: IRefreshApplicationInfo = { didRestart: false }; const deviceAppData = liveSyncInfo.deviceAppData; const localToDevicePaths = liveSyncInfo.modifiedFilesData; const deviceProjectRootDirname = await this.$devicePathProvider.getDeviceProjectRootPath(liveSyncInfo.deviceAppData.device, { @@ -48,8 +49,11 @@ export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBa (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(liveSyncInfo, localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); if (!canExecuteFastSync) { - return this.restartApplication(deviceAppData, projectData.projectName); + await this.restartApplication(deviceAppData, projectData.projectName); + result.didRestart = true; } + + return result; } private async cleanLivesyncDirectories(deviceAppData: Mobile.IDeviceAppData): Promise { diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index 43cd7f7fd1..4480bf080b 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -92,7 +92,8 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe return result; } - public async refreshApplication(projectData: IProjectData, liveSyncInfo: IAndroidLiveSyncResultInfo) { + public async refreshApplication(projectData: IProjectData, liveSyncInfo: IAndroidLiveSyncResultInfo): Promise { + const result: IRefreshApplicationInfo = { didRestart: false }; const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo, liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform); if (!canExecuteFastSync || !liveSyncInfo.didRefresh) { await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName }); @@ -103,7 +104,11 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe this.$logger.trace("Failed to connect after app restart."); } } + + result.didRestart = true; } + + return result; } public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 62e428c15d..03f693f0a9 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -37,12 +37,14 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen await Promise.all(_.map(localToDevicePaths, localToDevicePathData => this.device.fileSystem.deleteFile(localToDevicePathData.getDevicePath(), deviceAppData.appIdentifier))); } - public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { + public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { + const result: IRefreshApplicationInfo = { didRestart: false }; const deviceAppData = liveSyncInfo.deviceAppData; const localToDevicePaths = liveSyncInfo.modifiedFilesData; if (liveSyncInfo.isFullSync) { await this.restartApplication(deviceAppData, projectData.projectName); - return; + result.didRestart = true; + return result; } let scriptRelatedFiles: Mobile.ILocalToDevicePathData[] = []; @@ -54,14 +56,18 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen if (!canExecuteFastSync) { await this.restartApplication(deviceAppData, projectData.projectName); - return; + result.didRestart = true; + return result; } if (await this.setupSocketIfNeeded(projectData)) { await this.reloadPage(otherFiles); } else { await this.restartApplication(deviceAppData, projectData.projectName); + result.didRestart = true; } + + return result; } private async restartApplication(deviceAppData: Mobile.IDeviceAppData, projectName: string): Promise { diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 69654a1264..38e3b8603d 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -137,11 +137,13 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi return currentDescriptors || []; } - @cache() private attachToPreviewAppLiveSyncError(): void { - this.$previewAppLiveSyncService.on(LiveSyncEvents.previewAppLiveSyncError, liveSyncData => { - this.emit(LiveSyncEvents.previewAppLiveSyncError, liveSyncData); - }); + if (!this.$usbLiveSyncService.isInitialized) { + this.$previewAppLiveSyncService.on(LiveSyncEvents.previewAppLiveSyncError, liveSyncData => { + this.$logger.error(liveSyncData.error); + this.emit(LiveSyncEvents.previewAppLiveSyncError, liveSyncData); + }); + } } private handleWarnings(liveSyncData: ILiveSyncInfo, projectData: IProjectData) { @@ -156,7 +158,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi } } - private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise { + private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise { const deviceDescriptor = this.getDeviceDescriptor(liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, projectData.projectDir); return deviceDescriptor && deviceDescriptor.debugggingEnabled ? @@ -164,12 +166,13 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOpts, outputPath); } - private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IShouldSkipEmitLiveSyncNotification): Promise { + private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IRefreshApplicationSettings): Promise { + let result: IRefreshApplicationInfo = { didRestart: false }; const platform = liveSyncResultInfo.deviceAppData.platform; const platformLiveSyncService = this.getLiveSyncService(platform); const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()]; try { - await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo); + result = await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo); } catch (err) { this.$logger.info(`Error while trying to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err.message || err}`); const msg = `Unable to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`; @@ -183,7 +186,9 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi }); } - this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOpts, outputPath); + if (settings && settings.shouldCheckDeveloperDiscImage) { + this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOpts, outputPath); + } } this.emitLivesyncEvent(LiveSyncEvents.liveSyncExecuted, { @@ -193,12 +198,14 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, isFullSync: liveSyncResultInfo.isFullSync }); + + return result; } private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise { + let didRestart = false; const deviceAppData = liveSyncResultInfo.deviceAppData; debugOptions = debugOptions || {}; - const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; if (debugOptions.debugBrk) { await this.$debugService.debugStop(deviceIdentifier); @@ -211,13 +218,15 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOptions, outputPath); } } else { - await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true }); + const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true, shouldCheckDeveloperDiscImage: true }); + didRestart = refreshInfo.didRestart; } // we do not stop the application when debugBrk is false, so we need to attach, instead of launch // if we try to send the launch request, the debugger port will not be printed and the command will timeout debugOptions.start = !debugOptions.debugBrk; + debugOptions.forceDebuggerAttachedEvent = didRestart; const deviceOption = { deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, debugOptions: debugOptions, @@ -269,13 +278,17 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi debugData.pathToAppPackage = this.$platformService.lastOutputPath(settings.platform, buildConfig, projectData, settings.outputPath); const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions); - const result = this.printDebugInformation(debugInfo); + const fireDebuggerAttachedEvent = settings.debugOptions.forceDebuggerAttachedEvent || debugInfo.hasReconnected; + const result = this.printDebugInformation(debugInfo, fireDebuggerAttachedEvent); return result; } - public printDebugInformation(debugInformation: IDebugInformation): IDebugInformation { + public printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent: boolean = true): IDebugInformation { if (!!debugInformation.url) { - this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation); + if (fireDebuggerAttachedEvent) { + this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation); + } + this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${debugInformation.url}${EOL}`.cyan); } diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index 5d290f016b..5d1148048c 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -27,12 +27,16 @@ export abstract class PlatformLiveSyncServiceBase { protected abstract _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectData, frameworkVersion: string): INativeScriptDeviceLiveSyncService; - public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { + public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { + let result: IRefreshApplicationInfo = { didRestart: false }; + if (liveSyncInfo.isFullSync || liveSyncInfo.modifiedFilesData.length) { const deviceLiveSyncService = this.getDeviceLiveSyncService(liveSyncInfo.deviceAppData.device, projectData); this.$logger.info(`Refreshing application on device ${liveSyncInfo.deviceAppData.device.deviceInfo.identifier}...`); - await deviceLiveSyncService.refreshApplication(projectData, liveSyncInfo); + result = await deviceLiveSyncService.refreshApplication(projectData, liveSyncInfo); } + + return result; } public async fullSync(syncInfo: IFullSyncInfo): Promise { @@ -87,7 +91,7 @@ export abstract class PlatformLiveSyncServiceBase { const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, existingFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); - modifiedLocalToDevicePaths = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, liveSyncInfo.liveSyncDeviceInfo, { isFullSync: false, force: liveSyncInfo.force}); + modifiedLocalToDevicePaths = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, liveSyncInfo.liveSyncDeviceInfo, { isFullSync: false, force: liveSyncInfo.force }); } } diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts index ed0780cc75..2883dd1ccf 100644 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -26,20 +26,30 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA public async initialize(data: IPreviewAppLiveSyncData): Promise { await this.$previewSdkService.initialize(async (device: Device) => { - if (!device) { - this.$errors.failWithoutHelp("Sending initial preview files without a specified device is not supported."); - } + try { + if (!device) { + this.$errors.failWithoutHelp("Sending initial preview files without a specified device is not supported."); + } - if (this.deviceInitializationPromise[device.id]) { - return this.deviceInitializationPromise[device.id]; - } + if (this.deviceInitializationPromise[device.id]) { + return this.deviceInitializationPromise[device.id]; + } - this.deviceInitializationPromise[device.id] = this.getInitialFilesForDevice(data, device); - try { - const payloads = await this.deviceInitializationPromise[device.id]; - return payloads; - } finally { - this.deviceInitializationPromise[device.id] = null; + this.deviceInitializationPromise[device.id] = this.getInitialFilesForDevice(data, device); + try { + const payloads = await this.deviceInitializationPromise[device.id]; + return payloads; + } finally { + this.deviceInitializationPromise[device.id] = null; + } + } catch (error) { + this.$logger.trace(`Error while sending files on device ${device && device.id}. Error is`, error); + this.emit(PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, { + error, + data, + platform: device.platform, + deviceId: device.id + }); } }); } diff --git a/lib/services/livesync/playground/preview-app-plugins-service.ts b/lib/services/livesync/playground/preview-app-plugins-service.ts index 3c8c80a178..1c52a6ae48 100644 --- a/lib/services/livesync/playground/preview-app-plugins-service.ts +++ b/lib/services/livesync/playground/preview-app-plugins-service.ts @@ -7,8 +7,6 @@ import { NODE_MODULES_DIR_NAME } from "../../../common/constants"; import { PLATFORMS_DIR_NAME, PACKAGE_JSON_FILE_NAME } from "../../../constants"; export class PreviewAppPluginsService implements IPreviewAppPluginsService { - private previewAppVersionWarnings: IDictionary = {}; - constructor(private $errors: IErrors, private $fs: IFileSystem, private $logger: ILogger, @@ -23,20 +21,17 @@ export class PreviewAppPluginsService implements IPreviewAppPluginsService { this.$errors.failWithoutHelp("No version of preview app provided."); } - if (!this.previewAppVersionWarnings[device.previewAppVersion]) { - const devicePlugins = this.getDevicePlugins(device); - const localPlugins = this.getLocalPlugins(data.projectDir); - const warnings = _.keys(localPlugins) - .map(localPlugin => { - const localPluginVersion = localPlugins[localPlugin]; - const devicePluginVersion = devicePlugins[localPlugin]; - return this.getWarningForPlugin(data, localPlugin, localPluginVersion, devicePluginVersion, device); - }) - .filter(item => !!item); - this.previewAppVersionWarnings[device.previewAppVersion] = warnings; - } - - return this.previewAppVersionWarnings[device.previewAppVersion]; + const devicePlugins = this.getDevicePlugins(device); + const localPlugins = this.getLocalPlugins(data.projectDir); + const warnings = _.keys(localPlugins) + .map(localPlugin => { + const localPluginVersion = localPlugins[localPlugin]; + const devicePluginVersion = devicePlugins[localPlugin]; + return this.getWarningForPlugin(data, localPlugin, localPluginVersion, devicePluginVersion, device); + }) + .filter(item => !!item); + + return warnings; } public async comparePluginsOnDevice(data: IPreviewAppLiveSyncData, device: Device): Promise { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 57ab93eba3..fd6b5c244f 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2932,7 +2932,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } @@ -3981,9 +3981,9 @@ } }, "ios-sim-portable": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-4.0.5.tgz", - "integrity": "sha512-L/HSJDgR3roQDzKhx04Vpy+40vxTyHC+EbCAVu6OPGc3KK/axov4za2NuPib/0jAAYs83W1DnwEKR/m4Tj38VA==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-4.0.6.tgz", + "integrity": "sha512-V4f5aiQDnikC/ERM+RD9Kj5gRPoIaXv8zt9Zq6hoe8amQa7PP3lY4zSzvVAp8H+Cfts6rtrAaSKLtGpVzoZRPw==", "requires": { "bplist-parser": "https://github.com/telerik/node-bplist-parser/tarball/master", "colors": "0.6.2", @@ -3996,7 +3996,7 @@ "dependencies": { "colors": { "version": "0.6.2", - "resolved": "http://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" }, "is-fullwidth-code-point": { @@ -4029,7 +4029,7 @@ }, "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", @@ -6502,7 +6502,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { "ret": "~0.1.10" diff --git a/package.json b/package.json index 7648f24e6a..b3c6e2f907 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "inquirer": "6.2.0", "ios-device-lib": "0.4.15", "ios-mobileprovision-finder": "1.0.10", - "ios-sim-portable": "4.0.5", + "ios-sim-portable": "4.0.6", "istextorbinary": "2.2.1", "jimp": "0.2.28", "lockfile": "1.0.3", diff --git a/resources/assets/image-definitions.json b/resources/assets/image-definitions.json index 02289c926a..0e84684321 100644 --- a/resources/assets/image-definitions.json +++ b/resources/assets/image-definitions.json @@ -166,6 +166,14 @@ "filename": "LaunchScreen-AspectFill@3x.png", "resizeOperation": "blank", "scale": "3x" + }, + { + "width": 768, + "height": 1024, + "directory": "Assets.xcassets/LaunchScreen.AspectFill.imageset", + "filename": "LaunchScree.AspectFill@3x.png", + "resizeOperation": "blank", + "scale": "3x" } ], "splashCenterImages": [ @@ -184,6 +192,22 @@ "filename": "LaunchScreen-Center@2x.png", "resizeOperation": "overlayWith", "scale": "2x" + }, + { + "width": 384, + "height": 512, + "directory": "Assets.xcassets/LaunchScreen.Center.imageset", + "filename": "LaunchScreen-Center@3x.png", + "resizeOperation": "overlayWith", + "scale": "3x" + }, + { + "width": 384, + "height": 512, + "directory": "Assets.xcassets/LaunchScreen.Center.imageset", + "filename": "LaunchScreen.Center@3x.png", + "resizeOperation": "overlayWith", + "scale": "3x" } ], "splashImages": [ @@ -290,6 +314,38 @@ "filename": "Default-Landscape-X.png", "resizeOperation": "overlayWith", "scale": "1x" + }, + { + "width": 896, + "height": 414, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "iPhone XR - Landscape iOS 12.png", + "resizeOperation": "overlayWith", + "scale": "2x" + }, + { + "width": 414, + "height": 896, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "iPhone XR - Portarit iOS 12.png", + "resizeOperation": "overlayWith", + "scale": "2x" + }, + { + "width": 896, + "height": 414, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "iPhone XS Max – Landscape iOS 12.png", + "resizeOperation": "overlayWith", + "scale": "3x" + }, + { + "width": 414, + "height": 896, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Phone XS Max - Portarit iOS 12.png", + "resizeOperation": "overlayWith", + "scale": "3x" } ] }, diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index 93d5958203..5991165dcd 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -11,8 +11,8 @@ const fakeChromeDebugUrl = `fakeChromeDebugUrl?experiments=true&ws=localhost:${f const defaultDeviceIdentifier = "Nexus5"; class PlatformDebugService extends EventEmitter /* implements IPlatformDebugService */ { - public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - return fakeChromeDebugUrl; + public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + return { debugUrl: fakeChromeDebugUrl, hasReconnected: false }; } } @@ -226,7 +226,8 @@ describe("debugService", () => { assert.deepEqual(debugInfo, { url: fakeChromeDebugUrl, port: fakeChromeDebugPort, - deviceIdentifier: debugData.deviceIdentifier + deviceIdentifier: debugData.deviceIdentifier, + hasReconnected: false }); }); }); diff --git a/test/services/playground/preview-app-plugins-service.ts b/test/services/playground/preview-app-plugins-service.ts index 277b5be34b..776f72d5d3 100644 --- a/test/services/playground/preview-app-plugins-service.ts +++ b/test/services/playground/preview-app-plugins-service.ts @@ -86,55 +86,6 @@ function setup(localPlugins: IStringDictionary, previewAppPlugins: IStringDictio describe("previewAppPluginsService", () => { describe("comparePluginsOnDevice without bundle", () => { - it("should persist warnings per preview app's version", async () => { - const localPlugins = { - "nativescript-facebook": "2.2.3", - "nativescript-theme-core": "1.0.4", - "tns-core-modules": "4.2.0" - }; - const previewAppPlugins = { - "nativescript-theme-core": "2.0.4", - "tns-core-modules": "4.2.0" - }; - const injector = createTestInjector(localPlugins); - const previewAppPluginsService = injector.resolve("previewAppPluginsService"); - - let isGetDevicePluginsCalled = false; - const originalGetDevicePlugins = (previewAppPluginsService).getDevicePlugins; - (previewAppPluginsService).getDevicePlugins = (device: Device) => { - isGetDevicePluginsCalled = true; - return originalGetDevicePlugins(device); - }; - let isGetLocalPluginsCalled = false; - const originalGetLocalPlugins = (previewAppPluginsService).getLocalPlugins; - (previewAppPluginsService).getLocalPlugins = () => { - isGetLocalPluginsCalled = true; - return originalGetLocalPlugins.apply(previewAppPluginsService, [projectDir]); - }; - - const previewLiveSyncData = createPreviewLiveSyncData({ bundle: false }); - - await previewAppPluginsService.comparePluginsOnDevice(previewLiveSyncData, createDevice(JSON.stringify(previewAppPlugins))); - - const expectedWarnings = [ - util.format(PluginComparisonMessages.PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP, "nativescript-facebook", deviceId), - util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_DIFFERENCE_IN_MAJOR_VERSION, "nativescript-theme-core", "1.0.4", "2.0.4") - ]; - assert.isTrue(isGetDevicePluginsCalled); - assert.isTrue(isGetLocalPluginsCalled); - assert.deepEqual(warnParams, expectedWarnings); - - isGetDevicePluginsCalled = false; - isGetLocalPluginsCalled = false; - warnParams = []; - - await previewAppPluginsService.comparePluginsOnDevice(previewLiveSyncData, createDevice(JSON.stringify(previewAppPlugins))); - - assert.isFalse(isGetDevicePluginsCalled); - assert.isFalse(isGetLocalPluginsCalled); - assert.deepEqual(warnParams, expectedWarnings); - }); - const testCases = [ { name: "should show warning for plugin not included in preview app", diff --git a/test/stubs.ts b/test/stubs.ts index 1a47e49c09..b06e61e7b8 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -645,7 +645,7 @@ function unexpected(msg: string): Error { } export class DebugServiceStub extends EventEmitter implements IDeviceDebugService { - public async debug(): Promise { + public async debug(): Promise { return; } @@ -661,6 +661,10 @@ export class DebugServiceStub extends EventEmitter implements IDeviceDebugServic } export class LiveSyncServiceStub implements ILiveSyncService { + public async liveSyncToPreviewApp(data: IPreviewAppLiveSyncData): Promise { + return; + } + public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { return; }