import { base64ToBytes, bytesToBase64 } from './crypto-utils.js'; const DB_NAME = 'shine-wallet-plugin'; const DB_VERSION = 1; const STORE_META = 'meta'; const STORE_VAULT = 'vault'; const SESSION_ENTRY_ID = 'active-session'; const VAULT_KEY_ID = 'session-wrap-key'; function openDb() { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onupgradeneeded = () => { const db = request.result; if (!db.objectStoreNames.contains(STORE_META)) { db.createObjectStore(STORE_META, { keyPath: 'id' }); } if (!db.objectStoreNames.contains(STORE_VAULT)) { db.createObjectStore(STORE_VAULT, { keyPath: 'id' }); } }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error || new Error('IndexedDB недоступен')); }); } async function withStore(storeName, mode, run) { const db = await openDb(); try { return await new Promise((resolve, reject) => { const tx = db.transaction(storeName, mode); const store = tx.objectStore(storeName); let settled = false; const done = (fn) => (value) => { if (settled) return; settled = true; fn(value); }; tx.oncomplete = () => done(resolve)(undefined); tx.onerror = () => done(reject)(tx.error || new Error('IndexedDB transaction failed')); Promise.resolve(run(store, tx, done)).catch((error) => done(reject)(error)); }); } finally { db.close(); } } async function put(storeName, value) { return withStore(storeName, 'readwrite', (store) => { store.put(value); }); } async function get(storeName, key) { const db = await openDb(); try { return await new Promise((resolve, reject) => { const tx = db.transaction(storeName, 'readonly'); const req = tx.objectStore(storeName).get(key); req.onsuccess = () => resolve(req.result || null); req.onerror = () => reject(req.error || new Error('Ошибка чтения из IndexedDB')); }); } finally { db.close(); } } async function deleteById(storeName, key) { return withStore(storeName, 'readwrite', (store) => { store.delete(key); }); } async function getOrCreateVaultKey() { const current = await get(STORE_META, VAULT_KEY_ID); if (current?.key) return current.key; const key = await crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'], ); await put(STORE_META, { id: VAULT_KEY_ID, key, createdAtMs: Date.now() }); return key; } async function encryptJson(value) { const key = await getOrCreateVaultKey(); const iv = crypto.getRandomValues(new Uint8Array(12)); const plainBytes = new TextEncoder().encode(JSON.stringify(value)); const cipher = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plainBytes); return { ivB64: bytesToBase64(iv), cipherB64: bytesToBase64(new Uint8Array(cipher)), }; } async function decryptJson(envelope) { const key = await getOrCreateVaultKey(); const plain = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: base64ToBytes(envelope.ivB64) }, key, base64ToBytes(envelope.cipherB64), ); return JSON.parse(new TextDecoder().decode(plain)); } function storageApi() { if (globalThis.chrome?.storage?.local) return globalThis.chrome.storage.local; return null; } export async function savePluginSettings(settings) { const api = storageApi(); if (api) { await api.set({ shineWalletSettings: settings }); return; } localStorage.setItem('shineWalletSettings', JSON.stringify(settings)); } export async function loadPluginSettings() { const api = storageApi(); if (api) { const row = await api.get('shineWalletSettings'); return row?.shineWalletSettings || {}; } try { return JSON.parse(localStorage.getItem('shineWalletSettings') || '{}'); } catch { return {}; } } export async function saveSessionMaterial(sessionRecord) { const encrypted = await encryptJson(sessionRecord); await put(STORE_VAULT, { id: SESSION_ENTRY_ID, encrypted, updatedAtMs: Date.now(), }); } export async function loadSessionMaterial() { const row = await get(STORE_VAULT, SESSION_ENTRY_ID); if (!row?.encrypted) return null; return decryptJson(row.encrypted); } export async function clearSessionMaterial() { await deleteById(STORE_VAULT, SESSION_ENTRY_ID); }