diff --git a/packages/analytics-browser/src/browser-client.ts b/packages/analytics-browser/src/browser-client.ts index 203d27dba..192ec42dc 100644 --- a/packages/analytics-browser/src/browser-client.ts +++ b/packages/analytics-browser/src/browser-client.ts @@ -168,8 +168,14 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient, An this.config.diagnosticsClient = diagnosticsClient; this.config.remoteConfigClient = remoteConfigClient; + // Determine tracking configuration - prioritize autocapture, fall back to defaultTracking for backwards compatibility + // Note: Some legacy features (attribution, pageViews, sessions, fileDownloads, formInteractions) support + // both autocapture and defaultTracking. Newer features only support autocapture. + const trackingConfig = + this.config.autocapture !== undefined ? this.config.autocapture : this.config.defaultTracking; + // Add web attribution plugin - if (isAttributionTrackingEnabled(this.config.defaultTracking)) { + if (isAttributionTrackingEnabled(trackingConfig)) { const attributionTrackingOptions = getAttributionTrackingConfig(this.config); this.webAttribution = new WebAttribution(attributionTrackingOptions, this.config); // Fetch the current campaign, check if need to track web attribution later @@ -221,22 +227,23 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient, An // Notify if DET is enabled detNotify(this.config); - if (isFileDownloadTrackingEnabled(this.config.defaultTracking)) { + if (isFileDownloadTrackingEnabled(trackingConfig)) { this.config.loggerProvider.debug('Adding file download tracking plugin'); await this.add(fileDownloadTracking()).promise; } - if (isFormInteractionTrackingEnabled(this.config.defaultTracking)) { + if (isFormInteractionTrackingEnabled(trackingConfig)) { this.config.loggerProvider.debug('Adding form interaction plugin'); await this.add(formInteractionTracking()).promise; } // Add page view plugin - if (isPageViewTrackingEnabled(this.config.defaultTracking)) { + if (isPageViewTrackingEnabled(trackingConfig)) { this.config.loggerProvider.debug('Adding page view tracking plugin'); await this.add(pageViewTrackingPlugin(getPageViewTrackingConfig(this.config))).promise; } + // Newer autocapture-only features (no defaultTracking support) if (isElementInteractionsEnabled(this.config.autocapture)) { this.config.loggerProvider.debug('Adding user interactions plugin (autocapture plugin)'); await this.add(autocapturePlugin(getElementInteractionsConfig(this.config), { diagnosticsClient })).promise; @@ -358,7 +365,9 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient, An this.config.lastEventTime = undefined; this.config.pageCounter = 0; - if (isSessionTrackingEnabled(this.config.defaultTracking)) { + const trackingConfig = + this.config.autocapture !== undefined ? this.config.autocapture : this.config.defaultTracking; + if (isSessionTrackingEnabled(trackingConfig)) { if (previousSessionId && lastEventTime) { promises.push( this.track(DEFAULT_SESSION_END_EVENT, undefined, { @@ -378,7 +387,7 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient, An // 2. or shouldTrackNewCampaign (call setSessionId from async process(event) when there has new campaign and resetSessionOnNewCampaign = true ) const isCampaignEventTracked = this.trackCampaignEventIfNeeded(++lastEventId, promises); - if (isSessionTrackingEnabled(this.config.defaultTracking)) { + if (isSessionTrackingEnabled(trackingConfig)) { promises.push( this.track(DEFAULT_SESSION_START_EVENT, undefined, { event_id: isCampaignEventTracked ? ++lastEventId : lastEventId, diff --git a/packages/analytics-browser/src/default-tracking.ts b/packages/analytics-browser/src/default-tracking.ts index a1689ecaf..28ce71b4a 100644 --- a/packages/analytics-browser/src/default-tracking.ts +++ b/packages/analytics-browser/src/default-tracking.ts @@ -11,8 +11,12 @@ import { } from '@amplitude/analytics-core'; /** - * A subset of AutocaptureOptions that includes the autocapture features that - * are made available to users by default (even if "config.autocapture === undefined") + * Legacy autocapture features that support both autocapture and defaultTracking properties. + * These features are enabled by default (even if "config.autocapture === undefined") for + * backwards compatibility with the deprecated defaultTracking property. + * + * Note: Newer features (networkTracking, elementInteractions, frustrationInteractions, webVitals) + * only support autocapture and are disabled by default. */ type AutocaptureOptionsDefaultAvailable = Pick< AutocaptureOptions, @@ -20,8 +24,9 @@ type AutocaptureOptionsDefaultAvailable = Pick< >; /** - * Returns false if autocapture === false or if autocapture[event], - * otherwise returns true (even if "config.autocapture === undefined") + * Helper for legacy features that support both autocapture and defaultTracking. + * Returns false if autocapture === false or if autocapture[event] === false, + * otherwise returns true (even if "config.autocapture === undefined") for backwards compatibility. */ const isTrackingEnabled = ( autocapture: AutocaptureOptionsDefaultAvailable | boolean | undefined, @@ -38,6 +43,7 @@ const isTrackingEnabled = ( return true; }; +// Legacy features - enabled by default, support both autocapture and defaultTracking export const isAttributionTrackingEnabled = (autocapture: AutocaptureOptions | boolean | undefined) => isTrackingEnabled(autocapture, 'attribution'); @@ -56,6 +62,8 @@ export const isSessionTrackingEnabled = (autocapture: AutocaptureOptions | boole export const isPageUrlEnrichmentEnabled = (autocapture: AutocaptureOptions | boolean | undefined) => isTrackingEnabled(autocapture, 'pageUrlEnrichment'); +// Newer features - disabled by default, autocapture-only (no defaultTracking support) + /** * Returns true if * 1. if autocapture.networkTracking === true @@ -132,6 +140,8 @@ export const isFrustrationInteractionsEnabled = (autocapture: AutocaptureOptions return false; }; +// Config getters for newer autocapture-only features + export const getElementInteractionsConfig = (config: BrowserOptions): ElementInteractionsOptions | undefined => { if ( isElementInteractionsEnabled(config.autocapture) && @@ -185,33 +195,37 @@ export const getNetworkTrackingConfig = (config: BrowserOptions): NetworkTrackin return; }; +// Config getters for legacy features (support both autocapture and defaultTracking) + export const getPageViewTrackingConfig = (config: BrowserOptions): PageTrackingOptions => { let trackOn: PageTrackingTrackOn | undefined = () => false; let trackHistoryChanges: PageTrackingHistoryChanges | undefined = undefined; let eventType: string | undefined; const pageCounter = config.pageCounter; - const isDefaultPageViewTrackingEnabled = isPageViewTrackingEnabled(config.defaultTracking); + // Prioritize config.autocapture, but fall back to config.defaultTracking for backwards compatibility + const trackingConfig = config.autocapture !== undefined ? config.autocapture : config.defaultTracking; + const isDefaultPageViewTrackingEnabled = isPageViewTrackingEnabled(trackingConfig); if (isDefaultPageViewTrackingEnabled) { trackOn = undefined; eventType = undefined; if ( - config.defaultTracking && - typeof config.defaultTracking === 'object' && - config.defaultTracking.pageViews && - typeof config.defaultTracking.pageViews === 'object' + trackingConfig && + typeof trackingConfig === 'object' && + trackingConfig.pageViews && + typeof trackingConfig.pageViews === 'object' ) { - if ('trackOn' in config.defaultTracking.pageViews) { - trackOn = config.defaultTracking.pageViews.trackOn; + if ('trackOn' in trackingConfig.pageViews) { + trackOn = trackingConfig.pageViews.trackOn; } - if ('trackHistoryChanges' in config.defaultTracking.pageViews) { - trackHistoryChanges = config.defaultTracking.pageViews.trackHistoryChanges; + if ('trackHistoryChanges' in trackingConfig.pageViews) { + trackHistoryChanges = trackingConfig.pageViews.trackHistoryChanges; } - if ('eventType' in config.defaultTracking.pageViews && config.defaultTracking.pageViews.eventType) { - eventType = config.defaultTracking.pageViews.eventType; + if ('eventType' in trackingConfig.pageViews && trackingConfig.pageViews.eventType) { + eventType = trackingConfig.pageViews.eventType; } } } @@ -225,15 +239,18 @@ export const getPageViewTrackingConfig = (config: BrowserOptions): PageTrackingO }; export const getAttributionTrackingConfig = (config: BrowserOptions): AttributionOptions => { + // Prioritize config.autocapture, but fall back to config.defaultTracking for backwards compatibility + const trackingConfig = config.autocapture !== undefined ? config.autocapture : config.defaultTracking; + if ( - isAttributionTrackingEnabled(config.defaultTracking) && - config.defaultTracking && - typeof config.defaultTracking === 'object' && - config.defaultTracking.attribution && - typeof config.defaultTracking.attribution === 'object' + isAttributionTrackingEnabled(trackingConfig) && + trackingConfig && + typeof trackingConfig === 'object' && + trackingConfig.attribution && + typeof trackingConfig.attribution === 'object' ) { return { - ...config.defaultTracking.attribution, + ...trackingConfig.attribution, }; } diff --git a/packages/analytics-browser/test/browser-client.test.ts b/packages/analytics-browser/test/browser-client.test.ts index 54f5727a7..e1cc4acf0 100644 --- a/packages/analytics-browser/test/browser-client.test.ts +++ b/packages/analytics-browser/test/browser-client.test.ts @@ -531,6 +531,20 @@ describe('browser-client', () => { expect(formInteractionTrackingPlugin).toHaveBeenCalledTimes(0); }); + test('should add file download and form interaction tracking plugins using autocapture', async () => { + const fileDownloadTrackingPlugin = jest.spyOn(fileDownloadTracking, 'fileDownloadTracking'); + const formInteractionTrackingPlugin = jest.spyOn(formInteractionTracking, 'formInteractionTracking'); + await client.init(apiKey, userId, { + optOut: false, + autocapture: { + fileDownloads: true, + formInteractions: true, + }, + }).promise; + expect(fileDownloadTrackingPlugin).toHaveBeenCalledTimes(1); + expect(formInteractionTrackingPlugin).toHaveBeenCalledTimes(1); + }); + test('should add network connectivity checker plugin by default', async () => { const networkConnectivityCheckerPlugin = jest.spyOn( networkConnectivityChecker, @@ -1413,6 +1427,68 @@ describe('browser-client', () => { }); }); + test('should set session id with start and end session event and web attribution event using autocapture', async () => { + jest.spyOn(CookieMigration, 'parseLegacyCookies').mockResolvedValueOnce({ + optOut: false, + sessionId: 1, + lastEventId: 100, + lastEventTime: Date.now() - 1000, + }); + const result = { + promise: Promise.resolve({ + code: 200, + event: { + event_type: 'a', + }, + message: 'success', + }), + }; + const track = jest.spyOn(client, 'track').mockReturnValue(result); + await client.init(apiKey, { + sessionTimeout: 5000, + autocapture: { + attribution: true, + pageViews: false, + sessions: true, + }, + }).promise; + + client.setSessionId(2); + + expect(client.getSessionId()).toBe(2); + return new Promise((resolve) => { + setTimeout(() => { + expect(track).toHaveBeenCalledTimes(3); + resolve(); + }, 4000); + }); + }); + + test('should prioritize autocapture over defaultTracking for attribution', async () => { + jest.spyOn(CookieMigration, 'parseLegacyCookies').mockResolvedValueOnce({ + optOut: false, + sessionId: 1, + lastEventId: 100, + lastEventTime: Date.now() - 1000, + }); + await client.init(apiKey, { + sessionTimeout: 5000, + autocapture: { + attribution: { + resetSessionOnNewCampaign: true, + }, + }, + defaultTracking: { + attribution: { + resetSessionOnNewCampaign: false, + }, + }, + }).promise; + + // Verify web attribution is initialized (proving autocapture.attribution was used) + expect(client.webAttribution).toBeDefined(); + }); + test('should defer set session id', () => { return new Promise((resolve) => { void client.init(apiKey, { defaultTracking }).promise.then(() => { diff --git a/packages/analytics-browser/test/default-tracking.test.ts b/packages/analytics-browser/test/default-tracking.test.ts index 6289555f2..e045f54e9 100644 --- a/packages/analytics-browser/test/default-tracking.test.ts +++ b/packages/analytics-browser/test/default-tracking.test.ts @@ -378,6 +378,42 @@ describe('getPageViewTrackingConfig', () => { expect(config.trackHistoryChanges).toBe('all'); expect(config.eventType).toBe('Page View'); }); + + test('should return autocapture.pageViews config', () => { + const config = getPageViewTrackingConfig({ + autocapture: { + pageViews: { + trackOn: 'attribution', + trackHistoryChanges: 'all', + eventType: 'Page View', + }, + }, + }); + + expect(config.trackOn).toBe('attribution'); + expect(config.trackHistoryChanges).toBe('all'); + expect(config.eventType).toBe('Page View'); + }); + + test('should prioritize autocapture over defaultTracking', () => { + const config = getPageViewTrackingConfig({ + autocapture: { + pageViews: { + trackOn: 'attribution', + eventType: 'Autocapture Page View', + }, + }, + defaultTracking: { + pageViews: { + trackOn: () => false, + eventType: 'DefaultTracking Page View', + }, + }, + }); + + expect(config.trackOn).toBe('attribution'); + expect(config.eventType).toBe('Autocapture Page View'); + }); }); describe('getAttributionTrackingConfig', () => { @@ -423,6 +459,42 @@ describe('getAttributionTrackingConfig', () => { resetSessionOnNewCampaign: true, }); }); + + test('should return autocapture.attribution config', () => { + const config = getAttributionTrackingConfig({ + autocapture: { + attribution: { + excludeReferrers: ['google.com'], + initialEmptyValue: 'EMPTY', + resetSessionOnNewCampaign: true, + }, + }, + }); + expect(config).toEqual({ + excludeReferrers: ['google.com'], + initialEmptyValue: 'EMPTY', + resetSessionOnNewCampaign: true, + }); + }); + + test('should prioritize autocapture.attribution over defaultTracking.attribution', () => { + const config = getAttributionTrackingConfig({ + autocapture: { + attribution: { + resetSessionOnNewCampaign: true, + excludeReferrers: ['autocapture.com'], + }, + }, + defaultTracking: { + attribution: { + resetSessionOnNewCampaign: false, + excludeReferrers: ['defaultTracking.com'], + }, + }, + }); + expect(config.resetSessionOnNewCampaign).toBe(true); + expect(config.excludeReferrers).toEqual(['autocapture.com']); + }); }); describe('getNetworkTrackingConfig', () => {