Skip to content
Plurafy

Service Workers and Offline Support

Building offline-capable web applications

Enable offline functionality, cache assets intelligently, and handle network failures gracefully.

Service Workers are scripts that run in the background, enabling offline support and background sync.

// Register service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('Service Worker registered'))
.catch(err => console.error('Registration failed:', err));
}
// sw.js - The service worker file
self.addEventListener('install', event => {
console.log('Service Worker installing...');
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js'
]);
})
);
});
self.addEventListener('activate', event => {
console.log('Service Worker activating...');
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== 'v1') {
return caches.delete(cacheName);
}
})
);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response; // Cached
}
return fetch(event.request)
.then(response => {
// Don't cache non-successful responses
if (!response || response.status !== 200) {
return response;
}
// Clone and cache
const responseClone = response.clone();
caches.open('v1').then(cache => {
cache.put(event.request, responseClone);
});
return response;
});
})
.catch(() => {
// Fallback for failed requests
return new Response('Offline - resource not available');
})
);
});
// Network First
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => {
return caches.open('v1').then(cache => {
cache.put(event.request, response.clone());
return response;
});
})
.catch(() => {
return caches.match(event.request);
})
);
});
// Stale While Revalidate
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
const fetchPromise = fetch(event.request)
.then(response => {
caches.open('v1').then(cache => {
cache.put(event.request, response.clone());
});
return response;
});
return cachedResponse || fetchPromise;
})
);
});
// Cache Only
self.addEventListener('fetch', event => {
event.respondWith(caches.match(event.request));
});
// Register background sync
navigator.serviceWorker.ready.then(swReg => {
// Sync when online again
return swReg.sync.register('sync-comments');
});
// Handle sync event in SW
self.addEventListener('sync', event => {
if (event.tag === 'sync-comments') {
event.waitUntil(
syncComments()
.catch(() => {
// Retry sync
return self.registration.sync.register('sync-comments');
})
);
}
});
async function syncComments() {
const db = await openDatabase('comments');
const pending = await db.getAll('pending');
for (const comment of pending) {
const response = await fetch('/api/comments', {
method: 'POST',
body: JSON.stringify(comment)
});
if (response.ok) {
await db.delete('pending', comment.id);
}
}
}
// Request notification permission
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
navigator.serviceWorker.ready.then(swReg => {
swReg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicKey)
});
});
}
});
// Handle push in service worker
self.addEventListener('push', event => {
const data = event.data?.json() || {};
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.message,
icon: '/icon.png',
badge: '/badge.png'
})
);
});
// Handle notification click
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(
clients.matchAll({ type: 'window' })
.then(windowClients => {
if (windowClients.length > 0) {
windowClients[0].focus();
} else {
clients.openWindow('/');
}
})
);
});
// IndexedDB for structured data
const dbRequest = indexedDB.open('myApp', 1);
dbRequest.onupgradeneeded = event => {
const db = event.target.result;
const store = db.createObjectStore('tasks', { keyPath: 'id' });
store.createIndex('status', 'status', { unique: false });
};
dbRequest.onsuccess = event => {
const db = event.target.result;
const tx = db.transaction('tasks', 'readwrite');
const store = tx.objectStore('tasks');
// Add task
store.add({ id: 1, title: 'Task', status: 'pending' });
// Query
const getRequest = store.get(1);
getRequest.onsuccess = () => {
console.log(getRequest.result);
};
};
// LocalStorage for simple data
localStorage.setItem('offline-queue', JSON.stringify(queue));