remove all auth service work related code (#6294)

## Summary

Removes all service worker auth code, as it is being replaced by a more
robust standard solution for authenticating view and viewvideo requests
in https://github.com/Comfy-Org/ComfyUI_frontend/pull/6295.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6294-remove-all-auth-service-work-related-code-2986d73d36508170a24bf1c42cad401e)
by [Unito](https://www.unito.io)
This commit is contained in:
Christian Byrne
2025-10-25 23:08:41 -07:00
committed by GitHub
parent e5d5c042c7
commit d9e62985c6
9 changed files with 2 additions and 456 deletions

View File

@@ -60,8 +60,6 @@ export default defineConfig([
'**/vite.config.*.timestamp*',
'**/vitest.config.*.timestamp*',
'packages/registry-types/src/comfyRegistryTypes.ts',
'public/auth-dev-sw.js',
'public/auth-sw.js',
'src/extensions/core/*',
'src/scripts/*',
'src/types/generatedManagerTypes.ts',

View File

@@ -41,10 +41,7 @@ const config: KnipConfig = {
'src/workbench/extensions/manager/types/generatedManagerTypes.ts',
'packages/registry-types/src/comfyRegistryTypes.ts',
// Used by a custom node (that should move off of this)
'src/scripts/ui/components/splitButton.ts',
// Service workers - registered at runtime via navigator.serviceWorker.register()
'public/auth-sw.js',
'public/auth-dev-sw.js'
'src/scripts/ui/components/splitButton.ts'
],
compilers: {
// https://github.com/webpro-nl/knip/issues/1008#issuecomment-3207756199

View File

@@ -1,168 +0,0 @@
/**
* @fileoverview Authentication Service Worker (Development Version)
* Intercepts /api/view requests and rewrites them to a configurable base URL with auth token.
* Required for browser-native requests (img, video, audio) that cannot send custom headers.
* This version is used in development to proxy requests to staging/test environments.
* Default base URL: https://testcloud.comfy.org (configurable via SET_BASE_URL message)
*/
/**
* @typedef {Object} AuthHeader
* @property {string} Authorization - Bearer token for authentication
*/
/**
* @typedef {Object} CachedAuth
* @property {AuthHeader|null} header
* @property {number} expiresAt - Timestamp when cache expires
*/
const CACHE_TTL_MS = 50 * 60 * 1000 // 50 minutes (Firebase tokens expire in 1 hour)
/** @type {CachedAuth|null} */
let authCache = null
/** @type {Promise<AuthHeader|null>|null} */
let authRequestInFlight = null
/** @type {string} */
let baseUrl = 'https://testcloud.comfy.org'
self.addEventListener('message', (event) => {
if (event.data.type === 'INVALIDATE_AUTH_HEADER') {
authCache = null
authRequestInFlight = null
}
if (event.data.type === 'SET_BASE_URL') {
baseUrl = event.data.baseUrl
console.log('[Auth DEV SW] Base URL set to:', baseUrl)
}
})
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
if (
!url.pathname.startsWith('/api/view') &&
!url.pathname.startsWith('/api/viewvideo')
) {
return
}
event.respondWith(
(async () => {
try {
// Rewrite URL to use configured base URL (default: stagingcloud.comfy.org)
const originalUrl = new URL(event.request.url)
const rewrittenUrl = new URL(
originalUrl.pathname + originalUrl.search,
baseUrl
)
const authHeader = await getAuthHeader()
// With mode: 'no-cors', Authorization headers are stripped by the browser
// So we add the token to the URL as a query parameter instead
if (authHeader && authHeader.Authorization) {
const token = authHeader.Authorization.replace('Bearer ', '')
rewrittenUrl.searchParams.set('token', token)
}
// Cross-origin request requires no-cors mode
// - mode: 'no-cors' allows cross-origin fetches without CORS headers
// - Returns opaque response, which works fine for images/videos/audio
// - Auth token is sent via query parameter since headers are stripped in no-cors mode
// - Server may return redirect to GCS, which will be followed automatically
return fetch(rewrittenUrl, {
method: 'GET',
redirect: 'follow',
mode: 'no-cors'
})
} catch (error) {
console.error('[Auth DEV SW] Request failed:', error)
const originalUrl = new URL(event.request.url)
const rewrittenUrl = new URL(
originalUrl.pathname + originalUrl.search,
baseUrl
)
return fetch(rewrittenUrl, {
mode: 'no-cors',
redirect: 'follow'
})
}
})()
)
})
/**
* Gets auth header from cache or requests from main thread
* @returns {Promise<AuthHeader|null>}
*/
async function getAuthHeader() {
// Return cached value if valid
if (authCache && authCache.expiresAt > Date.now()) {
return authCache.header
}
// Clear expired cache
if (authCache) {
authCache = null
}
// Deduplicate concurrent requests
if (authRequestInFlight) {
return authRequestInFlight
}
authRequestInFlight = requestAuthHeaderFromMainThread()
const header = await authRequestInFlight
authRequestInFlight = null
// Cache the result
if (header) {
authCache = {
header,
expiresAt: Date.now() + CACHE_TTL_MS
}
}
return header
}
/**
* Requests auth header from main thread via MessageChannel
* @returns {Promise<AuthHeader|null>}
*/
async function requestAuthHeaderFromMainThread() {
const clients = await self.clients.matchAll()
if (clients.length === 0) {
return null
}
const messageChannel = new MessageChannel()
return new Promise((resolve) => {
let timeoutId
messageChannel.port1.onmessage = (event) => {
clearTimeout(timeoutId)
resolve(event.data.authHeader)
}
timeoutId = setTimeout(() => {
console.error(
'[Auth DEV SW] Timeout waiting for auth header from main thread'
)
resolve(null)
}, 1000)
clients[0].postMessage({ type: 'REQUEST_AUTH_HEADER' }, [
messageChannel.port2
])
})
}
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim())
})

