[feat] Integrate header registry with all HTTP clients

- Replace direct fetch() with fetchWithHeaders across entire codebase
- Replace axios.create() with createAxiosWithHeaders in all services
- Update tests to properly mock network client adapters
- Fix hoisting issues in test mocks for axios instances
- Ensure all network calls now support header injection

This completes Step 2: integrating the header registry infrastructure with all existing HTTP clients in the codebase.
This commit is contained in:
bymyself
2025-08-16 14:13:21 -07:00
parent d6695ea66e
commit d05153a0dc
19 changed files with 192 additions and 89 deletions

View File

@@ -2,6 +2,7 @@ import { whenever } from '@vueuse/core'
import { onMounted, ref } from 'vue'
import { useCivitaiModel } from '@/composables/useCivitaiModel'
import { fetchWithHeaders } from '@/services/networkClientAdapter'
import { downloadUrlToHfRepoUrl, isCivitaiModelUrl } from '@/utils/formatUtil'
export function useDownload(url: string, fileName?: string) {
@@ -14,7 +15,7 @@ export function useDownload(url: string, fileName?: string) {
const fetchFileSize = async () => {
try {
const response = await fetch(url, { method: 'HEAD' })
const response = await fetchWithHeaders(url, { method: 'HEAD' })
if (!response.ok) throw new Error('Failed to fetch file size')
const size = response.headers.get('content-length')

View File

@@ -3,6 +3,7 @@ import { useI18n } from 'vue-i18n'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { fetchWithHeaders } from '@/services/networkClientAdapter'
import { useDialogStore } from '@/stores/dialogStore'
import { useWorkflowTemplatesStore } from '@/stores/workflowTemplatesStore'
import type {
@@ -161,7 +162,9 @@ export function useTemplateWorkflows() {
const fetchTemplateJson = async (id: string, sourceModule: string) => {
if (sourceModule === 'default') {
// Default templates provided by frontend are served as static files
const response = await fetch(api.fileURL(`/templates/${id}.json`))
const response = await fetchWithHeaders(
api.fileURL(`/templates/${id}.json`)
)
return await response.json()
} else {
// Custom node templates served via API

View File

@@ -1,14 +1,16 @@
import axios from 'axios'
import { useChainCallback } from '@/composables/functional/useChainCallback'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { IWidget } from '@/lib/litegraph/src/litegraph'
import type { RemoteWidgetConfig } from '@/schemas/nodeDefSchema'
import { api } from '@/scripts/api'
import { createAxiosWithHeaders } from '@/services/networkClientAdapter'
const MAX_RETRIES = 5
const TIMEOUT = 4096
// Create axios client with header injection
const axiosClient = createAxiosWithHeaders()
export interface CacheEntry<T> {
data: T
timestamp?: number
@@ -58,7 +60,7 @@ const fetchData = async (
controller: AbortController
) => {
const { route, response_key, query_params, timeout = TIMEOUT } = config
const res = await axios.get(route, {
const res = await axiosClient.get(route, {
params: query_params,
signal: controller.signal,
timeout

View File

@@ -1,6 +1,7 @@
import { t } from '@/i18n'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { fetchWithHeaders } from '@/services/networkClientAdapter'
import { useToastStore } from '@/stores/toastStore'
class Load3dUtils {
@@ -9,7 +10,7 @@ class Load3dUtils {
prefix: string,
fileType: string = 'png'
) {
const blob = await fetch(imageData).then((r) => r.blob())
const blob = await fetchWithHeaders(imageData).then((r) => r.blob())
const name = `${prefix}_${Date.now()}.${fileType}`
const file = new File([blob], name, {
type: fileType === 'mp4' ? 'video/mp4' : 'image/png'

View File

@@ -5,6 +5,7 @@ import { STLExporter } from 'three/examples/jsm/exporters/STLExporter'
import { t } from '@/i18n'
import { api } from '@/scripts/api'
import { fetchWithHeaders } from '@/services/networkClientAdapter'
import { useToastStore } from '@/stores/toastStore'
export class ModelExporter {
@@ -43,10 +44,10 @@ export class ModelExporter {
let response: Response
if (isComfyUrl) {
// Use ComfyUI API client for internal URLs
response = await fetch(api.apiURL(url))
response = await fetchWithHeaders(api.apiURL(url))
} else {
// Use direct fetch for external URLs
response = await fetch(url)
response = await fetchWithHeaders(url)
}
const blob = await response.blob()

View File

@@ -1,5 +1,3 @@
import axios from 'axios'
import defaultClientFeatureFlags from '@/config/clientFeatureFlags.json'
import type {
DisplayComponentWsMessage,
@@ -35,6 +33,7 @@ import type {
NodeId
} from '@/schemas/comfyWorkflowSchema'
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
import { fetchWithHeaders } from '@/services/networkClientAdapter'
import type { NodeExecutionId } from '@/types/nodeIdentification'
import { WorkflowTemplates } from '@/types/workflowTemplateTypes'
@@ -329,7 +328,7 @@ export class ComfyApi extends EventTarget {
} else {
options.headers['Comfy-User'] = this.user
}
return fetch(this.apiURL(route), options)
return fetchWithHeaders(this.apiURL(route), options)
}
override addEventListener<TEvent extends keyof ApiEvents>(
@@ -599,9 +598,9 @@ export class ComfyApi extends EventTarget {
* Gets the index of core workflow templates.
*/
async getCoreWorkflowTemplates(): Promise<WorkflowTemplates[]> {
const res = await axios.get(this.fileURL('/templates/index.json'))
const contentType = res.headers['content-type']
return contentType?.includes('application/json') ? res.data : []
const res = await fetchWithHeaders(this.fileURL('/templates/index.json'))
const contentType = res.headers.get('content-type')
return contentType?.includes('application/json') ? await res.json() : []
}
/**
@@ -1002,22 +1001,31 @@ export class ComfyApi extends EventTarget {
}
async getLogs(): Promise<string> {
return (await axios.get(this.internalURL('/logs'))).data
const response = await fetchWithHeaders(this.internalURL('/logs'))
return response.text()
}
async getRawLogs(): Promise<LogsRawResponse> {
return (await axios.get(this.internalURL('/logs/raw'))).data
const response = await fetchWithHeaders(this.internalURL('/logs/raw'))
return response.json()
}
async subscribeLogs(enabled: boolean): Promise<void> {
return await axios.patch(this.internalURL('/logs/subscribe'), {
enabled,
clientId: this.clientId
await fetchWithHeaders(this.internalURL('/logs/subscribe'), {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
enabled,
clientId: this.clientId
})
})
}
async getFolderPaths(): Promise<Record<string, string[]>> {
return (await axios.get(this.internalURL('/folder_paths'))).data
const response = await fetchWithHeaders(this.internalURL('/folder_paths'))
return response.json()
}
/**
@@ -1026,7 +1034,8 @@ export class ComfyApi extends EventTarget {
* @returns The custom nodes i18n data
*/
async getCustomNodesI18n(): Promise<Record<string, any>> {
return (await axios.get(this.apiURL('/i18n'))).data
const response = await fetchWithHeaders(this.apiURL('/i18n'))
return response.json()
}
/**

View File

@@ -40,6 +40,7 @@ import { getSvgMetadata } from '@/scripts/metadata/svg'
import { useDialogService } from '@/services/dialogService'
import { useExtensionService } from '@/services/extensionService'
import { useLitegraphService } from '@/services/litegraphService'
import { fetchWithHeaders } from '@/services/networkClientAdapter'
import { useSubgraphService } from '@/services/subgraphService'
import { useWorkflowService } from '@/services/workflowService'
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
@@ -533,7 +534,7 @@ export class ComfyApp {
if (match) {
const uri = event.dataTransfer.getData(match)?.split('\n')?.[0]
if (uri) {
const blob = await (await fetch(uri)).blob()
const blob = await (await fetchWithHeaders(uri)).blob()
await this.handleFile(new File([blob], uri, { type: blob.type }))
}
}

View File

@@ -2,6 +2,7 @@ import axios, { AxiosError, AxiosResponse } from 'axios'
import { ref } from 'vue'
import { api } from '@/scripts/api'
import { createAxiosWithHeaders } from '@/services/networkClientAdapter'
import {
type InstallPackParams,
type InstalledPacksResponse,
@@ -35,7 +36,7 @@ enum ManagerRoute {
REBOOT = 'manager/reboot'
}
const managerApiClient = axios.create({
const managerApiClient = createAxiosWithHeaders({
baseURL: api.apiURL(''),
headers: {
'Content-Type': 'application/json'

View File

@@ -1,12 +1,13 @@
import axios, { AxiosError, AxiosResponse } from 'axios'
import { ref } from 'vue'
import { createAxiosWithHeaders } from '@/services/networkClientAdapter'
import type { components, operations } from '@/types/comfyRegistryTypes'
import { isAbortError } from '@/utils/typeGuardUtil'
const API_BASE_URL = 'https://api.comfy.org'
const registryApiClient = axios.create({
const registryApiClient = createAxiosWithHeaders({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json'

View File

@@ -3,6 +3,7 @@ import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { COMFY_API_BASE_URL } from '@/config/comfyApi'
import { createAxiosWithHeaders } from '@/services/networkClientAdapter'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import { type components, operations } from '@/types/comfyRegistryTypes'
import { isAbortError } from '@/utils/typeGuardUtil'
@@ -22,7 +23,7 @@ type CustomerEventsResponseQuery =
export type AuditLog = components['schemas']['AuditLog']
const customerApiClient = axios.create({
const customerApiClient = createAxiosWithHeaders({
baseURL: COMFY_API_BASE_URL,
headers: {
'Content-Type': 'application/json'

View File

@@ -1,4 +1,5 @@
import { api } from '@/scripts/api'
import { fetchWithHeaders } from '@/services/networkClientAdapter'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import { NodeSourceType, getNodeSource } from '@/types/nodeSource'
import { extractCustomNodeName } from '@/utils/nodeHelpUtil'
@@ -25,12 +26,12 @@ export class NodeHelpService {
// Try locale-specific path first
const localePath = `/extensions/${customNodeName}/docs/${node.name}/${locale}.md`
let res = await fetch(api.fileURL(localePath))
let res = await fetchWithHeaders(api.fileURL(localePath))
if (!res.ok) {
// Fall back to non-locale path
const fallbackPath = `/extensions/${customNodeName}/docs/${node.name}.md`
res = await fetch(api.fileURL(fallbackPath))
res = await fetchWithHeaders(api.fileURL(fallbackPath))
}
if (!res.ok) {
@@ -45,7 +46,7 @@ export class NodeHelpService {
locale: string
): Promise<string> {
const mdUrl = `/docs/${node.name}/${locale}.md`
const res = await fetch(api.fileURL(mdUrl))
const res = await fetchWithHeaders(api.fileURL(mdUrl))
if (!res.ok) {
throw new Error(res.statusText)

View File

@@ -2,10 +2,11 @@ import axios, { AxiosError, AxiosResponse } from 'axios'
import { ref } from 'vue'
import { COMFY_API_BASE_URL } from '@/config/comfyApi'
import { createAxiosWithHeaders } from '@/services/networkClientAdapter'
import type { components, operations } from '@/types/comfyRegistryTypes'
import { isAbortError } from '@/utils/typeGuardUtil'
const releaseApiClient = axios.create({
const releaseApiClient = createAxiosWithHeaders({
baseURL: COMFY_API_BASE_URL,
headers: {
'Content-Type': 'application/json'

View File

@@ -21,6 +21,7 @@ import { useFirebaseAuth } from 'vuefire'
import { COMFY_API_BASE_URL } from '@/config/comfyApi'
import { t } from '@/i18n'
import { createAxiosWithHeaders } from '@/services/networkClientAdapter'
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
import { type AuthHeader } from '@/types/authTypes'
import { operations } from '@/types/comfyRegistryTypes'
@@ -46,7 +47,8 @@ export class FirebaseAuthStoreError extends Error {
}
// Customer API client - follows the same pattern as other services
const customerApiClient = axios.create({
// Now with automatic header injection from the registry
const customerApiClient = createAxiosWithHeaders({
baseURL: COMFY_API_BASE_URL,
headers: {
'Content-Type': 'application/json'