const ALARM_NAME = "cacheCleanerTimer"; const DEFAULT_RANGE_KEY = "last_24h"; const MAX_INTERVAL_MINUTES = 30 * 24 * 60; const INTERVAL_UNITS = new Set(["minutes", "hours", "days"]); const RANGE_OPTIONS = { last_hour: { label: "Letzte Stunde", sinceMs: 60 * 60 * 1000 }, last_24h: { label: "Letzte 24 Stunden", sinceMs: 24 * 60 * 60 * 1000 }, last_7d: { label: "Letzte 7 Tage", sinceMs: 7 * 24 * 60 * 60 * 1000 }, last_4w: { label: "Letzte 4 Wochen", sinceMs: 28 * 24 * 60 * 60 * 1000 }, all_time: { label: "Gesamter Zeitraum", sinceMs: null } }; const DEFAULT_TIMER_CONFIG = { enabled: false, repeat: false, rangeKey: DEFAULT_RANGE_KEY, intervalValue: 1, intervalUnit: "hours", intervalMinutes: 60, nextRun: null }; function getSinceByRange(rangeKey) { const option = RANGE_OPTIONS[rangeKey]; if (!option) { throw new Error("Unbekannter Zeitraum."); } return option.sinceMs === null ? 0 : Date.now() - option.sinceMs; } function getStorage(keys) { return new Promise((resolve, reject) => { chrome.storage.local.get(keys, (result) => { const error = chrome.runtime.lastError; if (error) { reject(new Error(error.message)); return; } resolve(result || {}); }); }); } function setStorage(values) { return new Promise((resolve, reject) => { chrome.storage.local.set(values, () => { const error = chrome.runtime.lastError; if (error) { reject(new Error(error.message)); return; } resolve(); }); }); } function createAlarm(details) { return new Promise((resolve, reject) => { try { const result = chrome.alarms.create(ALARM_NAME, details); const error = chrome.runtime.lastError; if (error) { reject(new Error(error.message)); return; } if (result && typeof result.then === "function") { result.then(resolve).catch(reject); return; } resolve(); } catch (error) { reject(error); } }); } function clearAlarmOnly() { return new Promise((resolve, reject) => { chrome.alarms.clear(ALARM_NAME, () => { const error = chrome.runtime.lastError; if (error) { reject(new Error(error.message)); return; } resolve(); }); }); } function validateTimerConfig(config) { if (!config || typeof config !== "object") { throw new Error("Timer-Konfiguration fehlt."); } if (!RANGE_OPTIONS[config.rangeKey]) { throw new Error("Unbekannter Zeitraum."); } if (!INTERVAL_UNITS.has(config.intervalUnit)) { throw new Error("Unbekannte Timer-Einheit."); } if (!Number.isFinite(config.intervalValue)) { throw new Error("Das Intervall muss eine Zahl sein."); } if (config.intervalValue <= 0) { throw new Error("Das Intervall muss groesser als 0 sein."); } if (!Number.isFinite(config.intervalMinutes)) { throw new Error("Das Intervall konnte nicht berechnet werden."); } if (config.intervalMinutes < 1) { throw new Error("Das Mindestintervall betraegt 1 Minute."); } if (config.intervalMinutes > MAX_INTERVAL_MINUTES) { throw new Error("Das Maximalintervall betraegt 30 Tage."); } } async function clearCacheByRange(rangeKey) { const resolvedRangeKey = RANGE_OPTIONS[rangeKey] ? rangeKey : DEFAULT_RANGE_KEY; const since = getSinceByRange(resolvedRangeKey); // Only the cache flag is set so cookies, history, downloads, and passwords stay untouched. await new Promise((resolve, reject) => { chrome.browsingData.remove( { since }, { cache: true }, () => { const error = chrome.runtime.lastError; if (error) { reject(new Error(error.message)); return; } resolve(); } ); }); const lastRun = Date.now(); await setStorage({ selectedRange: resolvedRangeKey, lastRun }); return { lastRun, rangeKey: resolvedRangeKey }; } async function setupAlarm(config) { validateTimerConfig(config); const intervalMinutes = config.intervalMinutes; const nextRun = Date.now() + intervalMinutes * 60 * 1000; const timerConfig = { enabled: true, repeat: Boolean(config.repeat), rangeKey: config.rangeKey, intervalValue: config.intervalValue, intervalUnit: config.intervalUnit, intervalMinutes, nextRun }; const alarmDetails = timerConfig.repeat ? { delayInMinutes: intervalMinutes, periodInMinutes: intervalMinutes } : { delayInMinutes: intervalMinutes }; // Replacing the alarm avoids duplicate timers after repeated saves. await clearAlarmOnly(); await createAlarm(alarmDetails); await setStorage({ selectedRange: timerConfig.rangeKey, timerConfig }); return timerConfig; } async function clearAlarm() { const { timerConfig } = await getStorage(["timerConfig"]); const disabledConfig = { ...DEFAULT_TIMER_CONFIG, ...(timerConfig || {}), enabled: false, nextRun: null }; await clearAlarmOnly(); await setStorage({ timerConfig: disabledConfig }); return disabledConfig; } async function restoreAlarmOnStartup() { const { timerConfig } = await getStorage(["timerConfig"]); if (!timerConfig || timerConfig.enabled !== true) { return; } validateTimerConfig(timerConfig); const now = Date.now(); let nextRun = Number(timerConfig.nextRun); let delayInMinutes; // Extension restarts can miss an alarm, so restart from a future run instead of firing immediately. if (Number.isFinite(nextRun) && nextRun > now) { delayInMinutes = Math.max(1, Math.ceil((nextRun - now) / 60000)); nextRun = now + delayInMinutes * 60000; } else { delayInMinutes = timerConfig.intervalMinutes; nextRun = now + timerConfig.intervalMinutes * 60000; } const restoredConfig = { ...timerConfig, nextRun }; const alarmDetails = restoredConfig.repeat ? { delayInMinutes, periodInMinutes: restoredConfig.intervalMinutes } : { delayInMinutes }; await createAlarm(alarmDetails); await setStorage({ timerConfig: restoredConfig }); } async function handleTimerAlarm(alarm) { if (!alarm || alarm.name !== ALARM_NAME) { return; } const { timerConfig } = await getStorage(["timerConfig"]); if (!timerConfig || timerConfig.enabled !== true) { return; } const rangeKey = RANGE_OPTIONS[timerConfig.rangeKey] ? timerConfig.rangeKey : DEFAULT_RANGE_KEY; await clearCacheByRange(rangeKey); if (timerConfig.repeat) { const nextRun = Date.now() + timerConfig.intervalMinutes * 60 * 1000; await setStorage({ timerConfig: { ...timerConfig, rangeKey, nextRun } }); return; } await setStorage({ timerConfig: { ...timerConfig, rangeKey, enabled: false, nextRun: null } }); } function sendError(sendResponse, error) { sendResponse({ ok: false, error: error && error.message ? error.message : "Unbekannter Fehler." }); } chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { (async () => { if (!message || typeof message.type !== "string") { throw new Error("Unbekannte Nachricht."); } if (message.type === "CLEAR_CACHE") { const result = await clearCacheByRange(message.rangeKey); sendResponse({ ok: true, ...result }); return; } if (message.type === "SET_TIMER") { const timerConfig = await setupAlarm(message.config); sendResponse({ ok: true, timerConfig }); return; } if (message.type === "CLEAR_TIMER") { const timerConfig = await clearAlarm(); sendResponse({ ok: true, timerConfig }); return; } throw new Error("Unbekannter Nachrichtentyp."); })().catch((error) => { sendError(sendResponse, error); }); return true; }); chrome.alarms.onAlarm.addListener((alarm) => { handleTimerAlarm(alarm).catch((error) => { console.error("Timer konnte den Cache nicht loeschen.", error); }); }); chrome.runtime.onInstalled.addListener(() => { restoreAlarmOnStartup().catch((error) => { console.error("Timer konnte nicht wiederhergestellt werden.", error); }); }); chrome.runtime.onStartup.addListener(() => { restoreAlarmOnStartup().catch((error) => { console.error("Timer konnte nicht wiederhergestellt werden.", error); }); });