diff --git a/public/index.html b/public/index.html index eae5562..8e9f68e 100644 --- a/public/index.html +++ b/public/index.html @@ -4,6 +4,9 @@ CareSync + + +
diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..187071b --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "CareSync", + "name": "CareSync Health Tracker", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/public/sw.js b/public/sw.js index 2eacde5..dcf5681 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,124 +1,76 @@ -/* eslint-disable no-restricted-globals */ - -const CACHE_NAME = 'caresync-cache-v1'; - -/** - * CareSync Service Worker — medicine reminder notifications & offline caching. - * - * Handles incoming `showNotification` calls from the main thread and - * reacts to notification clicks by navigating to the Medicine Tracker. - * Also caches static assets and API responses for offline use. - * - * Registered in `src/index.js` via `navigator.serviceWorker.register`. - * - * @file public/sw.js - */ - -/** - * notificationclick — fired when the user taps/clicks a notification. - */ -globalThis.addEventListener('notificationclick', (event) => { - event.notification.close(); - - const targetUrl = '/medicine-tracker'; +const CACHE_NAME = 'caresync-v1'; +const urlsToCache = [ + '/', + '/index.html', + '/manifest.json' +]; +self.addEventListener('install', event => { event.waitUntil( - globalThis.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { - // Try to focus an existing tab that already has the app open. - for (const client of clientList) { - const rawPath = new URL(client.url).pathname; - const clientPath = rawPath.length > 1 && rawPath.endsWith('/') ? rawPath.slice(0, -1) : rawPath; - if (clientPath === targetUrl && 'focus' in client) { - return client.focus(); - } - } - // No matching tab — open a new one. - if (globalThis.clients.openWindow) { - return globalThis.clients.openWindow(targetUrl); - } - }) + caches.open(CACHE_NAME) + .then(cache => { + return cache.addAll(urlsToCache); + }) ); }); -globalThis.addEventListener('install', (event) => { - globalThis.skipWaiting(); +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + event.respondWith( + caches.match(event.request) + .then(response => { + if (response) { + return response; + } + + return fetch(event.request).then(networkResponse => { + if (!networkResponse || networkResponse.status !== 200 || networkResponse.type !== 'basic') { + return networkResponse; + } + const responseToCache = networkResponse.clone(); + caches.open(CACHE_NAME).then(cache => { + cache.put(event.request, responseToCache); + }); + return networkResponse; + }).catch(() => { + // Fallback if needed, e.g., return offline page + }); + }) + ); }); -/** - * activate — claim all open clients immediately and clean up old caches. - */ -globalThis.addEventListener('activate', (event) => { +self.addEventListener('activate', event => { + const cacheWhitelist = [CACHE_NAME]; event.waitUntil( - caches.keys().then((cacheNames) => { + caches.keys().then(cacheNames => { return Promise.all( - cacheNames.map((cacheName) => { - if (cacheName !== CACHE_NAME) { + cacheNames.map(cacheName => { + if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName); } }) ); - }).then(() => globalThis.clients.claim()) + }) ); }); -/** - * fetch — intercept network requests for offline caching. - */ -globalThis.addEventListener('fetch', (event) => { - const { request } = event; - - // Handle API GET requests (Network First strategy) - if (request.url.includes('/api/') && request.method === 'GET') { - event.respondWith( - fetch(request) - .then((response) => { - if (response.status === 200) { - const responseClone = response.clone(); - caches.open(CACHE_NAME).then((cache) => cache.put(request, responseClone)); - } - return response; - }) - .catch(() => { - return caches.match(request).then((cachedResponse) => { - if (cachedResponse) return cachedResponse; - return new Response( - JSON.stringify({ error: 'Offline', message: 'No cached data available' }), - { status: 503, headers: { 'Content-Type': 'application/json' } } - ); - }); - }) - ); - return; - } - - // Skip other non-GET requests (handled by axios interceptor in api.js) - if (request.method !== 'GET') return; - - // Static Assets (Network First with Cache Fallback) - event.respondWith( - fetch(request) - .then((response) => { - if (response.status === 200 || response.type === 'opaque') { - const responseClone = response.clone(); - caches.open(CACHE_NAME).then((cache) => cache.put(request, responseClone)); +self.addEventListener('notificationclick', event => { + event.notification.close(); + + event.waitUntil( + clients.matchAll({ type: 'window' }).then(windowClients => { + // Check if there is already a window/tab open with the target URL + for (let i = 0; i < windowClients.length; i++) { + const client = windowClients[i]; + if (client.url.includes(self.registration.scope) && 'focus' in client) { + return client.focus(); } - return response; - }) - .catch(() => { - return caches.match(request).then((cachedResponse) => { - if (cachedResponse) return cachedResponse; - - // For navigation requests, fallback to index.html if offline - if (request.mode === 'navigate') { - return caches.match('/index.html').then(idxResp => { - if (idxResp) return idxResp; - return new Response('Offline. App could not be loaded.', { status: 503, headers: { 'Content-Type': 'text/html' } }); - }); - } - - // Generic fallback to avoid TypeError: Failed to convert value to 'Response' - return new Response('', { status: 503, statusText: 'Service Unavailable (Offline)' }); - }); - }) + } + // If no window is open, open a new one + if (clients.openWindow) { + return clients.openWindow('/'); + } + }) ); });