import angular from 'angular' ; import autobind from 'autobind-decorator' ; import DOMPurify from 'dompurify' ; import { marked } from 'marked' ; import { ApiServiceStatus } from '../../../shared/api/api.enum' ; import { ApiService , ApiServiceInfo , ApiServiceInfoResponse } from '../../../shared/api/api.interface' ; import { ServiceOfflineError } from '../../../shared/errors/errors' ; import { ExceptionHandler } from '../../../shared/errors/errors.interface' ; import Globals from '../../../shared/global-shared.constants' ; import { PlatformService } from '../../../shared/global-shared.interface' ; import { LogService } from '../../../shared/log/log.service' ; import { StoreKey } from '../../../shared/store/store.enum' ; import { StoreService } from '../../../shared/store/store.service' ; import { Sync } from '../../../shared/sync/sync.interface' ; import { SyncService } from '../../../shared/sync/sync.service' ; import { UtilityService } from '../../../shared/utility/utility.service' ; import { WorkingService } from '../../../shared/working/working.service' ; import { RoutePath } from '../../app.enum' ; @ autobind export abstract class AppHelperService { Strings = require ( '../../../../../res/strings/en.json' ) ; $exceptionHandler : ExceptionHandler ; $location : ng . ILocationService ; $q : ng . IQService ; $timeout : ng . ITimeoutService ; apiSvc : ApiService ; logSvc : LogService ; platformSvc : PlatformService ; storeSvc : StoreService ; syncSvc : SyncService ; utilitySvc : UtilityService ; workingSvc : WorkingService ; constructor ( $exceptionHandler , $location , $q , $timeout , ApiSvc , LogSvc , PlatformSvc , StoreSvc , SyncSvc , UtilitySvc , WorkingSvc ) { this . $exceptionHandler = $exceptionHandler ; this . $location = $location ; this . $q = $q ; this . $timeout = $timeout ; this . apiSvc = ApiSvc ; this . logSvc = LogSvc ; this . platformSvc = PlatformSvc ; this . storeSvc = StoreSvc ; this . syncSvc = SyncSvc ; this . utilitySvc = UtilitySvc ; this . workingSvc = WorkingSvc ; } attachClickEventsToNewTabLinks ( element ? ) { this . $timeout ( ( ) => { const links = ( element ?? document ) . querySelectorAll ( 'a.new-tab' ) ; for ( let i = 0 ; i < links . length ; i += 1 ) { const link = links [ i ] as any ; link . onclick = this . openUrl ; } } , Globals . InterfaceReadyTimeout ) ; } abstract confirmBeforeSyncing ( ) ; abstract copyTextToClipboard ( text ) ; focusOnElement ( domSelector , select = false ) { this . $timeout ( ( ) => { const element = document . querySelector ( domSelector ) as HTMLInputElement ; if ( angular . isUndefined ( element ?? undefined ) ) { return ; } if ( select && element . select ) { element . select ( ) ; } else if ( element . setSelectionRange ) { const strLength = element . value . length * 2 ; element . focus ( ) ; element . setSelectionRange ( strLength , strLength ) ; } else if ( element . focus ) { element . focus ( ) ; } } , Globals . InterfaceReadyTimeout ) ; } formatServiceInfo ( serviceInfoResponse ? ) { return ( serviceInfoResponse ? this . $q . resolve ( serviceInfoResponse ) : this . apiSvc . checkServiceStatus ( ) ) . then ( ( response ) => { if ( angular . isUndefined ( response ?? undefined ) ) { return ; } let message = response . message ? marked ( response . message ) : '' ; if ( message ) { const messageDom = new DOMParser ( ) . parseFromString ( message , 'text/html' ) ; messageDom . querySelectorAll ( 'a' ) . forEach ( ( hyperlink ) => { hyperlink . className = 'new-tab' ; } ) ; message = DOMPurify . sanitize ( messageDom . body . firstElementChild . innerHTML ) ; } return { location : response . location , maxSyncSize : response . maxSyncSize / 1024 , message , status : response . status , version : response . version } ; } ) . catch ( ( err ) => { const status = err instanceof ServiceOfflineError ? ApiServiceStatus . Offline : ApiServiceStatus . Error ; return { status } ; } ) ; } abstract getHelpPages ( ) ; abstract getCurrentSync ( ) ; abstract getNextScheduledSyncUpdateCheck ( ) ; abstract getSyncQueueLength ( ) ; abstract openUrl ( event ? , url ? ) ; abstract removePermissions ( ) ; abstract requestPermissions ( ) ; switchView ( view ? ) { return this . $q ( ( resolve , reject ) => { return this . $q . resolve ( ) . then ( ( ) => { if ( ! angular . isUndefined ( view ) ) { return view ; } return this . $q . all ( [ this . storeSvc . get ( [ StoreKey . DisplayHelp , StoreKey . DisplayPermissions , StoreKey . DisplayUpdated , StoreKey . RemovedSync ] ) , this . utilitySvc . isSyncEnabled ( ) ] ) . then ( ( data ) => { const [ storeContent , syncEnabled ] = data ; switch ( true ) { case storeContent . displayUpdated : return RoutePath . Updated ; case storeContent . displayPermissions : return RoutePath . Permissions ; case storeContent . displayHelp : return RoutePath . Help ; case ! ! storeContent . removedSync : return RoutePath . SyncRemoved ; case syncEnabled : return RoutePath . Search ; default : return RoutePath . Login ; } } ) ; } ) . then ( ( newRoute ) => { this . $location . path ( newRoute ) ; this . $timeout ( resolve , Globals . InterfaceReadyTimeout ) ; } ) . catch ( reject ) ; } ) ; } syncBookmarksFailed ( err ) { return this . $q . resolve ( ) . then ( ( ) => { if ( this . syncSvc . shouldDisplayDefaultPageOnError ( err ) ) { return this . switchView ( ) ; } } ) ; } syncBookmarksSuccess ( ) { return this . switchView ( ) ; } updateServiceUrl ( newServiceUrl ) { return this . utilitySvc . updateServiceUrl ( newServiceUrl ) . then ( ( ) => this . formatServiceInfo ( ) ) ; } }