Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CareSync</title>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<meta name="theme-color" content="#000000" />
</head>
<body>
<div id="root"></div>
Expand Down
25 changes: 25 additions & 0 deletions public/manifest.json
Original file line number Diff line number Diff line change
@@ -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"
}
162 changes: 57 additions & 105 deletions public/sw.js
Original file line number Diff line number Diff line change
@@ -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'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
];

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);
})
);
});

Comment thread
coderabbitai[bot] marked this conversation as resolved.
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 => {

Check failure on line 27 in public/sw.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to not always return the same value.

See more on https://sonarcloud.io/project/issues?id=vallabhatech_CareSync&issues=AZ-jyDA38BvVdFy8EN3I&open=AZ-jyDA38BvVdFy8EN3I&pullRequest=334
if (!networkResponse || networkResponse.status !== 200 || networkResponse.type !== 'basic') {

Check warning on line 28 in public/sw.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=vallabhatech_CareSync&issues=AZ-jyDA38BvVdFy8EN3J&open=AZ-jyDA38BvVdFy8EN3J&pullRequest=334
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) {

Check warning on line 49 in public/sw.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use `.includes()`, rather than `.indexOf()`, when checking for existence.

See more on https://sonarcloud.io/project/issues?id=vallabhatech_CareSync&issues=AZ-jwHRFGn284K-KI_e4&open=AZ-jwHRFGn284K-KI_e4&pullRequest=334
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)' });
});
})
}

Check warning on line 69 in public/sw.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Expected a `for-of` loop instead of a `for` loop with this simple iteration.

See more on https://sonarcloud.io/project/issues?id=vallabhatech_CareSync&issues=AZ-jyDA38BvVdFy8EN3K&open=AZ-jyDA38BvVdFy8EN3K&pullRequest=334
// If no window is open, open a new one
if (clients.openWindow) {
return clients.openWindow('/');
}
})
);
});
Loading