Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,27 @@ import App from './App.vue'
import './assets/css/style.css'
import { i18n } from './i18n'

/**
* CRITICAL: Load remote config FIRST for cloud builds to ensure
* window.__CONFIG__is available for all modules during initialization
*/
const isCloud = __DISTRIBUTION__ === 'cloud'
const hasHostTelemetryBridge = Boolean(window.__comfyDesktop2?.Telemetry)
const requiresRemoteConfigBootstrap = isCloud || hasHostTelemetryBridge

if (isCloud) {
if (requiresRemoteConfigBootstrap) {
const { refreshRemoteConfig } =
await import('@/platform/remoteConfig/refreshRemoteConfig')
await refreshRemoteConfig({ useAuth: false })
}

if (isCloud) {
const { initTelemetry } = await import('@/platform/telemetry/initTelemetry')
await initTelemetry()
}

if (hasHostTelemetryBridge) {
const { initHostTelemetry } =
await import('@/platform/telemetry/initHostTelemetry')
initHostTelemetry()
}

const ComfyUIPreset = definePreset(Aura, {
semantic: {
// @ts-expect-error fixme ts strict error
Expand Down
13 changes: 8 additions & 5 deletions src/platform/remoteConfig/refreshRemoteConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { api } from '@/scripts/api'

import { remoteConfig, remoteConfigState } from './remoteConfig'

interface RefreshRemoteConfigOptions {
Expand All @@ -10,6 +8,13 @@ interface RefreshRemoteConfigOptions {
useAuth?: boolean
}

async function fetchRemoteConfig(useAuth: boolean): Promise<Response> {
if (!useAuth) return fetch('/api/features', { cache: 'no-store' })

const { api } = await import('@/scripts/api')
return api.fetchApi('/features', { cache: 'no-store' })
}

/**
* Loads remote configuration from the backend /features endpoint
* and updates the reactive remoteConfig ref.
Expand All @@ -25,9 +30,7 @@ export async function refreshRemoteConfig(
const { useAuth = true } = options

try {
const response = useAuth
? await api.fetchApi('/features', { cache: 'no-store' })
: await fetch('/api/features', { cache: 'no-store' })
const response = await fetchRemoteConfig(useAuth)

if (response.ok) {
const config = await response.json()
Expand Down
1 change: 1 addition & 0 deletions src/platform/remoteConfig/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export type RemoteConfig = {
comfy_platform_base_url?: string
firebase_config?: FirebaseRuntimeConfig
telemetry_disabled_events?: TelemetryEventName[]
enable_telemetry?: boolean
model_upload_button_enabled?: boolean
asset_rename_enabled?: boolean
private_models_enabled?: boolean
Expand Down
64 changes: 64 additions & 0 deletions src/platform/telemetry/initHostTelemetry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import { remoteConfig } from '@/platform/remoteConfig/remoteConfig'
import { setTelemetryRegistry, useTelemetry } from '@/platform/telemetry'
import { initHostTelemetry } from '@/platform/telemetry/initHostTelemetry'
import { TelemetryEvents } from '@/platform/telemetry/types'

const fetchMock = vi.fn()
const localStorageMock = {
getItem: vi.fn(() => null),
setItem: vi.fn(),
removeItem: vi.fn(),
clear: vi.fn()
}

describe('initHostTelemetry', () => {
beforeEach(() => {
vi.stubGlobal('fetch', fetchMock)
vi.stubGlobal('localStorage', localStorageMock)
})

afterEach(() => {
remoteConfig.value = {}
setTelemetryRegistry(null)
delete window.__comfyDesktop2
vi.unstubAllGlobals()
vi.clearAllMocks()
})

it('leaves the registry untouched when enable_telemetry is on but the host Telemetry bridge is absent', () => {
remoteConfig.value = { enable_telemetry: true }

initHostTelemetry()

expect(useTelemetry()).toBeNull()
expect(fetchMock).not.toHaveBeenCalled()
})

it('leaves the registry untouched when enable_telemetry is off', () => {
window.__comfyDesktop2 = {
isRemote: () => false,
Telemetry: { capture: vi.fn() }
}
remoteConfig.value = { enable_telemetry: false }

initHostTelemetry()

expect(useTelemetry()).toBeNull()
})

it('registers the host telemetry sink when enable_telemetry and the bridge are present', () => {
const capture = vi.fn()
window.__comfyDesktop2 = { isRemote: () => false, Telemetry: { capture } }
remoteConfig.value = { enable_telemetry: true }

initHostTelemetry()
useTelemetry()?.trackSignupOpened()

expect(capture).toHaveBeenCalledWith(
TelemetryEvents.USER_SIGN_UP_OPENED,
undefined
)
})
})
23 changes: 23 additions & 0 deletions src/platform/telemetry/initHostTelemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { setTelemetryRegistry } from './index'
import { remoteConfig } from '@/platform/remoteConfig/remoteConfig'
import { getDevOverride } from '@/utils/devFeatureFlagOverride'
import { TelemetryRegistry } from './TelemetryRegistry'
import { HostTelemetrySink } from './providers/host/HostTelemetrySink'

const ENABLE_TELEMETRY_FEATURE = 'enable_telemetry'

function isHostTelemetryEnabled(): boolean {
const override = getDevOverride<boolean>(ENABLE_TELEMETRY_FEATURE)
if (override !== undefined) return override

return remoteConfig.value.enable_telemetry === true
}

export function initHostTelemetry(): void {
if (!isHostTelemetryEnabled()) return
if (!window.__comfyDesktop2?.Telemetry) return

const registry = new TelemetryRegistry()
registry.registerProvider(new HostTelemetrySink())
setTelemetryRegistry(registry)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
WorkflowImportMetadata,
WorkflowSavedMetadata
} from '../../types'
import { TelemetryEvents } from '../../types'

/**
* Google Tag Manager telemetry provider.
Expand Down Expand Up @@ -152,7 +153,7 @@ export class GtmTelemetryProvider implements TelemetryProvider {
}

trackBeginCheckout(metadata: BeginCheckoutMetadata): void {
this.pushEvent('begin_checkout', metadata)
this.pushEvent(TelemetryEvents.BEGIN_CHECKOUT, metadata)
}

trackSubscription(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ const topupMocks = vi.hoisted(() => ({
}))
vi.mock('@/platform/telemetry/topupTracker', () => topupMocks)

const mockNormalizeSurveyResponses = vi.hoisted(() => vi.fn())
vi.mock('@/platform/telemetry/utils/surveyNormalization', () => ({
normalizeSurveyResponses: mockNormalizeSurveyResponses
}))

vi.mock('@/platform/remoteConfig/remoteConfig', () => ({
remoteConfig: { value: null }
}))

vi.mock('@/platform/telemetry/utils/getExecutionContext', () => ({
getExecutionContext: () => ({
is_template: false,
Expand All @@ -38,15 +47,6 @@ vi.mock('@/platform/telemetry/utils/getExecutionContext', () => ({
})
}))

const mockNormalizeSurveyResponses = vi.hoisted(() => vi.fn())
vi.mock('@/platform/telemetry/utils/surveyNormalization', () => ({
normalizeSurveyResponses: mockNormalizeSurveyResponses
}))

vi.mock('@/platform/remoteConfig/remoteConfig', () => ({
remoteConfig: { value: null }
}))

import { MixpanelTelemetryProvider } from '@/platform/telemetry/providers/cloud/MixpanelTelemetryProvider'
import type {
AuthMetadata,
Expand Down
Loading
Loading