mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 14:45:36 +00:00
Shorter function names improve ergonomics while maintaining clarity: - defineNode() - register node-scoped extensions - defineWidget() - register widget type extensions Old names kept as deprecated aliases for backwards compatibility. Will be removed in v1.0. Updates all docs, examples, tests, and internal references. Addresses review discussion item #4 from design-review-12142.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
261 lines
8.4 KiB
TypeScript
261 lines
8.4 KiB
TypeScript
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
|
import { useErrorHandling } from '@/composables/useErrorHandling'
|
|
import { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat'
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
import { api } from '@/scripts/api'
|
|
import { useCommandStore } from '@/stores/commandStore'
|
|
import { useExtensionStore } from '@/stores/extensionStore'
|
|
import { KeybindingImpl } from '@/platform/keybindings/keybinding'
|
|
import { useKeybindingStore } from '@/platform/keybindings/keybindingStore'
|
|
import { useMenuItemStore } from '@/stores/menuItemStore'
|
|
import { useWidgetStore } from '@/stores/widgetStore'
|
|
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
|
import type { ComfyExtension } from '@/types/comfy'
|
|
import type { AuthUserInfo } from '@/types/authTypes'
|
|
import { app } from '@/scripts/app'
|
|
import type { ComfyApp } from '@/scripts/app'
|
|
|
|
// Tracks which extensions have already received the beforeRegisterNodeDef deprecation warning
|
|
const _warnedBeforeRegisterNodeDef = new Set<string>()
|
|
|
|
export const useExtensionService = () => {
|
|
const extensionStore = useExtensionStore()
|
|
const settingStore = useSettingStore()
|
|
const keybindingStore = useKeybindingStore()
|
|
const {
|
|
wrapWithErrorHandling,
|
|
wrapWithErrorHandlingAsync,
|
|
toastErrorHandler
|
|
} = 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()
|
|
const handleUserResolved = wrapWithErrorHandlingAsync(
|
|
(user: AuthUserInfo) => extension.onAuthUserResolved?.(user, app),
|
|
(error) => {
|
|
console.error('[Extension Auth Hook Error]', {
|
|
extension: extension.name,
|
|
hook: 'onAuthUserResolved',
|
|
error
|
|
})
|
|
toastErrorHandler(error)
|
|
}
|
|
)
|
|
onUserResolved((user) => {
|
|
void handleUserResolved(user)
|
|
})
|
|
}
|
|
|
|
if (extension.onAuthTokenRefreshed) {
|
|
const { onTokenRefreshed } = useCurrentUser()
|
|
const handleTokenRefreshed = wrapWithErrorHandlingAsync(
|
|
() => extension.onAuthTokenRefreshed?.(),
|
|
(error) => {
|
|
console.error('[Extension Auth Hook Error]', {
|
|
extension: extension.name,
|
|
hook: 'onAuthTokenRefreshed',
|
|
error
|
|
})
|
|
toastErrorHandler(error)
|
|
}
|
|
)
|
|
onTokenRefreshed(() => {
|
|
void handleTokenRefreshed()
|
|
})
|
|
}
|
|
|
|
if (extension.onAuthUserLogout) {
|
|
const { onUserLogout } = useCurrentUser()
|
|
const handleUserLogout = wrapWithErrorHandlingAsync(
|
|
() => extension.onAuthUserLogout?.(),
|
|
(error) => {
|
|
console.error('[Extension Auth Hook Error]', {
|
|
extension: extension.name,
|
|
hook: 'onAuthUserLogout',
|
|
error
|
|
})
|
|
toastErrorHandler(error)
|
|
}
|
|
)
|
|
onUserLogout(() => {
|
|
void handleUserLogout()
|
|
})
|
|
}
|
|
}
|
|
|
|
type RemoveLastAppParam<T> = T extends (
|
|
...args: [...infer Rest, ComfyApp]
|
|
) => infer R
|
|
? (...args: Rest) => R
|
|
: T
|
|
|
|
type KnownExtensionMethods = Exclude<keyof ComfyExtension, number | symbol> &
|
|
string
|
|
|
|
type ComfyExtensionMethod<T extends KnownExtensionMethods> =
|
|
ComfyExtension[T] extends (...args: unknown[]) => unknown
|
|
? ComfyExtension[T]
|
|
: (...args: unknown[]) => unknown
|
|
|
|
type ComfyExtensionParamsWithoutApp<T extends KnownExtensionMethods> =
|
|
RemoveLastAppParam<ComfyExtensionMethod<T>>
|
|
/**
|
|
* Invoke an extension callback
|
|
* @param {keyof ComfyExtension} method The extension callback to execute
|
|
* @param {unknown[]} args Any arguments to pass to the callback
|
|
* @returns
|
|
*/
|
|
const invokeExtensions = <T extends KnownExtensionMethods>(
|
|
method: T,
|
|
...args: Parameters<ComfyExtensionParamsWithoutApp<T>>
|
|
) => {
|
|
const results: ReturnType<ComfyExtensionMethod<T>>[] = []
|
|
for (const ext of extensionStore.enabledExtensions) {
|
|
if (method in ext) {
|
|
try {
|
|
const fn = ext[method]
|
|
if (typeof fn === 'function') {
|
|
results.push(fn.call(ext, ...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 {...unknown} args Any arguments to pass to the callback
|
|
* @returns
|
|
*/
|
|
const invokeExtensionsAsync = async <T extends KnownExtensionMethods>(
|
|
method: T,
|
|
...args: Parameters<ComfyExtensionParamsWithoutApp<T>>
|
|
) => {
|
|
return await Promise.all(
|
|
extensionStore.enabledExtensions.map(async (ext) => {
|
|
if (method in ext) {
|
|
try {
|
|
const fn = ext[method]
|
|
if (typeof fn !== 'function') {
|
|
return
|
|
}
|
|
|
|
// Set current extension name for legacy compatibility tracking
|
|
if (method === 'setup') {
|
|
legacyMenuCompat.setCurrentExtension(ext.name)
|
|
}
|
|
|
|
// DEP1: warn once per extension that uses beforeRegisterNodeDef
|
|
if (
|
|
method === 'beforeRegisterNodeDef' &&
|
|
!_warnedBeforeRegisterNodeDef.has(ext.name)
|
|
) {
|
|
_warnedBeforeRegisterNodeDef.add(ext.name)
|
|
console.warn(
|
|
`[ComfyUI] Extension "${ext.name}" uses deprecated hook "beforeRegisterNodeDef". ` +
|
|
'Use defineNode({ nodeCreated(handle) { ... } }) with a nodeTypes filter instead. ' +
|
|
'See https://docs.comfy.org/extensions/api for the v2 API.'
|
|
)
|
|
}
|
|
|
|
const result = await fn.call(ext, ...args, app)
|
|
|
|
// Clear current extension after setup
|
|
if (method === 'setup') {
|
|
legacyMenuCompat.setCurrentExtension(null)
|
|
}
|
|
|
|
return result
|
|
} catch (error) {
|
|
// Clear current extension on error too
|
|
if (method === 'setup') {
|
|
legacyMenuCompat.setCurrentExtension(null)
|
|
}
|
|
|
|
console.error(
|
|
`Error calling extension '${ext.name}' method '${method}'`,
|
|
{ error },
|
|
{ extension: ext },
|
|
{ args }
|
|
)
|
|
}
|
|
}
|
|
})
|
|
)
|
|
}
|
|
|
|
return {
|
|
loadExtensions,
|
|
registerExtension,
|
|
invokeExtensions,
|
|
invokeExtensionsAsync
|
|
}
|
|
}
|