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,14 @@ 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 app files per device in order to execute batch upload on fast updates on failed HMR or --no-hmr
19+ private devicesAppFiles : IDictionary < string [ ] > = { } ;
20+ // holds the current HMR hash per device in order to watch the proper hash status on fast updates
21+ private devicesCurrentHmrHash : IDictionary < string > = { } ;
1522
1623 constructor (
1724 private $analyticsService : IAnalyticsService ,
@@ -89,6 +96,7 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon
8996
9097 if ( data . useHotModuleReload ) {
9198 this . $hmrStatusService . attachToHmrStatusEvent ( ) ;
99+ this . devicesCanExecuteHmr [ device . id ] = true ;
92100 }
93101
94102 await this . $previewAppPluginsService . comparePluginsOnDevice ( data , device ) ;
@@ -109,13 +117,13 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon
109117 await this . $prepareController . prepare ( prepareData ) ;
110118
111119 try {
112- const payloads = await this . getInitialFilesForPlatformSafe ( data , device . platform ) ;
120+ const payloads = await this . getInitialFilesForDeviceSafe ( data , device ) ;
113121 return payloads ;
114122 } finally {
115123 this . deviceInitializationPromise [ device . id ] = null ;
116124 }
117125 } catch ( error ) {
118- this . $logger . trace ( `Error while sending files on device ${ device && device . id } . Error is` , error ) ;
126+ this . $logger . trace ( `Error while sending files on device ' ${ device && device . id } ' . Error is` , error ) ;
119127 this . emit ( PreviewAppLiveSyncEvents . PREVIEW_APP_LIVE_SYNC_ERROR , {
120128 error,
121129 data,
@@ -129,52 +137,101 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon
129137
130138 @performanceLog ( )
131139 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- } ) ) ;
140+ const { hmrData, files, platform } = currentPrepareData ;
141+ const platformHmrData = _ . cloneDeep ( hmrData ) ;
142+ const connectedDevices = this . $previewDevicesService . getDevicesForPlatform ( platform ) ;
143+ if ( ! connectedDevices || ! connectedDevices . length ) {
144+ this . $logger . warn ( `Unable to find any connected devices for platform '${ platform } '. In order to execute live sync, open your Preview app and optionally re-scan the QR code using the Playground app.` ) ;
145+ return ;
146+ }
147+
148+ await Promise . all ( _ . map ( connectedDevices , async ( device ) => {
149+ const previousSync = this . devicesLiveSyncChain [ device . id ] || Promise . resolve ( ) ;
150+ const currentSyncDeferPromise = deferPromise < void > ( ) ;
151+ this . devicesLiveSyncChain [ device . id ] = currentSyncDeferPromise . promise ;
152+ this . devicesCurrentHmrHash [ device . id ] = this . devicesCurrentHmrHash [ device . id ] || platformHmrData . hash ;
153+ if ( data . useHotModuleReload ) {
154+ this . devicesHmrFiles [ device . id ] = this . devicesHmrFiles [ device . id ] || [ ] ;
155+ this . devicesHmrFiles [ device . id ] . push ( ...files ) ;
156+ this . devicesAppFiles [ device . id ] = platformHmrData . fallbackFiles ;
157+ } else {
158+ this . devicesHmrFiles [ device . id ] = [ ] ;
159+ this . devicesAppFiles [ device . id ] = files ;
160+ }
161+
162+ await previousSync ;
163+
164+ try {
165+ let canExecuteHmrSync = false ;
166+ const hmrHash = this . devicesCurrentHmrHash [ device . id ] ;
167+ this . devicesCurrentHmrHash [ device . id ] = null ;
168+ if ( data . useHotModuleReload && hmrHash ) {
169+ if ( this . devicesCanExecuteHmr [ device . id ] ) {
170+ canExecuteHmrSync = true ;
171+ }
152172 }
153- } ) ;
173+
174+ const filesToSync = canExecuteHmrSync ? this . devicesHmrFiles [ device . id ] : this . devicesAppFiles [ device . id ] ;
175+ if ( ! filesToSync || ! filesToSync . length ) {
176+ this . $logger . info ( `Skipping files sync for device ${ this . getDeviceDisplayName ( device ) } . The changes are already batch transferred in a previous sync.` ) ;
177+ currentSyncDeferPromise . resolve ( ) ;
178+ return ;
179+ }
180+
181+ this . devicesHmrFiles [ device . id ] = [ ] ;
182+ this . devicesAppFiles [ device . id ] = [ ] ;
183+ if ( canExecuteHmrSync ) {
184+ this . $hmrStatusService . watchHmrStatus ( device . id , hmrHash ) ;
185+ await this . syncFilesForPlatformSafe ( data , { filesToSync } , platform , device ) ;
186+ const status = await this . $hmrStatusService . getHmrStatus ( device . id , hmrHash ) ;
187+ if ( ! status ) {
188+ this . devicesCanExecuteHmr [ device . id ] = false ;
189+ this . $logger . warn ( `Unable to get LiveSync status from the Preview app for device ${ this . getDeviceDisplayName ( device ) } . Ensure the app is running in order to sync changes.` ) ;
190+ } else {
191+ this . devicesCanExecuteHmr [ device . id ] = status === HmrConstants . HMR_SUCCESS_STATUS ;
192+ }
193+ } else {
194+ const noHmrData = _ . assign ( { } , data , { useHotModuleReload : false } ) ;
195+ await this . syncFilesForPlatformSafe ( noHmrData , { filesToSync } , platform , device ) ;
196+ this . devicesCanExecuteHmr [ device . id ] = true ;
197+ }
198+ currentSyncDeferPromise . resolve ( ) ;
199+ } catch ( e ) {
200+ currentSyncDeferPromise . resolve ( ) ;
201+ }
202+ } ) ) ;
203+ }
204+
205+ private getDeviceDisplayName ( device : Device ) {
206+ return `${ device . name } (${ device . id } )` . cyan ;
154207 }
155208
156- private async getInitialFilesForPlatformSafe ( data : IPreviewAppLiveSyncData , platform : string ) : Promise < FilesPayload > {
157- this . $logger . info ( `Start sending initial files for platform ${ platform } .` ) ;
209+ private async getInitialFilesForDeviceSafe ( data : IPreviewAppLiveSyncData , device : Device ) : Promise < FilesPayload > {
210+ const platform = device . platform ;
211+ this . $logger . info ( `Start sending initial files for device ${ this . getDeviceDisplayName ( device ) } .` ) ;
158212
159213 try {
160214 const payloads = this . $previewAppFilesService . getInitialFilesPayload ( data , platform ) ;
161- this . $logger . info ( `Successfully sent initial files for platform ${ platform } .` ) ;
215+ this . $logger . info ( `Successfully sent initial files for device ${ this . getDeviceDisplayName ( device ) } .` ) ;
162216 return payloads ;
163217 } catch ( err ) {
164- this . $logger . warn ( `Unable to apply changes for platform ${ platform } . Error is: ${ err } , ${ stringify ( err ) } ` ) ;
218+ this . $logger . warn ( `Unable to apply changes for device ${ this . getDeviceDisplayName ( device ) } . Error is: ${ err } , ${ stringify ( err ) } ` ) ;
165219 }
166220 }
167221
168- private async syncFilesForPlatformSafe ( data : IPreviewAppLiveSyncData , filesData : IPreviewAppFilesData , platform : string , deviceId ?: string ) : Promise < void > {
222+ private async syncFilesForPlatformSafe ( data : IPreviewAppLiveSyncData , filesData : IPreviewAppFilesData , platform : string , device : Device ) : Promise < void > {
223+ const deviceId = device && device . id || "" ;
224+
169225 try {
170226 const payloads = this . $previewAppFilesService . getFilesPayload ( data , filesData , platform ) ;
227+ payloads . deviceId = deviceId ;
171228 if ( payloads && payloads . files && payloads . files . length ) {
172- this . $logger . info ( `Start syncing changes for platform ${ platform } .` ) ;
229+ this . $logger . info ( `Start syncing changes for device ${ this . getDeviceDisplayName ( device ) } .` ) ;
173230 await this . $previewSdkService . applyChanges ( payloads ) ;
174- this . $logger . info ( `Successfully synced ${ payloads . files . map ( filePayload => filePayload . file . yellow ) } for platform ${ platform } .` ) ;
231+ this . $logger . info ( `Successfully synced ' ${ payloads . files . map ( filePayload => filePayload . file . yellow ) } ' for device ${ this . getDeviceDisplayName ( device ) } .` ) ;
175232 }
176233 } catch ( error ) {
177- this . $logger . warn ( `Unable to apply changes for platform ${ platform } . Error is: ${ error } , ${ JSON . stringify ( error , null , 2 ) } .` ) ;
234+ this . $logger . warn ( `Unable to apply changes for device ${ this . getDeviceDisplayName ( device ) } . Error is: ${ error } , ${ JSON . stringify ( error , null , 2 ) } .` ) ;
178235 this . emit ( PreviewAppLiveSyncEvents . PREVIEW_APP_LIVE_SYNC_ERROR , {
179236 error,
180237 data,
0 commit comments