View File

@@ -1,179 +0,0 @@
/**
* @fileoverview Authentication Service Worker
* Intercepts /api/view requests and adds Firebase authentication headers.
* Required for browser-native requests (img, video, audio) that cannot send custom headers.
*/
/**
* @typedef {Object} AuthHeader
* @property {string} Authorization - Bearer token for authentication
*/
/**
* @typedef {Object} CachedAuth
* @property {AuthHeader|null} header
* @property {number} expiresAt - Timestamp when cache expires
*/
const CACHE_TTL_MS = 50 * 60 * 1000 // 50 minutes (Firebase tokens expire in 1 hour)
/** @type {CachedAuth|null} */
let authCache = null
/** @type {Promise<AuthHeader|null>|null} */
let authRequestInFlight = null
self.addEventListener('message', (event) => {
if (event.data.type === 'INVALIDATE_AUTH_HEADER') {
authCache = null
authRequestInFlight = null
}
})
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
if (
!url.pathname.startsWith('/api/view') &&
!url.pathname.startsWith('/api/viewvideo')
) {
return
}
event.respondWith(
(async () => {
try {
const authHeader = await getAuthHeader()
if (!authHeader) {
return fetch(event.request)
}
const headers = new Headers(event.request.headers)
for (const [key, value] of Object.entries(authHeader)) {
headers.set(key, value)
}
// Fetch with manual redirect to handle cross-origin redirects (e.g., GCS signed URLs)
const response = await fetch(
new Request(event.request.url, {
method: event.request.method,
headers: headers,
credentials: event.request.credentials,
cache: 'no-store',
redirect: 'manual',
referrer: event.request.referrer,
integrity: event.request.integrity
})
)
// 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' and mode: 'no-cors'
// - mode: 'no-cors' allows cross-origin fetches without CORS headers (GCS doesn't have CORS)
// - Returns opaque response, which works fine for images/videos/audio
// - Browser will send auth headers to /api/view (same-origin)
// - Browser will receive 302 redirect to GCS
// - Browser will follow redirect using GCS signed URL authentication
return fetch(event.request.url, {
method: 'GET',
headers: headers,
redirect: 'follow',
mode: 'no-cors'
})
}
// 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'
})
}
}
return response
} catch (error) {
console.error('[Auth SW] Request failed:', error)
return fetch(event.request)
}
})()
)
})
/**
* Gets auth header from cache or requests from main thread
* @returns {Promise<AuthHeader|null>}
*/
async function getAuthHeader() {
// Return cached value if valid
if (authCache && authCache.expiresAt > Date.now()) {
return authCache.header
}
// Clear expired cache
if (authCache) {
authCache = null
}
// Deduplicate concurrent requests
if (authRequestInFlight) {
return authRequestInFlight
}
authRequestInFlight = requestAuthHeaderFromMainThread()
const header = await authRequestInFlight
authRequestInFlight = null
// Cache the result
if (header) {
authCache = {
header,
expiresAt: Date.now() + CACHE_TTL_MS
}
}
return header
}
/**
* Requests auth header from main thread via MessageChannel
* @returns {Promise<AuthHeader|null>}
*/
async function requestAuthHeaderFromMainThread() {
const clients = await self.clients.matchAll()
if (clients.length === 0) {
return null
}
const messageChannel = new MessageChannel()
return new Promise((resolve) => {
let timeoutId
messageChannel.port1.onmessage = (event) => {
clearTimeout(timeoutId)
resolve(event.data.authHeader)
}
timeoutId = setTimeout(() => {
console.error(
'[Auth SW] Timeout waiting for auth header from main thread'
)
resolve(null)
}, 1000)
clients[0].postMessage({ type: 'REQUEST_AUTH_HEADER' }, [
messageChannel.port2
])
})
}
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim())
})

