import { ApiService } from "../abstractions/api.service" ; import { CipherService } from "../abstractions/cipher.service" ; import { CollectionService } from "../abstractions/collection.service" ; import { CryptoService } from "../abstractions/crypto.service" ; import { FolderService } from "../abstractions/folder.service" ; import { KeyConnectorService } from "../abstractions/keyConnector.service" ; import { LogService } from "../abstractions/log.service" ; import { MessagingService } from "../abstractions/messaging.service" ; import { OrganizationService } from "../abstractions/organization.service" ; import { PolicyService } from "../abstractions/policy.service" ; import { ProviderService } from "../abstractions/provider.service" ; import { SendService } from "../abstractions/send.service" ; import { SettingsService } from "../abstractions/settings.service" ; import { StateService } from "../abstractions/state.service" ; import { SyncService as SyncServiceAbstraction } from "../abstractions/sync.service" ; import { CipherData } from "../models/data/cipherData" ; import { CollectionData } from "../models/data/collectionData" ; import { FolderData } from "../models/data/folderData" ; import { OrganizationData } from "../models/data/organizationData" ; import { PolicyData } from "../models/data/policyData" ; import { ProviderData } from "../models/data/providerData" ; import { SendData } from "../models/data/sendData" ; import { CipherResponse } from "../models/response/cipherResponse" ; import { CollectionDetailsResponse } from "../models/response/collectionResponse" ; import { DomainsResponse } from "../models/response/domainsResponse" ; import { FolderResponse } from "../models/response/folderResponse" ; import { SyncCipherNotification , SyncFolderNotification , SyncSendNotification , } from "../models/response/notificationResponse" ; import { PolicyResponse } from "../models/response/policyResponse" ; import { ProfileResponse } from "../models/response/profileResponse" ; import { SendResponse } from "../models/response/sendResponse" ; export class SyncService implements SyncServiceAbstraction { syncInProgress : boolean = false ; constructor ( private apiService , private settingsService , private folderService , private cipherService , private cryptoService , private collectionService , private messagingService , private policyService , private sendService , private logService , private keyConnectorService , private stateService , private organizationService , private providerService , private logoutCallback ) { } async getLastSync ( ) { if ( ( await this . stateService . getUserId ( ) ) == null ) { return null ; } const lastSync = await this . stateService . getLastSync ( ) ; if ( lastSync ) { return new Date ( lastSync ) ; } return null ; } async setLastSync ( date , userId ? ) { await this . stateService . setLastSync ( date . toJSON ( ) , { userId : userId } ) ; } async fullSync ( forceSync , allowThrowOnError = false ) { this . syncStarted ( ) ; const isAuthenticated = await this . stateService . getIsAuthenticated ( ) ; if ( ! isAuthenticated ) { return this . syncCompleted ( false ) ; } const now = new Date ( ) ; let needsSync = false ; try { needsSync = await this . needsSyncing ( forceSync ) ; } catch ( e ) { if ( allowThrowOnError ) { throw e ; } } if ( ! needsSync ) { await this . setLastSync ( now ) ; return this . syncCompleted ( false ) ; } const userId = await this . stateService . getUserId ( ) ; try { await this . apiService . refreshIdentityToken ( ) ; const response = await this . apiService . getSync ( ) ; await this . syncProfile ( response . profile ) ; await this . syncFolders ( userId , response . folders ) ; await this . syncCollections ( response . collections ) ; await this . syncCiphers ( userId , response . ciphers ) ; await this . syncSends ( userId , response . sends ) ; await this . syncSettings ( response . domains ) ; await this . syncPolicies ( response . policies ) ; await this . setLastSync ( now ) ; return this . syncCompleted ( true ) ; } catch ( e ) { if ( allowThrowOnError ) { throw e ; } else { return this . syncCompleted ( false ) ; } } } async syncUpsertFolder ( notification , isEdit ) { this . syncStarted ( ) ; if ( await this . stateService . getIsAuthenticated ( ) ) { try { const localFolder = await this . folderService . get ( notification . id ) ; if ( ( ! isEdit && localFolder == null ) || ( isEdit && localFolder != null && localFolder . revisionDate < notification . revisionDate ) ) { const remoteFolder = await this . apiService . getFolder ( notification . id ) ; if ( remoteFolder != null ) { const userId = await this . stateService . getUserId ( ) ; await this . folderService . upsert ( new FolderData ( remoteFolder , userId ) ) ; this . messagingService . send ( "syncedUpsertedFolder" , { folderId : notification . id } ) ; return this . syncCompleted ( true ) ; } } } catch ( e ) { this . logService . error ( e ) ; } } return this . syncCompleted ( false ) ; } async syncDeleteFolder ( notification ) { this . syncStarted ( ) ; if ( await this . stateService . getIsAuthenticated ( ) ) { await this . folderService . delete ( notification . id ) ; this . messagingService . send ( "syncedDeletedFolder" , { folderId : notification . id } ) ; this . syncCompleted ( true ) ; return true ; } return this . syncCompleted ( false ) ; } async syncUpsertCipher ( notification , isEdit ) { this . syncStarted ( ) ; if ( await this . stateService . getIsAuthenticated ( ) ) { try { let shouldUpdate = true ; const localCipher = await this . cipherService . get ( notification . id ) ; if ( localCipher != null && localCipher . revisionDate >= notification . revisionDate ) { shouldUpdate = false ; } let checkCollections = false ; if ( shouldUpdate ) { if ( isEdit ) { shouldUpdate = localCipher != null ; checkCollections = true ; } else { if ( notification . collectionIds == null || notification . organizationId == null ) { shouldUpdate = localCipher == null ; } else { shouldUpdate = false ; checkCollections = true ; } } } if ( ! shouldUpdate && checkCollections && notification . organizationId != null && notification . collectionIds != null && notification . collectionIds . length > 0 ) { const collections = await this . collectionService . getAll ( ) ; if ( collections != null ) { for ( let i = 0 ; i < collections . length ; i ++ ) { if ( notification . collectionIds . indexOf ( collections [ i ] . id ) > - 1 ) { shouldUpdate = true ; break ; } } } } if ( shouldUpdate ) { const remoteCipher = await this . apiService . getCipher ( notification . id ) ; if ( remoteCipher != null ) { const userId = await this . stateService . getUserId ( ) ; await this . cipherService . upsert ( new CipherData ( remoteCipher , userId ) ) ; this . messagingService . send ( "syncedUpsertedCipher" , { cipherId : notification . id } ) ; return this . syncCompleted ( true ) ; } } } catch ( e ) { if ( e != null && e . statusCode === 404 && isEdit ) { await this . cipherService . delete ( notification . id ) ; this . messagingService . send ( "syncedDeletedCipher" , { cipherId : notification . id } ) ; return this . syncCompleted ( true ) ; } } } return this . syncCompleted ( false ) ; } async syncDeleteCipher ( notification ) { this . syncStarted ( ) ; if ( await this . stateService . getIsAuthenticated ( ) ) { await this . cipherService . delete ( notification . id ) ; this . messagingService . send ( "syncedDeletedCipher" , { cipherId : notification . id } ) ; return this . syncCompleted ( true ) ; } return this . syncCompleted ( false ) ; } async syncUpsertSend ( notification , isEdit ) { this . syncStarted ( ) ; if ( await this . stateService . getIsAuthenticated ( ) ) { try { const localSend = await this . sendService . get ( notification . id ) ; if ( ( ! isEdit && localSend == null ) || ( isEdit && localSend != null && localSend . revisionDate < notification . revisionDate ) ) { const remoteSend = await this . apiService . getSend ( notification . id ) ; if ( remoteSend != null ) { const userId = await this . stateService . getUserId ( ) ; await this . sendService . upsert ( new SendData ( remoteSend , userId ) ) ; this . messagingService . send ( "syncedUpsertedSend" , { sendId : notification . id } ) ; return this . syncCompleted ( true ) ; } } } catch ( e ) { this . logService . error ( e ) ; } } return this . syncCompleted ( false ) ; } async syncDeleteSend ( notification ) { this . syncStarted ( ) ; if ( await this . stateService . getIsAuthenticated ( ) ) { await this . sendService . delete ( notification . id ) ; this . messagingService . send ( "syncedDeletedSend" , { sendId : notification . id } ) ; this . syncCompleted ( true ) ; return true ; } return this . syncCompleted ( false ) ; } private syncStarted ( ) { this . syncInProgress = true ; this . messagingService . send ( "syncStarted" ) ; } private syncCompleted ( successfully ) { this . syncInProgress = false ; this . messagingService . send ( "syncCompleted" , { successfully : successfully } ) ; return successfully ; } private async needsSyncing ( forceSync ) { if ( forceSync ) { return true ; } const lastSync = await this . getLastSync ( ) ; if ( lastSync == null || lastSync . getTime ( ) === 0 ) { return true ; } const response = await this . apiService . getAccountRevisionDate ( ) ; if ( new Date ( response ) <= lastSync ) { return false ; } return true ; } private async syncProfile ( response ) { const stamp = await this . stateService . getSecurityStamp ( ) ; if ( stamp != null && stamp !== response . securityStamp ) { if ( this . logoutCallback != null ) { await this . logoutCallback ( true ) ; } throw new Error ( "Stamp has changed" ) ; } await this . cryptoService . setEncKey ( response . key ) ; await this . cryptoService . setEncPrivateKey ( response . privateKey ) ; await this . cryptoService . setProviderKeys ( response . providers ) ; await this . cryptoService . setOrgKeys ( response . organizations , response . providerOrganizations ) ; await this . stateService . setSecurityStamp ( response . securityStamp ) ; await this . stateService . setEmailVerified ( response . emailVerified ) ; await this . stateService . setForcePasswordReset ( response . forcePasswordReset ) ; await this . keyConnectorService . setUsesKeyConnector ( response . usesKeyConnector ) ; const organizations = { } ; response . organizations . forEach ( ( o ) => { organizations [ o . id ] = new OrganizationData ( o ) ; } ) ; const providers = { } ; response . providers . forEach ( ( p ) => { providers [ p . id ] = new ProviderData ( p ) ; } ) ; response . providerOrganizations . forEach ( ( o ) => { if ( organizations [ o . id ] == null ) { organizations [ o . id ] = new OrganizationData ( o ) ; organizations [ o . id ] . isProviderUser = true ; } } ) ; await Promise . all ( [ this . organizationService . save ( organizations ) , this . providerService . save ( providers ) , ] ) ; if ( await this . keyConnectorService . userNeedsMigration ( ) ) { this . messagingService . send ( "convertAccountToKeyConnector" ) ; } else { this . keyConnectorService . removeConvertAccountRequired ( ) ; } } private async syncFolders ( userId , response ) { const folders = { } ; response . forEach ( ( f ) => { folders [ f . id ] = new FolderData ( f , userId ) ; } ) ; return await this . folderService . replace ( folders ) ; } private async syncCollections ( response ) { const collections = { } ; response . forEach ( ( c ) => { collections [ c . id ] = new CollectionData ( c ) ; } ) ; return await this . collectionService . replace ( collections ) ; } private async syncCiphers ( userId , response ) { const ciphers = { } ; response . forEach ( ( c ) => { ciphers [ c . id ] = new CipherData ( c , userId ) ; } ) ; return await this . cipherService . replace ( ciphers ) ; } private async syncSends ( userId , response ) { const sends = { } ; response . forEach ( ( s ) => { sends [ s . id ] = new SendData ( s , userId ) ; } ) ; return await this . sendService . replace ( sends ) ; } private async syncSettings ( response ) { let eqDomains = [ ] ; if ( response != null && response . equivalentDomains != null ) { eqDomains = eqDomains . concat ( response . equivalentDomains ) ; } if ( response != null && response . globalEquivalentDomains != null ) { response . globalEquivalentDomains . forEach ( ( global ) => { if ( global . domains . length > 0 ) { eqDomains . push ( global . domains ) ; } } ) ; } return this . settingsService . setEquivalentDomains ( eqDomains ) ; } private async syncPolicies ( response ) { const policies = { } ; if ( response != null ) { response . forEach ( ( p ) => { policies [ p . id ] = new PolicyData ( p ) ; } ) ; } return await this . policyService . replace ( policies ) ; } }