From 91ae64c6a63e8f487a1673ad3f12b747b8a7b360 Mon Sep 17 00:00:00 2001 From: Samantha Puth Date: Wed, 4 Dec 2019 15:22:43 -0800 Subject: [PATCH 1/4] 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. --- src/amplitude-client.js | 99 +++++++++++++++++++++++++++++++++++----- test/amplitude-client.js | 65 ++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 12 deletions(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index f32473f1..056b7542 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -75,6 +75,12 @@ AmplitudeClient.prototype.init = function init(apiKey, opt_userId, opt_config, o return; } + var hasExistingCookie = !!this.cookieStorage.get(this.options.cookieName + this._storageSuffix); + if (opt_config && opt_config.deferInitialization && !hasExistingCookie) { + this._deferInitialization(apiKey, opt_userId, opt_config, opt_callback); + return; + } + try { this.options.apiKey = apiKey; this._storageSuffix = '_' + apiKey + this._legacyStorageSuffix; @@ -766,6 +772,10 @@ AmplitudeClient.prototype.saveEvents = function saveEvents() { * @example amplitudeClient.setDomain('.amplitude.com'); */ AmplitudeClient.prototype.setDomain = function setDomain(domain) { + if (this._shouldDeferCall()) { + return this._q.push(['setDomain', domain]); + } + if (!utils.validateInput(domain, 'domain', 'string')) { return; } @@ -791,6 +801,10 @@ AmplitudeClient.prototype.setDomain = function setDomain(domain) { * @example amplitudeClient.setUserId('joe@gmail.com'); */ AmplitudeClient.prototype.setUserId = function setUserId(userId) { + if (this._shouldDeferCall()) { + return this._q.push(['setUserId', userId]); + } + try { this.options.userId = (userId !== undefined && userId !== null && ('' + userId)) || null; _saveCookieData(this); @@ -813,6 +827,10 @@ AmplitudeClient.prototype.setUserId = function setUserId(userId) { * @example amplitudeClient.setGroup('orgId', 15); // this adds the current user to orgId 15. */ AmplitudeClient.prototype.setGroup = function(groupType, groupName) { + if (this._shouldDeferCall()) { + return this._q.push(['setGroup', groupType, groupName]); + } + if (!this._apiKeySet('setGroup()') || !utils.validateInput(groupType, 'groupType', 'string') || utils.isEmptyString(groupType)) { return; @@ -831,6 +849,10 @@ AmplitudeClient.prototype.setGroup = function(groupType, groupName) { * @example: amplitude.setOptOut(true); */ AmplitudeClient.prototype.setOptOut = function setOptOut(enable) { + if (this._shouldDeferCall()) { + return this._q.push(['setOptOut', enable]); + } + if (!utils.validateInput(enable, 'enable', 'boolean')) { return; } @@ -868,6 +890,10 @@ AmplitudeClient.prototype.resetSessionId = function resetSessionId() { * @public */ AmplitudeClient.prototype.regenerateDeviceId = function regenerateDeviceId() { + if (this._shouldDeferCall()) { + return this._q.push(['regenerateDeviceId']); + } + this.setDeviceId(UUID() + 'R'); }; @@ -880,6 +906,10 @@ AmplitudeClient.prototype.regenerateDeviceId = function regenerateDeviceId() { * @example amplitudeClient.setDeviceId('45f0954f-eb79-4463-ac8a-233a6f45a8f0'); */ AmplitudeClient.prototype.setDeviceId = function setDeviceId(deviceId) { + if (this._shouldDeferCall()) { + return this._q.push(['setDeviceId', deviceId]); + } + if (!utils.validateInput(deviceId, 'deviceId', 'string')) { return; } @@ -903,8 +933,8 @@ AmplitudeClient.prototype.setDeviceId = function setDeviceId(deviceId) { * @example amplitudeClient.setUserProperties({'gender': 'female', 'sign_up_complete': true}) */ AmplitudeClient.prototype.setUserProperties = function setUserProperties(userProperties) { - if (this._pendingReadStorage) { - return this._q.push(['identify', userProperties]); + if (this._shouldDeferCall()) { + return this._q.push(['setUserProperties', userProperties]); } if (!this._apiKeySet('setUserProperties()') || !utils.validateInput(userProperties, 'userProperties', 'object')) { return; @@ -931,6 +961,10 @@ AmplitudeClient.prototype.setUserProperties = function setUserProperties(userPro * @example amplitudeClient.clearUserProperties(); */ AmplitudeClient.prototype.clearUserProperties = function clearUserProperties(){ + if (this._shouldDeferCall()) { + return this._q.push(['clearUserProperties']); + } + if (!this._apiKeySet('clearUserProperties()')) { return; } @@ -967,8 +1001,8 @@ var _convertProxyObjectToRealObject = function _convertProxyObjectToRealObject(i * amplitude.identify(identify); */ AmplitudeClient.prototype.identify = function(identify_obj, opt_callback) { - if (this._pendingReadStorage) { - return this._q.push(['identify', identify_obj, opt_callback]); + if (this._shouldDeferCall()) { + return this._q.push(['identify', identify_obj, opt_callback]); } if (!this._apiKeySet('identify()')) { if (type(opt_callback) === 'function') { @@ -1002,8 +1036,8 @@ AmplitudeClient.prototype.identify = function(identify_obj, opt_callback) { }; AmplitudeClient.prototype.groupIdentify = function(group_type, group_name, identify_obj, opt_callback) { - if (this._pendingReadStorage) { - return this._q.push(['groupIdentify', group_type, group_name, identify_obj, opt_callback]); + if (this._shouldDeferCall()) { + return this._q.push(['groupIdentify', group_type, group_name, identify_obj, opt_callback]); } if (!this._apiKeySet('groupIdentify()')) { if (type(opt_callback) === 'function') { @@ -1058,6 +1092,10 @@ AmplitudeClient.prototype.groupIdentify = function(group_type, group_name, ident * @example amplitudeClient.setVersionName('1.12.3'); */ AmplitudeClient.prototype.setVersionName = function setVersionName(versionName) { + if (this._shouldDeferCall()) { + return this._q.push(['setVersionName', versionName]); + } + if (!utils.validateInput(versionName, 'versionName', 'string')) { return; } @@ -1217,8 +1255,8 @@ AmplitudeClient.prototype._limitEventsQueued = function _limitEventsQueued(queue * @example amplitudeClient.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15}); */ AmplitudeClient.prototype.logEvent = function logEvent(eventType, eventProperties, opt_callback) { - if (this._pendingReadStorage) { - return this._q.push(['logEvent', eventType, eventProperties, opt_callback]); + if (this._shouldDeferCall()) { + return this._q.push(['logEvent', eventType, eventProperties, opt_callback]); } return this.logEventWithTimestamp(eventType, eventProperties, null, opt_callback); }; @@ -1234,8 +1272,8 @@ AmplitudeClient.prototype.logEvent = function logEvent(eventType, eventPropertie * @example amplitudeClient.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15}); */ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent(eventType, eventProperties, timestamp, opt_callback) { - if (this._pendingReadStorage) { - return this._q.push(['logEventWithTimestamp', eventType, eventProperties, timestamp, opt_callback]); + if (this._shouldDeferCall()) { + return this._q.push(['logEventWithTimestamp', eventType, eventProperties, timestamp, opt_callback]); } if (!this._apiKeySet('logEvent()')) { if (type(opt_callback) === 'function') { @@ -1274,8 +1312,8 @@ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent(eventType, e * @example amplitudeClient.logEventWithGroups('Clicked Button', null, {'orgId': 24}); */ AmplitudeClient.prototype.logEventWithGroups = function(eventType, eventProperties, groups, opt_callback) { - if (this._pendingReadStorage) { - return this._q.push(['logEventWithGroups', eventType, eventProperties, groups, opt_callback]); + if (this._shouldDeferCall()) { + return this._q.push(['logEventWithGroups', eventType, eventProperties, groups, opt_callback]); } if (!this._apiKeySet('logEventWithGroups()')) { if (type(opt_callback) === 'function') { @@ -1311,6 +1349,10 @@ var _isNumber = function _isNumber(n) { * amplitude.logRevenueV2(revenue); */ AmplitudeClient.prototype.logRevenueV2 = function logRevenueV2(revenue_obj) { + if (this._shouldDeferCall()) { + return this._q.push(['logRevenueV2', revenue_obj]); + } + if (!this._apiKeySet('logRevenueV2()')) { return; } @@ -1341,6 +1383,10 @@ if (BUILD_COMPAT_2_0) { * @example amplitudeClient.logRevenue(3.99, 1, 'product_1234'); */ AmplitudeClient.prototype.logRevenue = function logRevenue(price, quantity, product) { + if (this._shouldDeferCall()) { + return this._q.push(['logRevenue', price, quantity, product]); + } + // Test that the parameters are of the right type. if (!this._apiKeySet('logRevenue()') || !_isNumber(price) || (quantity !== undefined && !_isNumber(quantity))) { // utils.log('Price and quantity arguments to logRevenue must be numbers'); @@ -1552,4 +1598,33 @@ if (BUILD_COMPAT_2_0) { */ AmplitudeClient.prototype.__VERSION__ = version; +/** + * Determines whether or not to push call to this._q or invoke it + * @private + */ +AmplitudeClient.prototype._shouldDeferCall = function _shouldDeferCall() { + return this._pendingReadStorage || this._initializationDeferred; +}; + +/** + * Defers Initialization by putting all functions into storage until users + * have accepted terms for tracking + * @private + */ +AmplitudeClient.prototype._deferInitialization = function _deferInitialization(apiKey, opt_userId, opt_config, opt_callback) { + this._initializationDeferred = true; + this._q.push(['init', apiKey, opt_userId, opt_config, opt_callback]); +}; + +/** + * Enable tracking via logging events and dropping a cookie + * Intended to be used with the deferInitialization configuration flag + * @public + */ +AmplitudeClient.prototype.enableTracking = function enableTracking() { + // This will call init (which drops the cookie) and will run any pending tasks + _saveCookieData(this); + this.runQueuedFunctions(); +}; + export default AmplitudeClient; diff --git a/test/amplitude-client.js b/test/amplitude-client.js index 711575b8..4120411e 100644 --- a/test/amplitude-client.js +++ b/test/amplitude-client.js @@ -3331,4 +3331,69 @@ describe('setVersionName', function() { assert.equal(cookieStorage.get(amplitude2.options.cookieName + '_' + apiKey).sessionId, newSessionId); }); }); + + describe('deferInitialization config', function () { + beforeEach(function () { + reset(); + amplitude.init(apiKey, null, { cookieExpiration: 365, deferInitialization: true }); + }); + + describe('prior to opting into analytics', function () { + it('should not initially drop a cookie if deferInitialization is set to true', function () { + var cookieData = cookie.get(amplitude.options.cookieName + '_' + apiKey); + assert.isNull(cookieData); + }); + it('should not send anything to amplitude', function () { + amplitude.identify(new Identify().set('prop1', 'value1')); + amplitude.logEvent('Event Type 1'); + amplitude.setDomain('.foobar.com'); + amplitude.setUserId(123456); + amplitude.setGroup('orgId', 15); + amplitude.setOptOut(true); + amplitude.regenerateDeviceId(); + amplitude.setDeviceId('deviceId'); + amplitude.setUserProperties({'prop': true, 'key': 'value'}); + amplitude.clearUserProperties(); + amplitude.groupIdentify(null, null, new amplitude.Identify().set('key', 'value')); + amplitude.setVersionName('testVersionName1'); + amplitude.logEventWithTimestamp('test', null, 2000, null); + amplitude.logEventWithGroups('Test', {'key': 'value' }, {group: 'abc'}); + amplitude.logRevenue(10.10); + + var revenue = new amplitude.Revenue().setProductId('testProductId').setQuantity(15).setPrice(10.99); + revenue.setRevenueType('testRevenueType').setEventProperties({'city': 'San Francisco'}); + amplitude.logRevenueV2(revenue); + + assert.lengthOf(server.requests, 0, 'should not send any requests to amplitude'); + assert.lengthOf(amplitude._unsentEvents, 0, 'should not queue events to be sent') + }); + }); + + describe('upon to opting into analytics', function () { + it('should drop a cookie', function () { + amplitude.enableTracking(); + var cookieData = cookie.get(amplitude.options.cookieName + '_' + apiKey); + assert.isNotNull(cookieData); + }); + it('should send pending calls and events', function () { + amplitude.identify(new Identify().set('prop1', 'value1')); + amplitude.logEvent('Event Type 1'); + amplitude.logEvent('Event Type 2'); + amplitude.logEventWithTimestamp('test', null, 2000, null); + amplitude.enableTracking(); + + var events = JSON.parse(queryString.parse(server.requests[0].requestBody).e); + assert.lengthOf(events, 1, 'should have sent a request to Amplitude'); + assert.lengthOf(amplitude._unsentEvents, 3, 'should have saved the remaining events') + }); + it('should not continue to deferInitialization if an amplitude cookie exists', function () { + amplitude.enableTracking(); + amplitude.init(apiKey, null, { cookieExpiration: 365, deferInitialization: true }); + amplitude.logEvent('Event Type 1'); + + var events = JSON.parse(queryString.parse(server.requests[0].requestBody).e); + assert.lengthOf(events, 1, 'should have sent a request to Amplitude'); + }); + }); + }) }); From 9ea307c45289ba46e0733d39963fe08b859f3bb0 Mon Sep 17 00:00:00 2001 From: Samantha Puth Date: Thu, 5 Dec 2019 14:46:44 -0800 Subject: [PATCH 2/4] Explicitly set _initializationDeferred to false upon enabling tracking (when a user explicitly opts into analytics), move deferInitialization check where this._storageSuffix is actually defined (in the try block) * set initializedDeferred = false in enableTracking() * make sure this._storageSuffix is defined when cookies are being checked * Added tests for sending events after tracking is enabled * Added assertions to ensure that each test starts with a clean slate (no unsentEvents) before enabling tracking --- src/amplitude-client.js | 14 ++++++++------ test/amplitude-client.js | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index 056b7542..ce275fe6 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -75,16 +75,16 @@ AmplitudeClient.prototype.init = function init(apiKey, opt_userId, opt_config, o return; } - var hasExistingCookie = !!this.cookieStorage.get(this.options.cookieName + this._storageSuffix); - if (opt_config && opt_config.deferInitialization && !hasExistingCookie) { - this._deferInitialization(apiKey, opt_userId, opt_config, opt_callback); - return; - } - try { this.options.apiKey = apiKey; this._storageSuffix = '_' + apiKey + this._legacyStorageSuffix; + var hasExistingCookie = !!this.cookieStorage.get(this.options.cookieName + this._storageSuffix); + if (opt_config && opt_config.deferInitialization && !hasExistingCookie) { + this._deferInitialization(apiKey, opt_userId, opt_config, opt_callback); + return; + } + _parseConfig(this.options, opt_config); if (type(this.options.logLevel) === 'string') { @@ -1619,10 +1619,12 @@ AmplitudeClient.prototype._deferInitialization = function _deferInitialization(a /** * Enable tracking via logging events and dropping a cookie * Intended to be used with the deferInitialization configuration flag + * This will drop a cookie and reset initialization deferred * @public */ AmplitudeClient.prototype.enableTracking = function enableTracking() { // This will call init (which drops the cookie) and will run any pending tasks + this._initializationDeferred = false; _saveCookieData(this); this.runQueuedFunctions(); }; diff --git a/test/amplitude-client.js b/test/amplitude-client.js index 4120411e..f165d8ee 100644 --- a/test/amplitude-client.js +++ b/test/amplitude-client.js @@ -3369,7 +3369,7 @@ describe('setVersionName', function() { }); }); - describe('upon to opting into analytics', function () { + describe('upon opting into analytics', function () { it('should drop a cookie', function () { amplitude.enableTracking(); var cookieData = cookie.get(amplitude.options.cookieName + '_' + apiKey); @@ -3380,12 +3380,28 @@ describe('setVersionName', function() { amplitude.logEvent('Event Type 1'); amplitude.logEvent('Event Type 2'); amplitude.logEventWithTimestamp('test', null, 2000, null); + assert.lengthOf(amplitude._unsentEvents, 0, 'should not have any pending events to be sent'); amplitude.enableTracking(); + assert.lengthOf(server.requests, 1, 'should have sent a request to Amplitude'); var events = JSON.parse(queryString.parse(server.requests[0].requestBody).e); assert.lengthOf(events, 1, 'should have sent a request to Amplitude'); assert.lengthOf(amplitude._unsentEvents, 3, 'should have saved the remaining events') }); + it('should send new events', function () { + assert.lengthOf(amplitude._unsentEvents, 0, 'should start with no pending events to be sent'); + amplitude.identify(new Identify().set('prop1', 'value1')); + amplitude.logEvent('Event Type 1'); + amplitude.logEvent('Event Type 2'); + amplitude.logEventWithTimestamp('test', null, 2000, null); + assert.lengthOf(amplitude._unsentEvents, 0, 'should not have any pending events to be sent'); + + amplitude.enableTracking(); + assert.lengthOf(amplitude._unsentEvents, 3, 'should have saved the remaining events') + + amplitude.logEvent('Event Type 3'); + assert.lengthOf(amplitude._unsentEvents, 4, 'should save the new events') + }); it('should not continue to deferInitialization if an amplitude cookie exists', function () { amplitude.enableTracking(); amplitude.init(apiKey, null, { cookieExpiration: 365, deferInitialization: true }); From 5a0ed4ca69ffe68681b50187f72098ec6fdfb813 Mon Sep 17 00:00:00 2001 From: Samantha Puth Date: Fri, 6 Dec 2019 13:33:48 -0800 Subject: [PATCH 3/4] When adding function call into this._q, send all the arguments by .slice.call(arguments) and add additional tests to verify that existing users (e.g. users with an amplitude cookie) will continue to be logged --- src/amplitude-client.js | 36 ++++++++++++++++++------------------ test/amplitude-client.js | 30 ++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index ce275fe6..d74f8026 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -773,7 +773,7 @@ AmplitudeClient.prototype.saveEvents = function saveEvents() { */ AmplitudeClient.prototype.setDomain = function setDomain(domain) { if (this._shouldDeferCall()) { - return this._q.push(['setDomain', domain]); + return this._q.push(['setDomain'].concat(Array.prototype.slice.call(arguments, 0))); } if (!utils.validateInput(domain, 'domain', 'string')) { @@ -802,7 +802,7 @@ AmplitudeClient.prototype.setDomain = function setDomain(domain) { */ AmplitudeClient.prototype.setUserId = function setUserId(userId) { if (this._shouldDeferCall()) { - return this._q.push(['setUserId', userId]); + return this._q.push(['setUserId'].concat(Array.prototype.slice.call(arguments, 0))); } try { @@ -828,7 +828,7 @@ AmplitudeClient.prototype.setUserId = function setUserId(userId) { */ AmplitudeClient.prototype.setGroup = function(groupType, groupName) { if (this._shouldDeferCall()) { - return this._q.push(['setGroup', groupType, groupName]); + return this._q.push(['setGroup'].concat(Array.prototype.slice.call(arguments, 0))); } if (!this._apiKeySet('setGroup()') || !utils.validateInput(groupType, 'groupType', 'string') || @@ -850,7 +850,7 @@ AmplitudeClient.prototype.setGroup = function(groupType, groupName) { */ AmplitudeClient.prototype.setOptOut = function setOptOut(enable) { if (this._shouldDeferCall()) { - return this._q.push(['setOptOut', enable]); + return this._q.push(['setOptOut'].concat(Array.prototype.slice.call(arguments, 0))); } if (!utils.validateInput(enable, 'enable', 'boolean')) { @@ -891,7 +891,7 @@ AmplitudeClient.prototype.resetSessionId = function resetSessionId() { */ AmplitudeClient.prototype.regenerateDeviceId = function regenerateDeviceId() { if (this._shouldDeferCall()) { - return this._q.push(['regenerateDeviceId']); + return this._q.push(['regenerateDeviceId'].concat(Array.prototype.slice.call(arguments, 0))); } this.setDeviceId(UUID() + 'R'); @@ -907,7 +907,7 @@ AmplitudeClient.prototype.regenerateDeviceId = function regenerateDeviceId() { */ AmplitudeClient.prototype.setDeviceId = function setDeviceId(deviceId) { if (this._shouldDeferCall()) { - return this._q.push(['setDeviceId', deviceId]); + return this._q.push(['setDeviceId'].concat(Array.prototype.slice.call(arguments, 0))); } if (!utils.validateInput(deviceId, 'deviceId', 'string')) { @@ -934,7 +934,7 @@ AmplitudeClient.prototype.setDeviceId = function setDeviceId(deviceId) { */ AmplitudeClient.prototype.setUserProperties = function setUserProperties(userProperties) { if (this._shouldDeferCall()) { - return this._q.push(['setUserProperties', userProperties]); + return this._q.push(['setUserProperties'].concat(Array.prototype.slice.call(arguments, 0))); } if (!this._apiKeySet('setUserProperties()') || !utils.validateInput(userProperties, 'userProperties', 'object')) { return; @@ -962,7 +962,7 @@ AmplitudeClient.prototype.setUserProperties = function setUserProperties(userPro */ AmplitudeClient.prototype.clearUserProperties = function clearUserProperties(){ if (this._shouldDeferCall()) { - return this._q.push(['clearUserProperties']); + return this._q.push(['clearUserProperties'].concat(Array.prototype.slice.call(arguments, 0))); } if (!this._apiKeySet('clearUserProperties()')) { @@ -1002,7 +1002,7 @@ var _convertProxyObjectToRealObject = function _convertProxyObjectToRealObject(i */ AmplitudeClient.prototype.identify = function(identify_obj, opt_callback) { if (this._shouldDeferCall()) { - return this._q.push(['identify', identify_obj, opt_callback]); + return this._q.push(['identify'].concat(Array.prototype.slice.call(arguments, 0))); } if (!this._apiKeySet('identify()')) { if (type(opt_callback) === 'function') { @@ -1037,7 +1037,7 @@ AmplitudeClient.prototype.identify = function(identify_obj, opt_callback) { AmplitudeClient.prototype.groupIdentify = function(group_type, group_name, identify_obj, opt_callback) { if (this._shouldDeferCall()) { - return this._q.push(['groupIdentify', group_type, group_name, identify_obj, opt_callback]); + return this._q.push(['groupIdentify'].concat(Array.prototype.slice.call(arguments, 0))); } if (!this._apiKeySet('groupIdentify()')) { if (type(opt_callback) === 'function') { @@ -1093,7 +1093,7 @@ AmplitudeClient.prototype.groupIdentify = function(group_type, group_name, ident */ AmplitudeClient.prototype.setVersionName = function setVersionName(versionName) { if (this._shouldDeferCall()) { - return this._q.push(['setVersionName', versionName]); + return this._q.push(['setVersionName'].concat(Array.prototype.slice.call(arguments, 0))); } if (!utils.validateInput(versionName, 'versionName', 'string')) { @@ -1256,7 +1256,7 @@ AmplitudeClient.prototype._limitEventsQueued = function _limitEventsQueued(queue */ AmplitudeClient.prototype.logEvent = function logEvent(eventType, eventProperties, opt_callback) { if (this._shouldDeferCall()) { - return this._q.push(['logEvent', eventType, eventProperties, opt_callback]); + return this._q.push(['logEvent'].concat(Array.prototype.slice.call(arguments, 0))); } return this.logEventWithTimestamp(eventType, eventProperties, null, opt_callback); }; @@ -1273,7 +1273,7 @@ AmplitudeClient.prototype.logEvent = function logEvent(eventType, eventPropertie */ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent(eventType, eventProperties, timestamp, opt_callback) { if (this._shouldDeferCall()) { - return this._q.push(['logEventWithTimestamp', eventType, eventProperties, timestamp, opt_callback]); + return this._q.push(['logEventWithTimestamp'].concat(Array.prototype.slice.call(arguments, 0))); } if (!this._apiKeySet('logEvent()')) { if (type(opt_callback) === 'function') { @@ -1313,7 +1313,7 @@ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent(eventType, e */ AmplitudeClient.prototype.logEventWithGroups = function(eventType, eventProperties, groups, opt_callback) { if (this._shouldDeferCall()) { - return this._q.push(['logEventWithGroups', eventType, eventProperties, groups, opt_callback]); + return this._q.push(['logEventWithGroups'].concat(Array.prototype.slice.call(arguments, 0))); } if (!this._apiKeySet('logEventWithGroups()')) { if (type(opt_callback) === 'function') { @@ -1350,7 +1350,7 @@ var _isNumber = function _isNumber(n) { */ AmplitudeClient.prototype.logRevenueV2 = function logRevenueV2(revenue_obj) { if (this._shouldDeferCall()) { - return this._q.push(['logRevenueV2', revenue_obj]); + return this._q.push(['logRevenueV2'].concat(Array.prototype.slice.call(arguments, 0))); } if (!this._apiKeySet('logRevenueV2()')) { @@ -1384,7 +1384,7 @@ if (BUILD_COMPAT_2_0) { */ AmplitudeClient.prototype.logRevenue = function logRevenue(price, quantity, product) { if (this._shouldDeferCall()) { - return this._q.push(['logRevenue', price, quantity, product]); + return this._q.push(['logRevenue'].concat(Array.prototype.slice.call(arguments, 0))); } // Test that the parameters are of the right type. @@ -1611,9 +1611,9 @@ AmplitudeClient.prototype._shouldDeferCall = function _shouldDeferCall() { * have accepted terms for tracking * @private */ -AmplitudeClient.prototype._deferInitialization = function _deferInitialization(apiKey, opt_userId, opt_config, opt_callback) { +AmplitudeClient.prototype._deferInitialization = function _deferInitialization() { this._initializationDeferred = true; - this._q.push(['init', apiKey, opt_userId, opt_config, opt_callback]); + this._q.push(['init'].concat(Array.prototype.slice.call(arguments, 0))); }; /** diff --git a/test/amplitude-client.js b/test/amplitude-client.js index f165d8ee..ef25f6f3 100644 --- a/test/amplitude-client.js +++ b/test/amplitude-client.js @@ -3333,12 +3333,30 @@ describe('setVersionName', function() { }); describe('deferInitialization config', function () { - beforeEach(function () { - reset(); + it('should keep tracking users who already have an amplitude cookie', function () { + var now = new Date().getTime(); + var cookieData = { + userId: 'test_user_id', + optOut: false, + sessionId: now, + lastEventTime: now, + eventId: 50, + identifyId: 60 + } + + cookie.set(amplitude.options.cookieName + keySuffix, cookieData); amplitude.init(apiKey, null, { cookieExpiration: 365, deferInitialization: true }); - }); + amplitude.identify(new Identify().set('prop1', 'value1')); + var events = JSON.parse(queryString.parse(server.requests[0].requestBody).e); + assert.lengthOf(server.requests, 1, 'should have sent a request to Amplitude'); + assert.equal(events[0].event_type, '$identify'); + }); describe('prior to opting into analytics', function () { + beforeEach(function () { + reset(); + amplitude.init(apiKey, null, { cookieExpiration: 365, deferInitialization: true }); + }); it('should not initially drop a cookie if deferInitialization is set to true', function () { var cookieData = cookie.get(amplitude.options.cookieName + '_' + apiKey); assert.isNull(cookieData); @@ -3370,6 +3388,10 @@ describe('setVersionName', function() { }); describe('upon opting into analytics', function () { + beforeEach(function () { + reset(); + amplitude.init(apiKey, null, { cookieExpiration: 365, deferInitialization: true }); + }); it('should drop a cookie', function () { amplitude.enableTracking(); var cookieData = cookie.get(amplitude.options.cookieName + '_' + apiKey); @@ -3411,5 +3433,5 @@ describe('setVersionName', function() { assert.lengthOf(events, 1, 'should have sent a request to Amplitude'); }); }); - }) + }); }); From 44207f3e807ed6364dbfcb5f9af770478ea70c97 Mon Sep 17 00:00:00 2001 From: Samantha Puth Date: Fri, 6 Dec 2019 13:42:05 -0800 Subject: [PATCH 4/4] Bump Minor Version --- CHANGELOG.md | 3 +++ README.md | 2 +- package.json | 2 +- src/amplitude-snippet.js | 6 +++--- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b06357f1..0899ab07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### 5.8.0 (December 6, 2019) +* Add support to defer saving an amplitude cookie and logging events until a user has opted in + ### 5.7.1 (December 2, 2019) * Fix issue where null unsentKey and unsentIdentifyKeys were causing log crashes diff --git a/README.md b/README.md index 3d7c7cfa..f0e67076 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Please see our [installation guide](https://amplitude.zendesk.com/hc/en-us/artic [![npm version](https://badge.fury.io/js/amplitude-js.svg)](https://badge.fury.io/js/amplitude-js) [![Bower version](https://badge.fury.io/bo/amplitude-js.svg)](https://badge.fury.io/bo/amplitude-js) -[5.7.1 - Released on December 3, 2019](https://github.com/amplitude/Amplitude-JavaScript/releases/latest) +[5.8.0 - Released on December 6, 2019](https://github.com/amplitude/Amplitude-JavaScript/releases/latest) # JavaScript SDK Reference # diff --git a/package.json b/package.json index a9b779ab..7eb9c59f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "amplitude-js", "author": "Amplitude ", - "version": "5.7.1", + "version": "5.8.0", "license": "MIT", "description": "Javascript library for Amplitude Analytics", "keywords": [ diff --git a/src/amplitude-snippet.js b/src/amplitude-snippet.js index 7d6ea141..fbed24a5 100644 --- a/src/amplitude-snippet.js +++ b/src/amplitude-snippet.js @@ -2,10 +2,10 @@ var amplitude = window.amplitude || {'_q':[],'_iq':{}}; var as = document.createElement('script'); as.type = 'text/javascript'; - as.integrity = 'sha384-Ik1BT1T0ZKcBQi93L3Lh8pYLQvUANkj37BjU140rtlIwQSj9ePR4dOoqfWj9u5qU'; + as.integrity = 'sha384-vYYnQ3LPdp/RkQjoKBTGSq0X5F73gXU3G2QopHaIfna0Ct1JRWzwrmEz115NzOta'; as.crossOrigin = 'anonymous'; as.async = true; - as.src = 'https://cdn.amplitude.com/libs/amplitude-5.7.1-min.gz.js'; + as.src = 'https://cdn.amplitude.com/libs/amplitude-5.8.0-min.gz.js'; as.onload = function() {if(!window.amplitude.runQueuedFunctions) {console.log('[Amplitude] Error: could not load SDK');}}; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(as, s); @@ -23,7 +23,7 @@ for (var j = 0; j < revenueFuncs.length; j++) {proxy(Revenue, revenueFuncs[j]);} amplitude.Revenue = Revenue; var funcs = ['init', 'logEvent', 'logRevenue', 'setUserId', 'setUserProperties', - 'setOptOut', 'setVersionName', 'setDomain', 'setDeviceId', + 'setOptOut', 'setVersionName', 'setDomain', 'setDeviceId', 'enableTracking', 'setGlobalUserProperties', 'identify', 'clearUserProperties', 'setGroup', 'logRevenueV2', 'regenerateDeviceId', 'groupIdentify', 'onInit', 'logEventWithTimestamp', 'logEventWithGroups', 'setSessionId', 'resetSessionId'];