Skip to content

Commit 804b67c

Browse files
rudder-devbox[bot]rudder-devboxSKannaniOS
authored
feat(@rudderstack/analytics-js-integrations): add amplitude v1/v2 sdk selection (#3042)
* feat(@rudderstack/analytics-js-integrations): add amplitude v1/v2 sdk selection * fix(@rudderstack/analytics-js-integrations): align amplitude config key with integrations-config SDK-4939 * refactor(@rudderstack/analytics-js-integrations): rename sdkVersion to apiVersion in amplitude browser integration * test(@rudderstack/analytics-js-integrations): add missing v2 attribution disabled test case for amplitude * fix(@rudderstack/analytics-js-integrations): align amplitude sdk version config key and values * fix(@rudderstack/analytics-js-integrations): rename apiVersion to sdkVersion in amplitude integration * test(@rudderstack/analytics-js-integrations): add missing amplitude sdk version selection test cases * fix(@rudderstack/analytics-js-integrations): correct amplitude v2 sdk config * fix(@rudderstack/analytics-js-integrations): align amplitude v2 review feedback * refactor(@rudderstack/analytics-js-integrations): split amplitude loader into verbatim v1/v2 snippets * chore(@rudderstack/analytics-js-integrations): clean amplitude constants * chore: address PR review comments --------- Co-authored-by: tech-infra-rudderstack <tech-infra+github-signing@rudderstack.com> Co-authored-by: Satheesh Kannan <satheesh@rudderstack.com>
1 parent a09c36e commit 804b67c

6 files changed

Lines changed: 341 additions & 11 deletions

File tree

packages/analytics-js-integrations/__tests__/integrations/Amplitude/browser.test.js

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
import { errorMock } from '../../../__mocks__/logger';
44
import Amplitude from '../../../src/integrations/Amplitude/browser';
55

6+
const AMPLITUDE_V1_SDK_URL = 'https://cdn.amplitude.com/libs/analytics-browser-1.9.1-min.js.gz';
7+
const AMPLITUDE_V2_SDK_URL = 'https://cdn.amplitude.com/libs/analytics-browser-2.32.0-min.js.gz';
8+
const AMPLITUDE_V1_SDK_INTEGRITY =
9+
'sha384-TPZhteUkZj8CAyBx+GZZytBdkuKnhKsSKcCoVCq0QSteWf/Kw5Kb9oVFUROLE1l3';
10+
const AMPLITUDE_V2_SDK_INTEGRITY =
11+
'sha384-hZ3s3uB8PfU4QYbgXXtU9kEy7lt8i7kRKIWzbVjnS//GQSyv42iEVDEniwLASsSh';
12+
613
const destinationConfig = {
714
apiKey: 'AMPLITUDE_API_KEY',
815
proxyServerUrl: 'https://some.proxyserverurl.com',
@@ -72,6 +79,9 @@ describe('Amplitude', () => {
7279
describe('init', () => {
7380
beforeEach(() => {
7481
window.amplitude = undefined;
82+
document
83+
.querySelectorAll(`script[src="${AMPLITUDE_V1_SDK_URL}"], script[src="${AMPLITUDE_V2_SDK_URL}"]`)
84+
.forEach(element => element.remove());
7585
});
7686

7787
it('should initialize the destination SDK', () => {
@@ -217,6 +227,127 @@ describe('Amplitude', () => {
217227

218228
expect(window.amplitude._q[0].args[2].serverZone).toBe('EU');
219229
});
230+
231+
it('should load v1 sdk by default when sdk version is not configured', () => {
232+
const amplitude = new Amplitude(destinationConfig, analyticsInstance, destinationInfo);
233+
amplitude.init();
234+
235+
expect(document.querySelector(`script[src="${AMPLITUDE_V1_SDK_URL}"]`)).toBeTruthy();
236+
});
237+
238+
it('should load v1 sdk when sdkVersion is explicitly set to 1', () => {
239+
const amplitude = new Amplitude(
240+
{ ...destinationConfig, sdkVersion: 1 },
241+
analyticsInstance,
242+
destinationInfo,
243+
);
244+
amplitude.init();
245+
246+
expect(document.querySelector(`script[src="${AMPLITUDE_V1_SDK_URL}"]`)).toBeTruthy();
247+
// v1 init passes an explicit null userId
248+
expect(window.amplitude._q[0].name).toBe('init');
249+
expect(window.amplitude._q[0].args[1]).toBeNull();
250+
});
251+
252+
it('should load v1 sdk when sdkVersion is an invalid value', () => {
253+
const amplitude = new Amplitude(
254+
{ ...destinationConfig, sdkVersion: 'foo' },
255+
analyticsInstance,
256+
destinationInfo,
257+
);
258+
amplitude.init();
259+
260+
expect(document.querySelector(`script[src="${AMPLITUDE_V1_SDK_URL}"]`)).toBeTruthy();
261+
});
262+
263+
it('should load v2 sdk and enable attribution autocapture when attribution is not disabled', () => {
264+
const amplitude = new Amplitude(
265+
{
266+
...destinationConfig,
267+
sdkVersion: 2,
268+
attribution: false,
269+
},
270+
analyticsInstance,
271+
destinationInfo,
272+
);
273+
amplitude.init();
274+
275+
expect(document.querySelector(`script[src="${AMPLITUDE_V2_SDK_URL}"]`)).toBeTruthy();
276+
// v2 init passes an undefined userId (not null)
277+
expect(window.amplitude._q[0].name).toBe('init');
278+
expect(window.amplitude._q[0].args[1]).toBeUndefined();
279+
expect(window.amplitude._q[0].args[2]).toMatchObject({
280+
autocapture: {
281+
attribution: true,
282+
pageViews: false,
283+
sessions: false,
284+
formInteractions: false,
285+
fileDownloads: false,
286+
elementInteractions: false,
287+
frustrationInteractions: false,
288+
networkTracking: false,
289+
webVitals: false,
290+
pageUrlEnrichment: false,
291+
},
292+
serverUrl: 'https://some.proxyserverurl.com',
293+
});
294+
expect(window.amplitude._q[0].args[2].attribution).toBeUndefined();
295+
});
296+
297+
it('should load v2 sdk and disable attribution autocapture when attribution is disabled', () => {
298+
const amplitude = new Amplitude(
299+
{
300+
...destinationConfig,
301+
sdkVersion: 2,
302+
attribution: true,
303+
},
304+
analyticsInstance,
305+
destinationInfo,
306+
);
307+
amplitude.init();
308+
309+
expect(document.querySelector(`script[src="${AMPLITUDE_V2_SDK_URL}"]`)).toBeTruthy();
310+
expect(window.amplitude._q[0].args[2]).toMatchObject({
311+
autocapture: {
312+
attribution: false,
313+
pageViews: false,
314+
sessions: false,
315+
formInteractions: false,
316+
fileDownloads: false,
317+
elementInteractions: false,
318+
frustrationInteractions: false,
319+
networkTracking: false,
320+
webVitals: false,
321+
pageUrlEnrichment: false,
322+
},
323+
serverUrl: 'https://some.proxyserverurl.com',
324+
});
325+
expect(window.amplitude._q[0].args[2].attribution).toBeUndefined();
326+
});
327+
328+
it('should set the correct SRI integrity on the injected v1 script', () => {
329+
const amplitude = new Amplitude(
330+
{ ...destinationConfig, sdkVersion: 1 },
331+
analyticsInstance,
332+
destinationInfo,
333+
);
334+
amplitude.init();
335+
336+
const script = document.querySelector(`script[src="${AMPLITUDE_V1_SDK_URL}"]`);
337+
expect(script.integrity).toBe(AMPLITUDE_V1_SDK_INTEGRITY);
338+
});
339+
340+
it('should set the correct SRI integrity on the injected v2 script', () => {
341+
const amplitude = new Amplitude(
342+
{ ...destinationConfig, sdkVersion: 2 },
343+
analyticsInstance,
344+
destinationInfo,
345+
);
346+
amplitude.init();
347+
348+
const script = document.querySelector(`script[src="${AMPLITUDE_V2_SDK_URL}"]`);
349+
expect(script.integrity).toBe(AMPLITUDE_V2_SDK_INTEGRITY);
350+
});
220351
});
221352

222353
describe('identify', () => {

packages/analytics-js-integrations/__tests__/integrations/Amplitude/util.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
getTraitsToIncrement,
55
getDestinationOptions,
66
formatUrl,
7+
getAmplitudeSdkVersion,
78
} from '../../../src/integrations/Amplitude/utils';
89

910
describe('getTraitsToSetOnce', () => {
@@ -183,3 +184,29 @@ describe('formatUrl', () => {
183184
expect(formatUrl(url)).toBe('https://example.com');
184185
});
185186
});
187+
188+
describe('getAmplitudeSdkVersion', () => {
189+
it('should fallback to v1 when sdkVersion is absent', () => {
190+
expect(getAmplitudeSdkVersion({})).toBe(1);
191+
});
192+
193+
it('should resolve v2 for sdkVersion as 2', () => {
194+
expect(getAmplitudeSdkVersion({ sdkVersion: 2 })).toBe(2);
195+
});
196+
197+
it('should resolve v1 for sdkVersion as 1', () => {
198+
expect(getAmplitudeSdkVersion({ sdkVersion: 1 })).toBe(1);
199+
});
200+
201+
it('should fallback to v1 for an invalid sdkVersion value', () => {
202+
expect(getAmplitudeSdkVersion({ sdkVersion: 'foo' })).toBe(1);
203+
});
204+
205+
it('should fallback to v1 when sdkVersion is null', () => {
206+
expect(getAmplitudeSdkVersion({ sdkVersion: null })).toBe(1);
207+
});
208+
209+
it('should parse numeric strings in sdkVersion', () => {
210+
expect(getAmplitudeSdkVersion({ sdkVersion: '2' })).toBe(2);
211+
});
212+
});

packages/analytics-js-integrations/src/integrations/Amplitude/browser.js

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
/* eslint-disable no-underscore-dangle */
22
/* eslint-disable class-methods-use-this */
3-
import { NAME, DISPLAY_NAME } from './constants';
3+
import { NAME, DISPLAY_NAME, AMPLITUDE_SDK_V2 } from './constants';
44
import { isDefinedAndNotNullAndNotEmpty } from '../../utils/commonUtils';
55
import Logger from '../../utils/logger';
6-
import { loadNativeSdk } from './nativeSdkLoader';
6+
import { loadNativeSdkV1, loadNativeSdkV2 } from './nativeSdkLoader';
77
import {
88
getTraitsToSetOnce,
99
getTraitsToIncrement,
1010
getDestinationOptions,
1111
getFieldsToUnset,
1212
formatUrl,
13+
getAmplitudeSdkVersion,
1314
} from './utils';
1415
import { getValueOrDefault } from '../../utils/utils';
1516

@@ -42,6 +43,7 @@ class Amplitude {
4243
this.versionName = config.versionName;
4344
this.groupTypeTrait = config.groupTypeTrait;
4445
this.groupValueTrait = config.groupValueTrait;
46+
this.sdkVersion = getAmplitudeSdkVersion(config);
4547
({
4648
shouldApplyDeviceModeTransformation: this.shouldApplyDeviceModeTransformation,
4749
propagateEventsUntransformedOnError: this.propagateEventsUntransformedOnError,
@@ -51,15 +53,40 @@ class Amplitude {
5153

5254
init() {
5355
if (this.analytics.loadIntegration) {
54-
loadNativeSdk(window, document);
56+
if (this.sdkVersion === AMPLITUDE_SDK_V2) {
57+
loadNativeSdkV2(window, document);
58+
} else {
59+
loadNativeSdkV1(window, document);
60+
}
5561
}
5662

57-
const initOptions = {
58-
attribution: { disabled: this.attribution, trackNewCampaigns: !this.trackNewCampaigns },
63+
const commonInitOptions = {
5964
flushQueueSize: this.flushQueueSize,
6065
flushIntervalMillis: this.flushIntervalMillis,
6166
appVersion: this.versionName,
6267
};
68+
const initOptions =
69+
this.sdkVersion === AMPLITUDE_SDK_V2
70+
? {
71+
...commonInitOptions,
72+
// Enable only attribution in v2; all other autocapture toggles stay off.
73+
autocapture: {
74+
attribution: !this.attribution,
75+
pageViews: false,
76+
sessions: false,
77+
formInteractions: false,
78+
fileDownloads: false,
79+
elementInteractions: false,
80+
frustrationInteractions: false,
81+
networkTracking: false,
82+
webVitals: false,
83+
pageUrlEnrichment: false,
84+
},
85+
}
86+
: {
87+
...commonInitOptions,
88+
attribution: { disabled: this.attribution, trackNewCampaigns: !this.trackNewCampaigns },
89+
};
6390

6491
if (isDefinedAndNotNullAndNotEmpty(this.proxyServerUrl)) {
6592
if (this.proxyServerUrl.startsWith('http://')) {
@@ -83,7 +110,9 @@ class Amplitude {
83110
}
84111
if (this.preferAnonymousIdForDeviceId && this.analytics)
85112
initOptions.deviceId = this.analytics.getAnonymousId();
86-
window.amplitude.init(this.apiKey, null, initOptions);
113+
// v2's init expects an absent (undefined) userId; v1 keeps the existing null.
114+
const userId = this.sdkVersion === AMPLITUDE_SDK_V2 ? undefined : null;
115+
window.amplitude.init(this.apiKey, userId, initOptions);
87116
}
88117

89118
isLoaded() {
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { AM_NAME as NAME, AM_DISPLAY_NAME as DISPLAY_NAME } from '../../constants/Destinations';
22

33
const DIR_NAME = 'Amplitude';
4+
const AMPLITUDE_SDK_VERSION_CONFIG_KEY = 'sdkVersion';
5+
const AMPLITUDE_SDK_V1 = 1;
6+
const AMPLITUDE_SDK_V2 = 2;
47

58
const CNameMapping = {
69
[NAME]: NAME,
@@ -9,4 +12,12 @@ const CNameMapping = {
912
am: NAME,
1013
};
1114

12-
export { NAME, CNameMapping, DISPLAY_NAME, DIR_NAME };
15+
export {
16+
NAME,
17+
CNameMapping,
18+
DISPLAY_NAME,
19+
DIR_NAME,
20+
AMPLITUDE_SDK_VERSION_CONFIG_KEY,
21+
AMPLITUDE_SDK_V1,
22+
AMPLITUDE_SDK_V2,
23+
};

0 commit comments

Comments
 (0)