fix: route gtm through telemetry entrypoint

This commit is contained in:
Benjamin Lu
2026-01-27 19:59:33 -08:00
parent 608ad1d74c
commit ede4aa24f6
5 changed files with 148 additions and 7 deletions

View File

@@ -31,6 +31,9 @@ if (isCloud) {
const { refreshRemoteConfig } =
await import('@/platform/remoteConfig/refreshRemoteConfig')
await refreshRemoteConfig({ useAuth: false })
const { initGtm } = await import('@/platform/telemetry')
initGtm()
}
const ComfyUIPreset = definePreset(Aura, {

View File

@@ -7,13 +7,20 @@ import { useErrorHandling } from '@/composables/useErrorHandling'
import { getComfyApiBaseUrl, getComfyPlatformBaseUrl } from '@/config/comfyApi'
import { t } from '@/i18n'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { pushDataLayerEvent, useTelemetry } from '@/platform/telemetry'
import {
FirebaseAuthStoreError,
useFirebaseAuthStore
} from '@/stores/firebaseAuthStore'
import { useDialogService } from '@/services/dialogService'
import { TIER_TO_KEY } from '@/platform/cloud/subscription/constants/tierPricing'
import {
getTierPrice,
TIER_TO_KEY
} from '@/platform/cloud/subscription/constants/tierPricing'
import {
clearPendingSubscriptionPurchase,
getPendingSubscriptionPurchase
} from '@/platform/cloud/subscription/utils/subscriptionPurchaseTracker'
import type { operations } from '@/types/comfyRegistryTypes'
import { useSubscriptionCancellationWatcher } from './useSubscriptionCancellationWatcher'
@@ -93,7 +100,45 @@ function useSubscriptionInternal() {
: baseName
})
const buildApiUrl = (path: string) => `${getComfyApiBaseUrl()}${path}`
function buildApiUrl(path: string): string {
return `${getComfyApiBaseUrl()}${path}`
}
function trackSubscriptionPurchase(
status: CloudSubscriptionStatusResponse | null
): void {
if (!status?.is_active || !status.subscription_id) return
const pendingPurchase = getPendingSubscriptionPurchase()
if (!pendingPurchase) return
const { tierKey, billingCycle } = pendingPurchase
const isYearly = billingCycle === 'yearly'
const baseName = t(`subscription.tiers.${tierKey}.name`)
const planName = isYearly
? t('subscription.tierNameYearly', { name: baseName })
: baseName
const unitPrice = getTierPrice(tierKey, isYearly)
const value = isYearly && tierKey !== 'founder' ? unitPrice * 12 : unitPrice
pushDataLayerEvent({
event: 'purchase',
transaction_id: status.subscription_id,
value,
currency: 'USD',
items: [
{
item_id: `${billingCycle}_${tierKey}`,
item_name: planName,
item_category: 'subscription',
item_variant: billingCycle,
price: value,
quantity: 1
}
]
})
clearPendingSubscriptionPurchase()
}
const fetchStatus = wrapWithErrorHandlingAsync(
fetchSubscriptionStatus,
@@ -194,6 +239,12 @@ function useSubscriptionInternal() {
const statusData = await response.json()
subscriptionStatus.value = statusData
try {
await trackSubscriptionPurchase(statusData)
} catch (error) {
console.error('Failed to track subscription purchase', error)
}
return statusData
}

View File

@@ -14,13 +14,25 @@
* This approach maintains complete separation between cloud and OSS builds
* while ensuring the open source version contains no telemetry dependencies.
*/
import { isCloud } from '@/platform/distribution/types'
import { MixpanelTelemetryProvider } from './providers/cloud/MixpanelTelemetryProvider'
import type { TelemetryProvider } from './types'
type GtmModule = {
initGtm: () => void
pushDataLayerEvent: (event: Record<string, unknown>) => void
}
// Singleton instance
let _telemetryProvider: TelemetryProvider | null = null
let gtmModulePromise: Promise<GtmModule> | null = null
const IS_CLOUD_BUILD = __DISTRIBUTION__ === 'cloud'
function loadGtmModule(): Promise<GtmModule> {
if (!gtmModulePromise) {
gtmModulePromise = import('./gtm')
}
return gtmModulePromise
}
/**
* Telemetry factory - conditionally creates provider based on distribution
@@ -32,7 +44,7 @@ let _telemetryProvider: TelemetryProvider | null = null
export function useTelemetry(): TelemetryProvider | null {
if (_telemetryProvider === null) {
// Use distribution check for tree-shaking
if (isCloud) {
if (IS_CLOUD_BUILD) {
_telemetryProvider = new MixpanelTelemetryProvider()
}
// For OSS builds, _telemetryProvider stays null
@@ -40,3 +52,17 @@ export function useTelemetry(): TelemetryProvider | null {
return _telemetryProvider
}
export function initGtm(): void {
if (!IS_CLOUD_BUILD || typeof window === 'undefined') return
void loadGtmModule().then(({ initGtm }) => {
initGtm()
})
}
export function pushDataLayerEvent(event: Record<string, unknown>): void {
if (!IS_CLOUD_BUILD || typeof window === 'undefined') return
void loadGtmModule().then(({ pushDataLayerEvent }) => {
pushDataLayerEvent(event)
})
}

View File

@@ -9,6 +9,7 @@ import type { RouteLocationNormalized } from 'vue-router'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { isCloud } from '@/platform/distribution/types'
import { pushDataLayerEvent } from '@/platform/telemetry'
import { useDialogService } from '@/services/dialogService'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import { useUserStore } from '@/stores/userStore'
@@ -36,6 +37,16 @@ function getBasePath(): string {
const basePath = getBasePath()
function pushPageView(): void {
if (!isCloud || typeof window === 'undefined') return
pushDataLayerEvent({
event: 'page_view',
page_location: window.location.href,
page_title: document.title
})
}
const router = createRouter({
history: isFileProtocol
? createWebHashHistory()
@@ -93,6 +104,10 @@ installPreservedQueryTracker(router, [
}
])
router.afterEach(() => {
pushPageView()
})
if (isCloud) {
const { flags } = useFeatureFlags()
const PUBLIC_ROUTE_NAMES = new Set([

View File

@@ -25,7 +25,10 @@ import { getComfyApiBaseUrl } from '@/config/comfyApi'
import { t } from '@/i18n'
import { WORKSPACE_STORAGE_KEYS } from '@/platform/auth/workspace/workspaceConstants'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import {
pushDataLayerEvent as pushDataLayerEventBase,
useTelemetry
} from '@/platform/telemetry'
import { useDialogService } from '@/services/dialogService'
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
import type { AuthHeader } from '@/types/authTypes'
@@ -81,6 +84,42 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
const buildApiUrl = (path: string) => `${getComfyApiBaseUrl()}${path}`
function pushDataLayerEvent(event: Record<string, unknown>): void {
if (!isCloud || typeof window === 'undefined') return
try {
pushDataLayerEventBase(event)
} catch (error) {
console.warn('Failed to push data layer event', error)
}
}
async function hashSha256(value: string): Promise<string | undefined> {
if (typeof crypto === 'undefined' || !crypto.subtle) return
if (typeof TextEncoder === 'undefined') return
const data = new TextEncoder().encode(value)
const hash = await crypto.subtle.digest('SHA-256', data)
return Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
}
async function trackSignUp(method: 'email' | 'google' | 'github') {
if (!isCloud || typeof window === 'undefined') return
try {
const userId = currentUser.value?.uid
const hashedUserId = userId ? await hashSha256(userId) : undefined
pushDataLayerEvent({
event: 'sign_up',
method,
...(hashedUserId ? { user_id: hashedUserId } : {})
})
} catch (error) {
console.warn('Failed to track sign up', error)
}
}
// Providers
const googleProvider = new GoogleAuthProvider()
googleProvider.addScope('email')
@@ -372,6 +411,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
method: 'email',
is_new_user: true
})
await trackSignUp('email')
}
return result
@@ -390,6 +430,9 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
method: 'google',
is_new_user: isNewUser
})
if (isNewUser) {
await trackSignUp('google')
}
}
return result
@@ -408,6 +451,9 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
method: 'github',
is_new_user: isNewUser
})
if (isNewUser) {
await trackSignUp('github')
}
}
return result