/* Copyright 2024 New Vector Ltd. Copyright 2019-2021 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ import { IndexedDBStore, IndexedDBCryptoStore } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { getIDBFactory } from "./StorageAccess"; const localStorage = window.localStorage; // The JS SDK will add a prefix of "matrix-js-sdk:" to the sync store name. const SYNC_STORE_NAME = "riot-web-sync"; const LEGACY_CRYPTO_STORE_NAME = "matrix-js-sdk:crypto"; const RUST_CRYPTO_STORE_NAME = "matrix-js-sdk::matrix-sdk-crypto"; function log(msg: string): void { logger.log(`StorageManager: ${msg}`); } function error(msg: string, ...args: any[]): void { logger.error(`StorageManager: ${msg}`, ...args); } export function tryPersistStorage(): void { if (navigator.storage && navigator.storage.persist) { navigator.storage.persist().then((persistent) => { logger.log("StorageManager: Persistent?", persistent); }); } else if (document.requestStorageAccess) { // Safari document.requestStorageAccess().then( () => logger.log("StorageManager: Persistent?", true), () => logger.log("StorageManager: Persistent?", false), ); } else { logger.log("StorageManager: Persistence unsupported"); } } export async function checkConsistency(): Promise<{ healthy: boolean; cryptoInited: boolean; dataInCryptoStore: boolean; dataInLocalStorage: boolean; }> { log("Checking storage consistency"); log(`Local storage supported? ${!!localStorage}`); log(`IndexedDB supported? ${!!getIDBFactory()}`); let dataInLocalStorage = false; let dataInCryptoStore = false; let cryptoInited = false; let healthy = true; if (localStorage) { dataInLocalStorage = localStorage.length > 0; log(`Local storage contains data? ${dataInLocalStorage}`); cryptoInited = !!localStorage.getItem("mx_crypto_initialised"); log(`Crypto initialised? ${cryptoInited}`); } else { healthy = false; error("Local storage cannot be used on this browser"); } if (getIDBFactory() && localStorage) { const results = await checkSyncStore(); if (!results.healthy) { healthy = false; } } else { healthy = false; error("Sync store cannot be used on this browser"); } if (getIDBFactory()) { const results = await checkCryptoStore(); dataInCryptoStore = results.exists; if (!results.healthy) { healthy = false; } } else { healthy = false; error("Crypto store cannot be used on this browser"); } if (dataInLocalStorage && cryptoInited && !dataInCryptoStore) { healthy = false; error( "Data exists in local storage and crypto is marked as initialised " + " but no data found in crypto store. " + "IndexedDB storage has likely been evicted by the browser!", ); } if (healthy) { log("Storage consistency checks passed"); } else { error("Storage consistency checks failed"); } return { dataInLocalStorage, dataInCryptoStore, cryptoInited, healthy, }; } interface StoreCheck { exists: boolean; healthy: boolean; } async function checkSyncStore(): Promise { let exists = false; try { exists = await IndexedDBStore.exists(getIDBFactory()!, SYNC_STORE_NAME); log(`Sync store using IndexedDB contains data? ${exists}`); return { exists, healthy: true }; } catch (e) { error("Sync store using IndexedDB inaccessible", e); } log("Sync store using memory only"); return { exists, healthy: false }; } async function checkCryptoStore(): Promise { // check first if there is a rust crypto store try { const rustDbExists = await IndexedDBCryptoStore.exists(getIDBFactory()!, RUST_CRYPTO_STORE_NAME); log(`Rust Crypto store using IndexedDB contains data? ${rustDbExists}`); if (rustDbExists) { // There was an existing rust database, so consider it healthy. return { exists: true, healthy: true }; } else { // No rust store, so let's check if there is a legacy store not yet migrated. try { const legacyIdbExists = await IndexedDBCryptoStore.existsAndIsNotMigrated( getIDBFactory()!, LEGACY_CRYPTO_STORE_NAME, ); log(`Legacy Crypto store using IndexedDB contains non migrated data? ${legacyIdbExists}`); return { exists: legacyIdbExists, healthy: true }; } catch (e) { error("Legacy crypto store using IndexedDB inaccessible", e); } // No need to check local storage or memory as rust stack doesn't support them. // Given that rust stack requires indexeddb, set healthy to false. return { exists: false, healthy: false }; } } catch (e) { error("Rust crypto store using IndexedDB inaccessible", e); return { exists: false, healthy: false }; } } /** * Sets whether crypto has ever been successfully * initialised on this client. * StorageManager uses this to determine whether indexeddb * has been wiped by the browser: this flag is saved to localStorage * and if it is true and not crypto data is found, an error is * presented to the user. * * @param {boolean} cryptoInited True if crypto has been set up */ export function setCryptoInitialised(cryptoInited: boolean): void { localStorage.setItem("mx_crypto_initialised", String(cryptoInited)); }