import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { VitePWA } from 'vite-plugin-pwa' export default defineConfig({ plugins: [ react(), VitePWA({ registerType: 'autoUpdate', workbox: { maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, globPatterns: ['**/*.{js,css,html,svg,png,woff,woff2,ttf}'], navigateFallback: 'index.html', navigateFallbackDenylist: [/^\/api/, /^\/uploads/, /^\/mcp/, /^\/oauth\//, /^\/.well-known\//], runtimeCaching: [ { // Carto map tiles (default provider) // maxEntries MUST stay >= MAX_TILES in src/sync/tilePrefetcher.ts // (both are 12288) so prefetched tiles aren't evicted on arrival. urlPattern: /^https:\/\/[a-d]\.basemaps\.cartocdn\.com\/.*/i, handler: 'CacheFirst', options: { cacheName: 'map-tiles', expiration: { maxEntries: 12288, maxAgeSeconds: 30 * 24 * 60 * 60 }, cacheableResponse: { statuses: [0, 200] }, }, }, { // OpenStreetMap tiles (fallback / alternative) // Shares the 'map-tiles' cache; keep maxEntries equal to the Carto // rule above and MAX_TILES in src/sync/tilePrefetcher.ts (12288). urlPattern: /^https:\/\/[a-c]\.tile\.openstreetmap\.org\/.*/i, handler: 'CacheFirst', options: { cacheName: 'map-tiles', expiration: { maxEntries: 12288, maxAgeSeconds: 30 * 24 * 60 * 60 }, cacheableResponse: { statuses: [0, 200] }, }, }, { // Leaflet CSS/JS from unpkg CDN urlPattern: /^https:\/\/unpkg\.com\/.*/i, handler: 'CacheFirst', options: { cacheName: 'cdn-libs', expiration: { maxEntries: 30, maxAgeSeconds: 365 * 24 * 60 * 60 }, cacheableResponse: { statuses: [0, 200] }, }, }, { // Mapbox GL style, glyphs, sprites and vector tiles. Best-effort // offline only: opportunistically caches what the user has already // viewed online. Full pre-download offline maps require the Leaflet // renderer (raster prefetch in tilePrefetcher.ts) — the GL vector // pipeline is not prefetched. StaleWhileRevalidate keeps the basemap // fresh online while still serving from cache when offline. Mapbox // sends CORS, so responses are non-opaque (real 200s, no quota pad). urlPattern: /^https:\/\/(api\.mapbox\.com|[a-d]\.tiles\.mapbox\.com)\/.*/i, handler: 'StaleWhileRevalidate', options: { cacheName: 'mapbox-tiles', expiration: { maxEntries: 3000, maxAgeSeconds: 30 * 24 * 60 * 60 }, cacheableResponse: { statuses: [200] }, }, }, { // API calls — network only. We deliberately do NOT cache API // responses in the Service Worker: Workbox keys entries by URL and // cannot vary on the httpOnly session cookie, so a shared device // could serve one user's cached data to the next (cross-user leak). // Offline reads are served from the per-user IndexedDB cache via the // repo layer instead. The urlPattern is kept so these requests still // bypass the SPA navigation fallback. urlPattern: /\/api\/(?!auth|admin|backup|settings|health).*/i, handler: 'NetworkOnly', }, { // Uploaded files (photos, covers — public assets only) urlPattern: /\/uploads\/(?:covers|avatars)\/.*/i, handler: 'CacheFirst', options: { cacheName: 'user-uploads', expiration: { maxEntries: 300, maxAgeSeconds: 7 * 24 * 60 * 60 }, cacheableResponse: { statuses: [200] }, }, }, ], }, manifest: { name: 'TREK \u2014 Travel Planner', short_name: 'TREK', description: 'Travel Resource & Exploration Kit', theme_color: '#111827', background_color: '#0f172a', display: 'standalone', scope: '/', start_url: '/', categories: ['travel', 'navigation'], icons: [ { src: 'icons/apple-touch-icon-180x180.png', sizes: '180x180', type: 'image/png' }, { src: 'icons/icon-192x192.png', sizes: '192x192', type: 'image/png' }, { src: 'icons/icon-512x512.png', sizes: '512x512', type: 'image/png' }, { src: 'icons/icon-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' }, { src: 'icons/icon.svg', sizes: 'any', type: 'image/svg+xml' }, ], }, }), ], build: { sourcemap: false, modulePreload: { polyfill: true }, }, server: { port: 5173, proxy: { '/api': { target: 'http://localhost:3001', changeOrigin: true, }, '/uploads': { target: 'http://localhost:3001', changeOrigin: true, }, '/ws': { target: 'http://localhost:3001', ws: true, }, '/mcp': { target: 'http://localhost:3001', changeOrigin: true, }, // OAuth 2.1 endpoints handled by backend (SDK authorize handler + token/revoke) // /oauth/authorize goes to backend so the SDK can redirect to /oauth/consent // /oauth/consent is served by Vite as a SPA route (no proxy entry needed) '/oauth/authorize': { target: 'http://localhost:3001', changeOrigin: true, }, '/oauth/token': { target: 'http://localhost:3001', changeOrigin: true, }, '/oauth/register': { target: 'http://localhost:3001', changeOrigin: true, }, '/oauth/revoke': { target: 'http://localhost:3001', changeOrigin: true, }, '/.well-known': { target: 'http://localhost:3001', changeOrigin: true, }, } } })