mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
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:
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
@@ -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())
|
||||
})
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
@@ -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
2
src/vite-env.d.ts
vendored
@@ -16,8 +16,6 @@ declare global {
|
||||
interface Window {
|
||||
__COMFYUI_FRONTEND_VERSION__: string
|
||||
}
|
||||
|
||||
const __DEV_SERVER_COMFYUI_URL__: string
|
||||
}
|
||||
|
||||
export {}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user