Skip to content
Closed
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
21 changes: 15 additions & 6 deletions packages/analytics-browser/src/browser-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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, {
Expand All @@ -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,
Expand Down
59 changes: 38 additions & 21 deletions packages/analytics-browser/src/default-tracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,22 @@ 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,
'pageViews' | 'sessions' | 'fileDownloads' | 'formInteractions' | 'attribution' | 'pageUrlEnrichment'
>;

/**
* 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,
Expand All @@ -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');

Expand All @@ -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
Expand Down Expand Up @@ -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) &&
Expand Down Expand Up @@ -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;
}
}
}
Expand All @@ -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,
};
}

Expand Down
76 changes: 76 additions & 0 deletions packages/analytics-browser/test/browser-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<void>((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<void>((resolve) => {
void client.init(apiKey, { defaultTracking }).promise.then(() => {
Expand Down
72 changes: 72 additions & 0 deletions packages/analytics-browser/test/default-tracking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down
Loading