(function(root, factory) { if (typeof module === 'object' && module.exports) module.exports = factory; else root.createPushNotifications = factory; })(typeof self !== 'undefined' ? self : this, function createPushNotifications({ control, status, deadlineControl, deadlineStatus, deadlineHour, notification, serviceWorker, fetchJson, }) { let configuration = null; function formattedHour(value) { return `${String(Number(value)).padStart(2, '0')}:00`; } function deadlineReadiness() { if (!configuration?.available || deadlineControl?.disabled) { return {state:'unavailable', detail:deadlineStatus?.textContent || 'Deadline reminders are unavailable.'}; } return configuration.deadline_enabled ? {state:'complete', detail:`Deadline reminders enabled for ${formattedHour(configuration.reminder_hour)} local time.`} : {state:'incomplete', detail:deadlineStatus?.textContent || 'Choose when to receive deadline reminders.'}; } function applicationServerKey(value) { const padded = value.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - value.length % 4) % 4); if (typeof atob === 'function') { return Uint8Array.from(atob(padded), character => character.charCodeAt(0)); } return Uint8Array.from(Buffer.from(padded, 'base64')); } async function disable() { const registration = await serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); await fetchJson('api/v1/push-subscription', {method:'DELETE'}); await subscription?.unsubscribe?.(); control.checked = false; if (deadlineControl) deadlineControl.checked = false; status.textContent = 'New update notifications are off for this device.'; if (deadlineStatus) deadlineStatus.textContent = 'Deadline reminders are off for this device.'; } async function ensureSubscription() { const permission = await notification.requestPermission(); if (permission !== 'granted') { control.checked = false; status.textContent = 'Notifications are blocked. Allow them in your browser settings to enable updates.'; return null; } const registration = await serviceWorker.ready; let subscription = await registration.pushManager.getSubscription(); if (!subscription) { subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: applicationServerKey(configuration.public_key), }); } await fetchJson('api/v1/push-subscription', { method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify(subscription.toJSON()), }); control.checked = true; status.textContent = 'New update notifications enabled for this device.'; configuration.subscribed = true; return subscription; } async function enable() { await ensureSubscription(); } async function change() { control.disabled = true; try { if (control.checked) await enable(); else await disable(); } catch (error) { control.checked = !control.checked; status.textContent = 'Could not change update notifications. Check your connection and try again.'; } finally { control.disabled = false; } } async function changeDeadline() { deadlineControl.disabled = true; if (deadlineHour) deadlineHour.disabled = true; try { const registration = await serviceWorker.ready; let subscription = await registration.pushManager.getSubscription(); if (deadlineControl.checked && !subscription) subscription = await ensureSubscription(); if (deadlineControl.checked && !subscription) { deadlineControl.checked = false; deadlineStatus.textContent = status.textContent; return false; } const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; const reminderHour = Number(deadlineHour?.value ?? configuration?.reminder_hour ?? 9); await fetchJson('api/v1/push-subscription/deadlines', { method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify({enabled:deadlineControl.checked, timezone, reminder_hour:reminderHour}), }); configuration.deadline_enabled = deadlineControl.checked; configuration.reminder_hour = reminderHour; configuration.timezone = timezone; deadlineStatus.textContent = deadlineControl.checked ? `Deadline reminders enabled for ${formattedHour(reminderHour)} local time.` : 'Deadline reminders are off for this device.'; return true; } catch (error) { deadlineControl.checked = !deadlineControl.checked; deadlineStatus.textContent = 'Could not change deadline reminders. Check your connection and try again.'; return false; } finally { deadlineControl.disabled = false; if (deadlineHour) deadlineHour.disabled = false; } } async function enableDeadline() { deadlineControl.checked = true; return changeDeadline(); } async function init() { if (!control || !notification || !serviceWorker) return; control.addEventListener('change', change); deadlineControl?.addEventListener('change', changeDeadline); configuration = await fetchJson('api/v1/push-subscription'); if (!configuration.available) { control.disabled = true; if (deadlineControl) deadlineControl.disabled = true; status.textContent = 'New update notifications are not available on this server.'; return; } control.checked = Boolean(configuration.subscribed); if (deadlineControl) deadlineControl.checked = Boolean(configuration.deadline_enabled); if (deadlineHour) deadlineHour.value = String(configuration.reminder_hour ?? 9); status.textContent = configuration.subscribed ? 'New update notifications enabled for this device.' : 'New update notifications are off for this device.'; if (deadlineStatus) deadlineStatus.textContent = configuration.deadline_enabled ? `Deadline reminders enabled for ${formattedHour(configuration.reminder_hour)} local time.` : 'Deadline reminders are off for this device.'; } return {init, change, changeDeadline, enableDeadline, deadlineReadiness}; });