import * as _ from "lodash" ; import * as Q from "q" ; import { ActivitiesChangesModel , CoreMessages , SyncedActivityModel , SyncResultModel , UserSettingsModel } from "@elevate/shared/models" ; import { BrowserStorage } from "../browser-storage" ; import { AppResourcesModel } from "../models/app-resources.model" ; import { MultipleActivityProcessor } from "./multiple-activity-processor" ; import { StravaActivityModel } from "../models/sync/strava-activity.model" ; import { SyncNotifyModel } from "../models/sync/sync-notify.model" ; import { StreamActivityModel } from "../models/sync/stream-activity.model" ; import { AthleteSnapshotResolver } from "@elevate/shared/resolvers" ; import { BrowserStorageType } from "../models/browser-storage-type.enum" ; export class ActivitiesSynchronize { constructor ( appResources , userSettings , athleteModelResolver ) { this . appResources = appResources ; this . userSettings = userSettings ; this . extensionId = this . appResources . extensionId ; this . _multipleActivityProcessor = new MultipleActivityProcessor ( this . appResources , this . userSettings , athleteModelResolver ) ; } get multipleActivityProcessor ( ) : MultipleActivityProcessor { return this . _multipleActivityProcessor ; } get hasBeenSyncedActivities ( ) : SyncedActivityModel [ ] { return this . _hasBeenSyncedActivities ; } get activitiesChanges ( ) : ActivitiesChangesModel { return this . _activitiesChanges ; } public static readonly LAST_SYNC_DATE_TIME_KEY = "syncDateTime" ; public static readonly SYNCED_ACTIVITIES_KEY = "syncedActivities" ; public static readonly PAGES_PER_GROUP = 1 ; public static readonly ACTIVITIES_PER_PAGE = 20 ; public static readonly SLEEP_TIME = 1750 ; protected appResources : AppResourcesModel ; protected userSettings : UserSettingsModel ; protected extensionId : string ; protected totalRawActivityIds : number [ ] = [ ] ; protected _hasBeenSyncedActivities : SyncedActivityModel [ ] = null ; protected _multipleActivityProcessor : MultipleActivityProcessor ; protected _endReached = false ; private _activitiesChanges : ActivitiesChangesModel = { added : [ ] , deleted : [ ] , edited : [ ] , } ; public static notifyBackgroundSyncDone ( extensionId , syncResult ) { chrome . runtime . sendMessage ( extensionId , { method : CoreMessages . ON_EXTERNAL_SYNC_DONE , params : { syncResult : syncResult , } , } , ( response ) => { console . log ( response ) ; } ) ; } param rawActivities param syncedActivities return public static findAddedAndEditedActivities ( rawActivities , syncedActivities ) { const added = [ ] ; const deleted = [ ] ; const edited = [ ] ; if ( _ . isNull ( syncedActivities ) || _ . isUndefined ( syncedActivities ) || ! syncedActivities ) { syncedActivities = [ ] ; } if ( ! _ . isEmpty ( rawActivities ) ) { _ . forEach ( rawActivities , ( rawActivity ) => { const foundSyncedActivity = _ . find ( syncedActivities , { id : rawActivity . id } ) ; if ( foundSyncedActivity ) { if ( foundSyncedActivity . name !== rawActivity . name || foundSyncedActivity . type !== rawActivity . type ) { edited . push ( { id : foundSyncedActivity . id , name : rawActivity . name , type : rawActivity . type , display_type : rawActivity . display_type , } ) ; } } else { added . push ( rawActivity . id ) ; } } ) ; } return { added : added , deleted : deleted , edited : edited } ; } param ms public static sleep ( ms ) { return new Promise ( resolve => { setTimeout ( ( ) => { resolve ( ) ; } , ms ) ; } ) ; } param rawActivityIds param syncedActivities returns { null } public static findDeletedActivities ( rawActivityIds , syncedActivities ) { const added = [ ] ; const deleted = [ ] ; const edited = [ ] ; _ . forEach ( syncedActivities , ( syncedActivityModel ) => { const notFound = ( _ . indexOf ( rawActivityIds , syncedActivityModel . id ) == - 1 ) ; if ( notFound ) { deleted . push ( syncedActivityModel . id ) ; } } ) ; return { added : added , deleted : deleted , edited : edited , } ; } public appendGlobalActivitiesChanges ( activitiesChangesModel ) { this . _activitiesChanges . added = _ . union ( this . _activitiesChanges . added , activitiesChangesModel . added ) ; this . _activitiesChanges . deleted = _ . union ( this . _activitiesChanges . deleted , activitiesChangesModel . deleted ) ; this . _activitiesChanges . edited = _ . union ( this . _activitiesChanges . edited , activitiesChangesModel . edited ) ; } return public fetchWithStream ( lastSyncDateTime , fromPage , pagesToRead ) { const deferred = Q . defer < StreamActivityModel [ ] > ( ) ; this . fetchRawActivitiesRecursive ( lastSyncDateTime , fromPage , pagesToRead ) . then ( ( rawActivities ) => { console . log ( "Activities fetched in group " + this . printGroupLimits ( fromPage , pagesToRead ) + ": " + rawActivities . length ) ; let fetchedActivitiesStreamCount = 0 ; let fetchedActivitiesProgress = 0 ; const promisesOfActivitiesStreamById = [ ] ; this . getSyncedActivitiesFromLocal ( ) . then ( ( syncedActivitiesStored ) => { const activitiesChangesModel = ActivitiesSynchronize . findAddedAndEditedActivities ( rawActivities , ( syncedActivitiesStored ) ? syncedActivitiesStored : [ ] ) ; this . appendGlobalActivitiesChanges ( activitiesChangesModel ) ; const sleepTime = this . getSleepTime ( ) ; const fetchStreamSequence = activitiesChangesModel . added . reduce ( ( prev , activityId ) => { return prev . then ( ( ) => { return this . fetchStreamByActivityId ( activityId ) . then ( ( streamActivityModel ) => { fetchedActivitiesStreamCount ++ ; fetchedActivitiesProgress = fetchedActivitiesStreamCount / activitiesChangesModel . added . length * 100 ; const notify = { step : "fetchedStreamsPercentage" , progress : fetchedActivitiesProgress , activityId : activityId , } ; deferred . notify ( notify ) ; promisesOfActivitiesStreamById . push ( Q . resolve ( streamActivityModel ) ) ; return ActivitiesSynchronize . sleep ( sleepTime ) . then ( ( ) => { return Q . resolve ( activityId ) ; } ) ; } ) ; } ) ; } , Promise . resolve ( null ) ) ; _ . forEach ( rawActivities , ( rawActivity ) => { this . totalRawActivityIds . push ( rawActivity . id ) ; } ) ; fetchStreamSequence . then ( ( ) => { Q . allSettled ( promisesOfActivitiesStreamById ) . then ( ( streamResults ) => { console . log ( "Stream length: " + streamResults . length + ", raw activities length: " + rawActivities . length + ")" ) ; const activitiesWithStream = [ ] ; _ . forEach ( streamResults , ( data ) => { if ( data . state === "rejected" ) { console . warn ( "Stream not found for activity <" + data . reason . activityId + ">" , data ) ; const newlyDetectedActivity = _ . find ( rawActivities , { id : < number > data . reason . activityId } ) ; const activityWithStream = newlyDetectedActivity as StreamActivityModel ; activityWithStream . hasPowerMeter = null ; activityWithStream . stream = null ; activitiesWithStream . push ( activityWithStream ) ; } else if ( data . state === "fulfilled" ) { const newlyDetectedActivity = _ . find ( rawActivities , { id : < number > data . value . activityId } ) ; let hasPowerMeter = true ; if ( _ . isEmpty ( data . value . watts ) ) { data . value . watts = data . value . watts_calc ; hasPowerMeter = false ; } const activityWithStream = newlyDetectedActivity as StreamActivityModel ; activityWithStream . hasPowerMeter = hasPowerMeter ; activityWithStream . stream = data . value ; activitiesWithStream . push ( activityWithStream ) ; } } ) ; const notify = { step : "fetchedStreamsPercentage" , progress : 100 , } ; deferred . notify ( notify ) ; deferred . resolve ( activitiesWithStream ) ; } , ( err ) => { } ) ; } , error => { deferred . reject ( error ) ; } ) ; } ) ; } , ( err ) => { deferred . reject ( err ) ; } , ( progress ) => { deferred . notify ( progress ) ; } ) ; return deferred . promise ; } public httpPageGet ( perPage , page ) { return $ . ajax ( "/athlete/training_activities?new_activity_only=false&per_page=" + perPage + "&page=" + page ) ; } public getFirstPageRemoteActivities ( ) { const deferred = Q . defer < { activitiesCountAllPages : number ; firstPageModels : StravaActivityModel [ ] ; } > ( ) ; const perPage = 1 ; const page = 1 ; const promise = this . httpPageGet ( perPage , page ) ; promise . then ( ( data , textStatus , jqXHR ) => { if ( data && _ . isNumber ( data . total ) ) { deferred . resolve ( { activitiesCountAllPages : data . total , firstPageModels : data . models } ) ; } else { deferred . reject ( "No remote total activities available" ) ; } } ) ; return deferred . promise ; } returns { Q . Promise < { hasMisMatch : boolean ; activitiesChangesModel : ActivitiesChangesModel } > } public hasRemoteFirstPageActivitiesMismatch ( ) { const deferred = Q . defer < { hasMisMatch : boolean ; activitiesChangesModel : ActivitiesChangesModel ; } > ( ) ; let localSyncedActivityModels = null ; this . getSyncedActivitiesFromLocal ( ) . then ( ( result ) => { if ( result && result . length > 0 ) { localSyncedActivityModels = result ; return this . getFirstPageRemoteActivities ( ) ; } else { deferred . reject ( "No local synced activities" ) ; return ; } } ) . then ( ( remoteFirstPage ) => { const activitiesChangesModel = ActivitiesSynchronize . findAddedAndEditedActivities ( remoteFirstPage . firstPageModels , localSyncedActivityModels ) ; const remoteFirstPageIds = _ . map ( remoteFirstPage . firstPageModels , ( stravaActivityModel ) => { return stravaActivityModel . id ; } ) ; activitiesChangesModel . deleted = ActivitiesSynchronize . findDeletedActivities ( remoteFirstPageIds , _ . slice ( localSyncedActivityModels , - 1 * ( remoteFirstPageIds . length - activitiesChangesModel . added . length ) ) ) . deleted ; const hasAddedOrEditedActivitiesMisMatch = ( activitiesChangesModel . added . length > 0 || activitiesChangesModel . edited . length > 0 || activitiesChangesModel . deleted . length > 0 ) ; const result = { hasMisMatch : hasAddedOrEditedActivitiesMisMatch , activitiesChangesModel : activitiesChangesModel } ; deferred . resolve ( result ) ; } ) ; return deferred . promise ; } param lastSyncDateTime param page param pagesToRead param pagesRidden param deferred param activitiesList return { Q . Promise < Array < StravaActivityModel > > } public fetchRawActivitiesRecursive ( lastSyncDateTime , page ? , pagesToRead ? , pagesRidden ? , deferred ? , activitiesList ? ) { if ( ! page ) { page = 1 ; } if ( ! pagesToRead ) { pagesToRead = 0 ; } if ( ! pagesRidden ) { pagesRidden = 0 ; } if ( ! deferred ) { deferred = Q . defer < StravaActivityModel [ ] > ( ) ; } if ( ! activitiesList ) { activitiesList = [ ] ; } const perPage = ActivitiesSynchronize . ACTIVITIES_PER_PAGE ; const promiseActivitiesRequest = this . httpPageGet ( perPage , page ) ; const notify = { step : "fetchActivitiesPercentage" , } ; promiseActivitiesRequest . then ( ( data , textStatus , jqXHR ) => { if ( textStatus !== "success" ) { deferred . reject ( "Unable to get models" + textStatus ) ; } else { if ( pagesToRead && pagesToRead === pagesRidden ) { console . log ( "[PagesRidden] Resolving with " + activitiesList . length + " activities found" ) ; deferred . resolve ( activitiesList ) ; } else if ( _ . isEmpty ( data . models ) ) { console . log ( "[EndReached] Resolving with " + activitiesList . length + " activities found." ) ; this . _endReached = true ; deferred . resolve ( activitiesList ) ; } else { notify . totalActivities = data . total ; activitiesList = _ . flatten ( _ . union ( activitiesList , data . models ) ) ; notify . progress = ( activitiesList . length / ( ( pagesToRead && perPage ) ? ( pagesToRead * perPage ) : notify . totalActivities ) ) * 100 ; deferred . notify ( notify ) ; setTimeout ( ( ) => { this . fetchRawActivitiesRecursive ( lastSyncDateTime , page + 1 , pagesToRead , pagesRidden + 1 , deferred , activitiesList ) ; } , 50 ) ; } } } , ( data , textStatus , errorThrown ) => { const err = { method : "ActivitiesSynchronize.fetchRawActivitiesRecursive" , page , data , textStatus , errorThrown , } ; console . error ( err ) ; deferred . reject ( err ) ; } ) ; return deferred . promise ; } param activityId return { Q . Promise < T > } public fetchStreamByActivityId ( activityId ) { const deferred = Q . defer < StreamActivityModel > ( ) ; const activityStreamUrl = "/activities/" + activityId + "/streams?stream_types[]=watts_calc&stream_types[]=watts&stream_types[]=velocity_smooth&stream_types[]=time&stream_types[]=distance&stream_types[]=cadence&stream_types[]=heartrate&stream_types[]=grade_smooth&stream_types[]=altitude&stream_types[]=latlng&stream_types[]=grade_adjusted_speed" ; const promiseActivityStream = $ . ajax ( activityStreamUrl ) ; promiseActivityStream . then ( ( data , textStatus , jqXHR ) => { deferred . notify ( activityId ) ; data . activityId = activityId ; deferred . resolve ( data ) ; } , ( jqXHR ) => { const shouldStopSync = jqXHR . status === 429 ; if ( shouldStopSync ) { deferred . reject ( { streamFailure : true , activityId : activityId , statusCode : jqXHR . status , statusText : jqXHR . statusText } ) ; } else { deferred . resolve ( { activityId : activityId } as any ) ; } } ) ; return deferred . promise ; } /**
     * Erase stored last sync date and synced activities
     */ public clearSyncCache ( ) { console . log ( "clearSyncCache requested" ) ; return BrowserStorage . getInstance ( ) . rm ( BrowserStorageType . LOCAL , ActivitiesSynchronize . SYNCED_ACTIVITIES_KEY ) . then ( ( ) => { console . log ( "syncedActivities removed from local storage" ) ; return BrowserStorage . getInstance ( ) . rm ( BrowserStorageType . LOCAL , ActivitiesSynchronize . LAST_SYNC_DATE_TIME_KEY ) ; } ) . then ( ( ) => { console . log ( "lastSyncDateTime removed from local storage" ) ; } ) ; } returns { Q . Promise < Array < SyncedActivityModel > > } public fetchAndComputeGroupOfPages ( lastSyncDateTime , fromPage , pagesToRead ) { const deferred = Q . defer ( ) ; this . fetchWithStream ( lastSyncDateTime , fromPage , pagesToRead ) . then ( ( activitiesWithStreams ) => { return this . _multipleActivityProcessor . compute ( activitiesWithStreams ) ; } , ( err ) => { deferred . reject ( err ) ; return null ; } , ( progress ) => { if ( progress ) { progress . fromPage = fromPage ; progress . toPage = fromPage + pagesToRead - 1 ; deferred . notify ( progress ) ; } } ) . then ( ( syncedActivities ) => { deferred . resolve ( syncedActivities ) ; } , ( err ) => { deferred . reject ( err ) ; } , ( progress ) => { if ( progress ) { progress . fromPage = fromPage ; progress . toPage = fromPage + pagesToRead - 1 ; deferred . notify ( progress ) ; } } ) ; return ( deferred . promise as Q . Promise < SyncedActivityModel [ ] > ) ; } protected printGroupLimits ( fromPage , pagesPerGroupToRead ) { return "[" + fromPage + " => " + ( fromPage - 1 + pagesPerGroupToRead ) + "]" ; } return { Q . Promise < Array < SyncedActivityModel > > } public computeActivitiesByGroupsOfPages ( lastSyncDateTime , fromPage ? , pagesPerGroupToRead ? , maxGroupCount ? , handledGroupCount ? , deferred ? ) { if ( ! maxGroupCount ) { maxGroupCount = 0 ; } if ( ! handledGroupCount ) { handledGroupCount = 0 ; } if ( ! fromPage ) { fromPage = 1 ; } if ( ! pagesPerGroupToRead ) { pagesPerGroupToRead = ActivitiesSynchronize . PAGES_PER_GROUP ; } if ( ! deferred ) { deferred = Q . defer < SyncedActivityModel [ ] > ( ) ; } let syncedActivitiesInGroup = null ; if ( this . _endReached ) { deferred . resolve ( this . _hasBeenSyncedActivities ) ; } else { this . fetchAndComputeGroupOfPages ( lastSyncDateTime , fromPage , pagesPerGroupToRead ) . then ( ( syncedActivitiesPromised ) => { handledGroupCount ++ ; syncedActivitiesInGroup = syncedActivitiesPromised ; syncedActivitiesPromised = null ; console . log ( "Group handled count: " + handledGroupCount ) ; return this . getSyncedActivitiesFromLocal ( ) ; } ) . then ( ( syncedActivitiesStored ) => { if ( syncedActivitiesInGroup !== null && syncedActivitiesInGroup . length > 0 ) { if ( _ . isEmpty ( syncedActivitiesStored ) ) { syncedActivitiesStored = [ ] ; } this . _hasBeenSyncedActivities = _ . flatten ( _ . union ( syncedActivitiesInGroup , syncedActivitiesStored ) ) ; this . _hasBeenSyncedActivities = _ . sortBy ( this . _hasBeenSyncedActivities , ( item ) => { return ( new Date ( item . start_time ) ) . getTime ( ) ; } ) ; this . _hasBeenSyncedActivities = _ . uniqBy ( this . _hasBeenSyncedActivities , ( item ) => { return item . id ; } ) ; console . log ( "Updating synced activities to extension local storage." ) ; this . saveSyncedActivitiesToLocal ( this . _hasBeenSyncedActivities ) . then ( ( ) => { const notify = { step : "savedSyncedActivities" , progress : 100 , pageGroupId : handledGroupCount + 1 , browsedActivitiesCount : this . totalRawActivityIds . length , } ; deferred . notify ( notify ) ; if ( maxGroupCount > 0 && handledGroupCount >= maxGroupCount ) { console . log ( "Max group count of " + maxGroupCount + " reached. Handled group count: " + handledGroupCount ) ; deferred . resolve ( ) ; } else { this . computeActivitiesByGroupsOfPages ( lastSyncDateTime , fromPage + pagesPerGroupToRead , pagesPerGroupToRead , maxGroupCount , handledGroupCount , deferred ) ; } syncedActivitiesInGroup = null ; syncedActivitiesStored = null ; } ) ; } else { console . log ( "Group " + this . printGroupLimits ( fromPage , pagesPerGroupToRead ) + " handled" ) ; const notify = { step : "savedSyncedActivities" , progress : 100 , pageGroupId : handledGroupCount + 1 , browsedActivitiesCount : this . totalRawActivityIds . length , } ; deferred . notify ( notify ) ; if ( maxGroupCount > 0 && handledGroupCount >= maxGroupCount ) { console . log ( "Max group count of " + maxGroupCount + " reached. Handled group count: " + handledGroupCount ) ; deferred . resolve ( ) ; } else { this . computeActivitiesByGroupsOfPages ( lastSyncDateTime , fromPage + pagesPerGroupToRead , pagesPerGroupToRead , maxGroupCount , handledGroupCount , deferred ) ; } syncedActivitiesInGroup = null ; syncedActivitiesStored = null ; } } , ( err ) => { deferred . reject ( err ) ; } , ( progress ) => { deferred . notify ( progress ) ; } ) ; } return deferred . promise ; } return public sync ( fastSync ? ) { const deferred = Q . defer < SyncResultModel > ( ) ; let syncNotify = { } ; let activitiesChangesModel = { added : [ ] , edited : [ ] , deleted : [ ] } ; this . initializeForSync ( ) ; this . getLastSyncDateFromLocal ( ) . then ( ( savedLastSyncDateTime ) => { const lastSyncDateTime = ( _ . isNumber ( savedLastSyncDateTime ) ) ? new Date ( savedLastSyncDateTime ) : null ; if ( fastSync && fastSync === true ) { console . log ( "Fast sync mode enabled" ) ; return this . hasRemoteFirstPageActivitiesMismatch ( ) . then ( ( result ) => { if ( result . hasMisMatch ) { activitiesChangesModel = result . activitiesChangesModel ; console . log ( "Mismatch found between local and remote activities. Syncing first page only." ) ; const fromPage = 1 ; const pagesPerGroupToRead = 1 ; const maxGroupCount = 1 ; return this . computeActivitiesByGroupsOfPages ( lastSyncDateTime , fromPage , pagesPerGroupToRead , maxGroupCount ) ; } else { console . log ( "Local and remote activities count matches." ) ; return null ; } } ) ; } else { return this . computeActivitiesByGroupsOfPages ( lastSyncDateTime ) ; } } ) . then ( ( ) => { return this . getSyncedActivitiesFromLocal ( ) ; } ) . then ( ( syncedActivitiesStored ) => { if ( syncedActivitiesStored ) { if ( fastSync && fastSync === true ) { const hasEditedChanges = activitiesChangesModel . edited . length > 0 ; const hasDeletedChanges = activitiesChangesModel . deleted . length > 0 ; if ( hasEditedChanges ) { syncedActivitiesStored = this . applyEditedActivitiesChanges ( syncedActivitiesStored , activitiesChangesModel . edited ) ; } if ( hasDeletedChanges ) { syncedActivitiesStored = this . applyDeletedActivitiesChanges ( syncedActivitiesStored , activitiesChangesModel . deleted ) ; } return ( hasEditedChanges || hasDeletedChanges ) ? this . saveSyncedActivitiesToLocal ( syncedActivitiesStored ) : null ; } else { activitiesChangesModel = this . _activitiesChanges ; activitiesChangesModel . deleted = ActivitiesSynchronize . findDeletedActivities ( this . totalRawActivityIds , syncedActivitiesStored ) . deleted ; const hasEditedChanges = activitiesChangesModel . edited . length > 0 ; const hasDeletedChanges = activitiesChangesModel . deleted . length > 0 ; if ( hasEditedChanges ) { syncedActivitiesStored = this . applyEditedActivitiesChanges ( syncedActivitiesStored , activitiesChangesModel . edited ) ; } if ( hasDeletedChanges ) { syncedActivitiesStored = this . applyDeletedActivitiesChanges ( syncedActivitiesStored , activitiesChangesModel . deleted ) ; } return ( hasEditedChanges || hasDeletedChanges ) ? this . saveSyncedActivitiesToLocal ( syncedActivitiesStored ) : null ; } } else { return null ; } } ) . then ( ( ) => { return this . updateLastSyncDateToNow ( ) . then ( ( ) => { return this . getAllSavedLocal ( ) ; } ) ; } ) . then ( ( saved ) => { syncNotify . step = "updatingLastSyncDateTime" ; syncNotify . progress = 100 ; deferred . notify ( syncNotify ) ; console . log ( "Last sync date time saved: " , new Date ( saved . lastSyncDateTime ) ) ; const syncResult = { activitiesChangesModel : activitiesChangesModel , syncedActivities : saved . syncedActivities , lastSyncDateTime : saved . lastSyncDateTime } ; deferred . resolve ( syncResult ) ; } , ( err ) => { deferred . reject ( err ) ; } , ( progress ) => { syncNotify = { step : progress . step , progress : progress . progress , index : progress . index , activityId : progress . activityId , fromPage : progress . fromPage , toPage : progress . toPage , pageGroupId : ( progress . pageGroupId ) ? progress . pageGroupId : ( ( syncNotify && syncNotify . pageGroupId ) ? syncNotify . pageGroupId : 1 ) , browsedActivitiesCount : ( progress . browsedActivitiesCount ) ? progress . browsedActivitiesCount : ( ( syncNotify && syncNotify . browsedActivitiesCount ) ? syncNotify . browsedActivitiesCount : 0 ) , totalActivities : ( progress . totalActivities ) ? progress . totalActivities : ( ( syncNotify && syncNotify . totalActivities ) ? syncNotify . totalActivities : null ) , } ; deferred . notify ( syncNotify ) ; } ) ; return deferred . promise ; } private applyDeletedActivitiesChanges ( syncedActivitiesStored , deleted ) { _ . forEach ( deleted , ( deleteId ) => { syncedActivitiesStored = _ . without ( syncedActivitiesStored , _ . find ( syncedActivitiesStored , { id : deleteId , } ) ) ; } ) ; return syncedActivitiesStored ; } public applyEditedActivitiesChanges ( syncedActivitiesStored , edited ) { _ . forEach ( edited , ( editData ) => { const activityToEdit = _ . find ( ( syncedActivitiesStored ) , { id : editData . id } ) ; activityToEdit . name = editData . name ; activityToEdit . type = editData . type ; activityToEdit . display_type = editData . display_type ; } ) ; return syncedActivitiesStored ; } public updateLastSyncDateToNow ( ) { return this . saveLastSyncDateToLocal ( ( new Date ( ) ) . getTime ( ) ) ; } protected initializeForSync ( ) { this . _hasBeenSyncedActivities = null ; this . _activitiesChanges = { added : [ ] , deleted : [ ] , edited : [ ] , } ; this . _endReached = false ; this . totalRawActivityIds = [ ] ; } public getSleepTime ( ) { return ActivitiesSynchronize . SLEEP_TIME ; } public getAllSavedLocal ( ) { return BrowserStorage . getInstance ( ) . get < any > ( BrowserStorageType . LOCAL ) ; } public saveLastSyncDateToLocal ( timestamp ) { return BrowserStorage . getInstance ( ) . set < number > ( BrowserStorageType . LOCAL , ActivitiesSynchronize . LAST_SYNC_DATE_TIME_KEY , timestamp ) ; } public getLastSyncDateFromLocal ( ) { const deferred = Q . defer < number > ( ) ; BrowserStorage . getInstance ( ) . get < number > ( BrowserStorageType . LOCAL , ActivitiesSynchronize . LAST_SYNC_DATE_TIME_KEY ) . then ( ( result ) => { deferred . resolve ( result ) ; } , error => deferred . reject ( error ) ) ; return deferred . promise ; } public saveSyncedActivitiesToLocal ( syncedActivities ) { return BrowserStorage . getInstance ( ) . set < SyncedActivityModel [ ] > ( BrowserStorageType . LOCAL , ActivitiesSynchronize . SYNCED_ACTIVITIES_KEY , syncedActivities ) ; } public getSyncedActivitiesFromLocal ( ) { const deferred = Q . defer < SyncedActivityModel [ ] > ( ) ; BrowserStorage . getInstance ( ) . get < SyncedActivityModel [ ] > ( BrowserStorageType . LOCAL , ActivitiesSynchronize . SYNCED_ACTIVITIES_KEY ) . then ( ( result ) => { deferred . resolve ( result ) ; } , error => deferred . reject ( error ) ) ; return deferred . promise ; } }