mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 14:21:46 +00:00
852f0085d1
- Add offline CRUD to todoRepo, budgetRepo, reservationRepo, accommodationRepo, dayRepo, tripRepo, fileRepo with optimistic Dexie writes and mutation queue - Wire all store slices (todo, budget, reservations, files, dayNotes, assignments, tripStore) through repos for offline-aware writes - Cover archive/unarchive, file toggleStar/update/delete, assignment create/delete, day title/notes update offline paths - Migrate service worker from generateSW to injectManifest (custom sw.ts) with runtime-configurable api-data (7d/500) and map-tiles (30d/1000) cache policies - Add Settings → Offline cache configuration UI with save/reset and live SW postMessage - Extend mutationQueue flush to cover all writable Dexie tables
129 lines
4.4 KiB
TypeScript
129 lines
4.4 KiB
TypeScript
/// <reference lib="webworker" />
|
|
|
|
import { clientsClaim } from 'workbox-core';
|
|
import {
|
|
precacheAndRoute,
|
|
cleanupOutdatedCaches,
|
|
createHandlerBoundToURL,
|
|
} from 'workbox-precaching';
|
|
import { registerRoute, NavigationRoute } from 'workbox-routing';
|
|
import { NetworkFirst, CacheFirst } from 'workbox-strategies';
|
|
import { ExpirationPlugin } from 'workbox-expiration';
|
|
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
|
|
import {
|
|
DEFAULT_SW_CONFIG,
|
|
readSwConfigFromIDB,
|
|
validateSwConfig,
|
|
type SwCacheConfig,
|
|
} from './sync/swConfig';
|
|
|
|
declare const self: ServiceWorkerGlobalScope;
|
|
|
|
self.skipWaiting();
|
|
clientsClaim();
|
|
|
|
// Inject precache manifest (replaced by vite-plugin-pwa at build time)
|
|
// @ts-expect-error __WB_MANIFEST is injected at build time
|
|
precacheAndRoute(self.__WB_MANIFEST);
|
|
cleanupOutdatedCaches();
|
|
|
|
// ── Static routes (not user-configurable) ─────────────────────────────────────
|
|
|
|
registerRoute(
|
|
new NavigationRoute(createHandlerBoundToURL('index.html'), {
|
|
denylist: [/^\/api/, /^\/uploads/, /^\/mcp/],
|
|
}),
|
|
);
|
|
|
|
registerRoute(
|
|
/^https:\/\/unpkg\.com\/.*/i,
|
|
new CacheFirst({
|
|
cacheName: 'cdn-libs',
|
|
plugins: [
|
|
new ExpirationPlugin({ maxEntries: 30, maxAgeSeconds: 365 * 24 * 60 * 60 }),
|
|
new CacheableResponsePlugin({ statuses: [0, 200] }),
|
|
],
|
|
}),
|
|
'GET',
|
|
);
|
|
|
|
registerRoute(
|
|
/\/uploads\/(?:covers|avatars)\/.*/i,
|
|
new CacheFirst({
|
|
cacheName: 'user-uploads',
|
|
plugins: [
|
|
new ExpirationPlugin({ maxEntries: 300, maxAgeSeconds: 7 * 24 * 60 * 60 }),
|
|
new CacheableResponsePlugin({ statuses: [200] }),
|
|
],
|
|
}),
|
|
'GET',
|
|
);
|
|
|
|
// ── Configurable routes ────────────────────────────────────────────────────────
|
|
// Routes are registered once. Strategy instances are replaced on config change
|
|
// so the stable handler wrapper always delegates to the current instance.
|
|
|
|
const DAY = 24 * 60 * 60;
|
|
|
|
function buildApiStrategy(cfg: SwCacheConfig): NetworkFirst {
|
|
return new NetworkFirst({
|
|
cacheName: 'api-data',
|
|
networkTimeoutSeconds: 5,
|
|
plugins: [
|
|
new ExpirationPlugin({
|
|
maxEntries: cfg.apiMaxEntries,
|
|
maxAgeSeconds: cfg.apiTtlDays * DAY,
|
|
}),
|
|
new CacheableResponsePlugin({ statuses: [200] }),
|
|
],
|
|
});
|
|
}
|
|
|
|
function buildTilesStrategy(cfg: SwCacheConfig): CacheFirst {
|
|
return new CacheFirst({
|
|
cacheName: 'map-tiles',
|
|
plugins: [
|
|
new ExpirationPlugin({
|
|
maxEntries: cfg.tilesMaxEntries,
|
|
maxAgeSeconds: cfg.tilesTtlDays * DAY,
|
|
}),
|
|
new CacheableResponsePlugin({ statuses: [0, 200] }),
|
|
],
|
|
});
|
|
}
|
|
|
|
let apiStrategy = buildApiStrategy(DEFAULT_SW_CONFIG);
|
|
let cartoStrategy = buildTilesStrategy(DEFAULT_SW_CONFIG);
|
|
let osmStrategy = buildTilesStrategy(DEFAULT_SW_CONFIG);
|
|
|
|
function applyConfig(cfg: SwCacheConfig): void {
|
|
apiStrategy = buildApiStrategy(cfg);
|
|
cartoStrategy = buildTilesStrategy(cfg);
|
|
osmStrategy = buildTilesStrategy(cfg);
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
registerRoute(/\/api\/(?!auth|admin|backup|settings).*/i, { handle: (o: any) => apiStrategy.handle(o) }, 'GET');
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
registerRoute(/^https:\/\/[a-d]\.basemaps\.cartocdn\.com\/.*/i, { handle: (o: any) => cartoStrategy.handle(o) }, 'GET');
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
registerRoute(/^https:\/\/[a-c]\.tile\.openstreetmap\.org\/.*/i, { handle: (o: any) => osmStrategy.handle(o) }, 'GET');
|
|
|
|
// Load persisted config asynchronously; replaces defaults if user has saved settings
|
|
readSwConfigFromIDB()
|
|
.then(cfg => { if (cfg) applyConfig(cfg); })
|
|
.catch(() => {});
|
|
|
|
// ── Message handler ────────────────────────────────────────────────────────────
|
|
|
|
self.addEventListener('message', (event: ExtendableMessageEvent) => {
|
|
const data = event.data as { type?: string; config?: unknown };
|
|
if (data?.type !== 'UPDATE_CACHE_CONFIG' || !data.config) return;
|
|
|
|
const validated = validateSwConfig(data.config as Partial<SwCacheConfig>);
|
|
applyConfig(validated);
|
|
|
|
// Acknowledge back to the sending client
|
|
(event.source as WindowClient | null)?.postMessage({ type: 'CACHE_CONFIG_APPLIED' });
|
|
});
|