From 772cac6f3d15386c2e137a7396bf867e25446df5 Mon Sep 17 00:00:00 2001 From: yuhao900914 Date: Sat, 13 Nov 2021 00:17:49 -0800 Subject: [PATCH 01/10] feat: add more interface for flutter web support --- src/amplitude-client.js | 219 ++++++++++++++++++++++++++++++++++- test/amplitude-client.js | 238 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 452 insertions(+), 5 deletions(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index 987bd751..c9cbb018 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -17,7 +17,7 @@ import base64Id from './base64Id'; import DEFAULT_OPTIONS from './options'; import getHost from './get-host'; import baseCookie from './base-cookie'; -import { getEventLogApi } from './server-zone'; +import { AmplitudeServerZone, getEventLogApi } from './server-zone'; import ConfigManager from './config-manager'; /** @@ -858,15 +858,29 @@ AmplitudeClient.prototype.setDomain = function setDomain(domain) { * Sets an identifier for the current user. * @public * @param {string} userId - identifier to set. Can be null. + * @param {boolean} startNewSession - (optional) if start a new session or not * @example amplitudeClient.setUserId('joe@gmail.com'); */ -AmplitudeClient.prototype.setUserId = function setUserId(userId) { +AmplitudeClient.prototype.setUserId = function setUserId(userId, startNewSession) { if (this._shouldDeferCall()) { return this._q.push(['setUserId'].concat(Array.prototype.slice.call(arguments, 0))); } try { this.options.userId = (userId !== undefined && userId !== null && '' + userId) || null; + if (startNewSession) { + if (this.options.unsetParamsReferrerOnNewSession) { + this._unsetUTMParams(); + } + this._newSession = true; + this._sessionId = new Date().getTime(); + + // only capture UTM params and referrer if new session + if (this.options.saveParamsReferrerOncePerSession) { + this._trackParamsAndReferrer(); + } + } + _saveCookieData(this); } catch (e) { utils.log.error(e); @@ -1232,7 +1246,9 @@ AmplitudeClient.prototype._logEvent = function _logEvent( timestamp, callback, errorCallback, + outOfSession, ) { + console.log('outOfSession in _logEvent'); _loadCookieData(this); // reload cookie before each log event to sync event meta-data between windows and tabs if (!eventType) { @@ -1257,7 +1273,13 @@ AmplitudeClient.prototype._logEvent = function _logEvent( } var sequenceNumber = this.nextSequenceNumber(); var eventTime = type(timestamp) === 'number' ? timestamp : new Date().getTime(); - if (!this._sessionId || !this._lastEventTime || eventTime - this._lastEventTime > this.options.sessionTimeout) { + if (outOfSession) { + this._sessionId = -1; + } else if ( + !this._sessionId || + !this._lastEventTime || + eventTime - this._lastEventTime > this.options.sessionTimeout + ) { this._sessionId = eventTime; } this._lastEventTime = eventTime; @@ -1385,11 +1407,18 @@ AmplitudeClient.prototype._limitEventsQueued = function _limitEventsQueued(queue * Note: the server response code and response body from the event upload are passed to the callback function. * @example amplitudeClient.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15}); */ -AmplitudeClient.prototype.logEvent = function logEvent(eventType, eventProperties, opt_callback, opt_error_callback) { +AmplitudeClient.prototype.logEvent = function logEvent( + eventType, + eventProperties, + opt_callback, + opt_error_callback, + outOfSession, +) { + console.log('in logEvent', opt_callback, opt_error_callback, outOfSession); if (this._shouldDeferCall()) { return this._q.push(['logEvent'].concat(Array.prototype.slice.call(arguments, 0))); } - return this.logEventWithTimestamp(eventType, eventProperties, null, opt_callback, opt_error_callback); + return this.logEventWithTimestamp(eventType, eventProperties, null, opt_callback, opt_error_callback, outOfSession); }; /** @@ -1411,6 +1440,7 @@ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent( timestamp, opt_callback, opt_error_callback, + outOfSession, ) { if (this._shouldDeferCall()) { return this._q.push(['logEventWithTimestamp'].concat(Array.prototype.slice.call(arguments, 0))); @@ -1435,6 +1465,11 @@ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent( }); return -1; } + /*if (!utils.validateInput(outOfSession, 'outOfSession', 'boolean')) { + _logErrorsWithCallbacks(opt_callback, opt_error_callback, 0, 'No request sent', { + reason: 'Invalid type for eventType', + }); +*/ return this._logEvent( eventType, eventProperties, @@ -1445,6 +1480,7 @@ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent( timestamp, opt_callback, opt_error_callback, + outOfSession, ); }; @@ -1851,6 +1887,26 @@ AmplitudeClient.prototype.setLibrary = function setLibrary(name, version) { this.options.library = { name: name, version: version }; }; +/** + * Sets the library name. + * @public + * @param {string} libraryName - the library name. + * @example amplitudeClient.setLibraryName('my_library_name'); + */ +AmplitudeClient.prototype.setLibraryName = function setLibraryName(libraryName) { + this.options.library.name = libraryName; +}; + +/** + * Sets the library Version. + * @public + * @param {string} userId - the library version. + * @example amplitudeClient.setLibraryVersion('1.0.0); + */ +AmplitudeClient.prototype.setLibraryVersion = function setLibraryVersion(libraryVersion) { + this.options.library.version = libraryVersion; +}; + /** * Determines whether or not to push call to this._q or invoke it * @private @@ -1897,4 +1953,157 @@ AmplitudeClient.prototype._refreshDynamicConfig = function _refreshDynamicConfig } }; +/****************************/ +/** + * Returns the deviceId value. + * @public + * @return {string} Id of current device. + */ +AmplitudeClient.prototype.getDeviceId = function getDeviceId() { + return this.options.deviceId; +}; + +/** + * Returns the userId. + * @public + * @return {string} Id of current user. + */ +AmplitudeClient.prototype.getUserId = function getUserId() { + return this.options.userId; +}; + +/** + * Set a custom session expiration time. + * @public + * @param {number} timeInMillis - session expireation time in milliseconds. + */ +AmplitudeClient.prototype.setMinTimeBetweenSessionsMillis = function setMinTimeBetweenSessionsMillis(timeInMillis) { + if (!utils.validateInput(timeInMillis, 'timeInMillis', 'number')) { + return; + } + + if (this._shouldDeferCall()) { + return this._q.push(['setMinTimeBetweenSessionsMillis'].concat(Array.prototype.slice.call(arguments, 0))); + } + + try { + this.options.sessionTimeout = timeInMillis; + } catch (e) { + utils.log.error(e); + } +}; + +/** + * Sets minimum number of events to batch together per request if batchEvents is true. + * @public + * @param {number} eventUploadThreshold - The number of the event upload threshold. Default value is 30. + * @example amplitudeClient.setEventUploadThreshold(10); + */ +AmplitudeClient.prototype.setEventUploadThreshold = function setEventUploadThreshold(eventUploadThreshold) { + if (!utils.validateInput(eventUploadThreshold, 'eventUploadThreshold', 'number')) { + return; + } + + if (this._shouldDeferCall()) { + return this._q.push(['setEventUploadThreshold'].concat(Array.prototype.slice.call(arguments, 0))); + } + + try { + this.options.eventUploadThreshold = eventUploadThreshold; + } catch (e) { + utils.log.error(e); + } +}; + +/** + * Dynamically adjust server URL + * @public + * @param {bool} useDynamicConfig - if enable dynamic config or not. + * @example amplitudeClient.setUseDynamicConfig(true); + */ +AmplitudeClient.prototype.setUseDynamicConfig = function setUseDynamicConfig(useDynamicConfig) { + if (!utils.validateInput(useDynamicConfig, 'useDynamicConfig', 'boolean')) { + return; + } + + if (this._shouldDeferCall()) { + return this._q.push(['setUseDynamicConfig'].concat(Array.prototype.slice.call(arguments, 0))); + } + + try { + this.options.useDynamicConfig = useDynamicConfig; + this._refreshDynamicConfig(); + } catch (e) { + utils.log.error(e); + } +}; + +/** + * Sets the server zone , used for server api endpoint and dynamic configuration.. + * @public + * @param {string} serverZone - the server zone value. AmplitudeServerZone.US or AmplitudeServerZone.EU. + * @param {bool} serverZoneBasedApi - (optional) update api endpoint with serverZone change or not. For data residency, recommend to enable it unless using own proxy server. + * @example amplitudeClient.setServerZone('joe@gmail.com', true); + */ +AmplitudeClient.prototype.setServerZone = function setServerZone(serverZone, serverZoneBasedApi = true) { + if ( + (serverZone !== AmplitudeServerZone.EU && serverZone !== AmplitudeServerZone.US) || + !utils.validateInput(serverZoneBasedApi, 'serverZoneBasedApi', 'boolean') + ) { + return; + } + + if (this._shouldDeferCall()) { + return this._q.push(['setServerZone'].concat(Array.prototype.slice.call(arguments, 0))); + } + + try { + this.options.serverZone = serverZone; + this.options.serverZoneBasedApi = serverZoneBasedApi; + if (serverZoneBasedApi) { + this.options.apiEndpoint = getEventLogApi(this.options.serverZone); + } + } catch (e) { + utils.log.error(e); + } +}; + +/** + * Sets the server URL for the request. + * @public + * @param {string} serverUrl - The value of the server URL. + * @example amplitudeClient.setServerUrl('api.amplitude.com'); + */ +AmplitudeClient.prototype.setServerUrl = function setServerUrl(serverUrl) { + if (!utils.validateInput(serverUrl, 'serverUrl', 'string')) { + return; + } + + if (this._shouldDeferCall()) { + return this._q.push(['setServerUrl'].concat(Array.prototype.slice.call(arguments, 0))); + } + + try { + this.options.apiEndpoint = serverUrl; + } catch (e) { + utils.log.error(e); + } +}; + +/** + * Upload all unsent events. + * @public + * @example amplitudeClient.uploadEvents(); + */ +AmplitudeClient.prototype.uploadEvents = function uploadEvents() { + if (this._shouldDeferCall()) { + return this._q.push(['uploadEvents'].concat(Array.prototype.slice.call(arguments, 0))); + } + + try { + this.sendEvents(); + } catch (e) { + utils.log.error(e); + } +}; export default AmplitudeClient; diff --git a/test/amplitude-client.js b/test/amplitude-client.js index f60aea56..01f58854 100644 --- a/test/amplitude-client.js +++ b/test/amplitude-client.js @@ -2840,6 +2840,24 @@ describe('AmplitudeClient', function () { }); }); + describe('setEventUploadThreshold', function () { + beforeEach(function () { + reset(); + }); + + it('should not set eventUploadThreshold with invalid eventUploadThreshold value', function () { + amplitude.init(apiKey); + amplitude.setEventUploadThreshold('invalid eventUploadThreshold'); + assert.equal(amplitude.options.eventUploadThreshold, 30); + }); + + it('should set eventUploadThreshold', function () { + amplitude.init(apiKey); + amplitude.setEventUploadThreshold(5); + assert.equal(amplitude.options.eventUploadThreshold, 5); + }); + }); + describe('optOut', function () { beforeEach(function () { amplitude.init(apiKey); @@ -2916,6 +2934,26 @@ describe('AmplitudeClient', function () { }); }); + describe('setOptOut', function () { + beforeEach(function () { + reset(); + }); + + it('should not set optOut with invalid input', function () { + amplitude.init(apiKey); + let optOut = 'invalid sessionTimeOut'; + amplitude.setOptOut(optOut); + assert.equal(amplitude.options.optOut, false); + }); + + it('should set optOut', function () { + amplitude.init(apiKey); + let optOut = true; + amplitude.setOptOut(optOut); + assert.equal(amplitude.options.optOut, true); + }); + }); + describe('gatherUtm', function () { var clock; beforeEach(function () { @@ -4149,4 +4187,204 @@ describe('AmplitudeClient', function () { assert.equal(version, '1.0-test'); }); }); + + describe('libraryName', function () { + beforeEach(function () { + reset(); + }); + it('should use the customize library name and default library version', function () { + amplitude.init(apiKey); + amplitude.setLibraryName('test-library'); + amplitude.logEvent('Event Type'); + + const { name, version } = JSON.parse(queryString.parse(server.requests[0].requestBody).e)[0].library; + + assert.equal(name, 'test-library'); + assert.equal(version, amplitude.options.library.version); + }); + }); + + describe('libraryVersion', function () { + beforeEach(function () { + reset(); + }); + it('should use the customize library version and default library name', function () { + amplitude.init(apiKey); + amplitude.setLibraryVersion('1.0-test'); + amplitude.logEvent('Event Type'); + + const { name, version } = JSON.parse(queryString.parse(server.requests[0].requestBody).e)[0].library; + + assert.equal(name, amplitude.options.library.name); + assert.equal(version, '1.0-test'); + }); + }); + + describe('setUseDynamicConfig', function () { + beforeEach(function () { + reset(); + }); + + it('EU serverZone should not set apiEndpoint to EU because of invaid useDynamicConfig value', function () { + assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_URL); + amplitude.init(apiKey, null, { + serverZone: AmplitudeServerZone.EU, + }); + amplitude.setUseDynamicConfig('invalid useDynamicConfig'); + assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_URL); + }); + + it('should not set apiEndpoint to EU because because of dynamic configuration not enable', function () { + assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_URL); + amplitude.init(apiKey, null, { + serverZone: AmplitudeServerZone.EU, + }); + amplitude.setUseDynamicConfig(false); + assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_URL); + }); + + it('EU serverZone with dynamic configuration enable should set apiEndpoint to EU', function () { + assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_URL); + amplitude.init(apiKey, null, { + serverZone: AmplitudeServerZone.EU, + }); + amplitude.setUseDynamicConfig(true); + server.respondWith('{"ingestionEndpoint": "api.eu.amplitude.com"}'); + server.respond(); + assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_EU_URL); + }); + }); + + describe('setServerUrl', function () { + beforeEach(function () { + reset(); + }); + + it('should not set serverUrl because of invalid serverUrl input', function () { + assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_URL); + amplitude.init(apiKey); + amplitude.setServerUrl(100); + assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_URL); + }); + + it('should set serverUrl with valid serverUrl input', function () { + assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_URL); + amplitude.init(apiKey); + amplitude.setServerUrl('api.eu.amplitude.com'); + assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_EU_URL); + }); + }); + + describe('setServerZone', function () { + beforeEach(function () { + reset(); + }); + + it('should not set serverZone with invalid serverZone value', function () { + assert.equal(amplitude.options.serverZone, 'US'); + amplitude.init(apiKey); + amplitude.setServerZone('invalid serverZone'); + assert.equal(amplitude.options.serverZone, 'US'); + }); + + it('should not set serverZone with invalid serverZoneBasedApi value', function () { + assert.equal(amplitude.options.serverZone, 'US'); + amplitude.init(apiKey); + amplitude.setServerZone('EU', 'invalid serverZoneBasedApi'); + assert.equal(amplitude.options.serverZone, 'US'); + }); + + it('should set serverZone to EU', function () { + assert.equal(amplitude.options.serverZone, 'US'); + amplitude.init(apiKey); + amplitude.setServerZone('EU'); + assert.equal(amplitude.options.serverZone, 'EU'); + }); + + it('should set serverZont to EU and serverUrl to EVENT_LOG_EU_URL with serverZoneBasedApi is true', function () { + assert.equal(amplitude.options.serverZone, 'US'); + amplitude.init(apiKey); + amplitude.setServerZone('EU', true); + assert.equal(amplitude.options.serverZone, 'EU'); + assert.equal(amplitude.options.apiEndpoint, 'api.eu.amplitude.com'); + }); + + it('should set serverZont to EU and keep the default serverUrl with serverZoneBasedApi is false', function () { + assert.equal(amplitude.options.serverZone, 'US'); + amplitude.init(apiKey); + amplitude.setServerZone('EU', false); + assert.equal(amplitude.options.serverZone, 'EU'); + assert.equal(amplitude.options.apiEndpoint, 'api.amplitude.com'); + }); + }); + + describe('getUserId', function () { + beforeEach(function () { + reset(); + }); + + it('should get userId', function () { + amplitude.init(apiKey, userId); + let currentUserId = amplitude.getUserId(); + assert.equal(amplitude.options.userId, currentUserId); + }); + + it('should get userId null', function () { + amplitude.init(apiKey); + assert.equal(amplitude.getUserId(), null); + }); + }); + + describe('getDeviceId', function () { + beforeEach(function () { + reset(); + }); + + it('should get a random deviceId', function () { + amplitude.init(apiKey, userId); + assert.lengthOf(amplitude.getDeviceId(), 22); + }); + + it('should get deviceId', function () { + const currentDeviceId = 'aa_bb_cc'; + amplitude.init(apiKey, null, { deviceId: currentDeviceId }); + assert.equal(amplitude.getDeviceId(), currentDeviceId); + }); + }); + + describe('setMinTimeBetweenSessionsMillis', function () { + beforeEach(function () { + reset(); + }); + + it('should not set sessionTimeout with invalid input', function () { + amplitude.init(apiKey); + let newSessionTimeOut = 'invalid sessionTimeOut'; + amplitude.setMinTimeBetweenSessionsMillis(newSessionTimeOut); + assert.equal(amplitude.options.sessionTimeout, 30 * 60 * 1000); + }); + + it('should set sessionTimeout', function () { + amplitude.init(apiKey); + let newSessionTimeOut = 100; + amplitude.setMinTimeBetweenSessionsMillis(newSessionTimeOut); + assert.equal(amplitude.options.sessionTimeout, newSessionTimeOut); + }); + }); + + describe('uploadEvents', function () { + beforeEach(function () { + reset(); + }); + + it('should flush the events', function () { + amplitude.init(apiKey, null, { batchEvents: true, eventUploadThreshold: 30 }); + amplitude.logEvent('Event Type'); + assert.equal(amplitude._unsentEvents.length, 1); + amplitude.uploadEvents(); + server.respondWith('success'); + server.respond(); + assert.equal(amplitude._unsentEvents.length, 0); + }); + }); }); From 0824fdd1ff58747464d194015dc0b026b6790909 Mon Sep 17 00:00:00 2001 From: yuhao900914 Date: Sat, 13 Nov 2021 01:00:38 -0800 Subject: [PATCH 02/10] patch: code clean up and other fix --- src/amplitude-client.js | 44 +++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index c9cbb018..d6820f5d 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -861,7 +861,11 @@ AmplitudeClient.prototype.setDomain = function setDomain(domain) { * @param {boolean} startNewSession - (optional) if start a new session or not * @example amplitudeClient.setUserId('joe@gmail.com'); */ -AmplitudeClient.prototype.setUserId = function setUserId(userId, startNewSession) { +AmplitudeClient.prototype.setUserId = function setUserId(userId, startNewSession = false) { + if (!utils.validateInput(startNewSession, 'startNewSession', 'boolean')) { + return; + } + if (this._shouldDeferCall()) { return this._q.push(['setUserId'].concat(Array.prototype.slice.call(arguments, 0))); } @@ -1248,7 +1252,6 @@ AmplitudeClient.prototype._logEvent = function _logEvent( errorCallback, outOfSession, ) { - console.log('outOfSession in _logEvent'); _loadCookieData(this); // reload cookie before each log event to sync event meta-data between windows and tabs if (!eventType) { @@ -1405,6 +1408,7 @@ AmplitudeClient.prototype._limitEventsQueued = function _limitEventsQueued(queue * @param {Amplitude~eventCallback} opt_error_callback - (optional) a callback function to run after the event logging * fails. The failure can be from the request being malformed or from a network failure * Note: the server response code and response body from the event upload are passed to the callback function. + * @param {boolean} outOfSession - (optional) if this event is out of session or not * @example amplitudeClient.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15}); */ AmplitudeClient.prototype.logEvent = function logEvent( @@ -1412,9 +1416,8 @@ AmplitudeClient.prototype.logEvent = function logEvent( eventProperties, opt_callback, opt_error_callback, - outOfSession, + outOfSession = false, ) { - console.log('in logEvent', opt_callback, opt_error_callback, outOfSession); if (this._shouldDeferCall()) { return this._q.push(['logEvent'].concat(Array.prototype.slice.call(arguments, 0))); } @@ -1465,11 +1468,13 @@ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent( }); return -1; } - /*if (!utils.validateInput(outOfSession, 'outOfSession', 'boolean')) { + + if (!utils.validateInput(outOfSession, 'outOfSession', 'boolean')) { _logErrorsWithCallbacks(opt_callback, opt_error_callback, 0, 'No request sent', { - reason: 'Invalid type for eventType', + reason: 'Invalid outOfSession value', }); -*/ + } + return this._logEvent( eventType, eventProperties, @@ -1509,6 +1514,7 @@ AmplitudeClient.prototype.logEventWithGroups = function ( groups, opt_callback, opt_error_callback, + outOfSession = false, ) { if (this._shouldDeferCall()) { return this._q.push(['logEventWithGroups'].concat(Array.prototype.slice.call(arguments, 0))); @@ -1526,7 +1532,25 @@ AmplitudeClient.prototype.logEventWithGroups = function ( }); return -1; } - return this._logEvent(eventType, eventProperties, null, null, groups, null, null, opt_callback, opt_error_callback); + + if (!utils.validateInput(outOfSession, 'outOfSession', 'boolean')) { + _logErrorsWithCallbacks(event.callback, event.errorCallback, 0, 'No request sent', { + reason: 'Invalid outOfSession value', + }); + } + + return this._logEvent( + eventType, + eventProperties, + null, + null, + groups, + null, + null, + opt_callback, + opt_error_callback, + outOfSession, + ); }; /** @@ -1901,7 +1925,7 @@ AmplitudeClient.prototype.setLibraryName = function setLibraryName(libraryName) * Sets the library Version. * @public * @param {string} userId - the library version. - * @example amplitudeClient.setLibraryVersion('1.0.0); + * @example amplitudeClient.setLibraryVersion('1.0.0'); */ AmplitudeClient.prototype.setLibraryVersion = function setLibraryVersion(libraryVersion) { this.options.library.version = libraryVersion; @@ -1953,7 +1977,6 @@ AmplitudeClient.prototype._refreshDynamicConfig = function _refreshDynamicConfig } }; -/****************************/ /** * Returns the deviceId value. * @public @@ -2106,4 +2129,5 @@ AmplitudeClient.prototype.uploadEvents = function uploadEvents() { utils.log.error(e); } }; + export default AmplitudeClient; From 60357ae8fa0a7f3f3a6f2124ea2e8927eabbe5b2 Mon Sep 17 00:00:00 2001 From: yuhao900914 Date: Sat, 13 Nov 2021 03:02:18 -0800 Subject: [PATCH 03/10] test: add setUserId test --- src/amplitude-client.js | 3 +- test/amplitude-client.js | 61 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index d6820f5d..46446053 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -1435,6 +1435,7 @@ AmplitudeClient.prototype.logEvent = function logEvent( * @param {Amplitude~eventCallback} opt_error_callback - (optional) a callback function to run after the event logging * fails. The failure can be from the request being malformed or from a network failure * Note: the server response code and response body from the event upload are passed to the callback function. + * @param {boolean} outOfSession - (optional) if out of the sessioin or not * @example amplitudeClient.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15}); */ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent( @@ -1443,7 +1444,7 @@ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent( timestamp, opt_callback, opt_error_callback, - outOfSession, + outOfSession = false, ) { if (this._shouldDeferCall()) { return this._q.push(['logEventWithTimestamp'].concat(Array.prototype.slice.call(arguments, 0))); diff --git a/test/amplitude-client.js b/test/amplitude-client.js index 01f58854..67f773c3 100644 --- a/test/amplitude-client.js +++ b/test/amplitude-client.js @@ -2840,6 +2840,18 @@ describe('AmplitudeClient', function () { }); }); + describe('logEvent with outOfSession', function () { + this.beforeEach(function () { + reset(); + }); + + it('should reset the sessionId', function () { + amplitude.init(apiKey); + amplitude.logEvent('Event Type', null, null, null, true); + assert.equal(amplitude._sessionId, -1); + }); + }); + describe('setEventUploadThreshold', function () { beforeEach(function () { reset(); @@ -4387,4 +4399,53 @@ describe('AmplitudeClient', function () { assert.equal(amplitude._unsentEvents.length, 0); }); }); + + describe('setUserId', function () { + let clock, startTime; + beforeEach(function () { + reset(); + startTime = Date.now(); + clock = sinon.useFakeTimers(startTime); + amplitude.init(apiKey); + }); + + it('should not renew the session id with invalid startNewSession input', function () { + var amplitude = new AmplitudeClient(); + // set up initial session + var sessionId = 1000; + clock.tick(sessionId); + amplitude.init(apiKey); + assert.equal(amplitude.getSessionId(), startTime); + + amplitude.setUserId('test user', 'invalid startNewSession'); + assert.notEqual(amplitude.getSessionId(), new Date().getTime()); + assert.notEqual(amplitude.options.userId, 'test user'); + }); + + it('should renew the session id with current timestemp', function () { + var amplitude = new AmplitudeClient(); + // set up initial session + var sessionId = 1000; + clock.tick(sessionId); + amplitude.init(apiKey); + assert.equal(amplitude.getSessionId(), startTime); + + amplitude.setUserId('test user', true); + assert.equal(amplitude.getSessionId(), new Date().getTime()); + assert.equal(amplitude.options.userId, 'test user'); + }); + + it('should continue the old session', function () { + var amplitude = new AmplitudeClient(); + // set up initial session + var sessionId = 1000; + clock.tick(sessionId); + amplitude.init(apiKey); + assert.equal(amplitude.getSessionId(), startTime); + + amplitude.setUserId('test user'); + assert.equal(amplitude.getSessionId(), startTime); + assert.equal(amplitude.options.userId, 'test user'); + }); + }); }); From 579b3beaf6dcff0a41e5ee139b605cd5c1a519f8 Mon Sep 17 00:00:00 2001 From: yuhao900914 Date: Sun, 14 Nov 2021 01:13:40 -0800 Subject: [PATCH 04/10] patch: test update --- test/amplitude-client.js | 57 +++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/test/amplitude-client.js b/test/amplitude-client.js index 67f773c3..fe361a80 100644 --- a/test/amplitude-client.js +++ b/test/amplitude-client.js @@ -2859,8 +2859,9 @@ describe('AmplitudeClient', function () { it('should not set eventUploadThreshold with invalid eventUploadThreshold value', function () { amplitude.init(apiKey); + let previousEventUploadThreshold = amplitude.options.eventUploadThreshold; amplitude.setEventUploadThreshold('invalid eventUploadThreshold'); - assert.equal(amplitude.options.eventUploadThreshold, 30); + assert.equal(amplitude.options.eventUploadThreshold, previousEventUploadThreshold); }); it('should set eventUploadThreshold', function () { @@ -2953,9 +2954,9 @@ describe('AmplitudeClient', function () { it('should not set optOut with invalid input', function () { amplitude.init(apiKey); - let optOut = 'invalid sessionTimeOut'; - amplitude.setOptOut(optOut); - assert.equal(amplitude.options.optOut, false); + let previousOptOut = amplitude.options.optOut; + amplitude.setOptOut('invalid sessionTimeOut'); + assert.equal(amplitude.options.optOut, previousOptOut); }); it('should set optOut', function () { @@ -4237,7 +4238,7 @@ describe('AmplitudeClient', function () { reset(); }); - it('EU serverZone should not set apiEndpoint to EU because of invaid useDynamicConfig value', function () { + it('EU serverZone should not set apiEndpoint to EU because of invalid useDynamicConfig value', function () { assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_URL); amplitude.init(apiKey, null, { serverZone: AmplitudeServerZone.EU, @@ -4282,7 +4283,7 @@ describe('AmplitudeClient', function () { it('should set serverUrl with valid serverUrl input', function () { assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_URL); amplitude.init(apiKey); - amplitude.setServerUrl('api.eu.amplitude.com'); + amplitude.setServerUrl(constants.EVENT_LOG_EU_URL); assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_EU_URL); }); }); @@ -4293,40 +4294,33 @@ describe('AmplitudeClient', function () { }); it('should not set serverZone with invalid serverZone value', function () { - assert.equal(amplitude.options.serverZone, 'US'); amplitude.init(apiKey); + let previousServerZone = amplitude.options.serverZone; amplitude.setServerZone('invalid serverZone'); - assert.equal(amplitude.options.serverZone, 'US'); + assert.equal(amplitude.options.serverZone, previousServerZone); }); it('should not set serverZone with invalid serverZoneBasedApi value', function () { - assert.equal(amplitude.options.serverZone, 'US'); amplitude.init(apiKey); - amplitude.setServerZone('EU', 'invalid serverZoneBasedApi'); - assert.equal(amplitude.options.serverZone, 'US'); + assert.equal(amplitude.options.serverZone, AmplitudeServerZone.US); + amplitude.setServerZone(AmplitudeServerZone.EU, 'invalid serverZoneBasedApi'); + assert.equal(amplitude.options.serverZone, AmplitudeServerZone.US); }); it('should set serverZone to EU', function () { - assert.equal(amplitude.options.serverZone, 'US'); - amplitude.init(apiKey); - amplitude.setServerZone('EU'); - assert.equal(amplitude.options.serverZone, 'EU'); - }); - - it('should set serverZont to EU and serverUrl to EVENT_LOG_EU_URL with serverZoneBasedApi is true', function () { - assert.equal(amplitude.options.serverZone, 'US'); amplitude.init(apiKey); - amplitude.setServerZone('EU', true); - assert.equal(amplitude.options.serverZone, 'EU'); - assert.equal(amplitude.options.apiEndpoint, 'api.eu.amplitude.com'); + assert.equal(amplitude.options.serverZone, AmplitudeServerZone.US); + amplitude.setServerZone(AmplitudeServerZone.EU); + assert.equal(amplitude.options.serverZone, AmplitudeServerZone.EU); + assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_EU_URL); }); - it('should set serverZont to EU and keep the default serverUrl with serverZoneBasedApi is false', function () { - assert.equal(amplitude.options.serverZone, 'US'); + it('should set serverZone to EU and keep the default serverUrl with serverZoneBasedApi is false', function () { amplitude.init(apiKey); - amplitude.setServerZone('EU', false); - assert.equal(amplitude.options.serverZone, 'EU'); - assert.equal(amplitude.options.apiEndpoint, 'api.amplitude.com'); + assert.equal(amplitude.options.serverZone, AmplitudeServerZone.US); + amplitude.setServerZone(AmplitudeServerZone.EU, false); + assert.equal(amplitude.options.serverZone, AmplitudeServerZone.EU); + assert.equal(amplitude.options.apiEndpoint, constants.EVENT_LOG_URL); }); }); @@ -4338,7 +4332,7 @@ describe('AmplitudeClient', function () { it('should get userId', function () { amplitude.init(apiKey, userId); let currentUserId = amplitude.getUserId(); - assert.equal(amplitude.options.userId, currentUserId); + assert.equal(currentUserId, userId); }); it('should get userId null', function () { @@ -4371,9 +4365,10 @@ describe('AmplitudeClient', function () { it('should not set sessionTimeout with invalid input', function () { amplitude.init(apiKey); + let previousSessionTimeOut = amplitude.options.sessionTimeout; let newSessionTimeOut = 'invalid sessionTimeOut'; amplitude.setMinTimeBetweenSessionsMillis(newSessionTimeOut); - assert.equal(amplitude.options.sessionTimeout, 30 * 60 * 1000); + assert.equal(amplitude.options.sessionTimeout, previousSessionTimeOut); }); it('should set sessionTimeout', function () { @@ -4416,13 +4411,15 @@ describe('AmplitudeClient', function () { clock.tick(sessionId); amplitude.init(apiKey); assert.equal(amplitude.getSessionId(), startTime); + assert.equal(amplitude.options.userId, null); amplitude.setUserId('test user', 'invalid startNewSession'); assert.notEqual(amplitude.getSessionId(), new Date().getTime()); assert.notEqual(amplitude.options.userId, 'test user'); + assert.equal(amplitude.options.userId, null); }); - it('should renew the session id with current timestemp', function () { + it('should set user id and renew the session id with current timestemp', function () { var amplitude = new AmplitudeClient(); // set up initial session var sessionId = 1000; From 963296644a5505276df2fb7644a161554e4737ac Mon Sep 17 00:00:00 2001 From: yuhao900914 Date: Mon, 15 Nov 2021 10:43:36 -0800 Subject: [PATCH 05/10] patch: remove unnecessary api interface --- src/amplitude-client.js | 17 ----------------- test/amplitude-client.js | 16 ---------------- 2 files changed, 33 deletions(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index 46446053..f229b47e 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -2114,21 +2114,4 @@ AmplitudeClient.prototype.setServerUrl = function setServerUrl(serverUrl) { } }; -/** - * Upload all unsent events. - * @public - * @example amplitudeClient.uploadEvents(); - */ -AmplitudeClient.prototype.uploadEvents = function uploadEvents() { - if (this._shouldDeferCall()) { - return this._q.push(['uploadEvents'].concat(Array.prototype.slice.call(arguments, 0))); - } - - try { - this.sendEvents(); - } catch (e) { - utils.log.error(e); - } -}; - export default AmplitudeClient; diff --git a/test/amplitude-client.js b/test/amplitude-client.js index fe361a80..ed864ec1 100644 --- a/test/amplitude-client.js +++ b/test/amplitude-client.js @@ -4379,22 +4379,6 @@ describe('AmplitudeClient', function () { }); }); - describe('uploadEvents', function () { - beforeEach(function () { - reset(); - }); - - it('should flush the events', function () { - amplitude.init(apiKey, null, { batchEvents: true, eventUploadThreshold: 30 }); - amplitude.logEvent('Event Type'); - assert.equal(amplitude._unsentEvents.length, 1); - amplitude.uploadEvents(); - server.respondWith('success'); - server.respond(); - assert.equal(amplitude._unsentEvents.length, 0); - }); - }); - describe('setUserId', function () { let clock, startTime; beforeEach(function () { From fc4d624ab0c7f633c137ae7855dc07091310e3e5 Mon Sep 17 00:00:00 2001 From: yuhao900914 Date: Tue, 16 Nov 2021 10:28:34 -0800 Subject: [PATCH 06/10] patch: make setLibrary function able to seprate set library name and version --- src/amplitude-client.js | 27 +++++---------------------- test/amplitude-client.js | 15 +++------------ 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index f229b47e..6fa987d6 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -1908,30 +1908,13 @@ AmplitudeClient.prototype.__VERSION__ = function getVersion() { * @param {string} name - Custom library name * @param {string} version - Custom library version */ -AmplitudeClient.prototype.setLibrary = function setLibrary(name, version) { +AmplitudeClient.prototype.setLibrary = function setLibrary( + name = this.options.libraryName, + version = this.options.libraryVersion, +) { this.options.library = { name: name, version: version }; }; -/** - * Sets the library name. - * @public - * @param {string} libraryName - the library name. - * @example amplitudeClient.setLibraryName('my_library_name'); - */ -AmplitudeClient.prototype.setLibraryName = function setLibraryName(libraryName) { - this.options.library.name = libraryName; -}; - -/** - * Sets the library Version. - * @public - * @param {string} userId - the library version. - * @example amplitudeClient.setLibraryVersion('1.0.0'); - */ -AmplitudeClient.prototype.setLibraryVersion = function setLibraryVersion(libraryVersion) { - this.options.library.version = libraryVersion; -}; - /** * Determines whether or not to push call to this._q or invoke it * @private @@ -2063,7 +2046,7 @@ AmplitudeClient.prototype.setUseDynamicConfig = function setUseDynamicConfig(use }; /** - * Sets the server zone , used for server api endpoint and dynamic configuration.. + * Sets the server zone , used for server api endpoint and dynamic configuration. * @public * @param {string} serverZone - the server zone value. AmplitudeServerZone.US or AmplitudeServerZone.EU. * @param {bool} serverZoneBasedApi - (optional) update api endpoint with serverZone change or not. For data residency, recommend to enable it unless using own proxy server. diff --git a/test/amplitude-client.js b/test/amplitude-client.js index ed864ec1..78bb6018 100644 --- a/test/amplitude-client.js +++ b/test/amplitude-client.js @@ -4172,6 +4172,7 @@ describe('AmplitudeClient', function () { beforeEach(function () { reset(); }); + it('should use default library options', function () { amplitude.init(apiKey); amplitude.logEvent('Event Type 1'); @@ -4199,15 +4200,10 @@ describe('AmplitudeClient', function () { assert.equal(name, 'test-library'); assert.equal(version, '1.0-test'); }); - }); - describe('libraryName', function () { - beforeEach(function () { - reset(); - }); it('should use the customize library name and default library version', function () { amplitude.init(apiKey); - amplitude.setLibraryName('test-library'); + amplitude.setLibrary('test-library', undefined); amplitude.logEvent('Event Type'); const { name, version } = JSON.parse(queryString.parse(server.requests[0].requestBody).e)[0].library; @@ -4215,15 +4211,10 @@ describe('AmplitudeClient', function () { assert.equal(name, 'test-library'); assert.equal(version, amplitude.options.library.version); }); - }); - describe('libraryVersion', function () { - beforeEach(function () { - reset(); - }); it('should use the customize library version and default library name', function () { amplitude.init(apiKey); - amplitude.setLibraryVersion('1.0-test'); + amplitude.setLibrary(undefined, '1.0-test'); amplitude.logEvent('Event Type'); const { name, version } = JSON.parse(queryString.parse(server.requests[0].requestBody).e)[0].library; From 05245f9329f0079c1f87a4fd94741f79ffe9e798 Mon Sep 17 00:00:00 2001 From: yuhao900914 Date: Tue, 16 Nov 2021 10:47:38 -0800 Subject: [PATCH 07/10] patch: make setLibrary function able to seprate set library name and version --- src/amplitude-client.js | 2 +- src/amplitude-snippet.js | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index 6fa987d6..76d24926 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -2046,7 +2046,7 @@ AmplitudeClient.prototype.setUseDynamicConfig = function setUseDynamicConfig(use }; /** - * Sets the server zone , used for server api endpoint and dynamic configuration. + * Sets the server zone, used for server api endpoint and dynamic configuration. * @public * @param {string} serverZone - the server zone value. AmplitudeServerZone.US or AmplitudeServerZone.EU. * @param {bool} serverZoneBasedApi - (optional) update api endpoint with serverZone change or not. For data residency, recommend to enable it unless using own proxy server. diff --git a/src/amplitude-snippet.js b/src/amplitude-snippet.js index 02be347d..fc24f5b1 100644 --- a/src/amplitude-snippet.js +++ b/src/amplitude-snippet.js @@ -79,6 +79,14 @@ 'logEventWithGroups', 'setSessionId', 'resetSessionId', + 'getDeviceId', + 'getUserId', + 'setMinTimeBetweenSessionsMillis', + 'setEventUploadThreshold', + 'setUseDynamicConfig', + 'setServerZone', + 'setServerUrl', + 'sendEvents', ]; function setUpProxy(instance) { function proxyMain(fn) { From fda6870de50eec4871779c2d57e72d2c278cf5dd Mon Sep 17 00:00:00 2001 From: yuhao900914 Date: Wed, 17 Nov 2021 11:19:24 -0800 Subject: [PATCH 08/10] patch: setLibrary improvement --- src/amplitude-client.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index 76d24926..30297486 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -1908,11 +1908,9 @@ AmplitudeClient.prototype.__VERSION__ = function getVersion() { * @param {string} name - Custom library name * @param {string} version - Custom library version */ -AmplitudeClient.prototype.setLibrary = function setLibrary( - name = this.options.libraryName, - version = this.options.libraryVersion, -) { - this.options.library = { name: name, version: version }; +AmplitudeClient.prototype.setLibrary = function setLibrary(name, version) { + if (name) this.options.library.name = name; + if (version) this.options.library.version = version; }; /** From aa3b2df830334e273b0726fa1930a64ae1423e78 Mon Sep 17 00:00:00 2001 From: yuhao900914 Date: Wed, 17 Nov 2021 11:44:36 -0800 Subject: [PATCH 09/10] patch: make groupIdentify and identify api enable to pass outOfSession info --- src/amplitude-client.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index 30297486..77c877fb 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -1114,7 +1114,7 @@ var _convertProxyObjectToRealObject = function _convertProxyObjectToRealObject(i * var identify = new amplitude.Identify().set('colors', ['rose', 'gold']).add('karma', 1).setOnce('sign_up_date', '2016-03-31'); * amplitude.identify(identify); */ -AmplitudeClient.prototype.identify = function (identify_obj, opt_callback, opt_error_callback) { +AmplitudeClient.prototype.identify = function (identify_obj, opt_callback, opt_error_callback, outOfSession) { if (this._shouldDeferCall()) { return this._q.push(['identify'].concat(Array.prototype.slice.call(arguments, 0))); } @@ -1142,6 +1142,7 @@ AmplitudeClient.prototype.identify = function (identify_obj, opt_callback, opt_e null, opt_callback, opt_error_callback, + outOfSession, ); } else { _logErrorsWithCallbacks(opt_callback, opt_error_callback, 0, 'No request sent', { @@ -1162,6 +1163,7 @@ AmplitudeClient.prototype.groupIdentify = function ( identify_obj, opt_callback, opt_error_callback, + outOfSession, ) { if (this._shouldDeferCall()) { return this._q.push(['groupIdentify'].concat(Array.prototype.slice.call(arguments, 0))); @@ -1205,6 +1207,7 @@ AmplitudeClient.prototype.groupIdentify = function ( null, opt_callback, opt_error_callback, + outOfSession, ); } else { _logErrorsWithCallbacks(opt_callback, opt_error_callback, 0, 'No request sent', { From f7febab09e373bf3aa37b2a3ab0332fde51e55ac Mon Sep 17 00:00:00 2001 From: yuhao900914 Date: Wed, 17 Nov 2021 14:22:22 -0800 Subject: [PATCH 10/10] test: add test and chagnes based on comments --- src/amplitude-client.js | 8 ++++++-- test/amplitude-client.js | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index 77c877fb..9d393a8b 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -1912,8 +1912,12 @@ AmplitudeClient.prototype.__VERSION__ = function getVersion() { * @param {string} version - Custom library version */ AmplitudeClient.prototype.setLibrary = function setLibrary(name, version) { - if (name) this.options.library.name = name; - if (version) this.options.library.version = version; + if (name !== null && typeof name !== 'undefined') { + this.options.library.name = name; + } + if (version !== null && typeof version !== 'undefined') { + this.options.library.version = version; + } }; /** diff --git a/test/amplitude-client.js b/test/amplitude-client.js index 78bb6018..ee6f0897 100644 --- a/test/amplitude-client.js +++ b/test/amplitude-client.js @@ -1526,6 +1526,15 @@ describe('AmplitudeClient', function () { assert.equal(value, 0); assert.equal(message, 'No request sent'); }); + + it('should out of session', function () { + var identify = new Identify().set('prop1', 'value1'); + amplitude.groupIdentify(group_type, group_name, identify, true); + + assert.lengthOf(amplitude._unsentEvents, 0); + assert.lengthOf(amplitude._unsentIdentifys, 1); + assert.deepEqual(amplitude._unsentIdentifys[0].event.session_id, -1); + }); }); describe('logEvent with tracking options', function () { @@ -2668,6 +2677,16 @@ describe('AmplitudeClient', function () { assert.deepEqual(amplitude._unsentIdentifys[0].event.user_properties, { $set: { 10: 10 } }); }); + it('should out of session', function () { + var identify = new Identify().set(10, 10); + amplitude.init(apiKey); + amplitude.identify(identify, null, null, true); + + assert.lengthOf(amplitude._unsentEvents, 0); + assert.lengthOf(amplitude._unsentIdentifys, 1); + assert.deepEqual(amplitude._unsentIdentifys[0].event.session_id, -1); + }); + it('should ignore event and user properties with too many items', function () { amplitude.init(apiKey, null, { batchEvents: true, eventUploadThreshold: 2 }); var eventProperties = {};