Skip to content

Commit 91ae64c

Browse files
committed
Add feature to deferInitialization
This is especially useful when you don't want to track any analytics or drop any analytic cookies prior to asking the users to opt in to analytics. I've added a configuration (deferInitialization) and an API (enableTracking) to manage this. If deferInitialization is set to true AND a customer has yet to opt in, we will locally store their pending logs until they either navigate away or opt in.
1 parent 9dcf242 commit 91ae64c

File tree

2 files changed

+152
-12
lines changed

2 files changed

+152
-12
lines changed

src/amplitude-client.js

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ AmplitudeClient.prototype.init = function init(apiKey, opt_userId, opt_config, o
7575
return;
7676
}
7777

78+
var hasExistingCookie = !!this.cookieStorage.get(this.options.cookieName + this._storageSuffix);
79+
if (opt_config && opt_config.deferInitialization && !hasExistingCookie) {
80+
this._deferInitialization(apiKey, opt_userId, opt_config, opt_callback);
81+
return;
82+
}
83+
7884
try {
7985
this.options.apiKey = apiKey;
8086
this._storageSuffix = '_' + apiKey + this._legacyStorageSuffix;
@@ -766,6 +772,10 @@ AmplitudeClient.prototype.saveEvents = function saveEvents() {
766772
* @example amplitudeClient.setDomain('.amplitude.com');
767773
*/
768774
AmplitudeClient.prototype.setDomain = function setDomain(domain) {
775+
if (this._shouldDeferCall()) {
776+
return this._q.push(['setDomain', domain]);
777+
}
778+
769779
if (!utils.validateInput(domain, 'domain', 'string')) {
770780
return;
771781
}
@@ -791,6 +801,10 @@ AmplitudeClient.prototype.setDomain = function setDomain(domain) {
791801
* @example amplitudeClient.setUserId('joe@gmail.com');
792802
*/
793803
AmplitudeClient.prototype.setUserId = function setUserId(userId) {
804+
if (this._shouldDeferCall()) {
805+
return this._q.push(['setUserId', userId]);
806+
}
807+
794808
try {
795809
this.options.userId = (userId !== undefined && userId !== null && ('' + userId)) || null;
796810
_saveCookieData(this);
@@ -813,6 +827,10 @@ AmplitudeClient.prototype.setUserId = function setUserId(userId) {
813827
* @example amplitudeClient.setGroup('orgId', 15); // this adds the current user to orgId 15.
814828
*/
815829
AmplitudeClient.prototype.setGroup = function(groupType, groupName) {
830+
if (this._shouldDeferCall()) {
831+
return this._q.push(['setGroup', groupType, groupName]);
832+
}
833+
816834
if (!this._apiKeySet('setGroup()') || !utils.validateInput(groupType, 'groupType', 'string') ||
817835
utils.isEmptyString(groupType)) {
818836
return;
@@ -831,6 +849,10 @@ AmplitudeClient.prototype.setGroup = function(groupType, groupName) {
831849
* @example: amplitude.setOptOut(true);
832850
*/
833851
AmplitudeClient.prototype.setOptOut = function setOptOut(enable) {
852+
if (this._shouldDeferCall()) {
853+
return this._q.push(['setOptOut', enable]);
854+
}
855+
834856
if (!utils.validateInput(enable, 'enable', 'boolean')) {
835857
return;
836858
}
@@ -868,6 +890,10 @@ AmplitudeClient.prototype.resetSessionId = function resetSessionId() {
868890
* @public
869891
*/
870892
AmplitudeClient.prototype.regenerateDeviceId = function regenerateDeviceId() {
893+
if (this._shouldDeferCall()) {
894+
return this._q.push(['regenerateDeviceId']);
895+
}
896+
871897
this.setDeviceId(UUID() + 'R');
872898
};
873899

@@ -880,6 +906,10 @@ AmplitudeClient.prototype.regenerateDeviceId = function regenerateDeviceId() {
880906
* @example amplitudeClient.setDeviceId('45f0954f-eb79-4463-ac8a-233a6f45a8f0');
881907
*/
882908
AmplitudeClient.prototype.setDeviceId = function setDeviceId(deviceId) {
909+
if (this._shouldDeferCall()) {
910+
return this._q.push(['setDeviceId', deviceId]);
911+
}
912+
883913
if (!utils.validateInput(deviceId, 'deviceId', 'string')) {
884914
return;
885915
}
@@ -903,8 +933,8 @@ AmplitudeClient.prototype.setDeviceId = function setDeviceId(deviceId) {
903933
* @example amplitudeClient.setUserProperties({'gender': 'female', 'sign_up_complete': true})
904934
*/
905935
AmplitudeClient.prototype.setUserProperties = function setUserProperties(userProperties) {
906-
if (this._pendingReadStorage) {
907-
return this._q.push(['identify', userProperties]);
936+
if (this._shouldDeferCall()) {
937+
return this._q.push(['setUserProperties', userProperties]);
908938
}
909939
if (!this._apiKeySet('setUserProperties()') || !utils.validateInput(userProperties, 'userProperties', 'object')) {
910940
return;
@@ -931,6 +961,10 @@ AmplitudeClient.prototype.setUserProperties = function setUserProperties(userPro
931961
* @example amplitudeClient.clearUserProperties();
932962
*/
933963
AmplitudeClient.prototype.clearUserProperties = function clearUserProperties(){
964+
if (this._shouldDeferCall()) {
965+
return this._q.push(['clearUserProperties']);
966+
}
967+
934968
if (!this._apiKeySet('clearUserProperties()')) {
935969
return;
936970
}
@@ -967,8 +1001,8 @@ var _convertProxyObjectToRealObject = function _convertProxyObjectToRealObject(i
9671001
* amplitude.identify(identify);
9681002
*/
9691003
AmplitudeClient.prototype.identify = function(identify_obj, opt_callback) {
970-
if (this._pendingReadStorage) {
971-
return this._q.push(['identify', identify_obj, opt_callback]);
1004+
if (this._shouldDeferCall()) {
1005+
return this._q.push(['identify', identify_obj, opt_callback]);
9721006
}
9731007
if (!this._apiKeySet('identify()')) {
9741008
if (type(opt_callback) === 'function') {
@@ -1002,8 +1036,8 @@ AmplitudeClient.prototype.identify = function(identify_obj, opt_callback) {
10021036
};
10031037

10041038
AmplitudeClient.prototype.groupIdentify = function(group_type, group_name, identify_obj, opt_callback) {
1005-
if (this._pendingReadStorage) {
1006-
return this._q.push(['groupIdentify', group_type, group_name, identify_obj, opt_callback]);
1039+
if (this._shouldDeferCall()) {
1040+
return this._q.push(['groupIdentify', group_type, group_name, identify_obj, opt_callback]);
10071041
}
10081042
if (!this._apiKeySet('groupIdentify()')) {
10091043
if (type(opt_callback) === 'function') {
@@ -1058,6 +1092,10 @@ AmplitudeClient.prototype.groupIdentify = function(group_type, group_name, ident
10581092
* @example amplitudeClient.setVersionName('1.12.3');
10591093
*/
10601094
AmplitudeClient.prototype.setVersionName = function setVersionName(versionName) {
1095+
if (this._shouldDeferCall()) {
1096+
return this._q.push(['setVersionName', versionName]);
1097+
}
1098+
10611099
if (!utils.validateInput(versionName, 'versionName', 'string')) {
10621100
return;
10631101
}
@@ -1217,8 +1255,8 @@ AmplitudeClient.prototype._limitEventsQueued = function _limitEventsQueued(queue
12171255
* @example amplitudeClient.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15});
12181256
*/
12191257
AmplitudeClient.prototype.logEvent = function logEvent(eventType, eventProperties, opt_callback) {
1220-
if (this._pendingReadStorage) {
1221-
return this._q.push(['logEvent', eventType, eventProperties, opt_callback]);
1258+
if (this._shouldDeferCall()) {
1259+
return this._q.push(['logEvent', eventType, eventProperties, opt_callback]);
12221260
}
12231261
return this.logEventWithTimestamp(eventType, eventProperties, null, opt_callback);
12241262
};
@@ -1234,8 +1272,8 @@ AmplitudeClient.prototype.logEvent = function logEvent(eventType, eventPropertie
12341272
* @example amplitudeClient.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15});
12351273
*/
12361274
AmplitudeClient.prototype.logEventWithTimestamp = function logEvent(eventType, eventProperties, timestamp, opt_callback) {
1237-
if (this._pendingReadStorage) {
1238-
return this._q.push(['logEventWithTimestamp', eventType, eventProperties, timestamp, opt_callback]);
1275+
if (this._shouldDeferCall()) {
1276+
return this._q.push(['logEventWithTimestamp', eventType, eventProperties, timestamp, opt_callback]);
12391277
}
12401278
if (!this._apiKeySet('logEvent()')) {
12411279
if (type(opt_callback) === 'function') {
@@ -1274,8 +1312,8 @@ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent(eventType, e
12741312
* @example amplitudeClient.logEventWithGroups('Clicked Button', null, {'orgId': 24});
12751313
*/
12761314
AmplitudeClient.prototype.logEventWithGroups = function(eventType, eventProperties, groups, opt_callback) {
1277-
if (this._pendingReadStorage) {
1278-
return this._q.push(['logEventWithGroups', eventType, eventProperties, groups, opt_callback]);
1315+
if (this._shouldDeferCall()) {
1316+
return this._q.push(['logEventWithGroups', eventType, eventProperties, groups, opt_callback]);
12791317
}
12801318
if (!this._apiKeySet('logEventWithGroups()')) {
12811319
if (type(opt_callback) === 'function') {
@@ -1311,6 +1349,10 @@ var _isNumber = function _isNumber(n) {
13111349
* amplitude.logRevenueV2(revenue);
13121350
*/
13131351
AmplitudeClient.prototype.logRevenueV2 = function logRevenueV2(revenue_obj) {
1352+
if (this._shouldDeferCall()) {
1353+
return this._q.push(['logRevenueV2', revenue_obj]);
1354+
}
1355+
13141356
if (!this._apiKeySet('logRevenueV2()')) {
13151357
return;
13161358
}
@@ -1341,6 +1383,10 @@ if (BUILD_COMPAT_2_0) {
13411383
* @example amplitudeClient.logRevenue(3.99, 1, 'product_1234');
13421384
*/
13431385
AmplitudeClient.prototype.logRevenue = function logRevenue(price, quantity, product) {
1386+
if (this._shouldDeferCall()) {
1387+
return this._q.push(['logRevenue', price, quantity, product]);
1388+
}
1389+
13441390
// Test that the parameters are of the right type.
13451391
if (!this._apiKeySet('logRevenue()') || !_isNumber(price) || (quantity !== undefined && !_isNumber(quantity))) {
13461392
// utils.log('Price and quantity arguments to logRevenue must be numbers');
@@ -1552,4 +1598,33 @@ if (BUILD_COMPAT_2_0) {
15521598
*/
15531599
AmplitudeClient.prototype.__VERSION__ = version;
15541600

1601+
/**
1602+
* Determines whether or not to push call to this._q or invoke it
1603+
* @private
1604+
*/
1605+
AmplitudeClient.prototype._shouldDeferCall = function _shouldDeferCall() {
1606+
return this._pendingReadStorage || this._initializationDeferred;
1607+
};
1608+
1609+
/**
1610+
* Defers Initialization by putting all functions into storage until users
1611+
* have accepted terms for tracking
1612+
* @private
1613+
*/
1614+
AmplitudeClient.prototype._deferInitialization = function _deferInitialization(apiKey, opt_userId, opt_config, opt_callback) {
1615+
this._initializationDeferred = true;
1616+
this._q.push(['init', apiKey, opt_userId, opt_config, opt_callback]);
1617+
};
1618+
1619+
/**
1620+
* Enable tracking via logging events and dropping a cookie
1621+
* Intended to be used with the deferInitialization configuration flag
1622+
* @public
1623+
*/
1624+
AmplitudeClient.prototype.enableTracking = function enableTracking() {
1625+
// This will call init (which drops the cookie) and will run any pending tasks
1626+
_saveCookieData(this);
1627+
this.runQueuedFunctions();
1628+
};
1629+
15551630
export default AmplitudeClient;

test/amplitude-client.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3331,4 +3331,69 @@ describe('setVersionName', function() {
33313331
assert.equal(cookieStorage.get(amplitude2.options.cookieName + '_' + apiKey).sessionId, newSessionId);
33323332
});
33333333
});
3334+
3335+
describe('deferInitialization config', function () {
3336+
beforeEach(function () {
3337+
reset();
3338+
amplitude.init(apiKey, null, { cookieExpiration: 365, deferInitialization: true });
3339+
});
3340+
3341+
describe('prior to opting into analytics', function () {
3342+
it('should not initially drop a cookie if deferInitialization is set to true', function () {
3343+
var cookieData = cookie.get(amplitude.options.cookieName + '_' + apiKey);
3344+
assert.isNull(cookieData);
3345+
});
3346+
it('should not send anything to amplitude', function () {
3347+
amplitude.identify(new Identify().set('prop1', 'value1'));
3348+
amplitude.logEvent('Event Type 1');
3349+
amplitude.setDomain('.foobar.com');
3350+
amplitude.setUserId(123456);
3351+
amplitude.setGroup('orgId', 15);
3352+
amplitude.setOptOut(true);
3353+
amplitude.regenerateDeviceId();
3354+
amplitude.setDeviceId('deviceId');
3355+
amplitude.setUserProperties({'prop': true, 'key': 'value'});
3356+
amplitude.clearUserProperties();
3357+
amplitude.groupIdentify(null, null, new amplitude.Identify().set('key', 'value'));
3358+
amplitude.setVersionName('testVersionName1');
3359+
amplitude.logEventWithTimestamp('test', null, 2000, null);
3360+
amplitude.logEventWithGroups('Test', {'key': 'value' }, {group: 'abc'});
3361+
amplitude.logRevenue(10.10);
3362+
3363+
var revenue = new amplitude.Revenue().setProductId('testProductId').setQuantity(15).setPrice(10.99);
3364+
revenue.setRevenueType('testRevenueType').setEventProperties({'city': 'San Francisco'});
3365+
amplitude.logRevenueV2(revenue);
3366+
3367+
assert.lengthOf(server.requests, 0, 'should not send any requests to amplitude');
3368+
assert.lengthOf(amplitude._unsentEvents, 0, 'should not queue events to be sent')
3369+
});
3370+
});
3371+
3372+
describe('upon to opting into analytics', function () {
3373+
it('should drop a cookie', function () {
3374+
amplitude.enableTracking();
3375+
var cookieData = cookie.get(amplitude.options.cookieName + '_' + apiKey);
3376+
assert.isNotNull(cookieData);
3377+
});
3378+
it('should send pending calls and events', function () {
3379+
amplitude.identify(new Identify().set('prop1', 'value1'));
3380+
amplitude.logEvent('Event Type 1');
3381+
amplitude.logEvent('Event Type 2');
3382+
amplitude.logEventWithTimestamp('test', null, 2000, null);
3383+
amplitude.enableTracking();
3384+
3385+
var events = JSON.parse(queryString.parse(server.requests[0].requestBody).e);
3386+
assert.lengthOf(events, 1, 'should have sent a request to Amplitude');
3387+
assert.lengthOf(amplitude._unsentEvents, 3, 'should have saved the remaining events')
3388+
});
3389+
it('should not continue to deferInitialization if an amplitude cookie exists', function () {
3390+
amplitude.enableTracking();
3391+
amplitude.init(apiKey, null, { cookieExpiration: 365, deferInitialization: true });
3392+
amplitude.logEvent('Event Type 1');
3393+
3394+
var events = JSON.parse(queryString.parse(server.requests[0].requestBody).e);
3395+
assert.lengthOf(events, 1, 'should have sent a request to Amplitude');
3396+
});
3397+
});
3398+
})
33343399
});

0 commit comments

Comments
 (0)