View File

@@ -82,10 +82,4 @@ app
modules: [VueFireAuth()]
})
// 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) {
await import('@/platform/auth/serviceWorker')
}
app.mount('#vue-app')

View File

@@ -1,9 +0,0 @@
import { isCloud } from '@/platform/distribution/types'
/**
* Auth service worker registration (cloud-only).
* Tree-shaken for desktop/localhost builds via compile-time constant.
*/
if (isCloud) {
await import('./register')
}

View File

@@ -1,84 +0,0 @@
import { watch } from 'vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
/**
* Registers the authentication service worker for cloud distribution.
* Intercepts /api/view requests to add auth headers for browser-native requests.
*/
async function registerAuthServiceWorker(): Promise<void> {
if (!('serviceWorker' in navigator)) {
return
}
try {
// Use dev service worker in development mode (rewrites to configured backend URL with token in query param)
// Use production service worker in production (same-origin requests with Authorization header)
const swPath = import.meta.env.DEV ? '/auth-dev-sw.js' : '/auth-sw.js'
const registration = await navigator.serviceWorker.register(swPath)
// Configure base URL for dev service worker
if (import.meta.env.DEV) {
console.warn('[Auth DEV SW] Registering development serviceworker')
// Use the same URL that Vite proxy is using
const baseUrl = __DEV_SERVER_COMFYUI_URL__
navigator.serviceWorker.controller?.postMessage({
type: 'SET_BASE_URL',
baseUrl
})
// Also set base URL when service worker becomes active
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing
newWorker?.addEventListener('statechange', () => {
if (newWorker.state === 'activated') {
navigator.serviceWorker.controller?.postMessage({
type: 'SET_BASE_URL',
baseUrl
})
}
})
})
}
setupAuthHeaderProvider()
setupCacheInvalidation()
} catch (error) {
console.error('[Auth SW] Registration failed:', error)
}
}
/**
* Listens for auth header requests from the service worker
*/
function setupAuthHeaderProvider(): void {
navigator.serviceWorker.addEventListener('message', async (event) => {
if (event.data.type === 'REQUEST_AUTH_HEADER') {
const firebaseAuthStore = useFirebaseAuthStore()
const authHeader = await firebaseAuthStore.getAuthHeader()
event.ports[0].postMessage({
type: 'AUTH_HEADER_RESPONSE',
authHeader
})
}
})
}
/**
* Invalidates cached auth header when user logs in/out
*/
function setupCacheInvalidation(): void {
const { isLoggedIn } = useCurrentUser()
watch(isLoggedIn, (newValue, oldValue) => {
if (newValue !== oldValue) {
navigator.serviceWorker.controller?.postMessage({
type: 'INVALIDATE_AUTH_HEADER'
})
}
})
}
await registerAuthServiceWorker()

2
src/vite-env.d.ts vendored
View File

@@ -16,8 +16,6 @@ declare global {
interface Window {
__COMFYUI_FRONTEND_VERSION__: string
}
const __DEV_SERVER_COMFYUI_URL__: string
}
export {}

View File

@@ -302,8 +302,7 @@ export default defineConfig({
__ALGOLIA_APP_ID__: JSON.stringify(process.env.ALGOLIA_APP_ID || ''),
__ALGOLIA_API_KEY__: JSON.stringify(process.env.ALGOLIA_API_KEY || ''),
__USE_PROD_CONFIG__: process.env.USE_PROD_CONFIG === 'true',
__DISTRIBUTION__: JSON.stringify(DISTRIBUTION),
__DEV_SERVER_COMFYUI_URL__: JSON.stringify(DEV_SERVER_COMFYUI_URL)
__DISTRIBUTION__: JSON.stringify(DISTRIBUTION)
},
resolve: {