import { Injectable } from "@angular/core" ; import { AlertService } from "../../alerts/alert.service" ; import { SessionService } from "./session.service" ; import { LocalSession } from "./local-session" ; import { RemoteSession } from "./remote-session" ; import { LoginState } from "../session-states/login-state.enum" ; import { Database } from "../../database/database" ; import { PouchDatabase } from "../../database/pouch-database" ; import { SyncState } from "../session-states/sync-state.enum" ; import { EntitySchemaService } from "../../entity/schema/entity-schema.service" ; import { LoggingService } from "../../logging/logging.service" ; import { HttpClient } from "@angular/common/http" ; import PouchDB from "pouchdb-browser" ; import { AppConfig } from "../../app-config/app-config" ; import { DatabaseUser } from "./local-user" ; import { waitForChangeTo } from "../session-states/session-utils" ; /**
 * A synced session creates and manages a LocalSession and a RemoteSession
 * and handles the setup of synchronisation.
 *
 * also see
 * [Session Handling, Authentication & Synchronisation] {@link /additional-documentation/concepts/session-and-authentication-system.html} @ Injectable ( ) export class SyncedSessionService extends SessionService { private readonly LOGIN_RETRY_TIMEOUT = 60000 ; private readonly POUCHDB_SYNC_BATCH_SIZE = 500 ; private readonly _localSession : LocalSession ; private readonly _remoteSession : RemoteSession ; private readonly pouchDB : PouchDB . Database ; private readonly database : Database ; private _liveSyncHandle : any ; private _liveSyncScheduledHandle : any ; private _offlineRetryLoginScheduleHandle : any ; constructor ( private _alertService , private _loggingService , private _entitySchemaService , private _httpClient ) { super ( ) ; this . pouchDB = new PouchDB ( AppConfig . settings . database . name ) ; this . database = new PouchDatabase ( this . pouchDB , this . _loggingService ) ; this . _localSession = new LocalSession ( this . database ) ; this . _remoteSession = new RemoteSession ( this . _httpClient , _loggingService ) ; } param username param password returns public async login ( username , password ) { this . cancelLoginOfflineRetry ( ) ; this . syncState . next ( SyncState . UNSYNCED ) ; const remoteLogin = this . _remoteSession . login ( username , password ) ; const syncPromise = this . _remoteSession . loginState . pipe ( waitForChangeTo ( LoginState . LOGGED_IN ) ) . toPromise ( ) . then ( ( ) => this . updateLocalUserAndStartSync ( password ) ) ; const localLoginState = await this . _localSession . login ( username , password ) ; if ( localLoginState === LoginState . LOGGED_IN ) { this . loginState . next ( LoginState . LOGGED_IN ) ; remoteLogin . then ( ( loginState ) => { if ( loginState === LoginState . LOGIN_FAILED ) { this . handleRemotePasswordChange ( username ) ; } if ( loginState === LoginState . UNAVAILABLE ) { this . retryLoginWhileOffline ( username , password ) ; } } ) ; } else { const remoteLoginState = await remoteLogin ; if ( remoteLoginState === LoginState . LOGGED_IN ) { await syncPromise ; const localLoginRetry = await this . _localSession . login ( username , password ) ; this . loginState . next ( localLoginRetry ) ; } else if ( remoteLoginState === LoginState . UNAVAILABLE && localLoginState === LoginState . UNAVAILABLE ) { this . loginState . next ( LoginState . UNAVAILABLE ) ; } else { this . loginState . next ( LoginState . LOGIN_FAILED ) ; } } return this . loginState . value ; } private handleRemotePasswordChange ( username ) { this . _localSession . logout ( ) ; this . _localSession . removeUser ( username ) ; this . loginState . next ( LoginState . LOGIN_FAILED ) ; this . _alertService . addDanger ( $localize `Your password was changed recently. Please retry with your new password!` ) ; } private retryLoginWhileOffline ( username , password ) { this . _offlineRetryLoginScheduleHandle = setTimeout ( ( ) => { this . login ( username , password ) ; } , this . LOGIN_RETRY_TIMEOUT ) ; } private updateLocalUserAndStartSync ( password ) { const remoteUser = this . _remoteSession . getCurrentUser ( ) ; this . _localSession . saveUser ( remoteUser , password ) ; return this . sync ( ) . then ( ( ) => this . liveSyncDeferred ( ) ) . catch ( ( ) => { if ( this . _localSession . loginState . value === LoginState . LOGGED_IN ) { this . liveSyncDeferred ( ) ; } } ) ; } public getCurrentUser ( ) { return this . _localSession . getCurrentUser ( ) ; } public checkPassword ( username , password ) { return this . _localSession . checkPassword ( username , password ) ; } /** see  SessionService public async sync ( ) { this . syncState . next ( SyncState . STARTED ) ; try { const result = await this . pouchDB . sync ( this . _remoteSession . pouchDB , { batch_size : this . POUCHDB_SYNC_BATCH_SIZE , } ) ; this . syncState . next ( SyncState . COMPLETED ) ; return result ; } catch ( error ) { this . syncState . next ( SyncState . FAILED ) ; throw error ; } } /**
     * Start live sync in background.
     */ public liveSync ( ) { this . cancelLiveSync ( ) ; this . syncState . next ( SyncState . STARTED ) ; this . _liveSyncHandle = ( this . pouchDB . sync ( this . _remoteSession . pouchDB , { live : true , retry : true , } ) as any ) . on ( "change" , ( change ) => { } ) . on ( "paused" , ( info ) => { if ( this . _remoteSession . loginState . value === LoginState . LOGGED_IN ) { this . syncState . next ( SyncState . COMPLETED ) ; } } ) . on ( "active" , ( info ) => { this . syncState . next ( SyncState . STARTED ) ; } ) . on ( "error" , ( err ) => { console . error ( "sync failed" , err ) ; this . syncState . next ( SyncState . FAILED ) ; } ) . on ( "complete" , ( info ) => { this . _liveSyncHandle = null ; } ) ; return this . _liveSyncHandle ; } param timeout public liveSyncDeferred ( timeout = 1000 ) { this . cancelLiveSync ( ) ; this . _liveSyncScheduledHandle = setTimeout ( ( ) => this . liveSync ( ) , timeout ) ; } /**
     * Cancels a pending login retry scheduled to start in the future.
     */ public cancelLoginOfflineRetry ( ) { if ( this . _offlineRetryLoginScheduleHandle ) { clearTimeout ( this . _offlineRetryLoginScheduleHandle ) ; } } /**
     * Cancels a currently running liveSync or a liveSync scheduled to start in the future.
     */ public cancelLiveSync ( ) { if ( this . _liveSyncScheduledHandle ) { clearTimeout ( this . _liveSyncScheduledHandle ) ; } if ( this . _liveSyncHandle ) { this . _liveSyncHandle . cancel ( ) ; } } /**
     * Get the local database instance that should be used for regular data access.
     * als see  SessionService public getDatabase ( ) { return this . database ; } /**
     * Logout and stop any existing sync.
     * also see  SessionService public logout ( ) { this . cancelLoginOfflineRetry ( ) ; this . cancelLiveSync ( ) ; this . loginState . next ( LoginState . LOGGED_OUT ) ; this . _localSession . logout ( ) ; this . _remoteSession . logout ( ) ; } }