const DB_NAME = 'duplicati-browser-secrets'; const STORE_NAME = 'source-secrets'; const DB_VERSION = 1; const GLOBAL_SOURCE_ID = '__global_defaults__'; function openDatabase() { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = () => reject(request.error ?? new Error('Failed to open IndexedDB.')); request.onupgradeneeded = () => { const database = request.result; if (!database.objectStoreNames.contains(STORE_NAME)) { database.createObjectStore(STORE_NAME, { keyPath: 'sourceId' }); } }; request.onsuccess = () => resolve(request.result); }); } async function withStore(mode, callback) { const database = await openDatabase(); return new Promise((resolve, reject) => { const transaction = database.transaction(STORE_NAME, mode); const store = transaction.objectStore(STORE_NAME); let callbackResult; try { callbackResult = callback(store); } catch (error) { transaction.abort(); database.close(); reject(error); return; } transaction.onerror = () => { database.close(); reject(transaction.error ?? new Error('IndexedDB transaction failed.')); }; transaction.oncomplete = () => { database.close(); resolve(callbackResult); }; }); } export async function loadClientSecrets(sourceId) { const database = await openDatabase(); return new Promise((resolve, reject) => { const transaction = database.transaction(STORE_NAME, 'readonly'); const store = transaction.objectStore(STORE_NAME); const request = store.get(sourceId); request.onerror = () => { database.close(); reject(request.error ?? new Error('Failed to load browser secrets.')); }; request.onsuccess = () => { database.close(); resolve(request.result ?? null); }; }); } export async function loadGlobalClientSecrets() { return loadClientSecrets(GLOBAL_SOURCE_ID); } export async function saveClientSecrets(sourceId, secrets) { const record = { sourceId, webdavBaseUrl: secrets.webdavBaseUrl, authMode: secrets.authMode, username: secrets.username, password: secrets.password, passphrase: secrets.passphrase, updatedAt: new Date().toISOString() }; return withStore('readwrite', (store) => store.put(record)); } export async function saveGlobalClientSecrets(secrets) { return saveClientSecrets(GLOBAL_SOURCE_ID, secrets); } export async function removeClientSecrets(sourceId) { return withStore('readwrite', (store) => store.delete(sourceId)); } export async function removeGlobalClientSecrets() { return removeClientSecrets(GLOBAL_SOURCE_ID); }