agent-ecosystem/src/renderer/services/draftStorage.ts

128 lines
3.1 KiB
TypeScript

import { del, get, keys, set } from 'idb-keyval';
const DRAFT_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
const DRAFT_KEY_PREFIX = 'draft:';
interface StoredDraft {
value: string;
timestamp: number;
}
let idbUnavailable = false;
let idbUnavailableLogged = false;
const fallbackStore = new Map<string, StoredDraft>();
function markIdbUnavailable(): void {
if (!idbUnavailableLogged) {
idbUnavailableLogged = true;
console.warn(
'[draftStorage] IndexedDB unavailable, using in-memory draft storage for this session.'
);
}
idbUnavailable = true;
}
function fallbackSave(key: string, value: string): void {
const fullKey = `${DRAFT_KEY_PREFIX}${key}`;
fallbackStore.set(fullKey, { value, timestamp: Date.now() });
}
function fallbackLoad(key: string): string | null {
const fullKey = `${DRAFT_KEY_PREFIX}${key}`;
const stored = fallbackStore.get(fullKey);
if (!stored) return null;
if (Date.now() - stored.timestamp > DRAFT_TTL_MS) {
fallbackStore.delete(fullKey);
return null;
}
return stored.value;
}
function fallbackDelete(key: string): void {
fallbackStore.delete(`${DRAFT_KEY_PREFIX}${key}`);
}
function fallbackCleanupExpired(): void {
const now = Date.now();
for (const [fullKey, stored] of fallbackStore.entries()) {
if (now - stored.timestamp > DRAFT_TTL_MS) fallbackStore.delete(fullKey);
}
}
async function saveDraft(key: string, value: string): Promise<void> {
if (idbUnavailable) {
fallbackSave(key, value);
return;
}
try {
const stored: StoredDraft = { value, timestamp: Date.now() };
await set(`${DRAFT_KEY_PREFIX}${key}`, stored);
} catch {
markIdbUnavailable();
fallbackSave(key, value);
}
}
async function loadDraft(key: string): Promise<string | null> {
if (idbUnavailable) return fallbackLoad(key);
try {
const stored = await get<StoredDraft>(`${DRAFT_KEY_PREFIX}${key}`);
if (!stored) return null;
const age = Date.now() - stored.timestamp;
if (age > DRAFT_TTL_MS) {
void deleteDraft(key);
return null;
}
return stored.value;
} catch {
markIdbUnavailable();
return fallbackLoad(key);
}
}
async function deleteDraft(key: string): Promise<void> {
if (idbUnavailable) {
fallbackDelete(key);
return;
}
try {
await del(`${DRAFT_KEY_PREFIX}${key}`);
} catch {
markIdbUnavailable();
fallbackDelete(key);
}
}
async function cleanupExpired(): Promise<void> {
if (idbUnavailable) {
fallbackCleanupExpired();
return;
}
try {
const allKeys = await keys();
const draftKeys = allKeys.filter(
(k): k is IDBValidKey & string => typeof k === 'string' && k.startsWith(DRAFT_KEY_PREFIX)
);
const now = Date.now();
for (const fullKey of draftKeys) {
try {
const stored = await get<StoredDraft>(fullKey);
if (stored && now - stored.timestamp > DRAFT_TTL_MS) await del(fullKey);
} catch {
markIdbUnavailable();
fallbackCleanupExpired();
return;
}
}
} catch {
markIdbUnavailable();
fallbackCleanupExpired();
}
}
export const draftStorage = {
saveDraft,
loadDraft,
deleteDraft,
cleanupExpired,
};