const storageKey = "ddc-notifications"; const finishedTitles = { ready: "Download ready", completed: "Download moved", failed: "Download failed", }; const previousStatuses = new Map(); function notificationsSupported() { return "Notification" in window; } function readPreference() { try { return localStorage.getItem(storageKey) === "on"; } catch { return false; } } function storePreference(enabled) { try { localStorage.setItem(storageKey, enabled ? "on" : "off"); } catch { return; } } export function notificationsEnabled() { return notificationsSupported() && Notification.permission === "granted" && readPreference(); } export async function setNotificationsEnabled(enabled) { if (!enabled) { storePreference(false); return; } if (!notificationsSupported()) throw new Error("This browser does not support notifications."); const permission = await Notification.requestPermission(); if (permission !== "granted") throw new Error("Notifications are blocked for this site in the browser settings."); storePreference(true); } export function collectTransitions(jobs) { const transitions = []; const seenIds = new Set(); for (const job of jobs) { seenIds.add(job.id); const previous = previousStatuses.get(job.id); if (previous && previous !== job.status) transitions.push({ job, previous }); previousStatuses.set(job.id, job.status); } for (const id of previousStatuses.keys()) { if (!seenIds.has(id)) previousStatuses.delete(id); } return transitions; } export function notifyTransitions(transitions) { if (!notificationsEnabled()) return; for (const { job, previous } of transitions) { const title = finishedTitles[job.status]; const wasWorking = previous === "queued" || previous === "downloading" || previous === "extracting" || previous === "moving"; if (!title || !wasWorking) continue; if (job.status === "ready" && previous === "moving") continue; new Notification(title, { body: job.fileName || job.sourceUrl, tag: `ddc-${job.id}` }); } }