My-Weekly-ToDo-List/public/sw.js
mARTin f9c7893341 feat: add push notifications for calendar event reminders
- Service worker + Web Push API for browser/mobile notifications
- PWA manifest with icons for home screen install (iOS 16.4+)
- Push subscription management (subscribe/unsubscribe API routes)
- Notification toggle in Settings > General
- Push scheduler script (cron) checks cached events and sends
  notifications at reminder times, with dedup tracking
- Test notification endpoint at /api/push/test
- Persists reminders in calendar event cache for scheduler access
- New DB models: PushSubscription, SentNotification
- New user field: notificationsEnabled

Setup: generate VAPID keys with `npx tsx scripts/generate-vapid-keys.ts`
and add to .env, then run scheduler via cron every minute.

v1.37.0
2026-03-16 21:38:15 +01:00

52 lines
1.3 KiB
JavaScript

// Push notification service worker
self.addEventListener('push', function(event) {
if (!event.data) return;
let data;
try {
data = event.data.json();
} catch (e) {
data = { title: 'Calendar Reminder', body: event.data.text() };
}
const options = {
body: data.body || '',
icon: data.icon || '/icons/icon-192.png',
badge: '/icons/icon-192.png',
data: { url: data.url || '/tasks' },
vibrate: [200, 100, 200],
tag: data.eventId || 'calendar-reminder',
renotify: true,
};
event.waitUntil(
self.registration.showNotification(data.title || 'Calendar Reminder', options)
);
});
self.addEventListener('notificationclick', function(event) {
event.notification.close();
const url = event.notification.data?.url || '/tasks';
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function(clientList) {
// Focus existing window if available
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (client.url.includes('/tasks') && 'focus' in client) {
return client.focus();
}
}
// Open new window
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
});
self.addEventListener('activate', function(event) {
event.waitUntil(clients.claim());
});