export default defineEventHandler(async (event) => { const url = getRequestURL(event) // ========================================== // PWA Critical Resources: Service Worker & Manifest // ========================================== if (url.pathname === '/sw.js' || url.pathname.startsWith('/workbox-')) { // Service Worker must NEVER be cached by browser - always check for updates setHeader(event, 'Content-Type', 'application/javascript; charset=utf-8') setHeader(event, 'Cache-Control', 'no-cache, no-store, must-revalidate') setHeader(event, 'Service-Worker-Allowed', '/') return } if (url.pathname === '/manifest.webmanifest' || url.pathname === '/manifest.json') { setHeader(event, 'Content-Type', 'application/manifest+json; charset=utf-8') setHeader(event, 'Cache-Control', 'public, max-age=0, must-revalidate') return } // ========================================== // Nuxt Build Assets: Let Service Worker control cache // ========================================== if (url.pathname.startsWith('/_nuxt/')) { const ext = url.pathname.split('.').pop()?.toLowerCase() // Set proper MIME types switch (ext) { case 'js': setHeader(event, 'Content-Type', 'application/javascript; charset=utf-8') break case 'mjs': setHeader(event, 'Content-Type', 'application/javascript; charset=utf-8') break case 'css': setHeader(event, 'Content-Type', 'text/css; charset=utf-8') break case 'json': setHeader(event, 'Content-Type', 'application/json; charset=utf-8') break case 'svg': setHeader(event, 'Content-Type', 'image/svg+xml; charset=utf-8') break } // Immutable assets (with hash in filename) can be cached aggressively if (url.pathname.match(/\.[a-f0-9]{8,}\.(js|css|json|svg|png|jpg|webp|woff2?)$/)) { setHeader(event, 'Cache-Control', 'public, max-age=31536000, immutable') } else { // Non-hashed assets: let Service Worker decide setHeader(event, 'Cache-Control', 'public, max-age=0, must-revalidate') } } // ========================================== // Static Assets (logos, icons, images) // ========================================== if (url.pathname.match(/\.(png|jpg|jpeg|svg|gif|webp|ico)$/)) { // Let Service Worker control static image caching setHeader(event, 'Cache-Control', 'public, max-age=0, must-revalidate') } // ========================================== // API Endpoints // ========================================== if (url.pathname.startsWith('/api/')) { // Trust proxy headers const realIP = getHeader(event, 'x-real-ip') || getHeader(event, 'x-forwarded-for') const proto = getHeader(event, 'x-forwarded-proto') || 'http' const host = getHeader(event, 'host') // Set CORS headers for PWA offline support setHeader(event, 'Access-Control-Allow-Origin', '*') setHeader(event, 'Access-Control-Allow-Methods', 'GET, POST, OPTIONS') setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization, Range') // Handle music file requests if (url.pathname.startsWith('/api/music/')) { // Music files: cache by Service Worker, not browser setHeader(event, 'Cache-Control', 'public, max-age=0, must-revalidate') setHeader(event, 'Accept-Ranges', 'bytes') // Security headers setHeader(event, 'X-Content-Type-Options', 'nosniff') if (process.env.NODE_ENV === 'production') { setHeader(event, 'X-Frame-Options', 'DENY') } } else { // Other API endpoints: no cache setHeader(event, 'Cache-Control', 'no-store, must-revalidate') } } // ========================================== // Handle OPTIONS preflight requests // ========================================== if (event.node.req.method === 'OPTIONS') { setHeader(event, 'Access-Control-Allow-Origin', '*') setHeader(event, 'Access-Control-Allow-Methods', 'GET, POST, OPTIONS') setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization, Range') setResponseStatus(event, 200) return '' } })