diff --git a/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts b/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts index 8182c782a64..8827b832ba7 100644 --- a/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts +++ b/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts @@ -3,6 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' const hoisted = vi.hoisted(() => { const analytics = { identify: vi.fn(), + page: vi.fn(), track: vi.fn(), reset: vi.fn(), register: vi.fn().mockResolvedValue(undefined) @@ -73,6 +74,51 @@ describe('CustomerIoTelemetryProvider', () => { expect(hoisted.analytics.register).toHaveBeenCalled() }) + it('reports the current page after registering the in-app plugin', async () => { + createProvider() + await vi.dynamicImportSettled() + + expect(hoisted.analytics.page).toHaveBeenCalledOnce() + expect(hoisted.analytics.page).toHaveBeenCalledWith() + expect(hoisted.analytics.register.mock.invocationCallOrder[0]).toBeLessThan( + hoisted.analytics.page.mock.invocationCallOrder[0] + ) + }) + + it('reports client-side route changes', async () => { + const provider = createProvider() + await vi.dynamicImportSettled() + hoisted.analytics.page.mockClear() + + provider.trackPageView('workflow_editor', { + path: 'https://cloud.comfy.org/' + }) + + expect(hoisted.analytics.page).toHaveBeenCalledOnce() + expect(hoisted.analytics.page).toHaveBeenCalledWith() + }) + + it('continues tracking when the in-app plugin fails to register', async () => { + vi.spyOn(console, 'error').mockImplementation(() => {}) + hoisted.analytics.register.mockRejectedValue( + new Error('in-app setup failed') + ) + const provider = createProvider() + provider.trackWorkflowExecution() + + await vi.dynamicImportSettled() + + expect(hoisted.analytics.track).toHaveBeenCalledWith( + 'execution_start', + SOURCE + ) + provider.trackAddApiCreditButtonClicked() + expect(hoisted.analytics.track).toHaveBeenCalledWith( + 'app:add_api_credit_button_clicked', + SOURCE + ) + }) + it('does not initialize without a write key', async () => { const provider = createProvider({ customer_io: { site_id: SITE_ID } }) await vi.dynamicImportSettled() diff --git a/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.ts b/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.ts index ecb34ad8f86..4fdabf12e34 100644 --- a/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.ts +++ b/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.ts @@ -6,6 +6,7 @@ import { TelemetryEvents } from '../../types' import type { AuthMetadata, ExecutionSuccessMetadata, + PageViewMetadata, ShareFlowMetadata, SubscriptionMetadata, TelemetryEventProperties, @@ -32,6 +33,7 @@ export class CustomerIoTelemetryProvider implements TelemetryProvider { private analytics: AnalyticsBrowser | null = null private isEnabled = true private eventQueue: QueuedEvent[] = [] + private pageViewQueued = true constructor() { const { @@ -47,7 +49,7 @@ export class CustomerIoTelemetryProvider implements TelemetryProvider { void import('@customerio/cdp-analytics-browser') .then(({ AnalyticsBrowser, InAppPlugin }) => { const analytics = AnalyticsBrowser.load({ writeKey }) - void analytics.register( + const inAppRegistration = analytics.register( InAppPlugin({ siteId, events: null, @@ -68,6 +70,14 @@ export class CustomerIoTelemetryProvider implements TelemetryProvider { currentUser.onUserLogout(() => void analytics.reset()) this.flushQueue() + void inAppRegistration + .then(() => this.flushPageView()) + .catch((error) => { + console.error( + 'Failed to initialize Customer.io in-app plugin:', + error + ) + }) }) .catch((error) => { console.error('Failed to load Customer.io:', error) @@ -100,6 +110,27 @@ export class CustomerIoTelemetryProvider implements TelemetryProvider { this.eventQueue = [] } + private sendPageView(): void { + void this.analytics?.page()?.catch((error) => { + console.error('Failed to track Customer.io page view:', error) + }) + } + + private flushPageView(): void { + if (!this.analytics || !this.pageViewQueued) return + this.pageViewQueued = false + this.sendPageView() + } + + trackPageView(_pageName: string, _properties?: PageViewMetadata): void { + if (!this.isEnabled) return + if (!this.analytics) { + this.pageViewQueued = true + return + } + this.sendPageView() + } + trackAuth(metadata: AuthMetadata): void { this.track(TelemetryEvents.USER_AUTH_COMPLETED, metadata) }