diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50077f24..38b11e9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
## Unreleased
+### 3.0.0 (May 27, 2016)
+
+* Add support for logging events to multiple Amplitude apps. **Note this is a major update, and may break backwards compatability.** See [Readme](https://github.com/amplitude/Amplitude-Javascript#300-update-and-logging-events-to-multiple-amplitude-apps) for details.
+* Init callback now passes the Amplitude instance as an argument to the callback function.
+
### 2.13.0 (May 26, 2016)
* Update our fork of [UAParser.js](https://github.com/faisalman/ua-parser-js) from v0.7.7 to v0.7.10. This will improve the resolution of user agent strings to device and OS information.
diff --git a/README.md b/README.md
index 8995c093..975bb360 100644
--- a/README.md
+++ b/README.md
@@ -11,18 +11,20 @@ This Readme will guide you through using Amplitude's Javascript SDK to track use
```html
```
@@ -32,11 +34,83 @@ This Readme will guide you through using Amplitude's Javascript SDK to track use
4. To track an event anywhere on the page, call:
```javascript
- amplitude.logEvent('EVENT_IDENTIFIER_HERE');
+ amplitude.getInstance().logEvent('EVENT_IDENTIFIER_HERE');
```
5. Events are uploaded immediately and saved to the browser's local storage until the server confirms the upload. After calling logEvent in your app, you will immediately see data appear on Amplitude.
+# 3.0.0 Update and Logging Events to Multiple Amplitude Apps #
+
+Version 3.0.0 is a major update that brings support for logging events to multiple Amplitude apps (multiple API keys). **Note: this change is not 100% backwards compatible and may break on your setup.** See the subsection below on backwards compatibility.
+
+### API Changes and Backwards Compatibility ###
+
+The `amplitude` object now maintains one or more instances, where each instance has separate apiKey, userId, deviceId, and settings. Having separate instances allows for the logging of events to separate Amplitude apps.
+
+The most important API change is how you interact with the `amplitude` object. Before v3.0.0, you would directly call `amplitude.logEvent('EVENT_NAME')`. Now the preferred way is to call functions on an instance as follows: `amplitude.getInstance('INSTANCE_NAME').logEvent('EVENT_NAME')` This notation will be familiar to people who have used our iOS and Android SDKs.
+
+Most people upgrading to v3.0.0 will continue logging events to a single Amplitude app. To make this transition as smooth as possible, we try to maintain backwards compatibility for most things by having a `default instance`, which you can fetch by calling `amplitude.getInstance()` with no instance name. The code examples in this README have been updated to follow this use case. All of the existing event data, existing settings, and returning users (users who already have a deviceId and/or userId) will stay with the `default instance`. You should initialize the default instance with your existing apiKey.
+
+All of the *public* methods of `amplitude` should still work as expected, as they have all been mapped to their equivalent on the default instance.
+
+For example `amplitude.init('API_KEY')` should still work as it has been mapped to `amplitude.getInstance().init('API_KEY')`.
+
+Likewise `amplitude.logEvent('EVENT_NAME')` should still work as it has been mapped to `amplitude.getInstance().logEvent('EVENT_NAME')`.
+
+`amplitude.options` will still work and will map to `amplitude.getInstance().options`, if for example you were using it to access the deviceId.
+
+**Things that will break:** if you were accessing private properties on the `amplitude` object, those will no longer work, e.g. `amplitude._sessionId`, `amplitude._eventId`, etc. You will need to update those references to fetch from the default instance like so: `amplitude.getInstance()._sessionId` and `amplitude.getInstance()._eventId`, etc.
+
+### Logging Events to a Single Amplitude App / API Key (Preferred Method) ###
+
+If you want to continue logging events to a single Amplitude App (and a single API key), then you should call functions on the `default instance`, which you can fetch by calling `amplitude.getInstance()` with no instance name. Here is an example:
+
+```javascript
+amplitude.getInstance().init('API_KEY');
+amplitude.getInstance().logEvent('EVENT_NAME');
+```
+
+You can also assign instances to a variable and call functions on that variable like so:
+
+```javascript
+var app = amplitude.getInstance();
+app.init('API_KEY');
+app.logEvent('EVENT_NAME');
+```
+
+### Logging Events to Multiple Amplitude Apps ###
+
+If you want to log events to multiple Amplitude apps, you will need to have separate instances for each Amplitude app. As mentioned earlier, each instance will allow for completely independent apiKeys, userIds, deviceIds, and settings.
+
+You need to assign a name to each Amplitude app / instance, and use that name consistently when fetching that instance to call functions. **IMPORTANT: Once you have chosen a name for that instance you cannot change it.** Every instance's data and settings are tied to its name, and you will need to continue using that instance name for all future versions of your app to maintain data continuity, so chose your instance names wisely. Note these names do not need to be the names of your apps in the Amplitude dashboards, but they need to remain consistent throughout your code. You also need to be sure that each instance is initialized with the correct apiKey.
+
+Instance names must be nonnull and nonempty strings. Names are case-insensitive. You can fetch each instance by name by calling `amplitude.getInstance('INSTANCE_NAME')`.
+
+As mentioned before, each new instance created will have its own apiKey, userId, deviceId, and settings. **You will have to reconfigure all the settings for each instance.** This gives you the freedom to have different settings for each instance.
+
+### Example of how to Set Up and Log Events to Two Separate Apps ###
+```javascript
+amplitude.getInstance().init('12345', null, {batchEvents: true}); // existing app, existing settings, and existing API key
+amplitude.getInstance('new_app').init('67890', null, {includeReferrer: true}); // new app, new API key
+
+amplitude.getInstance('new_app').setUserId('joe@gmail.com'); // need to reconfigure new app
+amplitude.getInstance('new_app').setUserProperties({'gender':'male'});
+amplitude.getInstance('new_app').logEvent('Clicked');
+
+var identify = new amplitude.Identify().add('karma', 1);
+amplitude.getInstance().identify(identify);
+amplitude.getInstance().logEvent('Viewed Home Page');
+```
+
+### Synchronizing Device Ids Between Apps ###
+
+As mentioned before, each instance will have its own deviceId. If you want your apps to share the same deviceId, you can do so *after init* via the `getDeviceId` and `setDeviceId` methods. Here's an example of how to copy the existing deviceId to the `new_app` instance:
+
+```javascript
+var deviceId = amplitude.getInstance().getDeviceId(); // existing deviceId
+amplitude.getInstance('new_app').setDeviceId(deviceId); // transferring existing deviceId to new_app
+```
+
# Tracking Events #
It's important to think about what types of events you care about as a developer. You should aim to track between 20 and 200 types of events on your site. Common event types are actions the user initiates (such as pressing a button) and events you want the user to complete (such as filling out a form, completing a level, or making a payment).
@@ -57,13 +131,13 @@ Anything past the above thresholds will not be visualized. **Note that the raw d
If your app has its own login system that you want to track users with, you can call `setUserId` at any time:
```javascript
-amplitude.setUserId('USER_ID_HERE');
+amplitude.getInstance().setUserId('USER_ID_HERE');
```
You can also add the user ID as an argument to the `init` call:
```javascript
-amplitude.init('YOUR_API_KEY_HERE', 'USER_ID_HERE');
+amplitude.getInstance().init('YOUR_API_KEY_HERE', 'USER_ID_HERE');
```
### Logging Out and Anonymous Users ###
@@ -71,8 +145,8 @@ amplitude.init('YOUR_API_KEY_HERE', 'USER_ID_HERE');
A user's data will be merged on the backend so that any events up to that point from the same browser will be tracked under the same user. Note: if a user logs out, or you want to log the events under an anonymous user, you need to do 2 things: 1) set the userId to `null` 2) regenerate a new deviceId. After doing that, events coming from the current user will appear as a brand new user in Amplitude dashboards. Note if you choose to do this, then you won't be able to see that the 2 users were using the same browser/device.
```javascript
-amplitude.setUserId(null); // not string 'null'
-amplitude.regenerateDeviceId();
+amplitude.getInstance().setUserId(null); // not string 'null'
+amplitude.getInstance().regenerateDeviceId();
```
# Setting Event Properties #
@@ -82,7 +156,7 @@ You can attach additional data to any event by passing a Javascript object as th
```javascript
var eventProperties = {};
eventProperties.key = 'value';
-amplitude.logEvent('EVENT_IDENTIFIER_HERE', eventProperties);
+amplitude.getInstance().logEvent('EVENT_IDENTIFIER_HERE', eventProperties);
```
Alternatively, you can set multiple event properties like this:
@@ -92,7 +166,7 @@ var eventProperties = {
'age': 20,
'key': 'value'
};
-amplitude.logEvent('EVENT_IDENTIFIER_HERE', eventProperties);
+amplitude.getInstance().logEvent('EVENT_IDENTIFIER_HERE', eventProperties);
```
# User Properties and User Property Operations #
@@ -103,45 +177,45 @@ The SDK supports the operations `set`, `setOnce`, `unset`, and `add` on individu
```javascript
var identify = new amplitude.Identify().set('gender', 'female').set('age', 20);
- amplitude.identify(identify);
+ amplitude.getInstance().identify(identify);
```
2. `setOnce`: this sets the value of a user property only once. Subsequent `setOnce` operations on that user property will be ignored. In the following example, `sign_up_date` will be set once to `08/24/2015`, and the following setOnce to `09/14/2015` will be ignored:
```javascript
var identify = new amplitude.Identify().setOnce('sign_up_date', '08/24/2015');
- amplitude.identify(identify);
+ amplitude.getInstance().identify(identify);
var identify = new amplitude.Identify().setOnce('sign_up_date', '09/14/2015');
- amplitude.identify(identify);
+ amplitude.getInstance().identify(identify);
```
3. `unset`: this will unset and remove a user property.
```javascript
var identify = new amplitude.Identify().unset('gender').unset('age');
- amplitude.identify(identify);
+ amplitude.getInstance().identify(identify);
```
4. `add`: this will increment a user property by some numerical value. If the user property does not have a value set yet, it will be initialized to 0 before being incremented.
```javascript
var identify = new amplitude.Identify().add('karma', 1).add('friends', 1);
- amplitude.identify(identify);
+ amplitude.getInstance().identify(identify);
```
5. `append`: this will append a value or values to a user property. If the user property does not have a value set yet, it will be initialized to an empty list before the new values are appended. If the user property has an existing value and it is not a list, it will be converted into a list with the new value appended.
```javascript
var identify = new amplitude.Identify().append('ab-tests', 'new-user-test').append('some_list', [1, 2, 3, 4, 'values']);
- amplitude.identify(identify);
+ amplitude.getInstance().identify(identify);
```
6. `prepend`: this will prepend a value or values to a user property. Prepend means inserting the value(s) at the front of a given list. If the user property does not have a value set yet, it will be initialized to an empty list before the new values are prepended. If the user property has an existing value and it is not a list, it will be converted into a list with the new value prepended.
```javascript
var identify = new amplitude.Identify().prepend('ab-tests', 'new-user-test').prepend('some_list', [1, 2, 3, 4, 'values']);
- amplitude.identify(identify);
+ amplitude.getInstance().identify(identify);
```
Note: if a user property is used in multiple operations on the same `Identify` object, only the first operation will be saved, and the rest will be ignored. In this example, only the set operation will be saved, and the add and unset will be ignored:
@@ -151,7 +225,7 @@ var identify = new amplitude.Identify()
.set('karma', 10)
.add('karma', 1)
.unset('karma');
-amplitude.identify(identify);
+amplitude.getInstance().identify(identify);
```
### Arrays in User Properties ###
@@ -163,7 +237,7 @@ var identify = new amplitude.Identify()
.set('colors', ['rose', 'gold'])
.append('ab-tests', 'campaign_a')
.append('existing_list', [4, 5]);
-amplitude.identify(identify);
+amplitude.getInstance().identify(identify);
```
### Setting Multiple Properties with `setUserProperties` ###
@@ -175,7 +249,7 @@ var userProperties = {
gender: 'female',
age: 20
};
-amplitude.setUserProperties(userProperties);
+amplitude.getInstance().setUserProperties(userProperties);
```
### Clearing User Properties ###
@@ -183,7 +257,7 @@ amplitude.setUserProperties(userProperties);
You may use `clearUserProperties` to clear all user properties at once. Note: the result is irreversible!
```javascript
-amplitude.clearUserProperties();
+amplitude.getInstance().clearUserProperties();
```
# Tracking Revenue #
@@ -193,7 +267,7 @@ The preferred method of tracking revenue for a user now is to use `logRevenueV2(
Each time a user generates revenue, you create a `Revenue` object and fill out the revenue properties:
```javascript
var revenue = new amplitude.Revenue().setProductId('com.company.productId').setPrice(3.99).setQuantity(3);
-amplitude.logRevenueV2(revenue);
+amplitude.getInstance().logRevenueV2(revenue);
```
`productId` and `price` are required fields. `quantity` defaults to 1 if not specified. Each field has a corresponding `set` method (for example `setProductId`, `setQuantity`, etc). This table describes the different fields available:
@@ -217,14 +291,14 @@ The existing `logRevenue` methods still work but are deprecated. Fields such as
You can turn off logging for a given user:
```javascript
-amplitude.setOptOut(true);
+amplitude.getInstance().setOptOut(true);
```
No events will be saved or sent to the server while opt out is enabled. The opt out
setting will persist across page loads. Calling
```javascript
-amplitude.setOptOut(false);
+amplitude.getInstance().setOptOut(false);
```
will reenable logging.
@@ -234,7 +308,7 @@ will reenable logging.
You can configure Amplitude by passing an object as the third argument to the `init`:
```javascript
-amplitude.init('YOUR_API_KEY_HERE', null, {
+amplitude.getInstance().init('YOUR_API_KEY_HERE', null, {
// optional configuration options
saveEvents: true,
includeUtm: true,
@@ -275,8 +349,8 @@ When setting groups you need to define a `groupType` and `groupName`(s). In the
You can use `setGroup(groupType, groupName)` to designate which groups a user belongs to. Note: this will also set the `groupType`: `groupName` as a user property. **This will overwrite any existing groupName value set for that user's groupType, as well as the corresponding user property value.** `groupType` is a string, and `groupName` can be either a string or an array of strings to indicate a user being in multiple groups (for example Joe is in orgId 10 and 16, so the `groupName` would be [10, 16]).
```javascript
-amplitude.setGroup('orgId', '15');
-amplitude.setGroup('sport', ['soccer', 'tennis']);
+amplitude.getInstance().setGroup('orgId', '15');
+amplitude.getInstance().setGroup('sport', ['soccer', 'tennis']);
```
You can also use `logEventWithGroups` to set event-level groups, meaning the group designation only applies for the specific event being logged and does not persist on the user (unless you explicitly set it with `setGroup`).
@@ -286,21 +360,21 @@ var eventProperties = {
'key': 'value'
}
-amplitude.logEventWithGroups('initialize_game', eventProperties, {'sport': 'soccer'});
+amplitude.getInstance().logEventWithGroups('initialize_game', eventProperties, {'sport': 'soccer'});
```
### Setting Version Name ###
By default, no version name is set. You can specify a version name to distinguish between different versions of your site by calling `setVersionName`:
```javascript
-amplitude.setVersionName('VERSION_NAME_HERE');
+amplitude.getInstance().setVersionName('VERSION_NAME_HERE');
```
### Custom Device Ids ###
Device IDs are generated randomly, although you can define a custom device ID setting it as a configuration option or by calling:
```javascript
-amplitude.setDeviceId('CUSTOM_DEVICE_ID');
+amplitude.getInstance().setDeviceId('CUSTOM_DEVICE_ID');
```
**Note: this is not recommended unless you really know what you are doing** (like if you have your own system for tracking user devices). Make sure the deviceId you set is sufficiently unique (we recommend something like a UUID - see `src/uuid.js` for an example of how to generate) to prevent conflicts with other devices in our system.
@@ -309,12 +383,12 @@ amplitude.setDeviceId('CUSTOM_DEVICE_ID');
You can pass a callback function to logEvent and identify, which will get called after receiving a response from the server:
```javascript
-amplitude.logEvent("EVENT_IDENTIFIER_HERE", null, callback_function);
+amplitude.getInstance().logEvent("EVENT_IDENTIFIER_HERE", null, callback_function);
```
```javascript
var identify = new amplitude.Identify().set('key', 'value');
-amplitude.identify(identify, callback_function);
+amplitude.getInstance().identify(identify, callback_function);
```
The status and response body from the server are passed to the callback function, which you might find useful. An example of a callback function which redirects the browser to another site after a response:
@@ -338,7 +412,7 @@ And then you would define a function that is called when the link is clicked lik
```javascript
var trackClickLinkA = function() {
- amplitude.logEvent('Clicked Link A', null, function() {
+ amplitude.getInstance().logEvent('Clicked Link A', null, function() {
window.location='LINK_A_URL';
});
};
@@ -347,11 +421,11 @@ var trackClickLinkA = function() {
In the case that `optOut` is true, then no event will be logged, but the callback will be called. In the case that `batchEvents` is true, if the batch requirements `eventUploadThreshold` and `eventUploadPeriodMillis` are not met when `logEvent` is called, then no request is sent, but the callback is still called. In these cases, the callback will be called with an input status of 0 and response 'No request sent'.
### Init Callbacks ###
-You can also pass a callback function to init, which will get called after the SDK finishes its asynchronous loading. *Note: no values are passed to the init callback function*:
+You can also pass a callback function to init, which will get called after the SDK finishes its asynchronous loading. *Note: the instance is passed as an argument to the callback*:
```javascript
-amplitude.init('YOUR_API_KEY_HERE', 'USER_ID_HERE', null, function() {
- console.log(amplitude.options.deviceId); // access Amplitude's deviceId after initialization
+amplitude.getInstance().init('YOUR_API_KEY_HERE', 'USER_ID_HERE', null, function(instance) {
+ console.log(instance.options.deviceId); // access Amplitude's deviceId after initialization
});
```
@@ -361,10 +435,10 @@ If you are using [RequireJS](http://requirejs.org/) to load your Javascript file
```html
```
@@ -375,19 +449,19 @@ You can also define the path in your RequireJS configuration like so:
```
@@ -399,5 +473,5 @@ You can also define the path in your RequireJS configuration like so:
2. Call SDK functions in Google Tag Manager using [Custom HTML tags](https://support.google.com/tagmanager/answer/6107167?hl=en) and adding Javascript in this form as the custom tag:
```html
-
+
```
diff --git a/amplitude-segment-snippet.min.js b/amplitude-segment-snippet.min.js
index 806fcd4b..4e63c623 100644
--- a/amplitude-segment-snippet.min.js
+++ b/amplitude-segment-snippet.min.js
@@ -1,8 +1,10 @@
-(function(e,t){var r=e.amplitude||{_q:[]};function n(e,t){e.prototype[t]=function(){
-this._q.push([t].concat(Array.prototype.slice.call(arguments,0)));return this}}var s=function(){
-this._q=[];return this};var i=["add","append","clearAll","prepend","set","setOnce","unset"];
-for(var o=0;o this.options.savedMaxCount) {
queue.splice(0, queue.length - this.options.savedMaxCount);
}
@@ -950,9 +1330,9 @@ Amplitude.prototype._limitEventsQueued = function _limitEventsQueued(queue) {
* @param {object} eventProperties - (optional) an object with string keys and values for the event properties.
* @param {Amplitude~eventCallback} opt_callback - (optional) a callback function to run after the event is logged.
* Note: the server response code and response body from the event upload are passed to the callback function.
- * @example amplitude.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15});
+ * @example amplitudeClient.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15});
*/
-Amplitude.prototype.logEvent = function logEvent(eventType, eventProperties, opt_callback) {
+AmplitudeClient.prototype.logEvent = function logEvent(eventType, eventProperties, opt_callback) {
if (!this._apiKeySet('logEvent()') || !utils.validateInput(eventType, 'eventType', 'string') ||
utils.isEmptyString(eventType)) {
if (type(opt_callback) === 'function') {
@@ -976,9 +1356,9 @@ Amplitude.prototype.logEvent = function logEvent(eventType, eventProperties, opt
* groupName can be a string or an array of strings.
* @param {Amplitude~eventCallback} opt_callback - (optional) a callback function to run after the event is logged.
* Note: the server response code and response body from the event upload are passed to the callback function.
- * @example amplitude.logEventWithGroups('Clicked Button', null, {'orgId': 24});
+ * @example amplitudeClient.logEventWithGroups('Clicked Button', null, {'orgId': 24});
*/
-Amplitude.prototype.logEventWithGroups = function(eventType, eventProperties, groups, opt_callback) {
+AmplitudeClient.prototype.logEventWithGroups = function(eventType, eventProperties, groups, opt_callback) {
if (!this._apiKeySet('logEventWithGroup()') ||
!utils.validateInput(eventType, 'eventType', 'string')) {
if (type(opt_callback) === 'function') {
@@ -1007,7 +1387,7 @@ var _isNumber = function _isNumber(n) {
* @example var revenue = new amplitude.Revenue().setProductId('productIdentifier').setPrice(10.99);
* amplitude.logRevenueV2(revenue);
*/
-Amplitude.prototype.logRevenueV2 = function logRevenueV2(revenue_obj) {
+AmplitudeClient.prototype.logRevenueV2 = function logRevenueV2(revenue_obj) {
if (!this._apiKeySet('logRevenueV2()')) {
return;
}
@@ -1034,9 +1414,9 @@ Amplitude.prototype.logRevenueV2 = function logRevenueV2(revenue_obj) {
* @param {number} price - price of revenue event
* @param {number} quantity - (optional) quantity of products in revenue event. If no quantity specified default to 1.
* @param {string} product - (optional) product identifier
- * @example amplitude.logRevenue(3.99, 1, 'product_1234');
+ * @example amplitudeClient.logRevenue(3.99, 1, 'product_1234');
*/
-Amplitude.prototype.logRevenue = function logRevenue(price, quantity, product) {
+AmplitudeClient.prototype.logRevenue = function 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');
@@ -1055,7 +1435,7 @@ Amplitude.prototype.logRevenue = function logRevenue(price, quantity, product) {
* Remove events in storage with event ids up to and including maxEventId.
* @private
*/
-Amplitude.prototype.removeEvents = function removeEvents(maxEventId, maxIdentifyId) {
+AmplitudeClient.prototype.removeEvents = function removeEvents(maxEventId, maxIdentifyId) {
_removeEvents(this, '_unsentEvents', maxEventId);
_removeEvents(this, '_unsentIdentifys', maxIdentifyId);
};
@@ -1086,7 +1466,7 @@ var _removeEvents = function _removeEvents(scope, eventQueue, maxId) {
* @param {Amplitude~eventCallback} callback - (optional) callback to run after events are sent.
* Note the server response code and response body are passed to the callback as input arguments.
*/
-Amplitude.prototype.sendEvents = function sendEvents(callback) {
+AmplitudeClient.prototype.sendEvents = function sendEvents(callback) {
if (!this._apiKeySet('sendEvents()') || this._sending || this.options.optOut || this._unsentCount() === 0) {
if (type(callback) === 'function') {
callback(0, 'No request sent');
@@ -1155,7 +1535,7 @@ Amplitude.prototype.sendEvents = function sendEvents(callback) {
* Merge unsent events and identifys together in sequential order based on their sequence number, for uploading.
* @private
*/
-Amplitude.prototype._mergeEventsAndIdentifys = function _mergeEventsAndIdentifys(numEvents) {
+AmplitudeClient.prototype._mergeEventsAndIdentifys = function _mergeEventsAndIdentifys(numEvents) {
// coalesce events from both queues
var eventsToSend = [];
var eventIndex = 0;
@@ -1214,7 +1594,7 @@ Amplitude.prototype._mergeEventsAndIdentifys = function _mergeEventsAndIdentifys
* @public
* @deprecated
*/
-Amplitude.prototype.setGlobalUserProperties = function setGlobalUserProperties(userProperties) {
+AmplitudeClient.prototype.setGlobalUserProperties = function setGlobalUserProperties(userProperties) {
this.setUserProperties(userProperties);
};
@@ -1224,13 +1604,14 @@ Amplitude.prototype.setGlobalUserProperties = function setGlobalUserProperties(u
* @returns {number} version number
* @example var amplitudeVersion = amplitude.__VERSION__;
*/
-Amplitude.prototype.__VERSION__ = version;
+AmplitudeClient.prototype.__VERSION__ = version;
-module.exports = Amplitude;
+module.exports = AmplitudeClient;
-}, {"./constants":3,"./cookiestorage":4,"./utm":5,"./identify":6,"json":7,"./localstorage":8,"JavaScript-MD5":9,"object":10,"./xhr":11,"./revenue":12,"./type":13,"ua-parser-js":14,"./utils":15,"./uuid":16,"./version":17,"./options":18}],
-3: [function(require, module, exports) {
+}, {"./constants":4,"./cookiestorage":12,"./utm":13,"./identify":5,"json":14,"./localstorage":15,"JavaScript-MD5":16,"object":6,"./xhr":17,"./revenue":7,"./type":8,"ua-parser-js":18,"./utils":9,"./uuid":19,"./version":10,"./options":11}],
+4: [function(require, module, exports) {
module.exports = {
+ DEFAULT_INSTANCE: '$default_instance',
API_VERSION: 2,
MAX_STRING_LENGTH: 4096,
IDENTIFY_EVENT: '$identify',
@@ -1260,7 +1641,7 @@ module.exports = {
};
}, {}],
-4: [function(require, module, exports) {
+12: [function(require, module, exports) {
/* jshint -W020, unused: false, noempty: false, boss: true */
/*
@@ -1354,8 +1735,8 @@ cookieStorage.prototype.getStorage = function() {
module.exports = cookieStorage;
-}, {"./constants":3,"./cookie":19,"json":7,"./localstorage":8}],
-19: [function(require, module, exports) {
+}, {"./constants":4,"./cookie":20,"json":14,"./localstorage":15}],
+20: [function(require, module, exports) {
/*
* Cookie data
*/
@@ -1485,8 +1866,8 @@ module.exports = {
};
-}, {"./base64":20,"json":7,"top-domain":21,"./utils":15}],
-20: [function(require, module, exports) {
+}, {"./base64":21,"json":14,"top-domain":22,"./utils":9}],
+21: [function(require, module, exports) {
/* jshint bitwise: false */
/* global escape, unescape */
@@ -1585,8 +1966,8 @@ var Base64 = {
module.exports = Base64;
-}, {"./utf8":22}],
-22: [function(require, module, exports) {
+}, {"./utf8":23}],
+23: [function(require, module, exports) {
/* jshint bitwise: false */
/*
@@ -1646,7 +2027,7 @@ var UTF8 = {
module.exports = UTF8;
}, {}],
-7: [function(require, module, exports) {
+14: [function(require, module, exports) {
var json = window.JSON || {};
var stringify = json.stringify;
@@ -1656,8 +2037,8 @@ module.exports = parse && stringify
? JSON
: require('json-fallback');
-}, {"json-fallback":23}],
-23: [function(require, module, exports) {
+}, {"json-fallback":24}],
+24: [function(require, module, exports) {
/*
json2.js
2014-02-04
@@ -2147,7 +2528,7 @@ module.exports = parse && stringify
}());
}, {}],
-21: [function(require, module, exports) {
+22: [function(require, module, exports) {
/**
* Module dependencies.
@@ -2195,8 +2576,8 @@ function domain(url){
return match ? match[0] : '';
};
-}, {"url":24}],
-24: [function(require, module, exports) {
+}, {"url":25}],
+25: [function(require, module, exports) {
/**
* Parse the given `url`.
@@ -2281,7 +2662,7 @@ function port (protocol){
}
}, {}],
-15: [function(require, module, exports) {
+9: [function(require, module, exports) {
var constants = require('./constants');
var type = require('./type');
@@ -2476,8 +2857,8 @@ module.exports = {
validateProperties: validateProperties
};
-}, {"./constants":3,"./type":13}],
-13: [function(require, module, exports) {
+}, {"./constants":4,"./type":8}],
+8: [function(require, module, exports) {
/**
* toString ref.
* @private
@@ -2524,7 +2905,7 @@ module.exports = function(val){
};
}, {}],
-8: [function(require, module, exports) {
+15: [function(require, module, exports) {
/* jshint -W020, unused: false, noempty: false, boss: true */
/*
@@ -2628,7 +3009,7 @@ if (!localStorage) {
module.exports = localStorage;
}, {}],
-5: [function(require, module, exports) {
+13: [function(require, module, exports) {
var utils = require('./utils');
var getUtmParam = function getUtmParam(name, query) {
@@ -2671,8 +3052,8 @@ var getUtmData = function getUtmData(rawCookie, query) {
module.exports = getUtmData;
-}, {"./utils":15}],
-6: [function(require, module, exports) {
+}, {"./utils":9}],
+5: [function(require, module, exports) {
var type = require('./type');
var utils = require('./utils');
@@ -2858,8 +3239,8 @@ Identify.prototype._addOperation = function(operation, property, value) {
module.exports = Identify;
-}, {"./type":13,"./utils":15}],
-9: [function(require, module, exports) {
+}, {"./type":8,"./utils":9}],
+16: [function(require, module, exports) {
/*
* JavaScript MD5 1.0.1
* https://github.com/blueimp/JavaScript-MD5
@@ -3147,7 +3528,7 @@ module.exports = Identify;
}(this));
}, {}],
-10: [function(require, module, exports) {
+6: [function(require, module, exports) {
/**
* HOP ref.
@@ -3233,7 +3614,7 @@ exports.isEmpty = function(obj){
return 0 == exports.length(obj);
};
}, {}],
-11: [function(require, module, exports) {
+17: [function(require, module, exports) {
var querystring = require('querystring');
/*
@@ -3279,8 +3660,8 @@ Request.prototype.send = function(callback) {
module.exports = Request;
-}, {"querystring":25}],
-25: [function(require, module, exports) {
+}, {"querystring":26}],
+26: [function(require, module, exports) {
/**
* Module dependencies.
@@ -3355,8 +3736,8 @@ exports.stringify = function(obj){
return pairs.join('&');
};
-}, {"trim":26,"type":27}],
-26: [function(require, module, exports) {
+}, {"trim":27,"type":28}],
+27: [function(require, module, exports) {
exports = module.exports = trim;
@@ -3376,7 +3757,7 @@ exports.right = function(str){
};
}, {}],
-27: [function(require, module, exports) {
+28: [function(require, module, exports) {
/**
* toString ref.
*/
@@ -3425,7 +3806,7 @@ function isBuffer(obj) {
}
}, {}],
-12: [function(require, module, exports) {
+7: [function(require, module, exports) {
var constants = require('./constants');
var type = require('./type');
var utils = require('./utils');
@@ -3585,8 +3966,8 @@ Revenue.prototype._toJSONObject = function _toJSONObject() {
module.exports = Revenue;
-}, {"./constants":3,"./type":13,"./utils":15}],
-14: [function(require, module, exports) {
+}, {"./constants":4,"./type":8,"./utils":9}],
+18: [function(require, module, exports) {
/* jshint eqeqeq: false, forin: false */
/* global define */
@@ -4494,7 +4875,7 @@ module.exports = Revenue;
})(typeof window === 'object' ? window : this);
}, {}],
-16: [function(require, module, exports) {
+19: [function(require, module, exports) {
/* jshint bitwise: false, laxbreak: true */
/**
@@ -4528,11 +4909,11 @@ var uuid = function(a) {
module.exports = uuid;
}, {}],
-17: [function(require, module, exports) {
-module.exports = '2.13.0';
+10: [function(require, module, exports) {
+module.exports = '3.0.0';
}, {}],
-18: [function(require, module, exports) {
+11: [function(require, module, exports) {
var language = require('./language');
// default options
@@ -4557,8 +4938,8 @@ module.exports = {
eventUploadPeriodMillis: 30 * 1000, // 30s
};
-}, {"./language":28}],
-28: [function(require, module, exports) {
+}, {"./language":29}],
+29: [function(require, module, exports) {
var getLanguage = function() {
return (navigator && ((navigator.languages && navigator.languages[0]) ||
navigator.language || navigator.userLanguage)) || undefined;
diff --git a/amplitude.min.js b/amplitude.min.js
index d6bc9020..102804a8 100644
--- a/amplitude.min.js
+++ b/amplitude.min.js
@@ -1,3 +1,3 @@
-(function umd(require){if("object"==typeof exports){module.exports=require("1")}else if("function"==typeof define&&define.amd){define(function(){return require("1")})}else{this["amplitude"]=require("1")}})(function outer(modules,cache,entries){var global=function(){return this}();function require(name,jumped){if(cache[name])return cache[name].exports;if(modules[name])return call(name,require);throw new Error('cannot find module "'+name+'"')}function call(id,require){var m=cache[id]={exports:{}};var mod=modules[id];var name=mod[2];var fn=mod[0];fn.call(m.exports,function(req){var dep=modules[id][1][req];return require(dep?dep:req)},m,m.exports,outer,modules,cache,entries);if(name)cache[name]=cache[id];return cache[id].exports}for(var id in entries){if(entries[id]){global[entries[id]]=require(id)}else{require(id)}}require.duo=true;require.cache=cache;require.modules=modules;return require}({1:[function(require,module,exports){var Amplitude=require("./amplitude");var old=window.amplitude||{};var instance=new Amplitude;instance._q=old._q||[];module.exports=instance},{"./amplitude":2}],2:[function(require,module,exports){var Constants=require("./constants");var cookieStorage=require("./cookiestorage");var getUtmData=require("./utm");var Identify=require("./identify");var JSON=require("json");var localStorage=require("./localstorage");var md5=require("JavaScript-MD5");var object=require("object");var Request=require("./xhr");var Revenue=require("./revenue");var type=require("./type");var UAParser=require("ua-parser-js");var utils=require("./utils");var UUID=require("./uuid");var version=require("./version");var DEFAULT_OPTIONS=require("./options");var Amplitude=function Amplitude(){this._unsentEvents=[];this._unsentIdentifys=[];this._ua=new UAParser(navigator.userAgent).getResult();this.options=object.merge({},DEFAULT_OPTIONS);this.cookieStorage=(new cookieStorage).getStorage();this._q=[];this._sending=false;this._updateScheduled=false;this._eventId=0;this._identifyId=0;this._lastEventTime=null;this._newSession=false;this._sequenceNumber=0;this._sessionId=null};Amplitude.prototype.Identify=Identify;Amplitude.prototype.Revenue=Revenue;Amplitude.prototype.init=function init(apiKey,opt_userId,opt_config,opt_callback){if(type(apiKey)!=="string"||utils.isEmptyString(apiKey)){utils.log("Invalid apiKey. Please re-initialize with a valid apiKey");return}try{this.options.apiKey=apiKey;_parseConfig(this.options,opt_config);this.cookieStorage.options({expirationDays:this.options.cookieExpiration,domain:this.options.domain});this.options.domain=this.cookieStorage.options().domain;_upgradeCookeData(this);_loadCookieData(this);this.options.deviceId=type(opt_config)==="object"&&type(opt_config.deviceId)==="string"&&!utils.isEmptyString(opt_config.deviceId)&&opt_config.deviceId||this.options.deviceId||UUID()+"R";this.options.userId=type(opt_userId)==="string"&&!utils.isEmptyString(opt_userId)&&opt_userId||this.options.userId||null;var now=(new Date).getTime();if(!this._sessionId||!this._lastEventTime||now-this._lastEventTime>this.options.sessionTimeout){this._newSession=true;this._sessionId=now}this._lastEventTime=now;_saveCookieData(this);if(this.options.saveEvents){this._unsentEvents=this._loadSavedUnsentEvents(this.options.unsentKey);this._unsentIdentifys=this._loadSavedUnsentEvents(this.options.unsentIdentifyKey);for(var i=0;i0){options[key]=inputValue}};for(var key in config){if(config.hasOwnProperty(key)){parseValidateAndLoad(key)}}};Amplitude.prototype.runQueuedFunctions=function(){for(var i=0;i=this.options.eventUploadThreshold){this.sendEvents(callback);return true}if(!this._updateScheduled){this._updateScheduled=true;setTimeout(function(){this._updateScheduled=false;this.sendEvents()}.bind(this),this.options.eventUploadPeriodMillis)}return false};Amplitude.prototype._getFromStorage=function _getFromStorage(storage,key){return storage.getItem(key)};Amplitude.prototype._setInStorage=function _setInStorage(storage,key,value){storage.setItem(key,value)};var _upgradeCookeData=function _upgradeCookeData(scope){var cookieData=scope.cookieStorage.get(scope.options.cookieName);if(type(cookieData)==="object"&&cookieData.deviceId&&cookieData.sessionId&&cookieData.lastEventTime){return}var _getAndRemoveFromLocalStorage=function _getAndRemoveFromLocalStorage(key){var value=localStorage.getItem(key);localStorage.removeItem(key);return value};var apiKeySuffix=type(scope.options.apiKey)==="string"&&"_"+scope.options.apiKey.slice(0,6)||"";var localStorageDeviceId=_getAndRemoveFromLocalStorage(Constants.DEVICE_ID+apiKeySuffix);var localStorageUserId=_getAndRemoveFromLocalStorage(Constants.USER_ID+apiKeySuffix);var localStorageOptOut=_getAndRemoveFromLocalStorage(Constants.OPT_OUT+apiKeySuffix);if(localStorageOptOut!==null&&localStorageOptOut!==undefined){localStorageOptOut=String(localStorageOptOut)==="true"}var localStorageSessionId=parseInt(_getAndRemoveFromLocalStorage(Constants.SESSION_ID));var localStorageLastEventTime=parseInt(_getAndRemoveFromLocalStorage(Constants.LAST_EVENT_TIME));var localStorageEventId=parseInt(_getAndRemoveFromLocalStorage(Constants.LAST_EVENT_ID));var localStorageIdentifyId=parseInt(_getAndRemoveFromLocalStorage(Constants.LAST_IDENTIFY_ID));var localStorageSequenceNumber=parseInt(_getAndRemoveFromLocalStorage(Constants.LAST_SEQUENCE_NUMBER));var _getFromCookie=function _getFromCookie(key){return type(cookieData)==="object"&&cookieData[key]};scope.options.deviceId=_getFromCookie("deviceId")||localStorageDeviceId;scope.options.userId=_getFromCookie("userId")||localStorageUserId;scope._sessionId=_getFromCookie("sessionId")||localStorageSessionId||scope._sessionId;scope._lastEventTime=_getFromCookie("lastEventTime")||localStorageLastEventTime||scope._lastEventTime;scope._eventId=_getFromCookie("eventId")||localStorageEventId||scope._eventId;scope._identifyId=_getFromCookie("identifyId")||localStorageIdentifyId||scope._identifyId;scope._sequenceNumber=_getFromCookie("sequenceNumber")||localStorageSequenceNumber||scope._sequenceNumber;scope.options.optOut=localStorageOptOut||false;if(cookieData&&cookieData.optOut!==undefined&&cookieData.optOut!==null){scope.options.optOut=String(cookieData.optOut)==="true"}_saveCookieData(scope)};var _loadCookieData=function _loadCookieData(scope){var cookieData=scope.cookieStorage.get(scope.options.cookieName);if(type(cookieData)==="object"){if(cookieData.deviceId){scope.options.deviceId=cookieData.deviceId}if(cookieData.userId){scope.options.userId=cookieData.userId}if(cookieData.optOut!==null&&cookieData.optOut!==undefined){scope.options.optOut=cookieData.optOut}if(cookieData.sessionId){scope._sessionId=parseInt(cookieData.sessionId)}if(cookieData.lastEventTime){scope._lastEventTime=parseInt(cookieData.lastEventTime)}if(cookieData.eventId){scope._eventId=parseInt(cookieData.eventId)}if(cookieData.identifyId){scope._identifyId=parseInt(cookieData.identifyId)}if(cookieData.sequenceNumber){scope._sequenceNumber=parseInt(cookieData.sequenceNumber)}}};var _saveCookieData=function _saveCookieData(scope){scope.cookieStorage.set(scope.options.cookieName,{deviceId:scope.options.deviceId,userId:scope.options.userId,optOut:scope.options.optOut,sessionId:scope._sessionId,lastEventTime:scope._lastEventTime,eventId:scope._eventId,identifyId:scope._identifyId,sequenceNumber:scope._sequenceNumber})};Amplitude.prototype._initUtmData=function _initUtmData(queryParams,cookieParams){queryParams=queryParams||location.search;cookieParams=cookieParams||this.cookieStorage.get("__utmz");var utmProperties=getUtmData(cookieParams,queryParams);_sendUserPropertiesOncePerSession(this,Constants.UTM_PROPERTIES,utmProperties)};var _sendUserPropertiesOncePerSession=function _sendUserPropertiesOncePerSession(scope,storageKey,userProperties){if(type(userProperties)!=="object"||Object.keys(userProperties).length===0){return}var identify=new Identify;for(var key in userProperties){if(userProperties.hasOwnProperty(key)){identify.setOnce("initial_"+key,userProperties[key])}}var hasSessionStorage=utils.sessionStorageEnabled();if(hasSessionStorage&&!scope._getFromStorage(sessionStorage,storageKey)||!hasSessionStorage){for(var property in userProperties){if(userProperties.hasOwnProperty(property)){identify.set(property,userProperties[property])}}if(hasSessionStorage){scope._setInStorage(sessionStorage,storageKey,JSON.stringify(userProperties))}}scope.identify(identify)};Amplitude.prototype._getReferrer=function _getReferrer(){return document.referrer};Amplitude.prototype._getReferringDomain=function _getReferringDomain(referrer){if(utils.isEmptyString(referrer)){return null}var parts=referrer.split("/");if(parts.length>=3){return parts[2]}return null};Amplitude.prototype._saveReferrer=function _saveReferrer(referrer){if(utils.isEmptyString(referrer)){return}var referrerInfo={referrer:referrer,referring_domain:this._getReferringDomain(referrer)};_sendUserPropertiesOncePerSession(this,Constants.REFERRER,referrerInfo)};Amplitude.prototype.saveEvents=function saveEvents(){try{this._setInStorage(localStorage,this.options.unsentKey,JSON.stringify(this._unsentEvents))}catch(e){}try{this._setInStorage(localStorage,this.options.unsentIdentifyKey,JSON.stringify(this._unsentIdentifys))}catch(e){}};Amplitude.prototype.setDomain=function setDomain(domain){if(!utils.validateInput(domain,"domain","string")){return}try{this.cookieStorage.options({domain:domain});this.options.domain=this.cookieStorage.options().domain;_loadCookieData(this);_saveCookieData(this)}catch(e){utils.log(e)}};Amplitude.prototype.setUserId=function setUserId(userId){try{this.options.userId=userId!==undefined&&userId!==null&&""+userId||null;_saveCookieData(this)}catch(e){utils.log(e)}};Amplitude.prototype.setGroup=function(groupType,groupName){if(!this._apiKeySet("setGroup()")||!utils.validateInput(groupType,"groupType","string")||utils.isEmptyString(groupType)){return}var groups={};groups[groupType]=groupName;var identify=(new Identify).set(groupType,groupName);this._logEvent(Constants.IDENTIFY_EVENT,null,null,identify.userPropertiesOperations,groups,null)};Amplitude.prototype.setOptOut=function setOptOut(enable){if(!utils.validateInput(enable,"enable","boolean")){return}try{this.options.optOut=enable;_saveCookieData(this)}catch(e){utils.log(e)}};Amplitude.prototype.regenerateDeviceId=function regenerateDeviceId(){this.setDeviceId(UUID()+"R")};Amplitude.prototype.setDeviceId=function setDeviceId(deviceId){if(!utils.validateInput(deviceId,"deviceId","string")){return}try{if(!utils.isEmptyString(deviceId)){this.options.deviceId=""+deviceId;_saveCookieData(this)}}catch(e){utils.log(e)}};Amplitude.prototype.setUserProperties=function setUserProperties(userProperties){if(!this._apiKeySet("setUserProperties()")||!utils.validateInput(userProperties,"userProperties","object")){return}var identify=new Identify;for(var property in userProperties){if(userProperties.hasOwnProperty(property)){identify.set(property,userProperties[property])}}this.identify(identify)};Amplitude.prototype.clearUserProperties=function clearUserProperties(){if(!this._apiKeySet("clearUserProperties()")){return}var identify=new Identify;identify.clearAll();this.identify(identify)};var _convertProxyObjectToRealObject=function _convertProxyObjectToRealObject(instance,proxy){for(var i=0;i0){return this._logEvent(Constants.IDENTIFY_EVENT,null,null,identify_obj.userPropertiesOperations,null,opt_callback)}}else{utils.log("Invalid identify input type. Expected Identify object but saw "+type(identify_obj))}if(type(opt_callback)==="function"){opt_callback(0,"No request sent")}};Amplitude.prototype.setVersionName=function setVersionName(versionName){if(!utils.validateInput(versionName,"versionName","string")){return}this.options.versionName=versionName};Amplitude.prototype._logEvent=function _logEvent(eventType,eventProperties,apiProperties,userProperties,groups,callback){_loadCookieData(this);if(!eventType||this.options.optOut){if(type(callback)==="function"){callback(0,"No request sent")}return}try{var eventId;if(eventType===Constants.IDENTIFY_EVENT){eventId=this.nextIdentifyId()}else{eventId=this.nextEventId()}var sequenceNumber=this.nextSequenceNumber();var eventTime=(new Date).getTime();if(!this._sessionId||!this._lastEventTime||eventTime-this._lastEventTime>this.options.sessionTimeout){this._sessionId=eventTime}this._lastEventTime=eventTime;_saveCookieData(this);userProperties=userProperties||{};apiProperties=apiProperties||{};eventProperties=eventProperties||{};groups=groups||{};var event={device_id:this.options.deviceId,user_id:this.options.userId,timestamp:eventTime,event_id:eventId,session_id:this._sessionId||-1,event_type:eventType,version_name:this.options.versionName||null,platform:this.options.platform,os_name:this._ua.browser.name||null,os_version:this._ua.browser.major||null,device_model:this._ua.os.name||null,language:this.options.language,api_properties:apiProperties,event_properties:utils.truncate(utils.validateProperties(eventProperties)),user_properties:utils.truncate(utils.validateProperties(userProperties)),uuid:UUID(),library:{name:"amplitude-js",version:version},sequence_number:sequenceNumber,groups:utils.truncate(utils.validateGroups(groups))};if(eventType===Constants.IDENTIFY_EVENT){this._unsentIdentifys.push(event);this._limitEventsQueued(this._unsentIdentifys)}else{this._unsentEvents.push(event);this._limitEventsQueued(this._unsentEvents)}if(this.options.saveEvents){this.saveEvents()}if(!this._sendEventsIfReady(callback)&&type(callback)==="function"){callback(0,"No request sent")}return eventId}catch(e){utils.log(e)}};Amplitude.prototype._limitEventsQueued=function _limitEventsQueued(queue){if(queue.length>this.options.savedMaxCount){queue.splice(0,queue.length-this.options.savedMaxCount)}};Amplitude.prototype.logEvent=function logEvent(eventType,eventProperties,opt_callback){if(!this._apiKeySet("logEvent()")||!utils.validateInput(eventType,"eventType","string")||utils.isEmptyString(eventType)){if(type(opt_callback)==="function"){opt_callback(0,"No request sent")}return-1}return this._logEvent(eventType,eventProperties,null,null,null,opt_callback)};Amplitude.prototype.logEventWithGroups=function(eventType,eventProperties,groups,opt_callback){if(!this._apiKeySet("logEventWithGroup()")||!utils.validateInput(eventType,"eventType","string")){if(type(opt_callback)==="function"){opt_callback(0,"No request sent")}return-1}return this._logEvent(eventType,eventProperties,null,null,groups,opt_callback)};var _isNumber=function _isNumber(n){return!isNaN(parseFloat(n))&&isFinite(n)};Amplitude.prototype.logRevenueV2=function logRevenueV2(revenue_obj){if(!this._apiKeySet("logRevenueV2()")){return}if(type(revenue_obj)==="object"&&revenue_obj.hasOwnProperty("_q")){revenue_obj=_convertProxyObjectToRealObject(new Revenue,revenue_obj)}if(revenue_obj instanceof Revenue){if(revenue_obj&&revenue_obj._isValidRevenue()){return this.logEvent(Constants.REVENUE_EVENT,revenue_obj._toJSONObject())}}else{utils.log("Invalid revenue input type. Expected Revenue object but saw "+type(revenue_obj))}};Amplitude.prototype.logRevenue=function logRevenue(price,quantity,product){if(!this._apiKeySet("logRevenue()")||!_isNumber(price)||quantity!==undefined&&!_isNumber(quantity)){return-1}return this._logEvent(Constants.REVENUE_EVENT,{},{productId:product,special:"revenue_amount",quantity:quantity||1,price:price},null,null,null)};Amplitude.prototype.removeEvents=function removeEvents(maxEventId,maxIdentifyId){_removeEvents(this,"_unsentEvents",maxEventId);_removeEvents(this,"_unsentIdentifys",maxIdentifyId)};var _removeEvents=function _removeEvents(scope,eventQueue,maxId){if(maxId<0){return}var filteredEvents=[];for(var i=0;imaxId){filteredEvents.push(scope[eventQueue][i])}}scope[eventQueue]=filteredEvents};Amplitude.prototype.sendEvents=function sendEvents(callback){if(!this._apiKeySet("sendEvents()")||this._sending||this.options.optOut||this._unsentCount()===0){if(type(callback)==="function"){callback(0,"No request sent")}return}this._sending=true;var url=("https:"===window.location.protocol?"https":"http")+"://"+this.options.apiEndpoint+"/";var numEvents=Math.min(this._unsentCount(),this.options.uploadBatchSize);var mergedEvents=this._mergeEventsAndIdentifys(numEvents);var maxEventId=mergedEvents.maxEventId;var maxIdentifyId=mergedEvents.maxIdentifyId;var events=JSON.stringify(mergedEvents.eventsToSend);var uploadTime=(new Date).getTime();var data={client:this.options.apiKey,e:events,v:Constants.API_VERSION,upload_time:uploadTime,checksum:md5(Constants.API_VERSION+this.options.apiKey+events+uploadTime)};var scope=this;new Request(url,data).send(function(status,response){scope._sending=false;try{if(status===200&&response==="success"){scope.removeEvents(maxEventId,maxIdentifyId);if(scope.options.saveEvents){scope.saveEvents()}if(!scope._sendEventsIfReady(callback)&&type(callback)==="function"){callback(status,response)}}else if(status===413){if(scope.options.uploadBatchSize===1){scope.removeEvents(maxEventId,maxIdentifyId)}scope.options.uploadBatchSize=Math.ceil(numEvents/2);scope.sendEvents(callback)}else if(type(callback)==="function"){callback(status,response)}}catch(e){}})};Amplitude.prototype._mergeEventsAndIdentifys=function _mergeEventsAndIdentifys(numEvents){var eventsToSend=[];var eventIndex=0;var maxEventId=-1;var identifyIndex=0;var maxIdentifyId=-1;while(eventsToSend.length=this._unsentIdentifys.length;var noEvents=eventIndex>=this._unsentEvents.length;if(noEvents&&noIdentifys){utils.log("Merging Events and Identifys, less events and identifys than expected");break}else if(noIdentifys){event=this._unsentEvents[eventIndex++];maxEventId=event.event_id}else if(noEvents){event=this._unsentIdentifys[identifyIndex++];maxIdentifyId=event.event_id}else{if(!("sequence_number"in this._unsentEvents[eventIndex])||this._unsentEvents[eventIndex].sequence_number>2;enc2=(chr1&3)<<4|chr2>>4;enc3=(chr2&15)<<2|chr3>>6;enc4=chr3&63;if(isNaN(chr2)){enc3=enc4=64}else if(isNaN(chr3)){enc4=64}output=output+Base64._keyStr.charAt(enc1)+Base64._keyStr.charAt(enc2)+Base64._keyStr.charAt(enc3)+Base64._keyStr.charAt(enc4)}return output},decode:function(input){try{if(window.btoa&&window.atob){return decodeURIComponent(escape(window.atob(input)))}}catch(e){}return Base64._decode(input)},_decode:function(input){var output="";var chr1,chr2,chr3;var enc1,enc2,enc3,enc4;var i=0;input=input.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(i>4;chr2=(enc2&15)<<4|enc3>>2;chr3=(enc3&3)<<6|enc4;output=output+String.fromCharCode(chr1);if(enc3!==64){output=output+String.fromCharCode(chr2)}if(enc4!==64){output=output+String.fromCharCode(chr3)}}output=UTF8.decode(output);return output}};module.exports=Base64},{"./utf8":22}],22:[function(require,module,exports){var UTF8={encode:function(s){var utftext="";for(var n=0;n127&&c<2048){utftext+=String.fromCharCode(c>>6|192);utftext+=String.fromCharCode(c&63|128)}else{utftext+=String.fromCharCode(c>>12|224);utftext+=String.fromCharCode(c>>6&63|128);utftext+=String.fromCharCode(c&63|128)}}return utftext},decode:function(utftext){var s="";var i=0;var c=0,c1=0,c2=0;while(i191&&c<224){c1=utftext.charCodeAt(i+1);s+=String.fromCharCode((c&31)<<6|c1&63);i+=2}else{c1=utftext.charCodeAt(i+1);c2=utftext.charCodeAt(i+2);s+=String.fromCharCode((c&15)<<12|(c1&63)<<6|c2&63);i+=3}}return s}};module.exports=UTF8},{}],7:[function(require,module,exports){var json=window.JSON||{};var stringify=json.stringify;var parse=json.parse;module.exports=parse&&stringify?JSON:require("json-fallback")},{"json-fallback":23}],23:[function(require,module,exports){(function(){"use strict";var JSON=module.exports={};function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()}}var cx,escapable,gap,indent,meta,rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;iconstants.MAX_STRING_LENGTH?value.substring(0,constants.MAX_STRING_LENGTH):value}return value};var validateInput=function validateInput(input,name,expectedType){if(type(input)!==expectedType){log("Invalid "+name+" input type. Expected "+expectedType+" but received "+type(input));return false}return true};var validateProperties=function validateProperties(properties){var propsType=type(properties);if(propsType!=="object"){log("Error: invalid event properties format. Expecting Javascript object, received "+propsType+", ignoring");return{}}var copy={};for(var property in properties){if(!properties.hasOwnProperty(property)){continue}var key=property;var keyType=type(key);if(keyType!=="string"){key=String(key);log("WARNING: Non-string property key, received type "+keyType+', coercing to string "'+key+'"')}var value=validatePropertyValue(key,properties[property]);if(value===null){continue}copy[key]=value}return copy};var invalidValueTypes=["null","nan","undefined","function","arguments","regexp","element"];var validatePropertyValue=function validatePropertyValue(key,value){var valueType=type(value);if(invalidValueTypes.indexOf(valueType)!==-1){log('WARNING: Property key "'+key+'" with invalid value type '+valueType+", ignoring");value=null}else if(valueType==="error"){value=String(value);log('WARNING: Property key "'+key+'" with value type error, coercing to '+value)}else if(valueType==="array"){var arrayCopy=[];for(var i=0;i0){if(!this.userPropertiesOperations.hasOwnProperty(AMP_OP_CLEAR_ALL)){utils.log("Need to send $clearAll on its own Identify object without any other operations, skipping $clearAll")}return this}this.userPropertiesOperations[AMP_OP_CLEAR_ALL]="-";return this};Identify.prototype.prepend=function(property,value){this._addOperation(AMP_OP_PREPEND,property,value);return this};Identify.prototype.set=function(property,value){this._addOperation(AMP_OP_SET,property,value);return this};Identify.prototype.setOnce=function(property,value){this._addOperation(AMP_OP_SET_ONCE,property,value);return this};Identify.prototype.unset=function(property){this._addOperation(AMP_OP_UNSET,property,"-");return this};Identify.prototype._addOperation=function(operation,property,value){if(this.userPropertiesOperations.hasOwnProperty(AMP_OP_CLEAR_ALL)){utils.log("This identify already contains a $clearAll operation, skipping operation "+operation);return}if(this.properties.indexOf(property)!==-1){utils.log('User property "'+property+'" already used in this identify, skipping operation '+operation);return}if(!this.userPropertiesOperations.hasOwnProperty(operation)){this.userPropertiesOperations[operation]={}}this.userPropertiesOperations[operation][property]=value;this.properties.push(property)};module.exports=Identify},{"./type":13,"./utils":15}],9:[function(require,module,exports){(function($){"use strict";function safe_add(x,y){var lsw=(x&65535)+(y&65535),msw=(x>>16)+(y>>16)+(lsw>>16);return msw<<16|lsw&65535}function bit_rol(num,cnt){return num<>>32-cnt}function md5_cmn(q,a,b,x,s,t){return safe_add(bit_rol(safe_add(safe_add(a,q),safe_add(x,t)),s),b)}function md5_ff(a,b,c,d,x,s,t){return md5_cmn(b&c|~b&d,a,b,x,s,t)}function md5_gg(a,b,c,d,x,s,t){return md5_cmn(b&d|c&~d,a,b,x,s,t)}function md5_hh(a,b,c,d,x,s,t){return md5_cmn(b^c^d,a,b,x,s,t)}function md5_ii(a,b,c,d,x,s,t){return md5_cmn(c^(b|~d),a,b,x,s,t)}function binl_md5(x,len){x[len>>5]|=128<>>9<<4)+14]=len;var i,olda,oldb,oldc,oldd,a=1732584193,b=-271733879,c=-1732584194,d=271733878;for(i=0;i>5]>>>i%32&255)}return output}function rstr2binl(input){var i,output=[];output[(input.length>>2)-1]=undefined;for(i=0;i>5]|=(input.charCodeAt(i/8)&255)<16){bkey=binl_md5(bkey,key.length*8)}for(i=0;i<16;i+=1){ipad[i]=bkey[i]^909522486;opad[i]=bkey[i]^1549556828}hash=binl_md5(ipad.concat(rstr2binl(data)),512+data.length*8);return binl2rstr(binl_md5(opad.concat(hash),512+128))}function rstr2hex(input){var hex_tab="0123456789abcdef",output="",x,i;for(i=0;i>>4&15)+hex_tab.charAt(x&15)}return output}function str2rstr_utf8(input){return unescape(encodeURIComponent(input))}function raw_md5(s){return rstr_md5(str2rstr_utf8(s))}function hex_md5(s){return rstr2hex(raw_md5(s))}function raw_hmac_md5(k,d){return rstr_hmac_md5(str2rstr_utf8(k),str2rstr_utf8(d))}function hex_hmac_md5(k,d){return rstr2hex(raw_hmac_md5(k,d))}function md5(string,key,raw){if(!key){if(!raw){return hex_md5(string)}return raw_md5(string)}if(!raw){return hex_hmac_md5(key,string)}return raw_hmac_md5(key,string)}if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports){exports=module.exports=md5}exports.md5=md5}else{if(typeof define==="function"&&define.amd){define(function(){return md5})}else{$.md5=md5}}})(this)},{}],10:[function(require,module,exports){var has=Object.prototype.hasOwnProperty;exports.keys=Object.keys||function(obj){var keys=[];for(var key in obj){if(has.call(obj,key)){keys.push(key)}}return keys};exports.values=function(obj){var vals=[];for(var key in obj){if(has.call(obj,key)){vals.push(obj[key])}}return vals};exports.merge=function(a,b){for(var key in b){if(has.call(b,key)){a[key]=b[key]}}return a};exports.length=function(obj){return exports.keys(obj).length};exports.isEmpty=function(obj){return 0==exports.length(obj)}},{}],11:[function(require,module,exports){var querystring=require("querystring");var Request=function(url,data){this.url=url;this.data=data||{}};Request.prototype.send=function(callback){var isIE=window.XDomainRequest?true:false;if(isIE){var xdr=new window.XDomainRequest;xdr.open("POST",this.url,true);xdr.onload=function(){callback(200,xdr.responseText)};xdr.onerror=function(){if(xdr.responseText==="Request Entity Too Large"){callback(413,xdr.responseText)}else{callback(500,xdr.responseText)}};xdr.ontimeout=function(){};xdr.onprogress=function(){};xdr.send(querystring.stringify(this.data))}else{var xhr=new XMLHttpRequest;xhr.open("POST",this.url,true);xhr.onreadystatechange=function(){if(xhr.readyState===4){callback(xhr.status,xhr.responseText)}};xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");xhr.send(querystring.stringify(this.data))}};module.exports=Request},{querystring:25}],25:[function(require,module,exports){var encode=encodeURIComponent;var decode=decodeURIComponent;var trim=require("trim");var type=require("type");exports.parse=function(str){if("string"!=typeof str)return{};str=trim(str);if(""==str)return{};if("?"==str.charAt(0))str=str.slice(1);var obj={};var pairs=str.split("&");for(var i=0;i0){if(q.length==2){if(typeof q[1]==FUNC_TYPE){result[q[0]]=q[1].call(this,match)}else{result[q[0]]=q[1]}}else if(q.length==3){if(typeof q[1]===FUNC_TYPE&&!(q[1].exec&&q[1].test)){result[q[0]]=match?q[1].call(this,match,q[2]):undefined}else{result[q[0]]=match?match.replace(q[1],q[2]):undefined}}else if(q.length==4){result[q[0]]=match?q[3].call(this,match.replace(q[1],q[2])):undefined}}else{result[q]=match?match:undefined}}}}i+=2}return result},str:function(str,map){for(var i in map){if(typeof map[i]===OBJ_TYPE&&map[i].length>0){for(var j=0;j