diff --git a/global.d.ts b/global.d.ts index 059e47732..7f7dd832f 100644 --- a/global.d.ts +++ b/global.d.ts @@ -8,7 +8,21 @@ declare const __USE_PROD_CONFIG__: boolean interface Window { __CONFIG__: { mixpanel_token?: string + require_whitelist?: boolean subscription_required?: boolean + max_upload_size?: number + comfy_api_base_url?: string + comfy_platform_base_url?: string + firebase_config?: { + apiKey: string + authDomain: string + databaseURL?: string + projectId: string + storageBucket: string + messagingSenderId: string + appId: string + measurementId?: string + } server_health_alert?: { message: string tooltip?: string diff --git a/src/components/dialog/content/SignInContent.vue b/src/components/dialog/content/SignInContent.vue index 19da83125..0cc9869c4 100644 --- a/src/components/dialog/content/SignInContent.vue +++ b/src/components/dialog/content/SignInContent.vue @@ -96,7 +96,7 @@ {{ t('auth.apiKey.helpText') }} @@ -145,11 +145,15 @@ import Button from 'primevue/button' import Divider from 'primevue/divider' import Message from 'primevue/message' -import { onMounted, onUnmounted, ref } from 'vue' +import { computed, onMounted, onUnmounted, ref } from 'vue' import { useI18n } from 'vue-i18n' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' -import { COMFY_PLATFORM_BASE_URL } from '@/config/comfyApi' +import { getComfyPlatformBaseUrl } from '@/config/comfyApi' +import { + configValueOrDefault, + remoteConfig +} from '@/platform/remoteConfig/remoteConfig' import type { SignInData, SignUpData } from '@/schemas/signInSchema' import { isHostWhitelisted, normalizeHost } from '@/utils/hostWhitelist' import { isInChina } from '@/utils/networkUtil' @@ -168,6 +172,13 @@ const isSecureContext = window.isSecureContext const isSignIn = ref(true) const showApiKeyForm = ref(false) const ssoAllowed = isHostWhitelisted(normalizeHost(window.location.hostname)) +const comfyPlatformBaseUrl = computed(() => + configValueOrDefault( + remoteConfig.value, + 'comfy_platform_base_url', + getComfyPlatformBaseUrl() + ) +) const toggleState = () => { isSignIn.value = !isSignIn.value diff --git a/src/components/dialog/content/signin/ApiKeyForm.test.ts b/src/components/dialog/content/signin/ApiKeyForm.test.ts index bf1ec2cdd..f5ba33411 100644 --- a/src/components/dialog/content/signin/ApiKeyForm.test.ts +++ b/src/components/dialog/content/signin/ApiKeyForm.test.ts @@ -9,7 +9,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { createApp } from 'vue' import { createI18n } from 'vue-i18n' -import { COMFY_PLATFORM_BASE_URL } from '@/config/comfyApi' +import { getComfyPlatformBaseUrl } from '@/config/comfyApi' import ApiKeyForm from './ApiKeyForm.vue' @@ -111,7 +111,7 @@ describe('ApiKeyForm', () => { const helpText = wrapper.find('small') expect(helpText.text()).toContain('Need an API key?') expect(helpText.find('a').attributes('href')).toBe( - `${COMFY_PLATFORM_BASE_URL}/login` + `${getComfyPlatformBaseUrl()}/login` ) }) }) diff --git a/src/components/dialog/content/signin/ApiKeyForm.vue b/src/components/dialog/content/signin/ApiKeyForm.vue index 2e70345fc..027f35b6d 100644 --- a/src/components/dialog/content/signin/ApiKeyForm.vue +++ b/src/components/dialog/content/signin/ApiKeyForm.vue @@ -48,7 +48,7 @@ {{ t('auth.apiKey.helpText') }} @@ -88,7 +88,11 @@ import Message from 'primevue/message' import { computed } from 'vue' import { useI18n } from 'vue-i18n' -import { COMFY_PLATFORM_BASE_URL } from '@/config/comfyApi' +import { getComfyPlatformBaseUrl } from '@/config/comfyApi' +import { + configValueOrDefault, + remoteConfig +} from '@/platform/remoteConfig/remoteConfig' import { apiKeySchema } from '@/schemas/signInSchema' import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore' import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore' @@ -96,6 +100,13 @@ import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore' const authStore = useFirebaseAuthStore() const apiKeyStore = useApiKeyAuthStore() const loading = computed(() => authStore.loading) +const comfyPlatformBaseUrl = computed(() => + configValueOrDefault( + remoteConfig.value, + 'comfy_platform_base_url', + getComfyPlatformBaseUrl() + ) +) const { t } = useI18n() diff --git a/src/config/comfyApi.ts b/src/config/comfyApi.ts index 272708264..8efd651bf 100644 --- a/src/config/comfyApi.ts +++ b/src/config/comfyApi.ts @@ -1,7 +1,43 @@ -export const COMFY_API_BASE_URL = __USE_PROD_CONFIG__ - ? 'https://api.comfy.org' - : 'https://stagingapi.comfy.org' +import { isCloud } from '@/platform/distribution/types' +import { + configValueOrDefault, + remoteConfig +} from '@/platform/remoteConfig/remoteConfig' -export const COMFY_PLATFORM_BASE_URL = __USE_PROD_CONFIG__ - ? 'https://platform.comfy.org' - : 'https://stagingplatform.comfy.org' +const PROD_API_BASE_URL = 'https://api.comfy.org' +const STAGING_API_BASE_URL = 'https://stagingapi.comfy.org' + +const PROD_PLATFORM_BASE_URL = 'https://platform.comfy.org' +const STAGING_PLATFORM_BASE_URL = 'https://stagingplatform.comfy.org' + +const BUILD_TIME_API_BASE_URL = __USE_PROD_CONFIG__ + ? PROD_API_BASE_URL + : STAGING_API_BASE_URL + +const BUILD_TIME_PLATFORM_BASE_URL = __USE_PROD_CONFIG__ + ? PROD_PLATFORM_BASE_URL + : STAGING_PLATFORM_BASE_URL + +export function getComfyApiBaseUrl(): string { + if (!isCloud) { + return BUILD_TIME_API_BASE_URL + } + + return configValueOrDefault( + remoteConfig.value, + 'comfy_api_base_url', + BUILD_TIME_API_BASE_URL + ) +} + +export function getComfyPlatformBaseUrl(): string { + if (!isCloud) { + return BUILD_TIME_PLATFORM_BASE_URL + } + + return configValueOrDefault( + remoteConfig.value, + 'comfy_platform_base_url', + BUILD_TIME_PLATFORM_BASE_URL + ) +} diff --git a/src/config/firebase.ts b/src/config/firebase.ts index e5fbd2af7..e976a2ef6 100644 --- a/src/config/firebase.ts +++ b/src/config/firebase.ts @@ -1,5 +1,8 @@ import type { FirebaseOptions } from 'firebase/app' +import { isCloud } from '@/platform/distribution/types' +import { remoteConfig } from '@/platform/remoteConfig/remoteConfig' + const DEV_CONFIG: FirebaseOptions = { apiKey: 'AIzaSyDa_YMeyzV0SkVe92vBZ1tVikWBmOU5KVE', authDomain: 'dreamboothy-dev.firebaseapp.com', @@ -22,7 +25,18 @@ const PROD_CONFIG: FirebaseOptions = { measurementId: 'G-3ZBD3MBTG4' } -// To test with prod config while using dev server, set USE_PROD_CONFIG=true in .env -export const FIREBASE_CONFIG: FirebaseOptions = __USE_PROD_CONFIG__ - ? PROD_CONFIG - : DEV_CONFIG +const BUILD_TIME_CONFIG = __USE_PROD_CONFIG__ ? PROD_CONFIG : DEV_CONFIG + +/** + * Returns the Firebase configuration for the current environment. + * - Cloud builds use runtime configuration delivered via feature flags + * - OSS / localhost builds fall back to the build-time config determined by __USE_PROD_CONFIG__ + */ +export function getFirebaseConfig(): FirebaseOptions { + if (!isCloud) { + return BUILD_TIME_CONFIG + } + + const runtimeConfig = remoteConfig.value.firebase_config + return runtimeConfig ?? BUILD_TIME_CONFIG +} diff --git a/src/main.ts b/src/main.ts index e063bc18c..659198d53 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,7 +11,7 @@ import Tooltip from 'primevue/tooltip' import { createApp } from 'vue' import { VueFire, VueFireAuth } from 'vuefire' -import { FIREBASE_CONFIG } from '@/config/firebase' +import { getFirebaseConfig } from '@/config/firebase' import '@/lib/litegraph/public/css/litegraph.css' import router from '@/router' @@ -40,7 +40,7 @@ const ComfyUIPreset = definePreset(Aura, { } }) -const firebaseApp = initializeApp(FIREBASE_CONFIG) +const firebaseApp = initializeApp(getFirebaseConfig()) const app = createApp(App) const pinia = createPinia() diff --git a/src/platform/cloud/subscription/composables/useSubscription.ts b/src/platform/cloud/subscription/composables/useSubscription.ts index 92588907f..b622cb97a 100644 --- a/src/platform/cloud/subscription/composables/useSubscription.ts +++ b/src/platform/cloud/subscription/composables/useSubscription.ts @@ -4,7 +4,7 @@ import { createSharedComposable } from '@vueuse/core' import { useCurrentUser } from '@/composables/auth/useCurrentUser' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' import { useErrorHandling } from '@/composables/useErrorHandling' -import { COMFY_API_BASE_URL } from '@/config/comfyApi' +import { getComfyApiBaseUrl, getComfyPlatformBaseUrl } from '@/config/comfyApi' import { MONTHLY_SUBSCRIPTION_PRICE } from '@/config/subscriptionPricesConfig' import { t } from '@/i18n' import { isCloud } from '@/platform/distribution/types' @@ -74,6 +74,8 @@ function useSubscriptionInternal() { () => `$${MONTHLY_SUBSCRIPTION_PRICE.toFixed(0)}` ) + const buildApiUrl = (path: string) => `${getComfyApiBaseUrl()}${path}` + const fetchStatus = wrapWithErrorHandlingAsync( fetchSubscriptionStatus, reportError @@ -114,7 +116,7 @@ function useSubscriptionInternal() { } const handleViewUsageHistory = () => { - window.open('https://platform.comfy.org/profile/usage', '_blank') + window.open(`${getComfyPlatformBaseUrl()}/profile/usage`, '_blank') } const handleLearnMore = () => { @@ -136,7 +138,7 @@ function useSubscriptionInternal() { } const response = await fetch( - `${COMFY_API_BASE_URL}/customers/cloud-subscription-status`, + buildApiUrl('/customers/cloud-subscription-status'), { headers: { ...authHeader, @@ -181,7 +183,7 @@ function useSubscriptionInternal() { } const response = await fetch( - `${COMFY_API_BASE_URL}/customers/cloud-subscription-checkout`, + buildApiUrl('/customers/cloud-subscription-checkout'), { method: 'POST', headers: { diff --git a/src/platform/remoteConfig/remoteConfig.ts b/src/platform/remoteConfig/remoteConfig.ts index a20eb7ebd..7d8f28c5f 100644 --- a/src/platform/remoteConfig/remoteConfig.ts +++ b/src/platform/remoteConfig/remoteConfig.ts @@ -21,6 +21,15 @@ import type { RemoteConfig } from './types' */ export const remoteConfig = ref({}) +export function configValueOrDefault( + remoteConfig: RemoteConfig, + key: K, + defaultValue: NonNullable +): NonNullable { + const configValue = remoteConfig[key] + return configValue || defaultValue +} + /** * Loads remote configuration from the backend /api/features endpoint * and updates the reactive remoteConfig ref diff --git a/src/platform/remoteConfig/types.ts b/src/platform/remoteConfig/types.ts index 939c6bbf1..43bfbd70f 100644 --- a/src/platform/remoteConfig/types.ts +++ b/src/platform/remoteConfig/types.ts @@ -8,6 +8,17 @@ type ServerHealthAlert = { badge?: string } +type FirebaseRuntimeConfig = { + apiKey: string + authDomain: string + databaseURL?: string + projectId: string + storageBucket: string + messagingSenderId: string + appId: string + measurementId?: string +} + /** * Remote configuration type * Configuration fetched from the server at runtime @@ -16,4 +27,8 @@ export type RemoteConfig = { mixpanel_token?: string subscription_required?: boolean server_health_alert?: ServerHealthAlert + max_upload_size?: number + comfy_api_base_url?: string + comfy_platform_base_url?: string + firebase_config?: FirebaseRuntimeConfig } diff --git a/src/platform/updates/common/releaseService.ts b/src/platform/updates/common/releaseService.ts index 189421678..6b9bfceed 100644 --- a/src/platform/updates/common/releaseService.ts +++ b/src/platform/updates/common/releaseService.ts @@ -1,18 +1,11 @@ import type { AxiosError, AxiosResponse } from 'axios' import axios from 'axios' -import { ref } from 'vue' +import { ref, watch } from 'vue' -import { COMFY_API_BASE_URL } from '@/config/comfyApi' +import { getComfyApiBaseUrl } from '@/config/comfyApi' import type { components, operations } from '@/types/comfyRegistryTypes' import { isAbortError } from '@/utils/typeGuardUtil' -const releaseApiClient = axios.create({ - baseURL: COMFY_API_BASE_URL, - headers: { - 'Content-Type': 'application/json' - } -}) - // Use generated types from OpenAPI spec export type ReleaseNote = components['schemas']['ReleaseNote'] type GetReleasesParams = operations['getReleaseNotes']['parameters']['query'] @@ -20,11 +13,25 @@ type GetReleasesParams = operations['getReleaseNotes']['parameters']['query'] // Use generated error response type type ErrorResponse = components['schemas']['ErrorResponse'] +const releaseApiClient = axios.create({ + baseURL: getComfyApiBaseUrl(), + headers: { + 'Content-Type': 'application/json' + } +}) + // Release service for fetching release notes export const useReleaseService = () => { const isLoading = ref(false) const error = ref(null) + watch( + () => getComfyApiBaseUrl(), + (url) => { + releaseApiClient.defaults.baseURL = url + } + ) + // No transformation needed - API response matches the generated type // Handle API errors with context diff --git a/src/services/customerEventsService.ts b/src/services/customerEventsService.ts index 04d0f1d66..830b2fc2d 100644 --- a/src/services/customerEventsService.ts +++ b/src/services/customerEventsService.ts @@ -1,9 +1,9 @@ import type { AxiosError, AxiosResponse } from 'axios' import axios from 'axios' -import { ref } from 'vue' +import { ref, watch } from 'vue' import { useI18n } from 'vue-i18n' -import { COMFY_API_BASE_URL } from '@/config/comfyApi' +import { getComfyApiBaseUrl } from '@/config/comfyApi' import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore' import type { components, operations } from '@/types/comfyRegistryTypes' import { isAbortError } from '@/utils/typeGuardUtil' @@ -24,7 +24,7 @@ type CustomerEventsResponseQuery = export type AuditLog = components['schemas']['AuditLog'] const customerApiClient = axios.create({ - baseURL: COMFY_API_BASE_URL, + baseURL: getComfyApiBaseUrl(), headers: { 'Content-Type': 'application/json' } @@ -35,6 +35,13 @@ export const useCustomerEventsService = () => { const error = ref(null) const { d } = useI18n() + watch( + () => getComfyApiBaseUrl(), + (url) => { + customerApiClient.defaults.baseURL = url + } + ) + const handleRequestError = ( err: unknown, context: string, diff --git a/src/stores/firebaseAuthStore.ts b/src/stores/firebaseAuthStore.ts index 08e441925..1c73539a9 100644 --- a/src/stores/firebaseAuthStore.ts +++ b/src/stores/firebaseAuthStore.ts @@ -21,7 +21,7 @@ import { defineStore } from 'pinia' import { computed, ref } from 'vue' import { useFirebaseAuth } from 'vuefire' -import { COMFY_API_BASE_URL } from '@/config/comfyApi' +import { getComfyApiBaseUrl } from '@/config/comfyApi' import { t } from '@/i18n' import { isCloud } from '@/platform/distribution/types' import { useTelemetry } from '@/platform/telemetry' @@ -65,6 +65,8 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { // Token refresh trigger - increments when token is refreshed const tokenRefreshTrigger = ref(0) + const buildApiUrl = (path: string) => `${getComfyApiBaseUrl()}${path}` + // Providers const googleProvider = new GoogleAuthProvider() googleProvider.addScope('email') @@ -163,7 +165,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { ) } - const response = await fetch(`${COMFY_API_BASE_URL}/customers/balance`, { + const response = await fetch(buildApiUrl('/customers/balance'), { headers: { ...authHeader, 'Content-Type': 'application/json' @@ -199,7 +201,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { throw new FirebaseAuthStoreError(t('toastMessages.userNotAuthenticated')) } - const createCustomerRes = await fetch(`${COMFY_API_BASE_URL}/customers`, { + const createCustomerRes = await fetch(buildApiUrl('/customers'), { method: 'POST', headers: { ...authHeader, @@ -367,7 +369,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { customerCreated.value = true } - const response = await fetch(`${COMFY_API_BASE_URL}/customers/credit`, { + const response = await fetch(buildApiUrl('/customers/credit'), { method: 'POST', headers: { ...authHeader, @@ -401,7 +403,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { throw new FirebaseAuthStoreError(t('toastMessages.userNotAuthenticated')) } - const response = await fetch(`${COMFY_API_BASE_URL}/customers/billing`, { + const response = await fetch(buildApiUrl('/customers/billing'), { method: 'POST', headers: { ...authHeader, diff --git a/tests-ui/tests/platform/cloud/subscription/useSubscription.test.ts b/tests-ui/tests/platform/cloud/subscription/useSubscription.test.ts index ec356cfc3..e113e626e 100644 --- a/tests-ui/tests/platform/cloud/subscription/useSubscription.test.ts +++ b/tests-ui/tests/platform/cloud/subscription/useSubscription.test.ts @@ -283,7 +283,7 @@ describe('useSubscription', () => { handleViewUsageHistory() expect(windowOpenSpy).toHaveBeenCalledWith( - 'https://platform.comfy.org/profile/usage', + 'https://stagingplatform.comfy.org/profile/usage', '_blank' ) diff --git a/vitest.setup.ts b/vitest.setup.ts index e3427e7c6..137b50db6 100644 --- a/vitest.setup.ts +++ b/vitest.setup.ts @@ -6,8 +6,27 @@ declare global { interface Window { __CONFIG__: { mixpanel_token?: string + require_whitelist?: boolean subscription_required?: boolean - server_health_alert?: string + max_upload_size?: number + comfy_api_base_url?: string + comfy_platform_base_url?: string + firebase_config?: { + apiKey: string + authDomain: string + databaseURL?: string + projectId: string + storageBucket: string + messagingSenderId: string + appId: string + measurementId?: string + } + server_health_alert?: { + message: string + tooltip?: string + severity?: 'info' | 'warning' | 'error' + badge?: string + } } } } @@ -24,7 +43,17 @@ globalThis.__DISTRIBUTION__ = 'localhost' // Define runtime config for tests window.__CONFIG__ = { subscription_required: true, - mixpanel_token: 'test-token' + mixpanel_token: 'test-token', + comfy_api_base_url: 'https://stagingapi.comfy.org', + comfy_platform_base_url: 'https://stagingplatform.comfy.org', + firebase_config: { + apiKey: 'test', + authDomain: 'test.firebaseapp.com', + projectId: 'test', + storageBucket: 'test.appspot.com', + messagingSenderId: '123', + appId: '123' + } } // Mock Worker for extendable-media-recorder