Files
ShieldAI/packages/extension/src/lib/cache.ts

81 lines
2.1 KiB
TypeScript

import { UrlCheckResult } from '../types';
const CACHE_TTL_MS = 5 * 60 * 1000;
const API_TIMEOUT_MS = 500;
const MAX_CACHE_SIZE = 5000;
export class UrlCache {
private cache: Map<string, { result: UrlCheckResult; expiresAt: number }> = new Map();
async get(url: string): Promise<UrlCheckResult | null> {
const normalized = this.normalizeUrl(url);
const entry = this.cache.get(normalized);
if (!entry) return null;
if (Date.now() > entry.expiresAt) {
this.cache.delete(normalized);
return null;
}
return { ...entry.result, cached: true };
}
async set(url: string, result: UrlCheckResult): Promise<void> {
const normalized = this.normalizeUrl(url);
if (this.cache.size >= MAX_CACHE_SIZE) {
const firstKey = this.cache.keys().next().value;
if (firstKey) this.cache.delete(firstKey);
}
this.cache.set(normalized, {
result,
expiresAt: Date.now() + CACHE_TTL_MS,
});
}
async persistToStorage(): Promise<void> {
const entries: Record<string, { result: UrlCheckResult; expiresAt: number }> = {};
for (const [key, value] of this.cache.entries()) {
entries[key] = value;
}
await chrome.storage.local.set({ urlCache: entries });
}
async loadFromStorage(): Promise<void> {
const data = await chrome.storage.local.get('urlCache') as { urlCache: Record<string, { result: UrlCheckResult; expiresAt: number }> };
if (data.urlCache) {
const now = Date.now();
for (const [key, entry] of Object.entries(data.urlCache)) {
if (now <= entry.expiresAt) {
this.cache.set(key, entry);
}
}
}
}
getStats(): { size: number; max: number } {
return { size: this.cache.size, max: MAX_CACHE_SIZE };
}
clear(): void {
this.cache.clear();
}
private normalizeUrl(url: string): string {
try {
const u = new URL(url);
u.hash = '';
u.search = '';
return u.toString();
} catch {
return url.toLowerCase();
}
}
}
export const urlCache = new UrlCache();
export const CACHE_TTL = CACHE_TTL_MS;
export const API_TIMEOUT = API_TIMEOUT_MS;