diff --git a/src/components/left/main/LeftMainHeader.tsx b/src/components/left/main/LeftMainHeader.tsx index 9c1dff9f6..ed3b7f39e 100644 --- a/src/components/left/main/LeftMainHeader.tsx +++ b/src/components/left/main/LeftMainHeader.tsx @@ -31,6 +31,7 @@ import { formatDateToString } from '../../../util/dateFormat'; import { setPermanentWebVersion } from '../../../util/permanentWebVersion'; import { clearWebsync } from '../../../util/websync'; import { + selectCanSetPasscode, selectCurrentMessageList, selectIsCurrentUserPremium, selectTabState, selectTheme, } from '../../../global/selectors'; import useLang from '../../../hooks/useLang'; @@ -82,7 +83,7 @@ type StateProps = isConnectionStatusMinimized: ISettings['isConnectionStatusMinimized']; areChatsLoaded?: boolean; hasPasscode?: boolean; - isAuthRememberMe?: boolean; + canSetPasscode?: boolean; } & Pick & Pick; @@ -114,7 +115,7 @@ const LeftMainHeader: FC = ({ isConnectionStatusMinimized, areChatsLoaded, hasPasscode, - isAuthRememberMe, + canSetPasscode, canInstall, archiveSettings, }) => { @@ -158,7 +159,7 @@ const LeftMainHeader: FC = ({ } }, [hasPasscode]); - useHotkeys(isAuthRememberMe ? { + useHotkeys(canSetPasscode ? { 'Ctrl+Shift+L': handleLockScreenHotkey, 'Alt+Shift+L': handleLockScreenHotkey, 'Meta+Shift+L': handleLockScreenHotkey, @@ -481,7 +482,7 @@ export default memo(withGlobal( hasPasscode: Boolean(global.passcode.hasPasscode), canInstall: Boolean(tabState.canInstall), archiveSettings, - isAuthRememberMe: global.authRememberMe, + canSetPasscode: selectCanSetPasscode(global), }; }, )(LeftMainHeader)); diff --git a/src/components/left/settings/SettingsPrivacy.tsx b/src/components/left/settings/SettingsPrivacy.tsx index 004747b5f..5decab197 100644 --- a/src/components/left/settings/SettingsPrivacy.tsx +++ b/src/components/left/settings/SettingsPrivacy.tsx @@ -5,7 +5,7 @@ import { getActions, withGlobal } from '../../../global'; import type { ApiPrivacySettings } from '../../../types'; import { SettingsScreens } from '../../../types'; -import { selectIsCurrentUserPremium } from '../../../global/selectors'; +import { selectCanSetPasscode, selectIsCurrentUserPremium } from '../../../global/selectors'; import useLang from '../../../hooks/useLang'; import useHistoryBack from '../../../hooks/useHistoryBack'; @@ -23,7 +23,7 @@ type StateProps = { isCurrentUserPremium?: boolean; hasPassword?: boolean; hasPasscode?: boolean; - isAuthRememberMe?: boolean; + canSetPasscode?: boolean; blockedCount: number; webAuthCount: number; isSensitiveEnabled?: boolean; @@ -63,7 +63,7 @@ const SettingsPrivacy: FC = ({ privacyPhoneP2P, onScreenSelect, onReset, - isAuthRememberMe, + canSetPasscode, }) => { const { loadPrivacySettings, @@ -160,7 +160,7 @@ const SettingsPrivacy: FC = ({ {lang('BlockedUsers')} {blockedCount || ''} - {isAuthRememberMe && ( + {canSetPasscode && ( ( privacyPhoneCall: privacy.phoneCall, privacyPhoneP2P: privacy.phoneP2P, canDisplayChatInTitle, - isAuthRememberMe: global.authRememberMe, + canSetPasscode: selectCanSetPasscode(global), }; }, )(SettingsPrivacy)); diff --git a/src/global/actions/ui/passcode.ts b/src/global/actions/ui/passcode.ts index 0b6579dc1..8eade0d40 100644 --- a/src/global/actions/ui/passcode.ts +++ b/src/global/actions/ui/passcode.ts @@ -3,14 +3,15 @@ import { addActionHandler, setGlobal, getGlobal } from '../../index'; import { clearPasscodeSettings, updatePasscodeSettings } from '../../reducers'; import { clearStoredSession, loadStoredSession, storeSession } from '../../../util/sessions'; import { - clearEncryptedSession, decryptSession, encryptSession, setupPasscode, + clearEncryptedSession, decryptSession, encryptSession, forgetPasscode, setupPasscode, } from '../../../util/passcode'; import { forceUpdateCache, migrateCache, serializeGlobal } from '../../cache'; import { onBeforeUnload } from '../../../util/schedulers'; import { cloneDeep } from '../../../util/iteratees'; import { INITIAL_GLOBAL_STATE } from '../../initialState'; import type { ActionReturnType } from '../../types'; -import { signalPasscodeHash } from '../../../util/establishMultitabRole'; +import { getCurrentTabId, signalPasscodeHash } from '../../../util/establishMultitabRole'; +import { SettingsScreens } from '../../../types'; let noLockOnUnload = false; onBeforeUnload(() => { @@ -21,7 +22,7 @@ onBeforeUnload(() => { }); addActionHandler('setPasscode', async (global, actions, payload): Promise => { - const { passcode } = payload; + const { passcode, tabId = getCurrentTabId() } = payload; global = updatePasscodeSettings(global, { isLoading: true, }); @@ -36,18 +37,34 @@ addActionHandler('setPasscode', async (global, actions, payload): Promise isLoading: false, })); - await encryptSession(sessionJson, globalJson); + try { + await encryptSession(sessionJson, globalJson); - signalPasscodeHash(); - global = getGlobal(); - global = updatePasscodeSettings(global, { - hasPasscode: true, - error: undefined, - isLoading: false, - }); - setGlobal(global); + signalPasscodeHash(); + global = getGlobal(); + global = updatePasscodeSettings(global, { + hasPasscode: true, + error: undefined, + isLoading: false, + }); + setGlobal(global); - forceUpdateCache(true); + forceUpdateCache(true); + } catch (err: any) { + forgetPasscode(); + + global = getGlobal(); + global = updatePasscodeSettings(global, { + isLoading: false, + }); + setGlobal(global); + + actions.showNotification({ + message: 'Failed to set passcode', + tabId, + }); + actions.requestNextSettingsScreen({ screen: SettingsScreens.PasscodeDisabled, tabId }); + } }); addActionHandler('clearPasscode', (global): ActionReturnType => { diff --git a/src/global/init.ts b/src/global/init.ts index ec76b41b8..ad9c79db1 100644 --- a/src/global/init.ts +++ b/src/global/init.ts @@ -16,6 +16,7 @@ import { getCurrentTabId, reestablishMasterToSelf } from '../util/establishMulti import { updateTabState } from './reducers/tabs'; import type { ActionReturnType, GlobalState } from './types'; import { isLocalMessageId } from './helpers'; +import { isCacheApiSupported } from '../util/cacheApi'; initCache(); @@ -129,6 +130,12 @@ addActionHandler('init', (global, actions, payload): ActionReturnType => { actions.initApi(); } + isCacheApiSupported().then((isSupported) => { + global = getGlobal(); + global.isCacheApiSupported = isSupported; + setGlobal(global); + }); + return updateTabState(global, { messageLists: parsedMessageList ? [parsedMessageList] : initialTabState.messageLists, }, tabId); diff --git a/src/global/selectors/settings.ts b/src/global/selectors/settings.ts index 7dd3fc834..8bd043da8 100644 --- a/src/global/selectors/settings.ts +++ b/src/global/selectors/settings.ts @@ -11,3 +11,7 @@ export function selectNotifyExceptions(global: T) { export function selectLanguageCode(global: T) { return global.settings.byKey.language.replace('-raw', ''); } + +export function selectCanSetPasscode(global: T) { + return global.authRememberMe && global.isCacheApiSupported; +} diff --git a/src/global/types.ts b/src/global/types.ts index be022c0ba..93a13bbed 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -579,6 +579,7 @@ export type GlobalState = { appConfig?: ApiAppConfig; hasWebAuthTokenFailed?: boolean; hasWebAuthTokenPasswordRequired?: true; + isCacheApiSupported?: boolean; connectionState?: ApiUpdateConnectionStateType; currentUserId?: string; isSyncing?: boolean; @@ -2334,7 +2335,7 @@ export interface ActionPayloads { connectToActivePhoneCall: undefined; // Passcode - setPasscode: { passcode: string }; + setPasscode: { passcode: string } & WithTabId; clearPasscode: undefined; lockScreen: undefined; decryptSession: { passcode: string }; diff --git a/src/util/cacheApi.ts b/src/util/cacheApi.ts index 717526031..4f8069e63 100644 --- a/src/util/cacheApi.ts +++ b/src/util/cacheApi.ts @@ -1,6 +1,13 @@ // eslint-disable-next-line no-restricted-globals const cacheApi = self.caches; +let isSupported: boolean | undefined; + +export async function isCacheApiSupported() { + isSupported = isSupported ?? await cacheApi.has('test').then(() => true).catch(() => false); + return isSupported; +} + export enum Type { Text, Blob, @@ -67,7 +74,7 @@ export async function fetch( export async function save(cacheName: string, key: string, data: AnyLiteral | Blob | ArrayBuffer | string) { if (!cacheApi) { - return undefined; + return false; } try { @@ -78,11 +85,12 @@ export async function save(cacheName: string, key: string, data: AnyLiteral | Bl const request = new Request(key.replace(/:/g, '_')); const response = new Response(cacheData); const cache = await cacheApi.open(cacheName); - return await cache.put(request, response); + await cache.put(request, response); + return true; } catch (err) { // eslint-disable-next-line no-console console.warn(err); - return undefined; + return false; } } diff --git a/src/util/passcode.ts b/src/util/passcode.ts index cea08d25d..46a0df27f 100644 --- a/src/util/passcode.ts +++ b/src/util/passcode.ts @@ -117,7 +117,12 @@ function sha256(plaintext: string) { } async function store(key: string, value: ArrayBuffer) { - await cacheApi.save(PASSCODE_CACHE_NAME, key, value); + const isSuccessful = await cacheApi.save(PASSCODE_CACHE_NAME, key, value); + if (isSuccessful) { + return; + } + + throw new Error('Failed to save to cache'); } function load(key: string) {