From 936da14dbcbaca4eff6b9aae02ef158c8a8f8683 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Fri, 24 Oct 2025 22:30:16 -0700 Subject: [PATCH] [bugfix] fix service worker opaqueredirect error and ensure SW controls page before mount (#6275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes service worker network error by handling opaqueredirect responses correctly and ensures SW registration completes before app mount to prevent race conditions on first load. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6275-bugfix-fix-service-worker-opaqueredirect-error-and-ensure-SW-controls-page-before-mount-2976d73d36508106bc65dc82cdc62779) by [Unito](https://www.unito.io) --- 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 48aa7d9b5..b5b4b844f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -83,8 +83,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()