1- import { Device , FilesPayload } from "nativescript-preview-sdk" ;
21import { TrackActionNames , PREPARE_READY_EVENT_NAME } from "../constants" ;
32import { PrepareController } from "./prepare-controller" ;
3+ import { Device , FilesPayload } from "nativescript-preview-sdk" ;
44import { performanceLog } from "../common/decorators" ;
5- import { stringify } from "../common/helpers" ;
5+ import { stringify , deferPromise } from "../common/helpers" ;
66import { HmrConstants } from "../common/constants" ;
77import { EventEmitter } from "events" ;
88import { PrepareDataService } from "../services/prepare-data-service" ;
@@ -11,7 +11,12 @@ import { PreviewAppLiveSyncEvents } from "../services/livesync/playground/previe
1111export class PreviewAppController extends EventEmitter implements IPreviewAppController {
1212 private prepareReadyEventHandler : any = null ;
1313 private deviceInitializationPromise : IDictionary < boolean > = { } ;
14- private promise = Promise . resolve ( ) ;
14+ private devicesLiveSyncChain : IDictionary < Promise < void > > = { } ;
15+ private devicesCanExecuteHmr : IDictionary < boolean > = { } ;
16+ // holds HMR files per device in order to execute batch upload on fast updates
17+ private devicesHmrFiles : IDictionary < string [ ] > = { } ;
18+ // holds the current HMR hash per device in order to watch the proper hash status on fast updates
19+ private devicesCurrentHmrHash : IDictionary < string > = { } ;
1520
1621 constructor (
1722 private $analyticsService : IAnalyticsService ,
@@ -89,6 +94,7 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon
8994
9095 if ( data . useHotModuleReload ) {
9196 this . $hmrStatusService . attachToHmrStatusEvent ( ) ;
97+ this . devicesCanExecuteHmr [ device . id ] = true ;
9298 }
9399
94100 await this . $previewAppPluginsService . comparePluginsOnDevice ( data , device ) ;
@@ -109,13 +115,13 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon
109115 await this . $prepareController . prepare ( prepareData ) ;
110116
111117 try {
112- const payloads = await this . getInitialFilesForPlatformSafe ( data , device . platform ) ;
118+ const payloads = await this . getInitialFilesForDeviceSafe ( data , device ) ;
113119 return payloads ;
114120 } finally {
115121 this . deviceInitializationPromise [ device . id ] = null ;
116122 }
117123 } catch ( error ) {
118- this . $logger . trace ( `Error while sending files on device ${ device && device . id } . Error is` , error ) ;
124+ this . $logger . trace ( `Error while sending files on device ' ${ device && device . id } ' . Error is` , error ) ;
119125 this . emit ( PreviewAppLiveSyncEvents . PREVIEW_APP_LIVE_SYNC_ERROR , {
120126 error,
121127 data,
@@ -129,52 +135,95 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon
129135
130136 @performanceLog ( )
131137 private async handlePrepareReadyEvent ( data : IPreviewAppLiveSyncData , currentPrepareData : IFilesChangeEventData ) {
132- await this . promise
133- . then ( async ( ) => {
134- const { hmrData, files, platform } = currentPrepareData ;
135- const platformHmrData = _ . cloneDeep ( hmrData ) ;
136-
137- this . promise = this . syncFilesForPlatformSafe ( data , { filesToSync : files } , platform ) ;
138- await this . promise ;
139-
140- if ( data . useHotModuleReload && platformHmrData . hash ) {
141- const devices = this . $previewDevicesService . getDevicesForPlatform ( platform ) ;
142-
143- await Promise . all ( _ . map ( devices , async ( previewDevice : Device ) => {
144- const status = await this . $hmrStatusService . getHmrStatus ( previewDevice . id , platformHmrData . hash ) ;
145- if ( status === HmrConstants . HMR_ERROR_STATUS ) {
146- const originalUseHotModuleReload = data . useHotModuleReload ;
147- data . useHotModuleReload = false ;
148- await this . syncFilesForPlatformSafe ( data , { filesToSync : platformHmrData . fallbackFiles } , platform , previewDevice . id ) ;
149- data . useHotModuleReload = originalUseHotModuleReload ;
150- }
151- } ) ) ;
138+ const { hmrData, files, platform } = currentPrepareData ;
139+ const platformHmrData = _ . cloneDeep ( hmrData ) ;
140+ const connectedDevices = this . $previewDevicesService . getDevicesForPlatform ( platform ) ;
141+ if ( ! connectedDevices || ! connectedDevices . length ) {
142+ this . $logger . warn ( "Unable to find any connected devices. In order to execute live sync, open your Preview app and optionally re-scan the QR code using the Playground app." ) ;
143+ return ;
144+ }
145+
146+ await Promise . all ( _ . map ( connectedDevices , async ( device ) => {
147+ const previousSync = this . devicesLiveSyncChain [ device . id ] || Promise . resolve ( ) ;
148+ const currentSyncDeferPromise = deferPromise < void > ( ) ;
149+ this . devicesLiveSyncChain [ device . id ] = currentSyncDeferPromise . promise ;
150+ this . devicesCurrentHmrHash [ device . id ] = this . devicesCurrentHmrHash [ device . id ] || platformHmrData . hash ;
151+ this . devicesHmrFiles [ device . id ] = this . devicesHmrFiles [ device . id ] || [ ] ;
152+ this . devicesHmrFiles [ device . id ] . push ( ...files ) ;
153+ await previousSync ;
154+ if ( ! this . devicesHmrFiles [ device . id ] || ! this . devicesHmrFiles [ device . id ] . length ) {
155+ this . $logger . info ( "Skipping files sync. The changes are already batch transferred in a previous sync." ) ;
156+ currentSyncDeferPromise . resolve ( ) ;
157+ return ;
158+ }
159+
160+ try {
161+
162+ let executeHmrSync = false ;
163+ const hmrHash = this . devicesCurrentHmrHash [ device . id ] ;
164+ this . devicesCurrentHmrHash [ device . id ] = null ;
165+ if ( data . useHotModuleReload && hmrHash ) {
166+ if ( this . devicesCanExecuteHmr [ device . id ] ) {
167+ executeHmrSync = true ;
168+ this . $hmrStatusService . watchHmrStatus ( device . id , hmrHash ) ;
169+ }
152170 }
153- } ) ;
171+
172+ const filesToSync = executeHmrSync ? this . devicesHmrFiles [ device . id ] : platformHmrData . fallbackFiles ;
173+ this . devicesHmrFiles [ device . id ] = [ ] ;
174+ if ( executeHmrSync ) {
175+ await this . syncFilesForPlatformSafe ( data , { filesToSync } , platform , device ) ;
176+ const status = await this . $hmrStatusService . getHmrStatus ( device . id , hmrHash ) ;
177+ if ( ! status ) {
178+ this . devicesCanExecuteHmr [ device . id ] = false ;
179+ const noStatusWarning = this . getDeviceMsg ( device . name ,
180+ "Unable to get LiveSync status from the Preview app. Ensure the app is running in order to sync changes." ) ;
181+ this . $logger . warn ( noStatusWarning ) ;
182+ } else {
183+ this . devicesCanExecuteHmr [ device . id ] = status === HmrConstants . HMR_SUCCESS_STATUS ;
184+ }
185+ } else {
186+ const noHmrData = _ . assign ( { } , data , { useHotModuleReload : false } ) ;
187+ await this . syncFilesForPlatformSafe ( noHmrData , { filesToSync } , platform , device ) ;
188+ this . devicesCanExecuteHmr [ device . id ] = true ;
189+ }
190+ currentSyncDeferPromise . resolve ( ) ;
191+ } catch ( e ) {
192+ currentSyncDeferPromise . resolve ( ) ;
193+ }
194+ } ) ) ;
154195 }
155196
156- private async getInitialFilesForPlatformSafe ( data : IPreviewAppLiveSyncData , platform : string ) : Promise < FilesPayload > {
157- this . $logger . info ( `Start sending initial files for platform ${ platform } .` ) ;
197+ private getDeviceMsg ( deviceId : string , message : string ) {
198+ return `[${ deviceId } ] ${ message } ` ;
199+ }
200+
201+ private async getInitialFilesForDeviceSafe ( data : IPreviewAppLiveSyncData , device : Device ) : Promise < FilesPayload > {
202+ const platform = device . platform ;
203+ this . $logger . info ( `Start sending initial files for device '${ device . name } '.` ) ;
158204
159205 try {
160206 const payloads = this . $previewAppFilesService . getInitialFilesPayload ( data , platform ) ;
161- this . $logger . info ( `Successfully sent initial files for platform ${ platform } .` ) ;
207+ this . $logger . info ( `Successfully sent initial files for device ' ${ device . name } ' .` ) ;
162208 return payloads ;
163209 } catch ( err ) {
164- this . $logger . warn ( `Unable to apply changes for platform ${ platform } . Error is: ${ err } , ${ stringify ( err ) } ` ) ;
210+ this . $logger . warn ( `Unable to apply changes for device ' ${ device . name } ' . Error is: ${ err } , ${ stringify ( err ) } ` ) ;
165211 }
166212 }
167213
168- private async syncFilesForPlatformSafe ( data : IPreviewAppLiveSyncData , filesData : IPreviewAppFilesData , platform : string , deviceId ?: string ) : Promise < void > {
214+ private async syncFilesForPlatformSafe ( data : IPreviewAppLiveSyncData , filesData : IPreviewAppFilesData , platform : string , device : Device ) : Promise < void > {
215+ const deviceId = device && device . id || "" ;
216+
169217 try {
170218 const payloads = this . $previewAppFilesService . getFilesPayload ( data , filesData , platform ) ;
219+ payloads . deviceId = deviceId ;
171220 if ( payloads && payloads . files && payloads . files . length ) {
172- this . $logger . info ( `Start syncing changes for platform ${ platform } .` ) ;
221+ this . $logger . info ( `Start syncing changes for device ' ${ device . name } ' .` ) ;
173222 await this . $previewSdkService . applyChanges ( payloads ) ;
174- this . $logger . info ( `Successfully synced ${ payloads . files . map ( filePayload => filePayload . file . yellow ) } for platform ${ platform } .` ) ;
223+ this . $logger . info ( `Successfully synced ' ${ payloads . files . map ( filePayload => filePayload . file . yellow ) } ' for device ' ${ device . name } ' .` ) ;
175224 }
176225 } catch ( error ) {
177- this . $logger . warn ( `Unable to apply changes for platform ${ platform } . Error is: ${ error } , ${ JSON . stringify ( error , null , 2 ) } .` ) ;
226+ this . $logger . warn ( `Unable to apply changes for device ' ${ device . name } ' . Error is: ${ error } , ${ JSON . stringify ( error , null , 2 ) } .` ) ;
178227 this . emit ( PreviewAppLiveSyncEvents . PREVIEW_APP_LIVE_SYNC_ERROR , {
179228 error,
180229 data,
0 commit comments