From b7c7f16d47823833ff4afd9d278fbb277dbc9cc3 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Thu, 9 Jul 2026 17:57:47 -0700 Subject: [PATCH 1/2] fix: report page views to Customer.io --- .../cloud/CustomerIoTelemetryProvider.test.ts | 25 +++++++++++++++++ .../cloud/CustomerIoTelemetryProvider.ts | 28 +++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts b/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts index 8182c782a64..e53aaa54020 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,30 @@ 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('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..3a06615ef16 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 { @@ -45,9 +47,9 @@ export class CustomerIoTelemetryProvider implements TelemetryProvider { } void import('@customerio/cdp-analytics-browser') - .then(({ AnalyticsBrowser, InAppPlugin }) => { + .then(async ({ AnalyticsBrowser, InAppPlugin }) => { const analytics = AnalyticsBrowser.load({ writeKey }) - void analytics.register( + await analytics.register( InAppPlugin({ siteId, events: null, @@ -68,6 +70,7 @@ export class CustomerIoTelemetryProvider implements TelemetryProvider { currentUser.onUserLogout(() => void analytics.reset()) this.flushQueue() + this.flushPageView() }) .catch((error) => { console.error('Failed to load Customer.io:', error) @@ -100,6 +103,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) } From 190df7ee9c7657b2ebbc297bf809ee2d43daf725 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Thu, 9 Jul 2026 18:30:44 -0700 Subject: [PATCH 2/2] fix: preserve Customer.io tracking on in-app failure --- .../cloud/CustomerIoTelemetryProvider.test.ts | 21 +++++++++++++++++++ .../cloud/CustomerIoTelemetryProvider.ts | 13 +++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts b/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts index e53aaa54020..8827b832ba7 100644 --- a/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts +++ b/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts @@ -98,6 +98,27 @@ describe('CustomerIoTelemetryProvider', () => { 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 3a06615ef16..4fdabf12e34 100644 --- a/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.ts +++ b/src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.ts @@ -47,9 +47,9 @@ export class CustomerIoTelemetryProvider implements TelemetryProvider { } void import('@customerio/cdp-analytics-browser') - .then(async ({ AnalyticsBrowser, InAppPlugin }) => { + .then(({ AnalyticsBrowser, InAppPlugin }) => { const analytics = AnalyticsBrowser.load({ writeKey }) - await analytics.register( + const inAppRegistration = analytics.register( InAppPlugin({ siteId, events: null, @@ -70,7 +70,14 @@ export class CustomerIoTelemetryProvider implements TelemetryProvider { currentUser.onUserLogout(() => void analytics.reset()) this.flushQueue() - this.flushPageView() + 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)