// Settings -> Reminders: turn on push notifications for tasks due today and events about to start (Web Push). const remSupported = () => 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window; const b64ToBytes = s => { const p = '='.repeat((4 - s.length % 4) % 4), r = atob((s + p).replace(/-/g, '+').replace(/_/g, '/')); return Uint8Array.from([...r].map(c => c.charCodeAt(0))); }; const myTz = () => Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; function remindersHtml() { return `

Reminders

Get a notification for tasks due today and events about to start, even when the app is closed. Turn it on for each device you want reminders on.

Checking…
`; } async function remindersInit() { const box = document.getElementById('remlist'); if (!box) return; if (!(allowed('tasks') || allowed('calendar'))) return document.getElementById('sec-rem').remove(); if (!remSupported()) { box.innerHTML = `
Not available here${/iPhone|iPad/.test(navigator.userAgent) ? 'On iPhone and iPad, add NE Hub to the Home Screen first (Share → Add to Home Screen), then open it from there.' : 'This browser does not support push notifications.'}
`; return; } let info; try { info = await jfetch('/api/push/key'); } catch (e) { box.innerHTML = `
${esc(e.message)}
`; return; } const reg = await Promise.race([navigator.serviceWorker.ready, new Promise(ok => setTimeout(ok, 4000))]); // (never ready: the browser refused the service worker) if (!reg) { box.innerHTML = '
Not available hereThe browser did not start the background service reminders need. Reload the page, or open NE Hub over https in a regular browser.
'; return; } const sub = await reg.pushManager.getSubscription(); const on = !!sub && Notification.permission === 'granted'; const post = (p, body) => jfetch('/api/push/' + p, { method: 'POST', body: JSON.stringify({ tz: myTz(), ...body }) }); box.innerHTML = ` ${allowed('tasks') ? `
Morning time
` : ''} ${allowed('mail') ? `` : ''} ${allowed('calendar') ? `
How early
` : ''}
Send a test to every device that is on
Dismiss every notification from NE Hub still showing on this device
`; const on1 = document.getElementById('remon'); on1.onchange = async () => { try { if (on1.checked) { const perm = Notification.permission === 'default' ? await Notification.requestPermission() : Notification.permission; if (perm !== 'granted') { on1.checked = false; return toast('Notifications were not allowed'); } const s = (await reg.pushManager.getSubscription()) || await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: b64ToBytes(info.publicKey) }); await post('subscribe', { subscription: s.toJSON() }); toast('Reminders are on for this device'); } else { const s = await reg.pushManager.getSubscription(); if (s) { await post('unsubscribe', { endpoint: s.endpoint }); await s.unsubscribe(); } toast('Reminders are off for this device'); } remindersInit(); } catch (e) { on1.checked = !on1.checked; toast(e.message); } }; const pref = (id, key, get) => { const el = document.getElementById(id); if (el) el.onchange = () => post('prefs', { [key]: get(el) }).catch(e => toast(e.message)); }; pref('remmail', 'mail', e => e.checked); pref('remt', 'tasks', e => e.checked); pref('remat', 'tasksAt', e => e.value); pref('reme', 'events', e => e.checked); pref('remm', 'eventMins', e => +e.value); document.getElementById('remclear').onclick = async () => { try { const list = await reg.getNotifications(); list.forEach(n => n.close()); try { navigator.clearAppBadge && await navigator.clearAppBadge(); } catch {} toast(list.length ? 'Cleared ' + list.length + (list.length === 1 ? ' notification' : ' notifications') : 'No notifications to clear'); } catch (e) { toast(e.message); } }; document.getElementById('remtest').onclick = () => post('test', {}).then(() => toast('Test sent')).catch(e => toast(e.message)); }