36 changed files with 1477 additions and 942 deletions
@ -0,0 +1,132 @@
@@ -0,0 +1,132 @@
|
||||
/** |
||||
* RSS feed caching with IndexedDB |
||||
*/ |
||||
|
||||
import { getDB } from './indexeddb-store.js'; |
||||
|
||||
export interface RSSItem { |
||||
title: string; |
||||
link: string; |
||||
description: string; |
||||
pubDate: Date; |
||||
feedUrl: string; |
||||
feedTitle?: string; |
||||
} |
||||
|
||||
export interface CachedRSSFeed { |
||||
feedUrl: string; |
||||
items: RSSItem[]; |
||||
cached_at: number; |
||||
} |
||||
|
||||
const CACHE_DURATION = 15 * 60 * 1000; // 15 minutes
|
||||
|
||||
/** |
||||
* Store RSS feed items in cache |
||||
*/ |
||||
export async function cacheRSSFeed(feedUrl: string, items: RSSItem[]): Promise<void> { |
||||
try { |
||||
const db = await getDB(); |
||||
const cached: CachedRSSFeed = { |
||||
feedUrl, |
||||
items: items.map(item => ({ |
||||
...item, |
||||
pubDate: item.pubDate // Store as Date, will be serialized
|
||||
})), |
||||
cached_at: Date.now() |
||||
}; |
||||
await db.put('rss', cached); |
||||
} catch (error) { |
||||
console.debug('Error caching RSS feed:', error); |
||||
// Don't throw - caching failures shouldn't break the app
|
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Get RSS feed items from cache |
||||
*/ |
||||
export async function getCachedRSSFeed(feedUrl: string): Promise<RSSItem[] | null> { |
||||
try { |
||||
const db = await getDB(); |
||||
const cached = await db.get('rss', feedUrl) as CachedRSSFeed | undefined; |
||||
|
||||
if (!cached) { |
||||
return null; |
||||
} |
||||
|
||||
// Check if cache is still valid
|
||||
const age = Date.now() - cached.cached_at; |
||||
if (age > CACHE_DURATION) { |
||||
// Cache expired, delete it
|
||||
await db.delete('rss', feedUrl); |
||||
return null; |
||||
} |
||||
|
||||
// Restore Date objects from serialized format
|
||||
const items: RSSItem[] = cached.items.map((item: RSSItem) => ({ |
||||
...item, |
||||
pubDate: new Date(item.pubDate) |
||||
})); |
||||
|
||||
return items; |
||||
} catch (error) { |
||||
console.debug('Error getting cached RSS feed:', error); |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Get all cached RSS feeds |
||||
*/ |
||||
export async function getAllCachedRSSFeeds(): Promise<Map<string, RSSItem[]>> { |
||||
try { |
||||
const db = await getDB(); |
||||
const allCached = await db.getAll('rss') as CachedRSSFeed[]; |
||||
const result = new Map<string, RSSItem[]>(); |
||||
|
||||
const now = Date.now(); |
||||
for (const cached of allCached) { |
||||
// Check if cache is still valid
|
||||
const age = now - cached.cached_at; |
||||
if (age <= CACHE_DURATION) { |
||||
// Restore Date objects
|
||||
const items: RSSItem[] = cached.items.map((item: RSSItem) => ({ |
||||
...item, |
||||
pubDate: new Date(item.pubDate) |
||||
})); |
||||
result.set(cached.feedUrl, items); |
||||
} else { |
||||
// Cache expired, delete it
|
||||
await db.delete('rss', cached.feedUrl); |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} catch (error) { |
||||
console.debug('Error getting all cached RSS feeds:', error); |
||||
return new Map(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Clear expired RSS feed caches |
||||
*/ |
||||
export async function clearExpiredRSSCaches(): Promise<void> { |
||||
try { |
||||
const db = await getDB(); |
||||
const index = db.transaction('rss', 'readwrite').store.index('cached_at'); |
||||
const now = Date.now(); |
||||
const maxAge = now - CACHE_DURATION; |
||||
|
||||
// Get all entries older than cache duration
|
||||
const range = IDBKeyRange.upperBound(maxAge); |
||||
const expired = await index.getAll(range) as CachedRSSFeed[]; |
||||
|
||||
for (const cached of expired) { |
||||
await db.delete('rss', cached.feedUrl); |
||||
} |
||||
} catch (error) { |
||||
console.debug('Error clearing expired RSS caches:', error); |
||||
// Don't throw - cleanup failures shouldn't break the app
|
||||
} |
||||
} |
||||
Loading…
Reference in new issue