feat: add service worker for PWA support (offline caching, installability)
- Add sw.js with stale-while-revalidate caching for static assets - Network-first for HTML navigation, skip API/WS requests - Register service worker in main.tsx on page load - Enhanced manifest.json with orientation, categories, and all icon sizes - App is now installable as a standalone PWA on mobile and desktop
This commit is contained in:
@@ -1,12 +1,24 @@
|
||||
{
|
||||
"name": "PinchChat",
|
||||
"short_name": "PinchChat",
|
||||
"description": "A sleek, dark-themed webchat UI for OpenClaw",
|
||||
"description": "A sleek, dark-themed webchat UI for OpenClaw — monitor sessions, stream responses, and inspect tool calls in real-time.",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#1e1e24",
|
||||
"theme_color": "#1e1e24",
|
||||
"orientation": "any",
|
||||
"categories": ["developer", "productivity", "utilities"],
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon-16.png",
|
||||
"sizes": "16x16",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicon-32.png",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/logo-192.png",
|
||||
"sizes": "192x192",
|
||||
|
||||
62
public/sw.js
Normal file
62
public/sw.js
Normal file
@@ -0,0 +1,62 @@
|
||||
// PinchChat Service Worker — cache static assets for offline/instant load
|
||||
const CACHE_NAME = 'pinchchat-v1';
|
||||
|
||||
// Cache static assets on install
|
||||
self.addEventListener('install', (event) => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
// Clean old caches on activate
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(
|
||||
caches.keys().then((keys) =>
|
||||
Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
|
||||
)
|
||||
);
|
||||
self.clients.claim();
|
||||
});
|
||||
|
||||
// Stale-while-revalidate for static assets, network-first for API/WS
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
// Skip non-GET, WebSocket upgrades, and chrome-extension requests
|
||||
if (event.request.method !== 'GET') return;
|
||||
if (url.protocol === 'chrome-extension:') return;
|
||||
|
||||
// Don't cache API calls or WebSocket-related requests
|
||||
if (url.pathname.startsWith('/api') || url.pathname.startsWith('/ws')) return;
|
||||
|
||||
// For navigation requests (HTML), always go network-first to get latest SPA shell
|
||||
if (event.request.mode === 'navigate') {
|
||||
event.respondWith(
|
||||
fetch(event.request)
|
||||
.then((response) => {
|
||||
const clone = response.clone();
|
||||
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
|
||||
return response;
|
||||
})
|
||||
.catch(() => caches.match(event.request))
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Static assets (JS, CSS, images, fonts): stale-while-revalidate
|
||||
if (
|
||||
url.pathname.match(/\.(js|css|png|jpg|jpeg|gif|webp|svg|woff2?|ttf|ico|json)$/) ||
|
||||
url.pathname.startsWith('/assets/')
|
||||
) {
|
||||
event.respondWith(
|
||||
caches.open(CACHE_NAME).then((cache) =>
|
||||
cache.match(event.request).then((cached) => {
|
||||
const fetchPromise = fetch(event.request).then((response) => {
|
||||
if (response.ok) cache.put(event.request, response.clone());
|
||||
return response;
|
||||
});
|
||||
return cached || fetchPromise;
|
||||
})
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user