From 274faf7c854b977382d44a8ef90ba3ddfe563fdf Mon Sep 17 00:00:00 2001 From: bymyself Date: Fri, 24 Oct 2025 22:10:10 -0700 Subject: [PATCH] [bugfix] fix service worker opaqueredirect error and ensure SW controls page before mount Fixes two related issues with the auth service worker: 1. Opaqueredirect network error: - Service workers cannot return opaqueredirect responses to the page - When using redirect: 'manual', cross-origin redirects produce opaqueredirect - Solution: Re-fetch with redirect: 'follow' when opaqueredirect detected - Browser automatically strips auth headers on cross-origin redirects to GCS 2. Race condition on first load: - App was mounting before service worker registration completed - Images could load before SW gained control of the page - Solution: Await SW registration before mounting app - Changed void import() to await import() at all levels This fixes the pattern where hard refresh works (bypasses SW) but normal refresh fails (SW intercepts but returns invalid opaqueredirect response). --- public/auth-sw.js | 27 +++++++++++++++------ src/main.ts | 3 ++- src/platform/auth/serviceWorker/index.ts | 2 +- src/platform/auth/serviceWorker/register.ts | 2 +- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/public/auth-sw.js b/public/auth-sw.js index a8929a77f..08405bf3f 100644 --- a/public/auth-sw.js +++ b/public/auth-sw.js @@ -67,15 +67,28 @@ self.addEventListener('fetch', (event) => { }) ) - // If redirected to external storage (GCS), follow without auth headers - // The signed URL contains its own authentication in query params - if ( - response.type === 'opaqueredirect' || - response.status === 302 || - response.status === 301 - ) { + // Handle redirects to external storage (e.g., GCS signed URLs) + if (response.type === 'opaqueredirect') { + // Opaqueredirect: redirect occurred but response is opaque (headers not accessible) + // Re-fetch the original /api/view URL with redirect: 'follow' + // Browser will: + // 1. Send auth headers to /api/view (same-origin) + // 2. Receive 302 redirect to GCS + // 3. Automatically strip auth headers when following cross-origin redirect + // 4. Use GCS signed URL authentication instead + return fetch(event.request.url, { + method: 'GET', + headers: headers, + redirect: 'follow' + }) + } + + // Non-opaque redirect (status visible) - shouldn't normally happen with redirect: 'manual' + // but handle as fallback + if (response.status === 302 || response.status === 301) { const location = response.headers.get('location') if (location) { + // Follow redirect manually - do NOT include auth headers for external URLs return fetch(location, { method: 'GET', redirect: 'follow' diff --git a/src/main.ts b/src/main.ts index 90e060bb8..301612f0f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -86,8 +86,9 @@ app }) // Register auth service worker after Pinia is initialized (cloud-only) +// Wait for registration to complete before mounting to ensure SW controls the page if (isCloud) { - void import('@/platform/auth/serviceWorker') + await import('@/platform/auth/serviceWorker') } app.mount('#vue-app') diff --git a/src/platform/auth/serviceWorker/index.ts b/src/platform/auth/serviceWorker/index.ts index c83e238d5..518723fc7 100644 --- a/src/platform/auth/serviceWorker/index.ts +++ b/src/platform/auth/serviceWorker/index.ts @@ -5,5 +5,5 @@ import { isCloud } from '@/platform/distribution/types' * Tree-shaken for desktop/localhost builds via compile-time constant. */ if (isCloud) { - void import('./register') + await import('./register') } diff --git a/src/platform/auth/serviceWorker/register.ts b/src/platform/auth/serviceWorker/register.ts index eda954dba..0f44f3b9b 100644 --- a/src/platform/auth/serviceWorker/register.ts +++ b/src/platform/auth/serviceWorker/register.ts @@ -54,4 +54,4 @@ function setupCacheInvalidation(): void { }) } -void registerAuthServiceWorker() +await registerAuthServiceWorker()