Skip to content

Commit 6bc9065

Browse files
authored
Merge pull request #214 from sputh/feature/defer-init
Add feature to deferInitialization
2 parents 9dcf242 + 44207f3 commit 6bc9065

File tree

6 files changed

+200
-17
lines changed

6 files changed

+200
-17
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
### 5.8.0 (December 6, 2019)
2+
* Add support to defer saving an amplitude cookie and logging events until a user has opted in
3+
14
### 5.7.1 (December 2, 2019)
25
* Fix issue where null unsentKey and unsentIdentifyKeys were causing log crashes
36

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Please see our [installation guide](https://amplitude.zendesk.com/hc/en-us/artic
1111
[![npm version](https://badge.fury.io/js/amplitude-js.svg)](https://badge.fury.io/js/amplitude-js)
1212
[![Bower version](https://badge.fury.io/bo/amplitude-js.svg)](https://badge.fury.io/bo/amplitude-js)
1313

14-
[5.7.1 - Released on December 3, 2019](https://github.com/amplitude/Amplitude-JavaScript/releases/latest)
14+
[5.8.0 - Released on December 6, 2019](https://github.com/amplitude/Amplitude-JavaScript/releases/latest)
1515

1616

1717
# JavaScript SDK Reference #

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "amplitude-js",
33
"author": "Amplitude <[email protected]>",
4-
"version": "5.7.1",
4+
"version": "5.8.0",
55
"license": "MIT",
66
"description": "Javascript library for Amplitude Analytics",
77
"keywords": [

src/amplitude-client.js

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ AmplitudeClient.prototype.init = function init(apiKey, opt_userId, opt_config, o
7979
this.options.apiKey = apiKey;
8080
this._storageSuffix = '_' + apiKey + this._legacyStorageSuffix;
8181

82+
var hasExistingCookie = !!this.cookieStorage.get(this.options.cookieName + this._storageSuffix);
83+
if (opt_config && opt_config.deferInitialization && !hasExistingCookie) {
84+
this._deferInitialization(apiKey, opt_userId, opt_config, opt_callback);
85+
return;
86+
}
87+
8288
_parseConfig(this.options, opt_config);
8389

8490
if (type(this.options.logLevel) === 'string') {
@@ -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'].concat(Array.prototype.slice.call(arguments, 0)));
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('[email protected]');
792802
*/
793803
AmplitudeClient.prototype.setUserId = function setUserId(userId) {
804+
if (this._shouldDeferCall()) {
805+
return this._q.push(['setUserId'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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'].concat(Array.prototype.slice.call(arguments, 0)));
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,35 @@ 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() {
1615+
this._initializationDeferred = true;
1616+
this._q.push(['init'].concat(Array.prototype.slice.call(arguments, 0)));
1617+
};
1618+
1619+
/**
1620+
* Enable tracking via logging events and dropping a cookie
1621+
* Intended to be used with the deferInitialization configuration flag
1622+
* This will drop a cookie and reset initialization deferred
1623+
* @public
1624+
*/
1625+
AmplitudeClient.prototype.enableTracking = function enableTracking() {
1626+
// This will call init (which drops the cookie) and will run any pending tasks
1627+
this._initializationDeferred = false;
1628+
_saveCookieData(this);
1629+
this.runQueuedFunctions();
1630+
};
1631+
15551632
export default AmplitudeClient;

src/amplitude-snippet.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
var amplitude = window.amplitude || {'_q':[],'_iq':{}};
33
var as = document.createElement('script');
44
as.type = 'text/javascript';
5-
as.integrity = 'sha384-Ik1BT1T0ZKcBQi93L3Lh8pYLQvUANkj37BjU140rtlIwQSj9ePR4dOoqfWj9u5qU';
5+
as.integrity = 'sha384-vYYnQ3LPdp/RkQjoKBTGSq0X5F73gXU3G2QopHaIfna0Ct1JRWzwrmEz115NzOta';
66
as.crossOrigin = 'anonymous';
77
as.async = true;
8-
as.src = 'https://cdn.amplitude.com/libs/amplitude-5.7.1-min.gz.js';
8+
as.src = 'https://cdn.amplitude.com/libs/amplitude-5.8.0-min.gz.js';
99
as.onload = function() {if(!window.amplitude.runQueuedFunctions) {console.log('[Amplitude] Error: could not load SDK');}};
1010
var s = document.getElementsByTagName('script')[0];
1111
s.parentNode.insertBefore(as, s);
@@ -23,7 +23,7 @@
2323
for (var j = 0; j < revenueFuncs.length; j++) {proxy(Revenue, revenueFuncs[j]);}
2424
amplitude.Revenue = Revenue;
2525
var funcs = ['init', 'logEvent', 'logRevenue', 'setUserId', 'setUserProperties',
26-
'setOptOut', 'setVersionName', 'setDomain', 'setDeviceId',
26+
'setOptOut', 'setVersionName', 'setDomain', 'setDeviceId', 'enableTracking',
2727
'setGlobalUserProperties', 'identify', 'clearUserProperties',
2828
'setGroup', 'logRevenueV2', 'regenerateDeviceId', 'groupIdentify', 'onInit',
2929
'logEventWithTimestamp', 'logEventWithGroups', 'setSessionId', 'resetSessionId'];

test/amplitude-client.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3331,4 +3331,107 @@ describe('setVersionName', function() {
33313331
assert.equal(cookieStorage.get(amplitude2.options.cookieName + '_' + apiKey).sessionId, newSessionId);
33323332
});
33333333
});
3334+
3335+
describe('deferInitialization config', function () {
3336+
it('should keep tracking users who already have an amplitude cookie', function () {
3337+
var now = new Date().getTime();
3338+
var cookieData = {
3339+
userId: 'test_user_id',
3340+
optOut: false,
3341+
sessionId: now,
3342+
lastEventTime: now,
3343+
eventId: 50,
3344+
identifyId: 60
3345+
}
3346+
3347+
cookie.set(amplitude.options.cookieName + keySuffix, cookieData);
3348+
amplitude.init(apiKey, null, { cookieExpiration: 365, deferInitialization: true });
3349+
amplitude.identify(new Identify().set('prop1', 'value1'));
3350+
3351+
var events = JSON.parse(queryString.parse(server.requests[0].requestBody).e);
3352+
assert.lengthOf(server.requests, 1, 'should have sent a request to Amplitude');
3353+
assert.equal(events[0].event_type, '$identify');
3354+
});
3355+
describe('prior to opting into analytics', function () {
3356+
beforeEach(function () {
3357+
reset();
3358+
amplitude.init(apiKey, null, { cookieExpiration: 365, deferInitialization: true });
3359+
});
3360+
it('should not initially drop a cookie if deferInitialization is set to true', function () {
3361+
var cookieData = cookie.get(amplitude.options.cookieName + '_' + apiKey);
3362+
assert.isNull(cookieData);
3363+
});
3364+
it('should not send anything to amplitude', function () {
3365+
amplitude.identify(new Identify().set('prop1', 'value1'));
3366+
amplitude.logEvent('Event Type 1');
3367+
amplitude.setDomain('.foobar.com');
3368+
amplitude.setUserId(123456);
3369+
amplitude.setGroup('orgId', 15);
3370+
amplitude.setOptOut(true);
3371+
amplitude.regenerateDeviceId();
3372+
amplitude.setDeviceId('deviceId');
3373+
amplitude.setUserProperties({'prop': true, 'key': 'value'});
3374+
amplitude.clearUserProperties();
3375+
amplitude.groupIdentify(null, null, new amplitude.Identify().set('key', 'value'));
3376+
amplitude.setVersionName('testVersionName1');
3377+
amplitude.logEventWithTimestamp('test', null, 2000, null);
3378+
amplitude.logEventWithGroups('Test', {'key': 'value' }, {group: 'abc'});
3379+
amplitude.logRevenue(10.10);
3380+
3381+
var revenue = new amplitude.Revenue().setProductId('testProductId').setQuantity(15).setPrice(10.99);
3382+
revenue.setRevenueType('testRevenueType').setEventProperties({'city': 'San Francisco'});
3383+
amplitude.logRevenueV2(revenue);
3384+
3385+
assert.lengthOf(server.requests, 0, 'should not send any requests to amplitude');
3386+
assert.lengthOf(amplitude._unsentEvents, 0, 'should not queue events to be sent')
3387+
});
3388+
});
3389+
3390+
describe('upon opting into analytics', function () {
3391+
beforeEach(function () {
3392+
reset();
3393+
amplitude.init(apiKey, null, { cookieExpiration: 365, deferInitialization: true });
3394+
});
3395+
it('should drop a cookie', function () {
3396+
amplitude.enableTracking();
3397+
var cookieData = cookie.get(amplitude.options.cookieName + '_' + apiKey);
3398+
assert.isNotNull(cookieData);
3399+
});
3400+
it('should send pending calls and events', function () {
3401+
amplitude.identify(new Identify().set('prop1', 'value1'));
3402+
amplitude.logEvent('Event Type 1');
3403+
amplitude.logEvent('Event Type 2');
3404+
amplitude.logEventWithTimestamp('test', null, 2000, null);
3405+
assert.lengthOf(amplitude._unsentEvents, 0, 'should not have any pending events to be sent');
3406+
amplitude.enableTracking();
3407+
3408+
assert.lengthOf(server.requests, 1, 'should have sent a request to Amplitude');
3409+
var events = JSON.parse(queryString.parse(server.requests[0].requestBody).e);
3410+
assert.lengthOf(events, 1, 'should have sent a request to Amplitude');
3411+
assert.lengthOf(amplitude._unsentEvents, 3, 'should have saved the remaining events')
3412+
});
3413+
it('should send new events', function () {
3414+
assert.lengthOf(amplitude._unsentEvents, 0, 'should start with no pending events to be sent');
3415+
amplitude.identify(new Identify().set('prop1', 'value1'));
3416+
amplitude.logEvent('Event Type 1');
3417+
amplitude.logEvent('Event Type 2');
3418+
amplitude.logEventWithTimestamp('test', null, 2000, null);
3419+
assert.lengthOf(amplitude._unsentEvents, 0, 'should not have any pending events to be sent');
3420+
3421+
amplitude.enableTracking();
3422+
assert.lengthOf(amplitude._unsentEvents, 3, 'should have saved the remaining events')
3423+
3424+
amplitude.logEvent('Event Type 3');
3425+
assert.lengthOf(amplitude._unsentEvents, 4, 'should save the new events')
3426+
});
3427+
it('should not continue to deferInitialization if an amplitude cookie exists', function () {
3428+
amplitude.enableTracking();
3429+
amplitude.init(apiKey, null, { cookieExpiration: 365, deferInitialization: true });
3430+
amplitude.logEvent('Event Type 1');
3431+
3432+
var events = JSON.parse(queryString.parse(server.requests[0].requestBody).e);
3433+
assert.lengthOf(events, 1, 'should have sent a request to Amplitude');
3434+
});
3435+
});
3436+
});
33343437
});

0 commit comments

Comments
 (0)