an independent Bluesky client using Constellation, PDS Queries, and other services reddwarf.app
frontend spa bluesky reddwarf microcosm client app
99
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 92 lines 2.8 kB view raw
1import { atom } from "jotai"; 2import { atomWithStorage } from "jotai/utils"; 3 4import type { ContentLabel, LabelerDefinition } from "~/types/moderation"; 5 6// --- Configuration --- 7export const CACHE_TIMEOUT_MS = 3600000; // 1 Hour 8const MAX_CACHE_ENTRIES = 2000; // Limit to prevent localStorage quota issues 9const STORAGE_KEY = "moderation-cache-v1"; 10 11// --- Types --- 12type CacheEntry = { labels: ContentLabel[]; timestamp: number }; 13type CacheMap = Map<string, CacheEntry>; 14 15// --- Custom Storage Implementation --- 16// We cannot use createJSONStorage because it fails to serialize Maps. 17// We must write the storage logic manually. 18const mapStorage = { 19 getItem: (key: string, initialValue: CacheMap): CacheMap => { 20 if (typeof window === "undefined" || !window.localStorage) { 21 return initialValue; 22 } 23 24 try { 25 const item = localStorage.getItem(key); 26 if (!item) return initialValue; 27 28 const parsed = JSON.parse(item); 29 30 // Ensure it is an array (Map serialization format) 31 if (!Array.isArray(parsed)) return initialValue; 32 33 const now = Date.now(); 34 const map = new Map<string, CacheEntry>(); 35 36 parsed.forEach(([uri, data]) => { 37 // 1. STALENESS CHECK (On Load) 38 // Only load if younger than timeout 39 if (data && now - data.timestamp < CACHE_TIMEOUT_MS) { 40 map.set(uri, data); 41 } 42 }); 43 44 console.log(`[Cache] Hydrated ${map.size} valid entries.`); 45 return map; 46 } catch (error) { 47 console.error("[Cache] Failed to load:", error); 48 return initialValue; 49 } 50 }, 51 52 setItem: (key: string, value: CacheMap) => { 53 if (typeof window === "undefined" || !window.localStorage) return; 54 55 try { 56 let entries = Array.from(value.entries()); 57 58 // 2. SAFETY CAP (On Save) 59 // If we have too many entries, keep only the newest ones 60 if (entries.length > MAX_CACHE_ENTRIES) { 61 // Sort by timestamp descending (newest first) 62 entries.sort((a, b) => b[1].timestamp - a[1].timestamp); 63 // Keep top N 64 entries = entries.slice(0, MAX_CACHE_ENTRIES); 65 } 66 67 // Convert Map -> Array -> JSON String 68 localStorage.setItem(key, JSON.stringify(entries)); 69 } catch (error) { 70 console.error("[Cache] Failed to save:", error); 71 } 72 }, 73 74 removeItem: (key: string) => { 75 if (typeof window !== "undefined" && window.localStorage) { 76 localStorage.removeItem(key); 77 } 78 }, 79}; 80 81// --- Atoms --- 82 83export const labelerConfigAtom = atom<LabelerDefinition[]>([]); 84 85export const moderationCacheAtom = atomWithStorage<CacheMap>( 86 STORAGE_KEY, 87 new Map(), 88 mapStorage // <--- Pass our custom object here 89); 90 91export const pendingUriQueueAtom = atom<Set<string>>(new Set<string>()); 92export const processingUriSetAtom = atom<Set<string>>(new Set<string>());