import { WebApplication } from '@/ui_models/application'; import { isDesktopApplication } from '@/utils'; import { AppStateEvent } from '@/ui_models/app_state'; const MILLISECONDS_PER_SECOND = 1000; const FOCUS_POLL_INTERVAL = 1 * MILLISECONDS_PER_SECOND; const LOCK_INTERVAL_NONE = 0; const LOCK_INTERVAL_IMMEDIATE = 1; const LOCK_INTERVAL_ONE_MINUTE = 60 * MILLISECONDS_PER_SECOND; const LOCK_INTERVAL_FIVE_MINUTES = 300 * MILLISECONDS_PER_SECOND; const LOCK_INTERVAL_ONE_HOUR = 3600 * MILLISECONDS_PER_SECOND; const STORAGE_KEY_AUTOLOCK_INTERVAL = "AutoLockIntervalKey"; export class LockManager { private application: WebApplication private unsubState: any private pollFocusInterval: any private lastFocusState?: 'hidden' | 'visible' private lockAfterDate?: Date private lockTimeout?: any constructor(application: WebApplication) { this.application = application; setImmediate(() => { this.observeVisibility(); }); } observeVisibility() { this.unsubState = this.application.getAppState().addObserver( async (eventName) => { if (eventName === AppStateEvent.WindowDidBlur) { this.documentVisibilityChanged(false); } else if (eventName === AppStateEvent.WindowDidFocus) { this.documentVisibilityChanged(true); } } ); if (!isDesktopApplication()) { this.beginWebFocusPolling(); } } deinit() { this.unsubState(); if (this.pollFocusInterval) { clearInterval(this.pollFocusInterval); } } async setAutoLockInterval(interval: number) { return this.application!.setValue( STORAGE_KEY_AUTOLOCK_INTERVAL, interval ); } async getAutoLockInterval() { const interval = await this.application!.getValue( STORAGE_KEY_AUTOLOCK_INTERVAL ); if (interval) { return interval; } else { return LOCK_INTERVAL_NONE; } } /** * Verify document is in focus every so often as visibilitychange event is * not triggered on a typical window blur event but rather on tab changes. */ beginWebFocusPolling() { this.pollFocusInterval = setInterval(() => { const hasFocus = document.hasFocus(); if (hasFocus && this.lastFocusState === 'hidden') { this.documentVisibilityChanged(true); } else if (!hasFocus && this.lastFocusState === 'visible') { this.documentVisibilityChanged(false); } /* Save this to compare against next time around */ this.lastFocusState = hasFocus ? 'visible' : 'hidden'; }, FOCUS_POLL_INTERVAL); } getAutoLockIntervalOptions() { return [ { value: LOCK_INTERVAL_NONE, label: "Off" }, { value: LOCK_INTERVAL_IMMEDIATE, label: "Immediately" }, { value: LOCK_INTERVAL_ONE_MINUTE, label: "1m" }, { value: LOCK_INTERVAL_FIVE_MINUTES, label: "5m" }, { value: LOCK_INTERVAL_ONE_HOUR, label: "1h" } ]; } async documentVisibilityChanged(visible: boolean) { if (visible) { const locked = await this.application.isLocked(); if ( !locked && this.lockAfterDate && new Date() > this.lockAfterDate ) { this.application.lock(); } this.cancelAutoLockTimer(); } else { this.beginAutoLockTimer(); } } async beginAutoLockTimer() { var interval = await this.getAutoLockInterval(); if (interval === LOCK_INTERVAL_NONE) { return; } /** * Use a timeout if possible, but if the computer is put to sleep, timeouts won't * work. Need to set a date as backup. this.lockAfterDate does not need to be * persisted, as living in memory is sufficient. If memory is cleared, then the * application will lock anyway. */ const addToNow = (seconds: number) => { const date = new Date(); date.setSeconds(date.getSeconds() + seconds); return date; }; this.lockAfterDate = addToNow(interval / MILLISECONDS_PER_SECOND); this.lockTimeout = setTimeout(() => { this.cancelAutoLockTimer(); this.application.lock(); this.lockAfterDate = undefined; }, interval); } cancelAutoLockTimer() { clearTimeout(this.lockTimeout); this.lockAfterDate = undefined; } }