712dc5632c
Add Progressive Web App support for installable mobile experience on iOS Safari and Android Chrome. - static/manifest.json: app name, standalone display, dark theme, icon references for 192px and 512px - static/sw.js: service worker with cache-first strategy for static assets, network-first for HTML/HTMX, offline fallback to cache - static/icon-192.png, static/icon-512.png: placeholder app icons - Apple meta tags: apple-mobile-web-app-capable, status bar style, apple-touch-icon for iOS Add to Home Screen - Service worker registration in base layout template Closes leeworks-agents/gitea-mobile#6 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
87 lines
2.4 KiB
JavaScript
87 lines
2.4 KiB
JavaScript
// Service Worker for Gitea Mobile PWA
|
|
// Caches the app shell for offline/fast loading.
|
|
|
|
const CACHE_NAME = 'gitea-mobile-v1';
|
|
const APP_SHELL = [
|
|
'/',
|
|
'/static/style.css',
|
|
'/static/manifest.json',
|
|
'/static/icon-192.png',
|
|
'/static/icon-512.png',
|
|
'https://unpkg.com/htmx.org@1.9.10'
|
|
];
|
|
|
|
// Install: cache app shell resources.
|
|
self.addEventListener('install', (event) => {
|
|
event.waitUntil(
|
|
caches.open(CACHE_NAME).then((cache) => {
|
|
return cache.addAll(APP_SHELL);
|
|
})
|
|
);
|
|
// Activate immediately.
|
|
self.skipWaiting();
|
|
});
|
|
|
|
// Activate: clean up old caches.
|
|
self.addEventListener('activate', (event) => {
|
|
event.waitUntil(
|
|
caches.keys().then((cacheNames) => {
|
|
return Promise.all(
|
|
cacheNames
|
|
.filter((name) => name !== CACHE_NAME)
|
|
.map((name) => caches.delete(name))
|
|
);
|
|
})
|
|
);
|
|
// Take control of all clients immediately.
|
|
self.clients.claim();
|
|
});
|
|
|
|
// Fetch: network-first for API/HTML, cache-first for static assets.
|
|
self.addEventListener('fetch', (event) => {
|
|
const url = new URL(event.request.url);
|
|
|
|
// Cache-first for static assets.
|
|
if (url.pathname.startsWith('/static/')) {
|
|
event.respondWith(
|
|
caches.match(event.request).then((cached) => {
|
|
return cached || fetch(event.request).then((response) => {
|
|
// Cache the fetched response for next time.
|
|
const responseClone = response.clone();
|
|
caches.open(CACHE_NAME).then((cache) => {
|
|
cache.put(event.request, responseClone);
|
|
});
|
|
return response;
|
|
});
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Network-first for HTML/API requests.
|
|
if (event.request.headers.get('accept')?.includes('text/html') ||
|
|
event.request.headers.get('HX-Request')) {
|
|
event.respondWith(
|
|
fetch(event.request)
|
|
.then((response) => {
|
|
// Cache successful GET responses.
|
|
if (event.request.method === 'GET' && response.ok) {
|
|
const responseClone = response.clone();
|
|
caches.open(CACHE_NAME).then((cache) => {
|
|
cache.put(event.request, responseClone);
|
|
});
|
|
}
|
|
return response;
|
|
})
|
|
.catch(() => {
|
|
// Fallback to cache if network fails.
|
|
return caches.match(event.request);
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Default: network only.
|
|
event.respondWith(fetch(event.request));
|
|
});
|