Files
ComfyUI_frontend/src/services/extensionService.ts
Christian Byrne cd50c54e61 add session cookie auth on cloud dist (#6295)
## Summary

Implemented cookie-based session authentication for cloud distribution,
replacing service worker approach with extension-based lifecycle hooks.

## Changes

- **What**: Added session cookie management via [extension
hooks](https://docs.comfy.org/comfyui/extensions) for login, token
refresh, and logout events
- **Architecture**: DDD-compliant structure with platform layer
(`src/platform/auth/session/`) and cloud-gated extension
- **New Extension Hooks**: `onAuthTokenRefreshed()` and
`onAuthUserLogout()` in [ComfyExtension
interface](src/types/comfy.ts:220-232)

```mermaid
sequenceDiagram
    participant User
    participant Firebase
    participant Extension
    participant Backend

    User->>Firebase: Login
    Firebase->>Extension: onAuthUserResolved
    Extension->>Backend: POST /auth/session (with JWT)
    Backend-->>Extension: Set-Cookie

    Firebase->>Firebase: Token Refresh
    Firebase->>Extension: onAuthTokenRefreshed
    Extension->>Backend: POST /auth/session (with new JWT)
    Backend-->>Extension: Update Cookie

    User->>Firebase: Logout
    Firebase->>Extension: onAuthUserLogout (user null)
    Extension->>Backend: DELETE /auth/session
    Backend-->>Extension: Clear Cookie
```

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6295-add-session-cookie-auth-on-cloud-dist-2986d73d365081868c56e5be1ad0d0d4)
by [Unito](https://www.unito.io)
2025-10-26 00:04:30 -07:00

160 lines
4.9 KiB
TypeScript

import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { useSettingStore } from '@/platform/settings/settingStore'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { useCommandStore } from '@/stores/commandStore'
import { useExtensionStore } from '@/stores/extensionStore'
import { KeybindingImpl, useKeybindingStore } from '@/stores/keybindingStore'
import { useMenuItemStore } from '@/stores/menuItemStore'
import { useWidgetStore } from '@/stores/widgetStore'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import type { ComfyExtension } from '@/types/comfy'
export const useExtensionService = () => {
const extensionStore = useExtensionStore()
const settingStore = useSettingStore()
const keybindingStore = useKeybindingStore()
const { wrapWithErrorHandling } = useErrorHandling()
/**
* Loads all extensions from the API into the window in parallel
*/
const loadExtensions = async () => {
extensionStore.loadDisabledExtensionNames(
settingStore.get('Comfy.Extension.Disabled')
)
const extensions = await api.getExtensions()
// Need to load core extensions first as some custom extensions
// may depend on them.
await import('../extensions/core/index')
extensionStore.captureCoreExtensions()
await Promise.all(
extensions
.filter((extension) => !extension.includes('extensions/core'))
.map(async (ext) => {
try {
await import(/* @vite-ignore */ api.fileURL(ext))
} catch (error) {
console.error('Error loading extension', ext, error)
}
})
)
}
/**
* Register an extension with the app
* @param extension The extension to register
*/
const registerExtension = (extension: ComfyExtension) => {
extensionStore.registerExtension(extension)
const addKeybinding = wrapWithErrorHandling(
keybindingStore.addDefaultKeybinding
)
const addSetting = wrapWithErrorHandling(settingStore.addSetting)
extension.keybindings?.forEach((keybinding) => {
addKeybinding(new KeybindingImpl(keybinding))
})
useCommandStore().loadExtensionCommands(extension)
useMenuItemStore().loadExtensionMenuCommands(extension)
extension.settings?.forEach(addSetting)
useBottomPanelStore().registerExtensionBottomPanelTabs(extension)
if (extension.getCustomWidgets) {
// TODO(huchenlei): We should deprecate the async return value of
// getCustomWidgets.
void (async () => {
if (extension.getCustomWidgets) {
const widgets = await extension.getCustomWidgets(app)
useWidgetStore().registerCustomWidgets(widgets)
}
})()
}
if (extension.onAuthUserResolved) {
const { onUserResolved } = useCurrentUser()
onUserResolved((user) => {
void extension.onAuthUserResolved?.(user, app)
})
}
if (extension.onAuthTokenRefreshed) {
const { onTokenRefreshed } = useCurrentUser()
onTokenRefreshed(() => {
void extension.onAuthTokenRefreshed?.()
})
}
if (extension.onAuthUserLogout) {
const { onUserLogout } = useCurrentUser()
onUserLogout(() => {
void extension.onAuthUserLogout?.()
})
}
}
/**
* Invoke an extension callback
* @param {keyof ComfyExtension} method The extension callback to execute
* @param {any[]} args Any arguments to pass to the callback
* @returns
*/
const invokeExtensions = (method: keyof ComfyExtension, ...args: any[]) => {
const results: any[] = []
for (const ext of extensionStore.enabledExtensions) {
if (method in ext) {
try {
results.push(ext[method](...args, app))
} catch (error) {
console.error(
`Error calling extension '${ext.name}' method '${method}'`,
{ error },
{ extension: ext },
{ args }
)
}
}
}
return results
}
/**
* Invoke an async extension callback
* Each callback will be invoked concurrently
* @param {string} method The extension callback to execute
* @param {...any} args Any arguments to pass to the callback
* @returns
*/
const invokeExtensionsAsync = async (
method: keyof ComfyExtension,
...args: any[]
) => {
return await Promise.all(
extensionStore.enabledExtensions.map(async (ext) => {
if (method in ext) {
try {
return await ext[method](...args, app)
} catch (error) {
console.error(
`Error calling extension '${ext.name}' method '${method}'`,
{ error },
{ extension: ext },
{ args }
)
}
}
})
)
}
return {
loadExtensions,
registerExtension,
invokeExtensions,
invokeExtensionsAsync
}
}