Skip to content

Commit 2f077da

Browse files
authored
fix: fix web attribution identify and session start order (#696)
1 parent 4820f11 commit 2f077da

File tree

16 files changed

+381
-171
lines changed

16 files changed

+381
-171
lines changed

packages/analytics-browser-test/test/index.test.ts

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -907,22 +907,6 @@ describe('integration', () => {
907907
{
908908
device_id: uuid,
909909
event_id: 101,
910-
event_type: 'session_start',
911-
insert_id: uuid,
912-
ip: '$remote',
913-
language: 'en-US',
914-
library,
915-
partner_id: undefined,
916-
plan: undefined,
917-
platform: 'Web',
918-
session_id: number,
919-
time: number,
920-
user_agent: userAgent,
921-
user_id: '[email protected]',
922-
},
923-
{
924-
device_id: uuid,
925-
event_id: 102,
926910
event_type: '$identify',
927911
insert_id: uuid,
928912
ip: '$remote',
@@ -980,6 +964,22 @@ describe('integration', () => {
980964
},
981965
},
982966
},
967+
{
968+
device_id: uuid,
969+
event_id: 102,
970+
event_type: 'session_start',
971+
insert_id: uuid,
972+
ip: '$remote',
973+
language: 'en-US',
974+
library,
975+
partner_id: undefined,
976+
plan: undefined,
977+
platform: 'Web',
978+
session_id: number,
979+
time: number,
980+
user_agent: userAgent,
981+
user_id: '[email protected]',
982+
},
983983
{
984984
device_id: uuid,
985985
event_id: 103,
@@ -1115,22 +1115,6 @@ describe('integration', () => {
11151115
{
11161116
device_id: uuid,
11171117
event_id: 0,
1118-
event_type: 'session_start',
1119-
insert_id: uuid,
1120-
ip: '$remote',
1121-
language: 'en-US',
1122-
library,
1123-
partner_id: undefined,
1124-
plan: undefined,
1125-
platform: 'Web',
1126-
session_id: number,
1127-
time: number,
1128-
user_agent: userAgent,
1129-
user_id: '[email protected]',
1130-
},
1131-
{
1132-
device_id: uuid,
1133-
event_id: 1,
11341118
event_type: '$identify',
11351119
insert_id: uuid,
11361120
ip: '$remote',
@@ -1188,6 +1172,22 @@ describe('integration', () => {
11881172
},
11891173
},
11901174
},
1175+
{
1176+
device_id: uuid,
1177+
event_id: 1,
1178+
event_type: 'session_start',
1179+
insert_id: uuid,
1180+
ip: '$remote',
1181+
language: 'en-US',
1182+
library,
1183+
partner_id: undefined,
1184+
plan: undefined,
1185+
platform: 'Web',
1186+
session_id: number,
1187+
time: number,
1188+
user_agent: userAgent,
1189+
user_id: '[email protected]',
1190+
},
11911191
{
11921192
device_id: uuid,
11931193
event_id: 2,

packages/analytics-browser/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
"@amplitude/analytics-core": "^2.2.4",
4949
"@amplitude/analytics-types": "^2.5.0",
5050
"@amplitude/plugin-page-view-tracking-browser": "^2.2.6",
51-
"@amplitude/plugin-web-attribution-browser": "^2.1.7",
5251
"tslib": "^2.4.1"
5352
},
5453
"devDependencies": {

packages/analytics-browser/playground/amplitude.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/analytics-browser/playground/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
<title>Amplitude SDK Playground</title>
88
</head>
99
<script src="./amplitude.js"></script>
10-
<script>
10+
11+
<script>
1112
amplitude.init('API_KEY', '[email protected]');
1213
</script>
1314
<body>

packages/analytics-browser/src/browser-client.ts

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
setConnectorUserId,
1313
isNewSession,
1414
isPageViewTrackingEnabled,
15+
WebAttribution,
1516
} from '@amplitude/analytics-client-common';
1617
import {
1718
BrowserClient,
@@ -23,11 +24,11 @@ import {
2324
Revenue as IRevenue,
2425
TransportType,
2526
OfflineDisabled,
27+
Result,
2628
} from '@amplitude/analytics-types';
2729
import { convertProxyObjectToRealObject, isInstanceProxy } from './utils/snippet-helper';
2830
import { Context } from './plugins/context';
2931
import { useBrowserConfig, createTransport } from './config';
30-
import { webAttributionPlugin } from '@amplitude/plugin-web-attribution-browser';
3132
import { pageViewTrackingPlugin } from '@amplitude/plugin-page-view-tracking-browser';
3233
import { formInteractionTracking } from './plugins/form-interaction-tracking';
3334
import { fileDownloadTracking } from './plugins/file-download-tracking';
@@ -41,6 +42,7 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
4142
config: BrowserConfig;
4243
previousSessionDeviceId: string | undefined;
4344
previousSessionUserId: string | undefined;
45+
webAttribution: WebAttribution | undefined;
4446

4547
init(apiKey = '', userIdOrOptions?: string | BrowserOptions, maybeOptions?: BrowserOptions) {
4648
let userId: string | undefined;
@@ -71,6 +73,14 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
7173
const browserOptions = await useBrowserConfig(options.apiKey, options, this);
7274
this.config = browserOptions;
7375

76+
// Add web attribution plugin
77+
if (isAttributionTrackingEnabled(this.config.defaultTracking)) {
78+
const attributionTrackingOptions = getAttributionTrackingConfig(this.config);
79+
this.webAttribution = new WebAttribution(attributionTrackingOptions, this.config);
80+
// Fetch the current campaign, check if need to track web attribution later
81+
await this.webAttribution.init();
82+
}
83+
7484
// Step 3: Set session ID
7585
// Priority 1: `options.sessionId`
7686
// Priority 2: last known sessionId from user identity storage
@@ -109,13 +119,6 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
109119
await this.add(formInteractionTracking()).promise;
110120
}
111121

112-
// Add web attribution plugin
113-
if (isAttributionTrackingEnabled(this.config.defaultTracking)) {
114-
const attributionTrackingOptions = getAttributionTrackingConfig(this.config);
115-
const webAttribution = webAttributionPlugin(attributionTrackingOptions);
116-
await this.add(webAttribution).promise;
117-
}
118-
119122
// Add page view plugin
120123
if (isPageViewTrackingEnabled(this.config.defaultTracking)) {
121124
await this.add(pageViewTrackingPlugin(getPageViewTrackingConfig(this.config))).promise;
@@ -170,14 +173,14 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
170173
}
171174

172175
setSessionId(sessionId: number) {
176+
const promises: Promise<Result>[] = [];
173177
if (!this.config) {
174178
this.q.push(this.setSessionId.bind(this, sessionId));
175-
return;
179+
return returnWrapper(Promise.resolve());
176180
}
177-
178181
// Prevents starting a new session with the same session ID
179182
if (sessionId === this.config.sessionId) {
180-
return;
183+
return returnWrapper(Promise.resolve());
181184
}
182185

183186
const previousSessionId = this.getSessionId();
@@ -190,25 +193,37 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
190193

191194
if (isSessionTrackingEnabled(this.config.defaultTracking)) {
192195
if (previousSessionId && lastEventTime) {
193-
this.track(DEFAULT_SESSION_END_EVENT, undefined, {
194-
device_id: this.previousSessionDeviceId,
195-
event_id: ++lastEventId,
196-
session_id: previousSessionId,
197-
time: lastEventTime + 1,
198-
user_id: this.previousSessionUserId,
199-
});
196+
promises.push(
197+
this.track(DEFAULT_SESSION_END_EVENT, undefined, {
198+
device_id: this.previousSessionDeviceId,
199+
event_id: ++lastEventId,
200+
session_id: previousSessionId,
201+
time: lastEventTime + 1,
202+
user_id: this.previousSessionUserId,
203+
}).promise,
204+
);
200205
}
201-
202206
this.config.lastEventTime = this.config.sessionId;
203-
this.track(DEFAULT_SESSION_START_EVENT, undefined, {
204-
event_id: ++lastEventId,
205-
session_id: this.config.sessionId,
206-
time: this.config.lastEventTime,
207-
});
207+
}
208+
209+
// Fire web attribution event when enable webAttribution tracking
210+
// 1. has new campaign (call setSessionId from init function)
211+
// 2. or shouldTrackNewCampaign (call setSessionId from async process(event) when there has new campaign and resetSessionOnNewCampaign = true )
212+
const isCampaignEventTracked = this.trackCampaignEventIfNeeded(++lastEventId, promises);
213+
214+
if (isSessionTrackingEnabled(this.config.defaultTracking)) {
215+
promises.push(
216+
this.track(DEFAULT_SESSION_START_EVENT, undefined, {
217+
event_id: isCampaignEventTracked ? ++lastEventId : lastEventId,
218+
session_id: this.config.sessionId,
219+
time: this.config.lastEventTime,
220+
}).promise,
221+
);
208222
}
209223

210224
this.previousSessionDeviceId = this.config.deviceId;
211225
this.previousSessionUserId = this.config.userId;
226+
return returnWrapper(Promise.all(promises));
212227
}
213228

214229
extendSession() {
@@ -260,17 +275,40 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
260275
return super.revenue(revenue, eventOptions);
261276
}
262277

278+
private trackCampaignEventIfNeeded(lastEventId?: number, promises?: Promise<Result>[]) {
279+
if (!this.webAttribution || !this.webAttribution.shouldTrackNewCampaign) {
280+
return false;
281+
}
282+
const campaignEvent = this.webAttribution.generateCampaignEvent(lastEventId);
283+
if (promises) {
284+
promises.push(this.track(campaignEvent).promise);
285+
} else {
286+
this.track(campaignEvent);
287+
}
288+
this.config.loggerProvider.log('Tracking attribution.');
289+
return true;
290+
}
291+
263292
async process(event: Event) {
264293
const currentTime = Date.now();
265294
const isEventInNewSession = isNewSession(this.config.sessionTimeout, this.config.lastEventTime);
295+
const shouldSetSessionIdOnNewCampaign =
296+
this.webAttribution && this.webAttribution.shouldSetSessionIdOnNewCampaign();
266297

267298
if (
268299
event.event_type !== DEFAULT_SESSION_START_EVENT &&
269300
event.event_type !== DEFAULT_SESSION_END_EVENT &&
270-
(!event.session_id || event.session_id === this.getSessionId()) &&
271-
isEventInNewSession
301+
(!event.session_id || event.session_id === this.getSessionId())
272302
) {
273-
this.setSessionId(currentTime);
303+
if (isEventInNewSession || shouldSetSessionIdOnNewCampaign) {
304+
this.setSessionId(currentTime);
305+
if (shouldSetSessionIdOnNewCampaign) {
306+
this.config.loggerProvider.log('Created a new session for new campaign.');
307+
}
308+
} else if (!isEventInNewSession) {
309+
// web attribution should be track during the middle of the session if there has any new campaign
310+
this.trackCampaignEventIfNeeded();
311+
}
274312
}
275313

276314
return super.process(event);

0 commit comments

Comments
 (0)