UNPKG

progresspulse-pwa

Version:

A modern PWA for tracking progress and achieving goals with iPhone-style design

322 lines (275 loc) โ€ข 9.52 kB
// Enhanced Service Worker for Background Notifications // Works for both Web and APK environments const CACHE_NAME = 'progresspulse-notifications-v1'; const NOTIFICATION_TAG_PREFIX = 'progresspulse-'; // Install event self.addEventListener('install', (event) => { console.log('๐Ÿ”” Notification Service Worker installing...'); self.skipWaiting(); }); // Activate event self.addEventListener('activate', (event) => { console.log('๐Ÿ”” Notification Service Worker activated'); event.waitUntil(self.clients.claim()); }); // Background sync for notifications self.addEventListener('sync', (event) => { console.log('๐Ÿ”” Background sync triggered:', event.tag); if (event.tag === 'daily-reminder') { event.waitUntil(sendDailyReminder()); } else if (event.tag === 'check-updates') { event.waitUntil(checkForUpdates()); } }); // Push event handler self.addEventListener('push', (event) => { console.log('๐Ÿ”” Push message received:', event); let notificationData = { title: '๐ŸŽฏ ProgressPulse', body: 'You have a new notification', icon: '/icons/icon-192x192.png', badge: '/icons/badge-72x72.png', tag: 'default', requireInteraction: false, data: {} }; if (event.data) { try { const payload = event.data.json(); notificationData = { ...notificationData, ...payload }; } catch (error) { console.error('Error parsing push data:', error); } } event.waitUntil( self.registration.showNotification(notificationData.title, { body: notificationData.body, icon: notificationData.icon, badge: notificationData.badge, tag: notificationData.tag, requireInteraction: notificationData.requireInteraction, data: notificationData.data, actions: notificationData.actions || [], vibrate: [200, 100, 200], // Works better on APK silent: false }) ); }); // Notification click handler self.addEventListener('notificationclick', (event) => { console.log('๐Ÿ”” Notification clicked:', event.notification.tag); event.notification.close(); const notificationData = event.notification.data || {}; const action = event.action || notificationData.action || 'open-app'; event.waitUntil( handleNotificationClick(action, notificationData) ); }); // Handle notification clicks async function handleNotificationClick(action, data) { const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true }); // Try to focus existing window first for (const client of clients) { if (client.url.includes(self.location.origin)) { await client.focus(); // Send message to client about the action client.postMessage({ type: 'NOTIFICATION_CLICK', action: action, data: data }); return; } } // No existing window, open new one let url = '/'; if (action === 'open-settings' || data.type === 'update') { url = '/settings?tab=updates'; } else if (action === 'open-goals') { url = '/goals'; } else if (action === 'open-progress') { url = '/progress'; } await self.clients.openWindow(url); } // Daily reminder function async function sendDailyReminder() { console.log('๐Ÿ”” Sending daily reminder...'); const reminders = [ { title: '๐ŸŽฏ Daily Progress Check', body: 'How are your goals coming along today?', data: { type: 'reminder', action: 'open-progress' } }, { title: '๐Ÿ“Š Track Your Progress', body: 'Take a moment to update your achievements!', data: { type: 'reminder', action: 'open-goals' } }, { title: '๐Ÿš€ Keep Going!', body: 'You\'re doing great! Check your progress.', data: { type: 'reminder', action: 'open-progress' } }, { title: 'โญ Goal Reminder', body: 'Don\'t forget to work on your goals today!', data: { type: 'reminder', action: 'open-goals' } } ]; const reminder = reminders[Math.floor(Math.random() * reminders.length)]; await self.registration.showNotification(reminder.title, { body: reminder.body, icon: '/icons/icon-192x192.png', badge: '/icons/badge-72x72.png', tag: 'daily-reminder', requireInteraction: false, data: reminder.data, vibrate: [200, 100, 200], actions: [ { action: 'open-app', title: 'Open App', icon: '/icons/action-open.png' }, { action: 'dismiss', title: 'Dismiss', icon: '/icons/action-dismiss.png' } ] }); } // Check for updates function async function checkForUpdates() { console.log('๐Ÿ”” Checking for updates in background...'); try { const response = await fetch('/update-manifest.json?' + Date.now()); if (!response.ok) return; const updateInfo = await response.json(); const currentVersion = await getCurrentVersion(); if (updateInfo.version !== currentVersion) { await self.registration.showNotification('๐Ÿš€ ProgressPulse Update Available', { body: `Version ${updateInfo.version} is ready to install!`, icon: '/icons/icon-192x192.png', badge: '/icons/badge-72x72.png', tag: 'app-update', requireInteraction: true, data: { type: 'update', version: updateInfo.version, action: 'open-settings' }, actions: [ { action: 'update-now', title: 'Update Now', icon: '/icons/action-update.png' }, { action: 'later', title: 'Later', icon: '/icons/action-later.png' } ], vibrate: [200, 100, 200, 100, 200] }); } } catch (error) { console.error('Error checking for updates:', error); } } // Get current version from storage or package info async function getCurrentVersion() { try { // First try to get from version.json const cache = await caches.open(CACHE_NAME); const response = await cache.match('/version.json'); if (response) { const data = await response.json(); console.log('๐Ÿ”” SW: Current version from cache:', data.version); return data.version; } // Fallback: try to get from update manifest const manifestResponse = await fetch('/update-manifest.json'); if (manifestResponse.ok) { const manifest = await manifestResponse.json(); console.log('๐Ÿ”” SW: Current version from manifest:', manifest.version); return manifest.version; } } catch (error) { console.error('๐Ÿ”” SW: Error getting current version:', error); } console.log('๐Ÿ”” SW: Using fallback version'); return '1.0.0'; // fallback } // Periodic background sync (for APK) self.addEventListener('periodicsync', (event) => { console.log('๐Ÿ”” Periodic sync triggered:', event.tag); if (event.tag === 'daily-reminder') { event.waitUntil(sendDailyReminder()); } else if (event.tag === 'check-updates') { event.waitUntil(checkForUpdates()); } }); // Message handler for communication with main app self.addEventListener('message', (event) => { console.log('๐Ÿ”” Service worker received message:', event.data); const { type, data } = event.data; if (type === 'SCHEDULE_REMINDER') { scheduleReminder(data.time); } else if (type === 'SEND_TEST_NOTIFICATION') { sendTestNotification(); } else if (type === 'UPDATE_VERSION') { updateStoredVersion(data.version); } }); // Schedule reminder using setTimeout (for immediate scheduling) function scheduleReminder(time) { console.log('๐Ÿ”” Scheduling reminder for:', time); const [hours, minutes] = time.split(':').map(Number); const now = new Date(); const reminderTime = new Date(); reminderTime.setHours(hours, minutes, 0, 0); // If time has passed today, set for tomorrow if (reminderTime <= now) { reminderTime.setDate(reminderTime.getDate() + 1); } const msUntilReminder = reminderTime.getTime() - now.getTime(); setTimeout(() => { sendDailyReminder(); // Set up daily interval setInterval(() => { sendDailyReminder(); }, 24 * 60 * 60 * 1000); }, msUntilReminder); } // Send test notification async function sendTestNotification() { await self.registration.showNotification('๐Ÿงช Test Notification', { body: 'This is a test notification to verify the system works!', icon: '/icons/icon-192x192.png', badge: '/icons/badge-72x72.png', tag: 'test-notification', requireInteraction: false, vibrate: [200, 100, 200], data: { type: 'test' } }); } // Update stored version async function updateStoredVersion(version) { try { const cache = await caches.open(CACHE_NAME); const versionData = { version, timestamp: Date.now() }; await cache.put('/version.json', new Response(JSON.stringify(versionData), { headers: { 'Content-Type': 'application/json' } })); console.log('๐Ÿ”” Version updated to:', version); } catch (error) { console.error('Error updating version:', error); } } console.log('๐Ÿ”” Notification Service Worker loaded and ready!');