import { Injectable } from "@angular/core" ; import { LastSyncDateTimeDao } from "../../dao/sync/last-sync-date-time.dao" ; import { ActivityDao } from "../../dao/activity/activity.dao" ; import { saveAs } from "file-saver" ; import * as moment from "moment" ; import * as _ from "lodash" ; import { SyncState } from "./sync-state.enum" ; import { environment } from "../../../../environments/environment" ; import { AthleteModel , SyncedActivityModel } from "@elevate/shared/models" ; import { SyncedBackupModel } from "./synced-backup.model" ; import * as semver from "semver" ; import { AthleteService } from "../athlete/athlete.service" ; import { UserSettingsService } from "../user-settings/user-settings.service" ; import { Constant } from "@elevate/shared/constants" ; import { LoggerService } from "../logging/logger.service" ; @ Injectable ( ) export class SyncService { public static readonly SYNC_URL_BASE : string = "https://www.strava.com/dashboard" ; public static readonly SYNC_WINDOW_WIDTH : number = 690 ; public static readonly SYNC_WINDOW_HEIGHT : number = 720 ; constructor ( public lastSyncDateTimeDao , public activityDao , public athleteService , public userSettingsService , public logger ) { } returns { Promise < number > } public getLastSyncDateTime ( ) { return ( < Promise < number > > this . lastSyncDateTimeDao . fetch ( ) ) ; } param { number } value returns { Promise < number > } public saveLastSyncTime ( value ) { return ( < Promise < number > > this . lastSyncDateTimeDao . save ( value ) ) ; } returns { Promise < number > } public clearLastSyncTime ( ) { return this . lastSyncDateTimeDao . clear ( ) ; } param { SyncedBackupModel } importedBackupModel returns { Promise < SyncedBackupModel > } public import ( importedBackupModel ) { if ( _ . isEmpty ( importedBackupModel . syncedActivities ) ) { return Promise . reject ( "Activities are not defined or empty in provided backup file. Try to perform a clean full re-sync." ) ; } if ( _ . isEmpty ( importedBackupModel . pluginVersion ) ) { return Promise . reject ( "Plugin version is not defined in provided backup file. Try to perform a clean full re-sync." ) ; } if ( ! environment . skipRestoreSyncedBackupCheck ) { if ( semver . lt ( importedBackupModel . pluginVersion , this . getCompatibleBackupVersionThreshold ( ) ) ) { return Promise . reject ( "Imported backup version " + importedBackupModel . pluginVersion + " is not compatible with current installed version " + this . getAppVersion ( ) + "." ) ; } } return this . clearSyncedData ( ) . then ( ( ) => { let promiseImportDatedAthleteSettings ; if ( _ . isEmpty ( importedBackupModel . athleteModel ) ) { promiseImportDatedAthleteSettings = this . athleteService . resetSettings ( ) ; } else { promiseImportDatedAthleteSettings = this . athleteService . save ( importedBackupModel . athleteModel ) ; } return Promise . all ( [ this . saveLastSyncTime ( importedBackupModel . lastSyncDateTime ) , this . activityDao . save ( importedBackupModel . syncedActivities ) , promiseImportDatedAthleteSettings , this . userSettingsService . clearLocalStorageOnNextLoad ( ) ] ) ; } ) . then ( ( result ) => { const lastSyncDateTime = result [ 0 ] as number ; const syncedActivityModels = result [ 1 ] as SyncedActivityModel [ ] ; const athleteModel = result [ 2 ] as AthleteModel ; const backupModel = { lastSyncDateTime : lastSyncDateTime , syncedActivities : syncedActivityModels , athleteModel : athleteModel , pluginVersion : importedBackupModel . pluginVersion } ; return Promise . resolve ( backupModel ) ; } ) ; } returns { Promise < { filename : string ; size : number } > } public export ( ) { return this . prepareForExport ( ) . then ( ( backupModel ) => { const blob = new Blob ( [ JSON . stringify ( backupModel ) ] , { type : "application/json; charset=utf-8" } ) ; const filename = moment ( ) . format ( "Y.M.D-H.mm" ) + "_v" + backupModel . pluginVersion + ".history.json" ; this . saveAs ( blob , filename ) ; return Promise . resolve ( { filename : filename , size : blob . size } ) ; } , error => { return Promise . reject ( error ) ; } ) ; } returns { Promise < SyncedBackupModel > } public prepareForExport ( ) { return Promise . all ( [ this . lastSyncDateTimeDao . fetch ( ) , this . activityDao . fetch ( ) , this . athleteService . fetch ( ) ] ) . then ( ( result ) => { const lastSyncDateTime = result [ 0 ] as number ; const syncedActivityModels = result [ 1 ] as SyncedActivityModel [ ] ; const athleteModel = result [ 2 ] as AthleteModel ; if ( ! _ . isNumber ( lastSyncDateTime ) ) { return Promise . reject ( "Cannot export. No last synchronization date found." ) ; } const backupModel = { lastSyncDateTime : lastSyncDateTime , syncedActivities : syncedActivityModels , athleteModel : athleteModel , pluginVersion : this . getAppVersion ( ) } ; return Promise . resolve ( backupModel ) ; } ) ; } returns { Promise < void > } public clearSyncedData ( ) { return Promise . all ( [ this . clearLastSyncTime ( ) , this . activityDao . clear ( ) ] ) . then ( ( ) => { return Promise . resolve ( ) ; } ) . catch ( error => { this . logger . error ( error ) ; return Promise . reject ( "Athlete synced data has not been cleared totally. Some properties cannot be deleted. You may need to uninstall/install the software." ) ; } ) ; } returns { Promise < SyncState > } public getSyncState ( ) { return Promise . all ( [ this . getLastSyncDateTime ( ) , this . activityDao . fetch ( ) ] ) . then ( ( result ) => { const lastSyncDateTime = result [ 0 ] as number ; const syncedActivityModels = result [ 1 ] as SyncedActivityModel [ ] ; const hasLastSyncDateTime = _ . isNumber ( lastSyncDateTime ) ; const hasSyncedActivityModels = ! _ . isEmpty ( syncedActivityModels ) ; let syncState ; if ( ! hasLastSyncDateTime && ! hasSyncedActivityModels ) { syncState = SyncState . NOT_SYNCED ; } else if ( ! hasLastSyncDateTime && hasSyncedActivityModels ) { syncState = SyncState . PARTIALLY_SYNCED ; } else { syncState = SyncState . SYNCED ; } return Promise . resolve ( syncState ) ; } ) ; } param { boolean } fastSync param { boolean } forceSync public sync ( fastSync , forceSync ) { this . getCurrentTab ( ( tab ) => { const params = "?elevateSync=true&fastSync=" + fastSync + "&forceSync=" + forceSync + "&sourceTabId=" + tab . id ; const features = "width=" + SyncService . SYNC_WINDOW_WIDTH + ", height=" + SyncService . SYNC_WINDOW_HEIGHT + ", location=0" ; window . open ( SyncService . SYNC_URL_BASE + params , "_blank" , features ) ; } ) ; } param { ( tab : chrome . tabs . Tab ) => void } callback public getCurrentTab ( callback ) { chrome . tabs . getCurrent ( ( tab ) => { callback ( tab ) ; } ) ; } returns { string } public getAppVersion ( ) { return chrome . runtime . getManifest ( ) . version ; } returns { string } public getCompatibleBackupVersionThreshold ( ) { return Constant . COMPATIBLE_BACKUP_VERSION_THRESHOLD ; } param { Blob } blob param { string } filename public saveAs ( blob , filename ) { saveAs ( blob , filename ) ; } }