import type { ActionReturnType } from '../../types'; import { SettingsScreens } from '../../../types'; import { IS_SCREEN_LOCKED_CACHE_KEY } from '../../../config'; import { getCurrentTabId, signalPasscodeHash } from '../../../util/establishMultitabRole'; import { cloneDeep } from '../../../util/iteratees'; import { clearEncryptedSession, encryptSession, forgetPasscode, setupPasscode, } from '../../../util/passcode'; import { onBeforeUnload } from '../../../util/schedulers'; import { clearStoredSession, loadStoredSession, storeSession } from '../../../util/sessions'; import { forceUpdateCache, migrateCache, serializeGlobal } from '../../cache'; import { addActionHandler, getGlobal, setGlobal } from '../../index'; import { INITIAL_GLOBAL_STATE } from '../../initialState'; import { clearPasscodeSettings, updatePasscodeSettings } from '../../reducers'; let noLockOnUnload = false; onBeforeUnload(() => { const global = getGlobal(); if (!global.isInited) return; if (global.passcode.hasPasscode && !noLockOnUnload && Object.keys(global.byTabId).length === 1) { clearStoredSession(); } }); addActionHandler('setPasscode', async (global, actions, payload): Promise => { const { passcode, tabId = getCurrentTabId() } = payload; global = updatePasscodeSettings(global, { isLoading: true, }); setGlobal(global); await setupPasscode(passcode); const sessionJson = JSON.stringify({ ...loadStoredSession(), userId: global.currentUserId }); global = getGlobal(); const globalJson = serializeGlobal(updatePasscodeSettings(global, { hasPasscode: true, error: undefined, isLoading: false, })); try { await encryptSession(sessionJson, globalJson); signalPasscodeHash(); global = getGlobal(); global = updatePasscodeSettings(global, { hasPasscode: true, error: undefined, isLoading: false, }); setGlobal(global); forceUpdateCache(true); } catch (err: any) { forgetPasscode(); global = getGlobal(); global = updatePasscodeSettings(global, { isLoading: false, }); setGlobal(global); actions.showNotification({ message: 'Failed to set passcode', tabId, }); actions.openSettingsScreen({ screen: SettingsScreens.PasscodeDisabled, tabId }); } }); addActionHandler('clearPasscode', (global): ActionReturnType => { void clearEncryptedSession(); localStorage.removeItem(IS_SCREEN_LOCKED_CACHE_KEY); return clearPasscodeSettings(global); }); addActionHandler('unlockScreen', (global, actions, payload): ActionReturnType => { const beforeTabStates = Object.values(global.byTabId); const { sessionJson, globalJson } = payload; const session = JSON.parse(sessionJson); storeSession(session); const previousGlobal = global; global = JSON.parse(globalJson); global.byTabId = previousGlobal.byTabId; migrateCache(global, cloneDeep(INITIAL_GLOBAL_STATE)); global = updatePasscodeSettings( global, { isScreenLocked: false, error: undefined, invalidAttemptsCount: 0, }, ); setGlobal(global); signalPasscodeHash(); beforeTabStates.forEach(({ id: tabId, isMasterTab }) => actions.init({ tabId, isMasterTab })); actions.initApi(); }); const MAX_INVALID_ATTEMPTS = 5; const TIMEOUT_RESET_INVALID_ATTEMPTS_MS = 1000 * 15;// 180000; // 3 minutes addActionHandler('logInvalidUnlockAttempt', (global): ActionReturnType => { const invalidAttemptsCount = (global.passcode?.invalidAttemptsCount ?? 0) + 1; return updatePasscodeSettings(global, { invalidAttemptsCount, timeoutUntil: (invalidAttemptsCount >= MAX_INVALID_ATTEMPTS ? Date.now() + TIMEOUT_RESET_INVALID_ATTEMPTS_MS : undefined), }); }); addActionHandler('resetInvalidUnlockAttempts', (global): ActionReturnType => { return updatePasscodeSettings(global, { invalidAttemptsCount: 0, timeoutUntil: undefined, }); }); addActionHandler('setPasscodeError', (global, actions, payload): ActionReturnType => { const { error } = payload; return updatePasscodeSettings(global, { error }); }); addActionHandler('clearPasscodeError', (global): ActionReturnType => { return updatePasscodeSettings(global, { error: undefined }); }); addActionHandler('skipLockOnUnload', (): ActionReturnType => { noLockOnUnload = true; });