feat: implement vault synchronization and decryption improvements

- Added background synchronization for vault core data, including optional folder updates.
- Introduced a new API endpoint to retrieve the vault revision date.
- Enhanced vault synchronization logic to utilize a caching mechanism for improved performance.
- Created a new vault cache module to handle IndexedDB storage for vault core snapshots.
- Implemented a worker for asynchronous decryption of vault data, improving UI responsiveness.
- Updated main application settings to adjust query stale time for better data freshness.
- Refactored vault-related API functions to support cache keys for more efficient data retrieval.
This commit is contained in:
shuaiplus
2026-04-28 22:10:34 +08:00
parent aa6f9210b4
commit 1b0386bf78
10 changed files with 702 additions and 331 deletions
+52 -7
View File
@@ -1,4 +1,6 @@
import type { Cipher, Folder, Send } from '../types';
import { getVaultRevisionDate } from './auth';
import { loadCachedVaultCoreSnapshot, saveCachedVaultCoreSnapshot, type VaultCoreSnapshot } from '../vault-cache';
import { parseJson, type AuthedFetch } from './shared';
interface VaultSyncResponse {
@@ -7,13 +9,53 @@ interface VaultSyncResponse {
sends?: Send[];
}
const pendingVaultCoreRequests = new WeakMap<AuthedFetch, Promise<VaultSyncResponse>>();
const pendingVaultCoreRequests = new Map<string, Promise<VaultCoreSnapshot>>();
const memoryVaultCoreCache = new Map<string, { revisionStamp: number; snapshot: VaultCoreSnapshot }>();
export async function loadVaultCoreSyncSnapshot(authedFetch: AuthedFetch): Promise<VaultSyncResponse> {
const existing = pendingVaultCoreRequests.get(authedFetch);
function normalizeSnapshot(body: VaultSyncResponse | null | undefined): VaultCoreSnapshot {
return {
ciphers: Array.isArray(body?.ciphers) ? body!.ciphers! : [],
folders: Array.isArray(body?.folders) ? body!.folders! : [],
};
}
export async function getCachedVaultCoreSnapshot(cacheKey: string): Promise<VaultCoreSnapshot | null> {
const normalizedKey = String(cacheKey || '').trim();
if (!normalizedKey) return null;
const memory = memoryVaultCoreCache.get(normalizedKey);
if (memory) return memory.snapshot;
const cached = await loadCachedVaultCoreSnapshot(normalizedKey);
if (!cached?.snapshot) return null;
memoryVaultCoreCache.set(normalizedKey, {
revisionStamp: cached.revisionStamp,
snapshot: cached.snapshot,
});
return cached.snapshot;
}
export async function loadVaultCoreSyncSnapshot(authedFetch: AuthedFetch, cacheKey: string): Promise<VaultCoreSnapshot> {
const normalizedKey = String(cacheKey || '').trim();
if (!normalizedKey) return { ciphers: [], folders: [] };
const existing = pendingVaultCoreRequests.get(normalizedKey);
if (existing) return existing;
const request = (async () => {
const revisionStamp = await getVaultRevisionDate(authedFetch);
const memory = memoryVaultCoreCache.get(normalizedKey);
if (memory?.revisionStamp === revisionStamp) {
return memory.snapshot;
}
const cached = await loadCachedVaultCoreSnapshot(normalizedKey);
if (cached?.revisionStamp === revisionStamp && cached.snapshot) {
memoryVaultCoreCache.set(normalizedKey, {
revisionStamp,
snapshot: cached.snapshot,
});
return cached.snapshot;
}
const resp = await authedFetch('/api/sync?excludeSends=true&excludeDomains=true', {
cache: 'no-store',
headers: {
@@ -23,15 +65,18 @@ export async function loadVaultCoreSyncSnapshot(authedFetch: AuthedFetch): Promi
});
if (!resp.ok) throw new Error('Failed to load vault');
const body = await parseJson<VaultSyncResponse>(resp);
return body || {};
const snapshot = normalizeSnapshot(body);
memoryVaultCoreCache.set(normalizedKey, { revisionStamp, snapshot });
void saveCachedVaultCoreSnapshot(normalizedKey, revisionStamp, snapshot);
return snapshot;
})();
pendingVaultCoreRequests.set(authedFetch, request);
pendingVaultCoreRequests.set(normalizedKey, request);
try {
return await request;
} finally {
if (pendingVaultCoreRequests.get(authedFetch) === request) {
pendingVaultCoreRequests.delete(authedFetch);
if (pendingVaultCoreRequests.get(normalizedKey) === request) {
pendingVaultCoreRequests.delete(normalizedKey);
}
}
}