mixpanel-browser 2.43.0 → 2.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ define(function () { 'use strict';
2
2
 
3
3
  var Config = {
4
4
  DEBUG: false,
5
- LIB_VERSION: '2.43.0'
5
+ LIB_VERSION: '2.45.0'
6
6
  };
7
7
 
8
8
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
@@ -36,11 +36,11 @@ define(function () { 'use strict';
36
36
  var toString = ObjProto.toString;
37
37
  var hasOwnProperty = ObjProto.hasOwnProperty;
38
38
  var windowConsole = window$1.console;
39
- var navigator$1 = window$1.navigator;
39
+ var navigator = window$1.navigator;
40
40
  var document$1 = window$1.document;
41
41
  var windowOpera = window$1.opera;
42
42
  var screen = window$1.screen;
43
- var userAgent = navigator$1.userAgent;
43
+ var userAgent = navigator.userAgent;
44
44
  var nativeBind = FuncProto.bind;
45
45
  var nativeForEach = ArrayProto.forEach;
46
46
  var nativeIndexOf = ArrayProto.indexOf;
@@ -152,14 +152,6 @@ define(function () { 'use strict';
152
152
  return bound;
153
153
  };
154
154
 
155
- _.bind_instance_methods = function(obj) {
156
- for (var func in obj) {
157
- if (typeof(obj[func]) === 'function') {
158
- obj[func] = _.bind(obj[func], obj);
159
- }
160
- }
161
- };
162
-
163
155
  /**
164
156
  * @param {*=} obj
165
157
  * @param {function(...*)=} iterator
@@ -188,19 +180,6 @@ define(function () { 'use strict';
188
180
  }
189
181
  };
190
182
 
191
- _.escapeHTML = function(s) {
192
- var escaped = s;
193
- if (escaped && _.isString(escaped)) {
194
- escaped = escaped
195
- .replace(/&/g, '&')
196
- .replace(/</g, '&lt;')
197
- .replace(/>/g, '&gt;')
198
- .replace(/"/g, '&quot;')
199
- .replace(/'/g, '&#039;');
200
- }
201
- return escaped;
202
- };
203
-
204
183
  _.extend = function(obj) {
205
184
  _.each(slice.call(arguments, 1), function(source) {
206
185
  for (var prop in source) {
@@ -376,33 +355,6 @@ define(function () { 'use strict';
376
355
  pad(d.getUTCSeconds());
377
356
  };
378
357
 
379
- _.safewrap = function(f) {
380
- return function() {
381
- try {
382
- return f.apply(this, arguments);
383
- } catch (e) {
384
- console.critical('Implementation error. Please turn on debug and contact support@mixpanel.com.');
385
- if (Config.DEBUG){
386
- console.critical(e);
387
- }
388
- }
389
- };
390
- };
391
-
392
- _.safewrap_class = function(klass, functions) {
393
- for (var i = 0; i < functions.length; i++) {
394
- klass.prototype[functions[i]] = _.safewrap(klass.prototype[functions[i]]);
395
- }
396
- };
397
-
398
- _.safewrap_instance_methods = function(obj) {
399
- for (var func in obj) {
400
- if (typeof(obj[func]) === 'function') {
401
- obj[func] = _.safewrap(obj[func]);
402
- }
403
- }
404
- };
405
-
406
358
  _.strip_empty_properties = function(p) {
407
359
  var ret = {};
408
360
  _.each(p, function(v, k) {
@@ -1646,13 +1598,13 @@ define(function () { 'use strict';
1646
1598
  properties: function() {
1647
1599
  return _.extend(_.strip_empty_properties({
1648
1600
  '$os': _.info.os(),
1649
- '$browser': _.info.browser(userAgent, navigator$1.vendor, windowOpera),
1601
+ '$browser': _.info.browser(userAgent, navigator.vendor, windowOpera),
1650
1602
  '$referrer': document$1.referrer,
1651
1603
  '$referring_domain': _.info.referringDomain(document$1.referrer),
1652
1604
  '$device': _.info.device(userAgent)
1653
1605
  }), {
1654
1606
  '$current_url': window$1.location.href,
1655
- '$browser_version': _.info.browserVersion(userAgent, navigator$1.vendor, windowOpera),
1607
+ '$browser_version': _.info.browserVersion(userAgent, navigator.vendor, windowOpera),
1656
1608
  '$screen_height': screen.height,
1657
1609
  '$screen_width': screen.width,
1658
1610
  'mp_lib': 'web',
@@ -1665,9 +1617,9 @@ define(function () { 'use strict';
1665
1617
  people_properties: function() {
1666
1618
  return _.extend(_.strip_empty_properties({
1667
1619
  '$os': _.info.os(),
1668
- '$browser': _.info.browser(userAgent, navigator$1.vendor, windowOpera)
1620
+ '$browser': _.info.browser(userAgent, navigator.vendor, windowOpera)
1669
1621
  }), {
1670
- '$browser_version': _.info.browserVersion(userAgent, navigator$1.vendor, windowOpera)
1622
+ '$browser_version': _.info.browserVersion(userAgent, navigator.vendor, windowOpera)
1671
1623
  });
1672
1624
  },
1673
1625
 
@@ -1675,7 +1627,7 @@ define(function () { 'use strict';
1675
1627
  return _.strip_empty_properties({
1676
1628
  'mp_page': page,
1677
1629
  'mp_referrer': document$1.referrer,
1678
- 'mp_browser': _.info.browser(userAgent, navigator$1.vendor, windowOpera),
1630
+ 'mp_browser': _.info.browser(userAgent, navigator.vendor, windowOpera),
1679
1631
  'mp_platform': _.info.os()
1680
1632
  });
1681
1633
  }
@@ -2057,6 +2009,7 @@ define(function () { 'use strict';
2057
2009
  options = options || {};
2058
2010
  this.storageKey = storageKey;
2059
2011
  this.storage = options.storage || window.localStorage;
2012
+ this.reportError = options.errorReporter || _.bind(logger$1.error, logger$1);
2060
2013
  this.lock = new SharedLock(storageKey, {storage: this.storage});
2061
2014
 
2062
2015
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
@@ -2094,18 +2047,18 @@ define(function () { 'use strict';
2094
2047
  this.memQueue.push(queueEntry);
2095
2048
  }
2096
2049
  } catch(err) {
2097
- logger$1.error('Error enqueueing item', item);
2050
+ this.reportError('Error enqueueing item', item);
2098
2051
  succeeded = false;
2099
2052
  }
2100
2053
  if (cb) {
2101
2054
  cb(succeeded);
2102
2055
  }
2103
- }, this), function lockFailure(err) {
2104
- logger$1.error('Error acquiring storage lock', err);
2056
+ }, this), _.bind(function lockFailure(err) {
2057
+ this.reportError('Error acquiring storage lock', err);
2105
2058
  if (cb) {
2106
2059
  cb(false);
2107
2060
  }
2108
- }, this.pid);
2061
+ }, this), this.pid);
2109
2062
  };
2110
2063
 
2111
2064
  /**
@@ -2165,25 +2118,61 @@ define(function () { 'use strict';
2165
2118
  _.each(ids, function(id) { idSet[id] = true; });
2166
2119
 
2167
2120
  this.memQueue = filterOutIDsAndInvalid(this.memQueue, idSet);
2168
- this.lock.withLock(_.bind(function lockAcquired() {
2121
+
2122
+ var removeFromStorage = _.bind(function() {
2169
2123
  var succeeded;
2170
2124
  try {
2171
2125
  var storedQueue = this.readFromStorage();
2172
2126
  storedQueue = filterOutIDsAndInvalid(storedQueue, idSet);
2173
2127
  succeeded = this.saveToStorage(storedQueue);
2128
+
2129
+ // an extra check: did storage report success but somehow
2130
+ // the items are still there?
2131
+ if (succeeded) {
2132
+ storedQueue = this.readFromStorage();
2133
+ for (var i = 0; i < storedQueue.length; i++) {
2134
+ var item = storedQueue[i];
2135
+ if (item['id'] && !!idSet[item['id']]) {
2136
+ this.reportError('Item not removed from storage');
2137
+ return false;
2138
+ }
2139
+ }
2140
+ }
2174
2141
  } catch(err) {
2175
- logger$1.error('Error removing items', ids);
2142
+ this.reportError('Error removing items', ids);
2176
2143
  succeeded = false;
2177
2144
  }
2145
+ return succeeded;
2146
+ }, this);
2147
+
2148
+ this.lock.withLock(function lockAcquired() {
2149
+ var succeeded = removeFromStorage();
2178
2150
  if (cb) {
2179
2151
  cb(succeeded);
2180
2152
  }
2181
- }, this), function lockFailure(err) {
2182
- logger$1.error('Error acquiring storage lock', err);
2153
+ }, _.bind(function lockFailure(err) {
2154
+ var succeeded = false;
2155
+ this.reportError('Error acquiring storage lock', err);
2156
+ if (!localStorageSupported(this.storage, true)) {
2157
+ // Looks like localStorage writes have stopped working sometime after
2158
+ // initialization (probably full), and so nobody can acquire locks
2159
+ // anymore. Consider it temporarily safe to remove items without the
2160
+ // lock, since nobody's writing successfully anyway.
2161
+ succeeded = removeFromStorage();
2162
+ if (!succeeded) {
2163
+ // OK, we couldn't even write out the smaller queue. Try clearing it
2164
+ // entirely.
2165
+ try {
2166
+ this.storage.removeItem(this.storageKey);
2167
+ } catch(err) {
2168
+ this.reportError('Error clearing queue', err);
2169
+ }
2170
+ }
2171
+ }
2183
2172
  if (cb) {
2184
- cb(false);
2173
+ cb(succeeded);
2185
2174
  }
2186
- }, this.pid);
2175
+ }, this), this.pid);
2187
2176
  };
2188
2177
 
2189
2178
  // internal helper for RequestQueue.updatePayloads
@@ -2218,18 +2207,18 @@ define(function () { 'use strict';
2218
2207
  storedQueue = updatePayloads(storedQueue, itemsToUpdate);
2219
2208
  succeeded = this.saveToStorage(storedQueue);
2220
2209
  } catch(err) {
2221
- logger$1.error('Error updating items', itemsToUpdate);
2210
+ this.reportError('Error updating items', itemsToUpdate);
2222
2211
  succeeded = false;
2223
2212
  }
2224
2213
  if (cb) {
2225
2214
  cb(succeeded);
2226
2215
  }
2227
- }, this), function lockFailure(err) {
2228
- logger$1.error('Error acquiring storage lock', err);
2216
+ }, this), _.bind(function lockFailure(err) {
2217
+ this.reportError('Error acquiring storage lock', err);
2229
2218
  if (cb) {
2230
2219
  cb(false);
2231
2220
  }
2232
- }, this.pid);
2221
+ }, this), this.pid);
2233
2222
  };
2234
2223
 
2235
2224
  /**
@@ -2243,12 +2232,12 @@ define(function () { 'use strict';
2243
2232
  if (storageEntry) {
2244
2233
  storageEntry = JSONParse(storageEntry);
2245
2234
  if (!_.isArray(storageEntry)) {
2246
- logger$1.error('Invalid storage entry:', storageEntry);
2235
+ this.reportError('Invalid storage entry:', storageEntry);
2247
2236
  storageEntry = null;
2248
2237
  }
2249
2238
  }
2250
2239
  } catch (err) {
2251
- logger$1.error('Error retrieving queue', err);
2240
+ this.reportError('Error retrieving queue', err);
2252
2241
  storageEntry = null;
2253
2242
  }
2254
2243
  return storageEntry || [];
@@ -2262,7 +2251,7 @@ define(function () { 'use strict';
2262
2251
  this.storage.setItem(this.storageKey, JSONStringify(queue));
2263
2252
  return true;
2264
2253
  } catch (err) {
2265
- logger$1.error('Error saving queue', err);
2254
+ this.reportError('Error saving queue', err);
2266
2255
  return false;
2267
2256
  }
2268
2257
  };
@@ -2289,17 +2278,23 @@ define(function () { 'use strict';
2289
2278
  * @constructor
2290
2279
  */
2291
2280
  var RequestBatcher = function(storageKey, options) {
2292
- this.queue = new RequestQueue(storageKey, {storage: options.storage});
2281
+ this.errorReporter = options.errorReporter;
2282
+ this.queue = new RequestQueue(storageKey, {
2283
+ errorReporter: _.bind(this.reportError, this),
2284
+ storage: options.storage
2285
+ });
2293
2286
 
2294
2287
  this.libConfig = options.libConfig;
2295
2288
  this.sendRequest = options.sendRequestFunc;
2296
2289
  this.beforeSendHook = options.beforeSendHook;
2290
+ this.stopAllBatching = options.stopAllBatchingFunc;
2297
2291
 
2298
2292
  // seed variable batch size + flush interval with configured values
2299
2293
  this.batchSize = this.libConfig['batch_size'];
2300
2294
  this.flushInterval = this.libConfig['batch_flush_interval_ms'];
2301
2295
 
2302
2296
  this.stopped = !this.libConfig['batch_autostart'];
2297
+ this.consecutiveRemovalFailures = 0;
2303
2298
  };
2304
2299
 
2305
2300
  /**
@@ -2315,6 +2310,7 @@ define(function () { 'use strict';
2315
2310
  */
2316
2311
  RequestBatcher.prototype.start = function() {
2317
2312
  this.stopped = false;
2313
+ this.consecutiveRemovalFailures = 0;
2318
2314
  this.flush();
2319
2315
  };
2320
2316
 
@@ -2419,7 +2415,7 @@ define(function () { 'use strict';
2419
2415
  res.error === 'timeout' &&
2420
2416
  new Date().getTime() - startTime >= timeoutMS
2421
2417
  ) {
2422
- logger.error('Network timeout; retrying');
2418
+ this.reportError('Network timeout; retrying');
2423
2419
  this.flush();
2424
2420
  } else if (
2425
2421
  _.isObject(res) &&
@@ -2436,17 +2432,17 @@ define(function () { 'use strict';
2436
2432
  }
2437
2433
  }
2438
2434
  retryMS = Math.min(MAX_RETRY_INTERVAL_MS, retryMS);
2439
- logger.error('Error; retry in ' + retryMS + ' ms');
2435
+ this.reportError('Error; retry in ' + retryMS + ' ms');
2440
2436
  this.scheduleFlush(retryMS);
2441
2437
  } else if (_.isObject(res) && res.xhr_req && res.xhr_req['status'] === 413) {
2442
2438
  // 413 Payload Too Large
2443
2439
  if (batch.length > 1) {
2444
2440
  var halvedBatchSize = Math.max(1, Math.floor(currentBatchSize / 2));
2445
2441
  this.batchSize = Math.min(this.batchSize, halvedBatchSize, batch.length - 1);
2446
- logger.error('413 response; reducing batch size to ' + this.batchSize);
2442
+ this.reportError('413 response; reducing batch size to ' + this.batchSize);
2447
2443
  this.resetFlush();
2448
2444
  } else {
2449
- logger.error('Single-event request too large; dropping', batch);
2445
+ this.reportError('Single-event request too large; dropping', batch);
2450
2446
  this.resetBatchSize();
2451
2447
  removeItemsFromQueue = true;
2452
2448
  }
@@ -2459,12 +2455,25 @@ define(function () { 'use strict';
2459
2455
  if (removeItemsFromQueue) {
2460
2456
  this.queue.removeItemsByID(
2461
2457
  _.map(batch, function(item) { return item['id']; }),
2462
- _.bind(this.flush, this) // handle next batch if the queue isn't empty
2458
+ _.bind(function(succeeded) {
2459
+ if (succeeded) {
2460
+ this.consecutiveRemovalFailures = 0;
2461
+ this.flush(); // handle next batch if the queue isn't empty
2462
+ } else {
2463
+ this.reportError('Failed to remove items from queue');
2464
+ if (++this.consecutiveRemovalFailures > 5) {
2465
+ this.reportError('Too many queue failures; disabling batching system.');
2466
+ this.stopAllBatching();
2467
+ } else {
2468
+ this.resetFlush();
2469
+ }
2470
+ }
2471
+ }, this)
2463
2472
  );
2464
2473
  }
2465
2474
 
2466
2475
  } catch(err) {
2467
- logger.error('Error handling API response', err);
2476
+ this.reportError('Error handling API response', err);
2468
2477
  this.resetFlush();
2469
2478
  }
2470
2479
  }, this);
@@ -2481,11 +2490,28 @@ define(function () { 'use strict';
2481
2490
  this.sendRequest(dataForRequest, requestOptions, batchSendCallback);
2482
2491
 
2483
2492
  } catch(err) {
2484
- logger.error('Error flushing request queue', err);
2493
+ this.reportError('Error flushing request queue', err);
2485
2494
  this.resetFlush();
2486
2495
  }
2487
2496
  };
2488
2497
 
2498
+ /**
2499
+ * Log error to global logger and optional user-defined logger.
2500
+ */
2501
+ RequestBatcher.prototype.reportError = function(msg, err) {
2502
+ logger.error.apply(logger.error, arguments);
2503
+ if (this.errorReporter) {
2504
+ try {
2505
+ if (!(err instanceof Error)) {
2506
+ err = new Error(msg);
2507
+ }
2508
+ this.errorReporter(msg, err);
2509
+ } catch(err) {
2510
+ logger.error(err);
2511
+ }
2512
+ }
2513
+ };
2514
+
2489
2515
  /**
2490
2516
  * A function used to track a Mixpanel event (e.g. MixpanelLib.track)
2491
2517
  * @callback trackFunction
@@ -3057,2755 +3083,929 @@ define(function () { 'use strict';
3057
3083
  MixpanelGroup.prototype['unset'] = MixpanelGroup.prototype.unset;
3058
3084
  MixpanelGroup.prototype['toString'] = MixpanelGroup.prototype.toString;
3059
3085
 
3060
- /*
3061
- * Constants
3062
- */
3063
- /** @const */ var SET_QUEUE_KEY = '__mps';
3064
- /** @const */ var SET_ONCE_QUEUE_KEY = '__mpso';
3065
- /** @const */ var UNSET_QUEUE_KEY = '__mpus';
3066
- /** @const */ var ADD_QUEUE_KEY = '__mpa';
3067
- /** @const */ var APPEND_QUEUE_KEY = '__mpap';
3068
- /** @const */ var REMOVE_QUEUE_KEY = '__mpr';
3069
- /** @const */ var UNION_QUEUE_KEY = '__mpu';
3070
- // This key is deprecated, but we want to check for it to see whether aliasing is allowed.
3071
- /** @const */ var PEOPLE_DISTINCT_ID_KEY = '$people_distinct_id';
3072
- /** @const */ var ALIAS_ID_KEY = '__alias';
3073
- /** @const */ var CAMPAIGN_IDS_KEY = '__cmpns';
3074
- /** @const */ var EVENT_TIMERS_KEY = '__timers';
3075
- /** @const */ var RESERVED_PROPERTIES = [
3076
- SET_QUEUE_KEY,
3077
- SET_ONCE_QUEUE_KEY,
3078
- UNSET_QUEUE_KEY,
3079
- ADD_QUEUE_KEY,
3080
- APPEND_QUEUE_KEY,
3081
- REMOVE_QUEUE_KEY,
3082
- UNION_QUEUE_KEY,
3083
- PEOPLE_DISTINCT_ID_KEY,
3084
- ALIAS_ID_KEY,
3085
- CAMPAIGN_IDS_KEY,
3086
- EVENT_TIMERS_KEY
3087
- ];
3088
-
3089
3086
  /**
3090
- * Mixpanel Persistence Object
3087
+ * Mixpanel People Object
3091
3088
  * @constructor
3092
3089
  */
3093
- var MixpanelPersistence = function(config) {
3094
- this['props'] = {};
3095
- this.campaign_params_saved = false;
3096
-
3097
- if (config['persistence_name']) {
3098
- this.name = 'mp_' + config['persistence_name'];
3099
- } else {
3100
- this.name = 'mp_' + config['token'] + '_mixpanel';
3101
- }
3102
-
3103
- var storage_type = config['persistence'];
3104
- if (storage_type !== 'cookie' && storage_type !== 'localStorage') {
3105
- console.critical('Unknown persistence type ' + storage_type + '; falling back to cookie');
3106
- storage_type = config['persistence'] = 'cookie';
3107
- }
3108
-
3109
- if (storage_type === 'localStorage' && _.localStorage.is_supported()) {
3110
- this.storage = _.localStorage;
3111
- } else {
3112
- this.storage = _.cookie;
3113
- }
3090
+ var MixpanelPeople = function() {};
3114
3091
 
3115
- this.load();
3116
- this.update_config(config);
3117
- this.upgrade(config);
3118
- this.save();
3119
- };
3092
+ _.extend(MixpanelPeople.prototype, apiActions);
3120
3093
 
3121
- MixpanelPersistence.prototype.properties = function() {
3122
- var p = {};
3123
- // Filter out reserved properties
3124
- _.each(this['props'], function(v, k) {
3125
- if (!_.include(RESERVED_PROPERTIES, k)) {
3126
- p[k] = v;
3127
- }
3128
- });
3129
- return p;
3094
+ MixpanelPeople.prototype._init = function(mixpanel_instance) {
3095
+ this._mixpanel = mixpanel_instance;
3130
3096
  };
3131
3097
 
3132
- MixpanelPersistence.prototype.load = function() {
3133
- if (this.disabled) { return; }
3134
-
3135
- var entry = this.storage.parse(this.name);
3136
-
3137
- if (entry) {
3138
- this['props'] = _.extend({}, entry);
3098
+ /*
3099
+ * Set properties on a user record.
3100
+ *
3101
+ * ### Usage:
3102
+ *
3103
+ * mixpanel.people.set('gender', 'm');
3104
+ *
3105
+ * // or set multiple properties at once
3106
+ * mixpanel.people.set({
3107
+ * 'Company': 'Acme',
3108
+ * 'Plan': 'Premium',
3109
+ * 'Upgrade date': new Date()
3110
+ * });
3111
+ * // properties can be strings, integers, dates, or lists
3112
+ *
3113
+ * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
3114
+ * @param {*} [to] A value to set on the given property name
3115
+ * @param {Function} [callback] If provided, the callback will be called after tracking the event.
3116
+ */
3117
+ MixpanelPeople.prototype.set = addOptOutCheckMixpanelPeople(function(prop, to, callback) {
3118
+ var data = this.set_action(prop, to);
3119
+ if (_.isObject(prop)) {
3120
+ callback = to;
3139
3121
  }
3140
- };
3141
-
3142
- MixpanelPersistence.prototype.upgrade = function(config) {
3143
- var upgrade_from_old_lib = config['upgrade'],
3144
- old_cookie_name,
3145
- old_cookie;
3146
-
3147
- if (upgrade_from_old_lib) {
3148
- old_cookie_name = 'mp_super_properties';
3149
- // Case where they had a custom cookie name before.
3150
- if (typeof(upgrade_from_old_lib) === 'string') {
3151
- old_cookie_name = upgrade_from_old_lib;
3152
- }
3153
-
3154
- old_cookie = this.storage.parse(old_cookie_name);
3155
-
3156
- // remove the cookie
3157
- this.storage.remove(old_cookie_name);
3158
- this.storage.remove(old_cookie_name, true);
3159
-
3160
- if (old_cookie) {
3161
- this['props'] = _.extend(
3162
- this['props'],
3163
- old_cookie['all'],
3164
- old_cookie['events']
3165
- );
3166
- }
3122
+ // make sure that the referrer info has been updated and saved
3123
+ if (this._get_config('save_referrer')) {
3124
+ this._mixpanel['persistence'].update_referrer_info(document.referrer);
3167
3125
  }
3168
3126
 
3169
- if (!config['cookie_name'] && config['name'] !== 'mixpanel') {
3170
- // special case to handle people with cookies of the form
3171
- // mp_TOKEN_INSTANCENAME from the first release of this library
3172
- old_cookie_name = 'mp_' + config['token'] + '_' + config['name'];
3173
- old_cookie = this.storage.parse(old_cookie_name);
3174
-
3175
- if (old_cookie) {
3176
- this.storage.remove(old_cookie_name);
3177
- this.storage.remove(old_cookie_name, true);
3127
+ // update $set object with default people properties
3128
+ data[SET_ACTION] = _.extend(
3129
+ {},
3130
+ _.info.people_properties(),
3131
+ this._mixpanel['persistence'].get_referrer_info(),
3132
+ data[SET_ACTION]
3133
+ );
3134
+ return this._send_request(data, callback);
3135
+ });
3178
3136
 
3179
- // Save the prop values that were in the cookie from before -
3180
- // this should only happen once as we delete the old one.
3181
- this.register_once(old_cookie);
3182
- }
3137
+ /*
3138
+ * Set properties on a user record, only if they do not yet exist.
3139
+ * This will not overwrite previous people property values, unlike
3140
+ * people.set().
3141
+ *
3142
+ * ### Usage:
3143
+ *
3144
+ * mixpanel.people.set_once('First Login Date', new Date());
3145
+ *
3146
+ * // or set multiple properties at once
3147
+ * mixpanel.people.set_once({
3148
+ * 'First Login Date': new Date(),
3149
+ * 'Starting Plan': 'Premium'
3150
+ * });
3151
+ *
3152
+ * // properties can be strings, integers or dates
3153
+ *
3154
+ * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
3155
+ * @param {*} [to] A value to set on the given property name
3156
+ * @param {Function} [callback] If provided, the callback will be called after tracking the event.
3157
+ */
3158
+ MixpanelPeople.prototype.set_once = addOptOutCheckMixpanelPeople(function(prop, to, callback) {
3159
+ var data = this.set_once_action(prop, to);
3160
+ if (_.isObject(prop)) {
3161
+ callback = to;
3183
3162
  }
3163
+ return this._send_request(data, callback);
3164
+ });
3184
3165
 
3185
- if (this.storage === _.localStorage) {
3186
- old_cookie = _.cookie.parse(this.name);
3187
-
3188
- _.cookie.remove(this.name);
3189
- _.cookie.remove(this.name, true);
3190
-
3191
- if (old_cookie) {
3192
- this.register_once(old_cookie);
3193
- }
3194
- }
3195
- };
3166
+ /*
3167
+ * Unset properties on a user record (permanently removes the properties and their values from a profile).
3168
+ *
3169
+ * ### Usage:
3170
+ *
3171
+ * mixpanel.people.unset('gender');
3172
+ *
3173
+ * // or unset multiple properties at once
3174
+ * mixpanel.people.unset(['gender', 'Company']);
3175
+ *
3176
+ * @param {Array|String} prop If a string, this is the name of the property. If an array, this is a list of property names.
3177
+ * @param {Function} [callback] If provided, the callback will be called after tracking the event.
3178
+ */
3179
+ MixpanelPeople.prototype.unset = addOptOutCheckMixpanelPeople(function(prop, callback) {
3180
+ var data = this.unset_action(prop);
3181
+ return this._send_request(data, callback);
3182
+ });
3196
3183
 
3197
- MixpanelPersistence.prototype.save = function() {
3198
- if (this.disabled) { return; }
3199
- this._expire_notification_campaigns();
3200
- this.storage.set(
3201
- this.name,
3202
- _.JSONEncode(this['props']),
3203
- this.expire_days,
3204
- this.cross_subdomain,
3205
- this.secure,
3206
- this.cross_site,
3207
- this.cookie_domain
3208
- );
3209
- };
3210
-
3211
- MixpanelPersistence.prototype.remove = function() {
3212
- // remove both domain and subdomain cookies
3213
- this.storage.remove(this.name, false, this.cookie_domain);
3214
- this.storage.remove(this.name, true, this.cookie_domain);
3215
- };
3216
-
3217
- // removes the storage entry and deletes all loaded data
3218
- // forced name for tests
3219
- MixpanelPersistence.prototype.clear = function() {
3220
- this.remove();
3221
- this['props'] = {};
3222
- };
3223
-
3224
- /**
3225
- * @param {Object} props
3226
- * @param {*=} default_value
3227
- * @param {number=} days
3184
+ /*
3185
+ * Increment/decrement numeric people analytics properties.
3186
+ *
3187
+ * ### Usage:
3188
+ *
3189
+ * mixpanel.people.increment('page_views', 1);
3190
+ *
3191
+ * // or, for convenience, if you're just incrementing a counter by
3192
+ * // 1, you can simply do
3193
+ * mixpanel.people.increment('page_views');
3194
+ *
3195
+ * // to decrement a counter, pass a negative number
3196
+ * mixpanel.people.increment('credits_left', -1);
3197
+ *
3198
+ * // like mixpanel.people.set(), you can increment multiple
3199
+ * // properties at once:
3200
+ * mixpanel.people.increment({
3201
+ * counter1: 1,
3202
+ * counter2: 6
3203
+ * });
3204
+ *
3205
+ * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and numeric values.
3206
+ * @param {Number} [by] An amount to increment the given property
3207
+ * @param {Function} [callback] If provided, the callback will be called after tracking the event.
3228
3208
  */
3229
- MixpanelPersistence.prototype.register_once = function(props, default_value, days) {
3230
- if (_.isObject(props)) {
3231
- if (typeof(default_value) === 'undefined') { default_value = 'None'; }
3232
- this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days;
3233
-
3234
- _.each(props, function(val, prop) {
3235
- if (!this['props'].hasOwnProperty(prop) || this['props'][prop] === default_value) {
3236
- this['props'][prop] = val;
3209
+ MixpanelPeople.prototype.increment = addOptOutCheckMixpanelPeople(function(prop, by, callback) {
3210
+ var data = {};
3211
+ var $add = {};
3212
+ if (_.isObject(prop)) {
3213
+ _.each(prop, function(v, k) {
3214
+ if (!this._is_reserved_property(k)) {
3215
+ if (isNaN(parseFloat(v))) {
3216
+ console.error('Invalid increment value passed to mixpanel.people.increment - must be a number');
3217
+ return;
3218
+ } else {
3219
+ $add[k] = v;
3220
+ }
3237
3221
  }
3238
3222
  }, this);
3223
+ callback = by;
3224
+ } else {
3225
+ // convenience: mixpanel.people.increment('property'); will
3226
+ // increment 'property' by 1
3227
+ if (_.isUndefined(by)) {
3228
+ by = 1;
3229
+ }
3230
+ $add[prop] = by;
3231
+ }
3232
+ data[ADD_ACTION] = $add;
3239
3233
 
3240
- this.save();
3234
+ return this._send_request(data, callback);
3235
+ });
3241
3236
 
3242
- return true;
3237
+ /*
3238
+ * Append a value to a list-valued people analytics property.
3239
+ *
3240
+ * ### Usage:
3241
+ *
3242
+ * // append a value to a list, creating it if needed
3243
+ * mixpanel.people.append('pages_visited', 'homepage');
3244
+ *
3245
+ * // like mixpanel.people.set(), you can append multiple
3246
+ * // properties at once:
3247
+ * mixpanel.people.append({
3248
+ * list1: 'bob',
3249
+ * list2: 123
3250
+ * });
3251
+ *
3252
+ * @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values.
3253
+ * @param {*} [value] value An item to append to the list
3254
+ * @param {Function} [callback] If provided, the callback will be called after tracking the event.
3255
+ */
3256
+ MixpanelPeople.prototype.append = addOptOutCheckMixpanelPeople(function(list_name, value, callback) {
3257
+ if (_.isObject(list_name)) {
3258
+ callback = value;
3243
3259
  }
3244
- return false;
3245
- };
3260
+ var data = this.append_action(list_name, value);
3261
+ return this._send_request(data, callback);
3262
+ });
3246
3263
 
3247
- /**
3248
- * @param {Object} props
3249
- * @param {number=} days
3264
+ /*
3265
+ * Remove a value from a list-valued people analytics property.
3266
+ *
3267
+ * ### Usage:
3268
+ *
3269
+ * mixpanel.people.remove('School', 'UCB');
3270
+ *
3271
+ * @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values.
3272
+ * @param {*} [value] value Item to remove from the list
3273
+ * @param {Function} [callback] If provided, the callback will be called after tracking the event.
3250
3274
  */
3251
- MixpanelPersistence.prototype.register = function(props, days) {
3252
- if (_.isObject(props)) {
3253
- this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days;
3254
-
3255
- _.extend(this['props'], props);
3256
-
3257
- this.save();
3258
-
3259
- return true;
3275
+ MixpanelPeople.prototype.remove = addOptOutCheckMixpanelPeople(function(list_name, value, callback) {
3276
+ if (_.isObject(list_name)) {
3277
+ callback = value;
3260
3278
  }
3261
- return false;
3262
- };
3279
+ var data = this.remove_action(list_name, value);
3280
+ return this._send_request(data, callback);
3281
+ });
3263
3282
 
3264
- MixpanelPersistence.prototype.unregister = function(prop) {
3265
- if (prop in this['props']) {
3266
- delete this['props'][prop];
3267
- this.save();
3283
+ /*
3284
+ * Merge a given list with a list-valued people analytics property,
3285
+ * excluding duplicate values.
3286
+ *
3287
+ * ### Usage:
3288
+ *
3289
+ * // merge a value to a list, creating it if needed
3290
+ * mixpanel.people.union('pages_visited', 'homepage');
3291
+ *
3292
+ * // like mixpanel.people.set(), you can append multiple
3293
+ * // properties at once:
3294
+ * mixpanel.people.union({
3295
+ * list1: 'bob',
3296
+ * list2: 123
3297
+ * });
3298
+ *
3299
+ * // like mixpanel.people.append(), you can append multiple
3300
+ * // values to the same list:
3301
+ * mixpanel.people.union({
3302
+ * list1: ['bob', 'billy']
3303
+ * });
3304
+ *
3305
+ * @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values.
3306
+ * @param {*} [value] Value / values to merge with the given property
3307
+ * @param {Function} [callback] If provided, the callback will be called after tracking the event.
3308
+ */
3309
+ MixpanelPeople.prototype.union = addOptOutCheckMixpanelPeople(function(list_name, values, callback) {
3310
+ if (_.isObject(list_name)) {
3311
+ callback = values;
3268
3312
  }
3269
- };
3313
+ var data = this.union_action(list_name, values);
3314
+ return this._send_request(data, callback);
3315
+ });
3270
3316
 
3271
- MixpanelPersistence.prototype._expire_notification_campaigns = _.safewrap(function() {
3272
- var campaigns_shown = this['props'][CAMPAIGN_IDS_KEY],
3273
- EXPIRY_TIME = Config.DEBUG ? 60 * 1000 : 60 * 60 * 1000; // 1 minute (Config.DEBUG) / 1 hour (PDXN)
3274
- if (!campaigns_shown) {
3275
- return;
3276
- }
3277
- for (var campaign_id in campaigns_shown) {
3278
- if (1 * new Date() - campaigns_shown[campaign_id] > EXPIRY_TIME) {
3279
- delete campaigns_shown[campaign_id];
3317
+ /*
3318
+ * Record that you have charged the current user a certain amount
3319
+ * of money. Charges recorded with track_charge() will appear in the
3320
+ * Mixpanel revenue report.
3321
+ *
3322
+ * ### Usage:
3323
+ *
3324
+ * // charge a user $50
3325
+ * mixpanel.people.track_charge(50);
3326
+ *
3327
+ * // charge a user $30.50 on the 2nd of january
3328
+ * mixpanel.people.track_charge(30.50, {
3329
+ * '$time': new Date('jan 1 2012')
3330
+ * });
3331
+ *
3332
+ * @param {Number} amount The amount of money charged to the current user
3333
+ * @param {Object} [properties] An associative array of properties associated with the charge
3334
+ * @param {Function} [callback] If provided, the callback will be called when the server responds
3335
+ */
3336
+ MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function(amount, properties, callback) {
3337
+ if (!_.isNumber(amount)) {
3338
+ amount = parseFloat(amount);
3339
+ if (isNaN(amount)) {
3340
+ console.error('Invalid value passed to mixpanel.people.track_charge - must be a number');
3341
+ return;
3280
3342
  }
3281
3343
  }
3282
- if (_.isEmptyObject(campaigns_shown)) {
3283
- delete this['props'][CAMPAIGN_IDS_KEY];
3284
- }
3344
+
3345
+ return this.append('$transactions', _.extend({
3346
+ '$amount': amount
3347
+ }, properties), callback);
3285
3348
  });
3286
3349
 
3287
- MixpanelPersistence.prototype.update_campaign_params = function() {
3288
- if (!this.campaign_params_saved) {
3289
- this.register_once(_.info.campaignParams());
3290
- this.campaign_params_saved = true;
3350
+ /*
3351
+ * Permanently clear all revenue report transactions from the
3352
+ * current user's people analytics profile.
3353
+ *
3354
+ * ### Usage:
3355
+ *
3356
+ * mixpanel.people.clear_charges();
3357
+ *
3358
+ * @param {Function} [callback] If provided, the callback will be called after tracking the event.
3359
+ */
3360
+ MixpanelPeople.prototype.clear_charges = function(callback) {
3361
+ return this.set('$transactions', [], callback);
3362
+ };
3363
+
3364
+ /*
3365
+ * Permanently deletes the current people analytics profile from
3366
+ * Mixpanel (using the current distinct_id).
3367
+ *
3368
+ * ### Usage:
3369
+ *
3370
+ * // remove the all data you have stored about the current user
3371
+ * mixpanel.people.delete_user();
3372
+ *
3373
+ */
3374
+ MixpanelPeople.prototype.delete_user = function() {
3375
+ if (!this._identify_called()) {
3376
+ console.error('mixpanel.people.delete_user() requires you to call identify() first');
3377
+ return;
3291
3378
  }
3379
+ var data = {'$delete': this._mixpanel.get_distinct_id()};
3380
+ return this._send_request(data);
3292
3381
  };
3293
3382
 
3294
- MixpanelPersistence.prototype.update_search_keyword = function(referrer) {
3295
- this.register(_.info.searchInfo(referrer));
3383
+ MixpanelPeople.prototype.toString = function() {
3384
+ return this._mixpanel.toString() + '.people';
3296
3385
  };
3297
3386
 
3298
- // EXPORTED METHOD, we test this directly.
3299
- MixpanelPersistence.prototype.update_referrer_info = function(referrer) {
3300
- // If referrer doesn't exist, we want to note the fact that it was type-in traffic.
3301
- this.register_once({
3302
- '$initial_referrer': referrer || '$direct',
3303
- '$initial_referring_domain': _.info.referringDomain(referrer) || '$direct'
3304
- }, '');
3305
- };
3387
+ MixpanelPeople.prototype._send_request = function(data, callback) {
3388
+ data['$token'] = this._get_config('token');
3389
+ data['$distinct_id'] = this._mixpanel.get_distinct_id();
3390
+ var device_id = this._mixpanel.get_property('$device_id');
3391
+ var user_id = this._mixpanel.get_property('$user_id');
3392
+ var had_persisted_distinct_id = this._mixpanel.get_property('$had_persisted_distinct_id');
3393
+ if (device_id) {
3394
+ data['$device_id'] = device_id;
3395
+ }
3396
+ if (user_id) {
3397
+ data['$user_id'] = user_id;
3398
+ }
3399
+ if (had_persisted_distinct_id) {
3400
+ data['$had_persisted_distinct_id'] = had_persisted_distinct_id;
3401
+ }
3306
3402
 
3307
- MixpanelPersistence.prototype.get_referrer_info = function() {
3308
- return _.strip_empty_properties({
3309
- '$initial_referrer': this['props']['$initial_referrer'],
3310
- '$initial_referring_domain': this['props']['$initial_referring_domain']
3311
- });
3312
- };
3403
+ var date_encoded_data = _.encodeDates(data);
3313
3404
 
3314
- // safely fills the passed in object with stored properties,
3315
- // does not override any properties defined in both
3316
- // returns the passed in object
3317
- MixpanelPersistence.prototype.safe_merge = function(props) {
3318
- _.each(this['props'], function(val, prop) {
3319
- if (!(prop in props)) {
3320
- props[prop] = val;
3405
+ if (!this._identify_called()) {
3406
+ this._enqueue(data);
3407
+ if (!_.isUndefined(callback)) {
3408
+ if (this._get_config('verbose')) {
3409
+ callback({status: -1, error: null});
3410
+ } else {
3411
+ callback(-1);
3412
+ }
3321
3413
  }
3322
- });
3414
+ return _.truncate(date_encoded_data, 255);
3415
+ }
3323
3416
 
3324
- return props;
3417
+ return this._mixpanel._track_or_batch({
3418
+ type: 'people',
3419
+ data: date_encoded_data,
3420
+ endpoint: this._get_config('api_host') + '/engage/',
3421
+ batcher: this._mixpanel.request_batchers.people
3422
+ }, callback);
3325
3423
  };
3326
3424
 
3327
- MixpanelPersistence.prototype.update_config = function(config) {
3328
- this.default_expiry = this.expire_days = config['cookie_expiration'];
3329
- this.set_disabled(config['disable_persistence']);
3330
- this.set_cookie_domain(config['cookie_domain']);
3331
- this.set_cross_site(config['cross_site_cookie']);
3332
- this.set_cross_subdomain(config['cross_subdomain_cookie']);
3333
- this.set_secure(config['secure_cookie']);
3425
+ MixpanelPeople.prototype._get_config = function(conf_var) {
3426
+ return this._mixpanel.get_config(conf_var);
3334
3427
  };
3335
3428
 
3336
- MixpanelPersistence.prototype.set_disabled = function(disabled) {
3337
- this.disabled = disabled;
3338
- if (this.disabled) {
3339
- this.remove();
3429
+ MixpanelPeople.prototype._identify_called = function() {
3430
+ return this._mixpanel._flags.identify_called === true;
3431
+ };
3432
+
3433
+ // Queue up engage operations if identify hasn't been called yet.
3434
+ MixpanelPeople.prototype._enqueue = function(data) {
3435
+ if (SET_ACTION in data) {
3436
+ this._mixpanel['persistence']._add_to_people_queue(SET_ACTION, data);
3437
+ } else if (SET_ONCE_ACTION in data) {
3438
+ this._mixpanel['persistence']._add_to_people_queue(SET_ONCE_ACTION, data);
3439
+ } else if (UNSET_ACTION in data) {
3440
+ this._mixpanel['persistence']._add_to_people_queue(UNSET_ACTION, data);
3441
+ } else if (ADD_ACTION in data) {
3442
+ this._mixpanel['persistence']._add_to_people_queue(ADD_ACTION, data);
3443
+ } else if (APPEND_ACTION in data) {
3444
+ this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, data);
3445
+ } else if (REMOVE_ACTION in data) {
3446
+ this._mixpanel['persistence']._add_to_people_queue(REMOVE_ACTION, data);
3447
+ } else if (UNION_ACTION in data) {
3448
+ this._mixpanel['persistence']._add_to_people_queue(UNION_ACTION, data);
3340
3449
  } else {
3341
- this.save();
3450
+ console.error('Invalid call to _enqueue():', data);
3342
3451
  }
3343
3452
  };
3344
3453
 
3345
- MixpanelPersistence.prototype.set_cookie_domain = function(cookie_domain) {
3346
- if (cookie_domain !== this.cookie_domain) {
3347
- this.remove();
3348
- this.cookie_domain = cookie_domain;
3349
- this.save();
3454
+ MixpanelPeople.prototype._flush_one_queue = function(action, action_method, callback, queue_to_params_fn) {
3455
+ var _this = this;
3456
+ var queued_data = _.extend({}, this._mixpanel['persistence']._get_queue(action));
3457
+ var action_params = queued_data;
3458
+
3459
+ if (!_.isUndefined(queued_data) && _.isObject(queued_data) && !_.isEmptyObject(queued_data)) {
3460
+ _this._mixpanel['persistence']._pop_from_people_queue(action, queued_data);
3461
+ if (queue_to_params_fn) {
3462
+ action_params = queue_to_params_fn(queued_data);
3463
+ }
3464
+ action_method.call(_this, action_params, function(response, data) {
3465
+ // on bad response, we want to add it back to the queue
3466
+ if (response === 0) {
3467
+ _this._mixpanel['persistence']._add_to_people_queue(action, queued_data);
3468
+ }
3469
+ if (!_.isUndefined(callback)) {
3470
+ callback(response, data);
3471
+ }
3472
+ });
3350
3473
  }
3351
3474
  };
3352
3475
 
3353
- MixpanelPersistence.prototype.set_cross_site = function(cross_site) {
3354
- if (cross_site !== this.cross_site) {
3355
- this.cross_site = cross_site;
3356
- this.remove();
3357
- this.save();
3476
+ // Flush queued engage operations - order does not matter,
3477
+ // and there are network level race conditions anyway
3478
+ MixpanelPeople.prototype._flush = function(
3479
+ _set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback
3480
+ ) {
3481
+ var _this = this;
3482
+ var $append_queue = this._mixpanel['persistence']._get_queue(APPEND_ACTION);
3483
+ var $remove_queue = this._mixpanel['persistence']._get_queue(REMOVE_ACTION);
3484
+
3485
+ this._flush_one_queue(SET_ACTION, this.set, _set_callback);
3486
+ this._flush_one_queue(SET_ONCE_ACTION, this.set_once, _set_once_callback);
3487
+ this._flush_one_queue(UNSET_ACTION, this.unset, _unset_callback, function(queue) { return _.keys(queue); });
3488
+ this._flush_one_queue(ADD_ACTION, this.increment, _add_callback);
3489
+ this._flush_one_queue(UNION_ACTION, this.union, _union_callback);
3490
+
3491
+ // we have to fire off each $append individually since there is
3492
+ // no concat method server side
3493
+ if (!_.isUndefined($append_queue) && _.isArray($append_queue) && $append_queue.length) {
3494
+ var $append_item;
3495
+ var append_callback = function(response, data) {
3496
+ if (response === 0) {
3497
+ _this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, $append_item);
3498
+ }
3499
+ if (!_.isUndefined(_append_callback)) {
3500
+ _append_callback(response, data);
3501
+ }
3502
+ };
3503
+ for (var i = $append_queue.length - 1; i >= 0; i--) {
3504
+ $append_item = $append_queue.pop();
3505
+ if (!_.isEmptyObject($append_item)) {
3506
+ _this.append($append_item, append_callback);
3507
+ }
3508
+ }
3509
+ // Save the shortened append queue
3510
+ _this._mixpanel['persistence'].save();
3358
3511
  }
3359
- };
3360
3512
 
3361
- MixpanelPersistence.prototype.set_cross_subdomain = function(cross_subdomain) {
3362
- if (cross_subdomain !== this.cross_subdomain) {
3363
- this.cross_subdomain = cross_subdomain;
3364
- this.remove();
3365
- this.save();
3513
+ // same for $remove
3514
+ if (!_.isUndefined($remove_queue) && _.isArray($remove_queue) && $remove_queue.length) {
3515
+ var $remove_item;
3516
+ var remove_callback = function(response, data) {
3517
+ if (response === 0) {
3518
+ _this._mixpanel['persistence']._add_to_people_queue(REMOVE_ACTION, $remove_item);
3519
+ }
3520
+ if (!_.isUndefined(_remove_callback)) {
3521
+ _remove_callback(response, data);
3522
+ }
3523
+ };
3524
+ for (var j = $remove_queue.length - 1; j >= 0; j--) {
3525
+ $remove_item = $remove_queue.pop();
3526
+ if (!_.isEmptyObject($remove_item)) {
3527
+ _this.remove($remove_item, remove_callback);
3528
+ }
3529
+ }
3530
+ _this._mixpanel['persistence'].save();
3366
3531
  }
3367
3532
  };
3368
3533
 
3369
- MixpanelPersistence.prototype.get_cross_subdomain = function() {
3370
- return this.cross_subdomain;
3534
+ MixpanelPeople.prototype._is_reserved_property = function(prop) {
3535
+ return prop === '$distinct_id' || prop === '$token' || prop === '$device_id' || prop === '$user_id' || prop === '$had_persisted_distinct_id';
3371
3536
  };
3372
3537
 
3373
- MixpanelPersistence.prototype.set_secure = function(secure) {
3374
- if (secure !== this.secure) {
3375
- this.secure = secure ? true : false;
3376
- this.remove();
3377
- this.save();
3538
+ // MixpanelPeople Exports
3539
+ MixpanelPeople.prototype['set'] = MixpanelPeople.prototype.set;
3540
+ MixpanelPeople.prototype['set_once'] = MixpanelPeople.prototype.set_once;
3541
+ MixpanelPeople.prototype['unset'] = MixpanelPeople.prototype.unset;
3542
+ MixpanelPeople.prototype['increment'] = MixpanelPeople.prototype.increment;
3543
+ MixpanelPeople.prototype['append'] = MixpanelPeople.prototype.append;
3544
+ MixpanelPeople.prototype['remove'] = MixpanelPeople.prototype.remove;
3545
+ MixpanelPeople.prototype['union'] = MixpanelPeople.prototype.union;
3546
+ MixpanelPeople.prototype['track_charge'] = MixpanelPeople.prototype.track_charge;
3547
+ MixpanelPeople.prototype['clear_charges'] = MixpanelPeople.prototype.clear_charges;
3548
+ MixpanelPeople.prototype['delete_user'] = MixpanelPeople.prototype.delete_user;
3549
+ MixpanelPeople.prototype['toString'] = MixpanelPeople.prototype.toString;
3550
+
3551
+ /*
3552
+ * Constants
3553
+ */
3554
+ /** @const */ var SET_QUEUE_KEY = '__mps';
3555
+ /** @const */ var SET_ONCE_QUEUE_KEY = '__mpso';
3556
+ /** @const */ var UNSET_QUEUE_KEY = '__mpus';
3557
+ /** @const */ var ADD_QUEUE_KEY = '__mpa';
3558
+ /** @const */ var APPEND_QUEUE_KEY = '__mpap';
3559
+ /** @const */ var REMOVE_QUEUE_KEY = '__mpr';
3560
+ /** @const */ var UNION_QUEUE_KEY = '__mpu';
3561
+ // This key is deprecated, but we want to check for it to see whether aliasing is allowed.
3562
+ /** @const */ var PEOPLE_DISTINCT_ID_KEY = '$people_distinct_id';
3563
+ /** @const */ var ALIAS_ID_KEY = '__alias';
3564
+ /** @const */ var EVENT_TIMERS_KEY = '__timers';
3565
+ /** @const */ var RESERVED_PROPERTIES = [
3566
+ SET_QUEUE_KEY,
3567
+ SET_ONCE_QUEUE_KEY,
3568
+ UNSET_QUEUE_KEY,
3569
+ ADD_QUEUE_KEY,
3570
+ APPEND_QUEUE_KEY,
3571
+ REMOVE_QUEUE_KEY,
3572
+ UNION_QUEUE_KEY,
3573
+ PEOPLE_DISTINCT_ID_KEY,
3574
+ ALIAS_ID_KEY,
3575
+ EVENT_TIMERS_KEY
3576
+ ];
3577
+
3578
+ /**
3579
+ * Mixpanel Persistence Object
3580
+ * @constructor
3581
+ */
3582
+ var MixpanelPersistence = function(config) {
3583
+ this['props'] = {};
3584
+ this.campaign_params_saved = false;
3585
+
3586
+ if (config['persistence_name']) {
3587
+ this.name = 'mp_' + config['persistence_name'];
3588
+ } else {
3589
+ this.name = 'mp_' + config['token'] + '_mixpanel';
3378
3590
  }
3379
- };
3380
3591
 
3381
- MixpanelPersistence.prototype._add_to_people_queue = function(queue, data) {
3382
- var q_key = this._get_queue_key(queue),
3383
- q_data = data[queue],
3384
- set_q = this._get_or_create_queue(SET_ACTION),
3385
- set_once_q = this._get_or_create_queue(SET_ONCE_ACTION),
3386
- unset_q = this._get_or_create_queue(UNSET_ACTION),
3387
- add_q = this._get_or_create_queue(ADD_ACTION),
3388
- union_q = this._get_or_create_queue(UNION_ACTION),
3389
- remove_q = this._get_or_create_queue(REMOVE_ACTION, []),
3390
- append_q = this._get_or_create_queue(APPEND_ACTION, []);
3592
+ var storage_type = config['persistence'];
3593
+ if (storage_type !== 'cookie' && storage_type !== 'localStorage') {
3594
+ console.critical('Unknown persistence type ' + storage_type + '; falling back to cookie');
3595
+ storage_type = config['persistence'] = 'cookie';
3596
+ }
3391
3597
 
3392
- if (q_key === SET_QUEUE_KEY) {
3393
- // Update the set queue - we can override any existing values
3394
- _.extend(set_q, q_data);
3395
- // if there was a pending increment, override it
3396
- // with the set.
3397
- this._pop_from_people_queue(ADD_ACTION, q_data);
3398
- // if there was a pending union, override it
3399
- // with the set.
3400
- this._pop_from_people_queue(UNION_ACTION, q_data);
3401
- this._pop_from_people_queue(UNSET_ACTION, q_data);
3402
- } else if (q_key === SET_ONCE_QUEUE_KEY) {
3403
- // only queue the data if there is not already a set_once call for it.
3404
- _.each(q_data, function(v, k) {
3405
- if (!(k in set_once_q)) {
3406
- set_once_q[k] = v;
3407
- }
3408
- });
3409
- this._pop_from_people_queue(UNSET_ACTION, q_data);
3410
- } else if (q_key === UNSET_QUEUE_KEY) {
3411
- _.each(q_data, function(prop) {
3412
-
3413
- // undo previously-queued actions on this key
3414
- _.each([set_q, set_once_q, add_q, union_q], function(enqueued_obj) {
3415
- if (prop in enqueued_obj) {
3416
- delete enqueued_obj[prop];
3417
- }
3418
- });
3419
- _.each(append_q, function(append_obj) {
3420
- if (prop in append_obj) {
3421
- delete append_obj[prop];
3422
- }
3423
- });
3424
-
3425
- unset_q[prop] = true;
3426
-
3427
- });
3428
- } else if (q_key === ADD_QUEUE_KEY) {
3429
- _.each(q_data, function(v, k) {
3430
- // If it exists in the set queue, increment
3431
- // the value
3432
- if (k in set_q) {
3433
- set_q[k] += v;
3434
- } else {
3435
- // If it doesn't exist, update the add
3436
- // queue
3437
- if (!(k in add_q)) {
3438
- add_q[k] = 0;
3439
- }
3440
- add_q[k] += v;
3441
- }
3442
- }, this);
3443
- this._pop_from_people_queue(UNSET_ACTION, q_data);
3444
- } else if (q_key === UNION_QUEUE_KEY) {
3445
- _.each(q_data, function(v, k) {
3446
- if (_.isArray(v)) {
3447
- if (!(k in union_q)) {
3448
- union_q[k] = [];
3449
- }
3450
- // We may send duplicates, the server will dedup them.
3451
- union_q[k] = union_q[k].concat(v);
3452
- }
3453
- });
3454
- this._pop_from_people_queue(UNSET_ACTION, q_data);
3455
- } else if (q_key === REMOVE_QUEUE_KEY) {
3456
- remove_q.push(q_data);
3457
- this._pop_from_people_queue(APPEND_ACTION, q_data);
3458
- } else if (q_key === APPEND_QUEUE_KEY) {
3459
- append_q.push(q_data);
3460
- this._pop_from_people_queue(UNSET_ACTION, q_data);
3461
- }
3462
-
3463
- console.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):');
3464
- console.log(data);
3465
-
3466
- this.save();
3467
- };
3468
-
3469
- MixpanelPersistence.prototype._pop_from_people_queue = function(queue, data) {
3470
- var q = this._get_queue(queue);
3471
- if (!_.isUndefined(q)) {
3472
- _.each(data, function(v, k) {
3473
- if (queue === APPEND_ACTION || queue === REMOVE_ACTION) {
3474
- // list actions: only remove if both k+v match
3475
- // e.g. remove should not override append in a case like
3476
- // append({foo: 'bar'}); remove({foo: 'qux'})
3477
- _.each(q, function(queued_action) {
3478
- if (queued_action[k] === v) {
3479
- delete queued_action[k];
3480
- }
3481
- });
3482
- } else {
3483
- delete q[k];
3484
- }
3485
- }, this);
3486
-
3487
- this.save();
3488
- }
3489
- };
3490
-
3491
- MixpanelPersistence.prototype._get_queue_key = function(queue) {
3492
- if (queue === SET_ACTION) {
3493
- return SET_QUEUE_KEY;
3494
- } else if (queue === SET_ONCE_ACTION) {
3495
- return SET_ONCE_QUEUE_KEY;
3496
- } else if (queue === UNSET_ACTION) {
3497
- return UNSET_QUEUE_KEY;
3498
- } else if (queue === ADD_ACTION) {
3499
- return ADD_QUEUE_KEY;
3500
- } else if (queue === APPEND_ACTION) {
3501
- return APPEND_QUEUE_KEY;
3502
- } else if (queue === REMOVE_ACTION) {
3503
- return REMOVE_QUEUE_KEY;
3504
- } else if (queue === UNION_ACTION) {
3505
- return UNION_QUEUE_KEY;
3598
+ if (storage_type === 'localStorage' && _.localStorage.is_supported()) {
3599
+ this.storage = _.localStorage;
3506
3600
  } else {
3507
- console.error('Invalid queue:', queue);
3601
+ this.storage = _.cookie;
3508
3602
  }
3509
- };
3510
-
3511
- MixpanelPersistence.prototype._get_queue = function(queue) {
3512
- return this['props'][this._get_queue_key(queue)];
3513
- };
3514
- MixpanelPersistence.prototype._get_or_create_queue = function(queue, default_val) {
3515
- var key = this._get_queue_key(queue);
3516
- default_val = _.isUndefined(default_val) ? {} : default_val;
3517
3603
 
3518
- return this['props'][key] || (this['props'][key] = default_val);
3519
- };
3520
-
3521
- MixpanelPersistence.prototype.set_event_timer = function(event_name, timestamp) {
3522
- var timers = this['props'][EVENT_TIMERS_KEY] || {};
3523
- timers[event_name] = timestamp;
3524
- this['props'][EVENT_TIMERS_KEY] = timers;
3604
+ this.load();
3605
+ this.update_config(config);
3606
+ this.upgrade(config);
3525
3607
  this.save();
3526
3608
  };
3527
3609
 
3528
- MixpanelPersistence.prototype.remove_event_timer = function(event_name) {
3529
- var timers = this['props'][EVENT_TIMERS_KEY] || {};
3530
- var timestamp = timers[event_name];
3531
- if (!_.isUndefined(timestamp)) {
3532
- delete this['props'][EVENT_TIMERS_KEY][event_name];
3533
- this.save();
3534
- }
3535
- return timestamp;
3610
+ MixpanelPersistence.prototype.properties = function() {
3611
+ var p = {};
3612
+ // Filter out reserved properties
3613
+ _.each(this['props'], function(v, k) {
3614
+ if (!_.include(RESERVED_PROPERTIES, k)) {
3615
+ p[k] = v;
3616
+ }
3617
+ });
3618
+ return p;
3536
3619
  };
3537
3620
 
3538
- /*
3539
- * This file is a js implementation for a subset in eval_node.c
3540
- */
3621
+ MixpanelPersistence.prototype.load = function() {
3622
+ if (this.disabled) { return; }
3541
3623
 
3542
- /*
3543
- * Constants
3544
- */
3545
- // Metadata keys
3546
- /** @const */ var OPERATOR_KEY = 'operator';
3547
- /** @const */ var PROPERTY_KEY = 'property';
3548
- /** @const */ var WINDOW_KEY = 'window';
3549
- /** @const */ var UNIT_KEY = 'unit';
3550
- /** @const */ var VALUE_KEY = 'value';
3551
- /** @const */ var HOUR_KEY = 'hour';
3552
- /** @const */ var DAY_KEY = 'day';
3553
- /** @const */ var WEEK_KEY = 'week';
3554
- /** @const */ var MONTH_KEY = 'month';
3555
-
3556
- // Operands
3557
- /** @const */ var EVENT_PROPERTY = 'event';
3558
- /** @const */ var LITERAL_PROPERTY = 'literal';
3559
-
3560
- // Binary Operators
3561
- /** @const */ var AND_OPERATOR = 'and';
3562
- /** @const */ var OR_OPERATOR = 'or';
3563
- /** @const */ var IN_OPERATOR = 'in';
3564
- /** @const */ var NOT_IN_OPERATOR = 'not in';
3565
- /** @const */ var PLUS_OPERATOR = '+';
3566
- /** @const */ var MINUS_OPERATOR = '-';
3567
- /** @const */ var MUL_OPERATOR = '*';
3568
- /** @const */ var DIV_OPERATOR = '/';
3569
- /** @const */ var MOD_OPERATOR = '%';
3570
- /** @const */ var EQUALS_OPERATOR = '==';
3571
- /** @const */ var NOT_EQUALS_OPERATOR = '!=';
3572
- /** @const */ var GREATER_OPERATOR = '>';
3573
- /** @const */ var LESS_OPERATOR = '<';
3574
- /** @const */ var GREATER_EQUAL_OPERATOR = '>=';
3575
- /** @const */ var LESS_EQUAL_OPERATOR = '<=';
3576
-
3577
- // Typecast Operators
3578
- /** @const */ var BOOLEAN_OPERATOR = 'boolean';
3579
- /** @const */ var DATETIME_OPERATOR = 'datetime';
3580
- /** @const */ var LIST_OPERATOR = 'list';
3581
- /** @const */ var NUMBER_OPERATOR = 'number';
3582
- /** @const */ var STRING_OPERATOR = 'string';
3583
-
3584
- // Unary Operators
3585
- /** @const */ var NOT_OPERATOR = 'not';
3586
- /** @const */ var DEFINED_OPERATOR = 'defined';
3587
- /** @const */ var NOT_DEFINED_OPERATOR = 'not defined';
3588
-
3589
- // Special literals
3590
- /** @const */ var NOW_LITERAL = 'now';
3591
-
3592
- // Type cast functions
3593
- function toNumber(value) {
3594
- if (value === null) {
3595
- return null;
3596
- }
3624
+ var entry = this.storage.parse(this.name);
3597
3625
 
3598
- switch (typeof(value)) {
3599
- case 'object':
3600
- if (_.isDate(value) && value.getTime() >= 0) {
3601
- return value.getTime();
3602
- }
3603
- return null;
3604
- case 'boolean':
3605
- return Number(value);
3606
- case 'number':
3607
- return value;
3608
- case 'string':
3609
- value = Number(value);
3610
- if (!isNaN(value)) {
3611
- return value;
3612
- }
3613
- return 0;
3626
+ if (entry) {
3627
+ this['props'] = _.extend({}, entry);
3614
3628
  }
3615
- return null;
3616
- }
3629
+ };
3617
3630
 
3618
- function evaluateNumber(op, properties) {
3619
- if (!op['operator'] || op['operator'] !== NUMBER_OPERATOR || !op['children'] || op['children'].length !== 1) {
3620
- throw ('Invalid cast operator: number ' + op);
3621
- }
3631
+ MixpanelPersistence.prototype.upgrade = function(config) {
3632
+ var upgrade_from_old_lib = config['upgrade'],
3633
+ old_cookie_name,
3634
+ old_cookie;
3622
3635
 
3623
- return toNumber(evaluateSelector(op['children'][0], properties));
3624
- }
3636
+ if (upgrade_from_old_lib) {
3637
+ old_cookie_name = 'mp_super_properties';
3638
+ // Case where they had a custom cookie name before.
3639
+ if (typeof(upgrade_from_old_lib) === 'string') {
3640
+ old_cookie_name = upgrade_from_old_lib;
3641
+ }
3625
3642
 
3626
- function toBoolean(value) {
3627
- if (value === null) {
3628
- return false;
3629
- }
3643
+ old_cookie = this.storage.parse(old_cookie_name);
3630
3644
 
3631
- switch (typeof value) {
3632
- case 'boolean':
3633
- return value;
3634
- case 'number':
3635
- return value !== 0.0;
3636
- case 'string':
3637
- return value.length > 0;
3638
- case 'object':
3639
- if (_.isArray(value) && value.length > 0) {
3640
- return true;
3641
- }
3642
- if (_.isDate(value) && value.getTime() > 0) {
3643
- return true;
3644
- }
3645
- if (_.isObject(value) && !_.isEmptyObject(value)) {
3646
- return true;
3647
- }
3648
- return false;
3649
- }
3650
- return false;
3651
- }
3645
+ // remove the cookie
3646
+ this.storage.remove(old_cookie_name);
3647
+ this.storage.remove(old_cookie_name, true);
3652
3648
 
3653
- function evaluateBoolean(op, properties) {
3654
- if (!op['operator'] || op['operator'] !== BOOLEAN_OPERATOR || !op['children'] || op['children'].length !== 1) {
3655
- throw ('Invalid cast operator: boolean ' + op);
3649
+ if (old_cookie) {
3650
+ this['props'] = _.extend(
3651
+ this['props'],
3652
+ old_cookie['all'],
3653
+ old_cookie['events']
3654
+ );
3655
+ }
3656
3656
  }
3657
3657
 
3658
- return toBoolean(evaluateSelector(op['children'][0], properties));
3659
- }
3660
-
3661
- function evaluateDateTime(op, properties) {
3662
- if (!op['operator'] || op['operator'] !== DATETIME_OPERATOR || !op['children'] || op['children'].length !== 1) {
3663
- throw ('Invalid cast operator: datetime ' + op);
3664
- }
3658
+ if (!config['cookie_name'] && config['name'] !== 'mixpanel') {
3659
+ // special case to handle people with cookies of the form
3660
+ // mp_TOKEN_INSTANCENAME from the first release of this library
3661
+ old_cookie_name = 'mp_' + config['token'] + '_' + config['name'];
3662
+ old_cookie = this.storage.parse(old_cookie_name);
3665
3663
 
3666
- var v = evaluateSelector(op['children'][0], properties);
3667
- if (v === null) {
3668
- return null;
3669
- }
3664
+ if (old_cookie) {
3665
+ this.storage.remove(old_cookie_name);
3666
+ this.storage.remove(old_cookie_name, true);
3670
3667
 
3671
- switch (typeof(v)) {
3672
- case 'number':
3673
- case 'string':
3674
- var d = new Date(v);
3675
- if (isNaN(d.getTime())) {
3676
- return null;
3677
- }
3678
- return d;
3679
- case 'object':
3680
- if (_.isDate(v)) {
3681
- return v;
3682
- }
3668
+ // Save the prop values that were in the cookie from before -
3669
+ // this should only happen once as we delete the old one.
3670
+ this.register_once(old_cookie);
3671
+ }
3683
3672
  }
3684
3673
 
3685
- return null;
3686
- }
3674
+ if (this.storage === _.localStorage) {
3675
+ old_cookie = _.cookie.parse(this.name);
3687
3676
 
3688
- function evaluateList(op, properties) {
3689
- if (!op['operator'] || op['operator'] !== LIST_OPERATOR || !op['children'] || op['children'].length !== 1) {
3690
- throw ('Invalid cast operator: list ' + op);
3691
- }
3677
+ _.cookie.remove(this.name);
3678
+ _.cookie.remove(this.name, true);
3692
3679
 
3693
- var v = evaluateSelector(op['children'][0], properties);
3694
- if (v === null) {
3695
- return null;
3680
+ if (old_cookie) {
3681
+ this.register_once(old_cookie);
3682
+ }
3696
3683
  }
3684
+ };
3697
3685
 
3698
- if (_.isArray(v)) {
3699
- return v;
3700
- }
3686
+ MixpanelPersistence.prototype.save = function() {
3687
+ if (this.disabled) { return; }
3688
+ this.storage.set(
3689
+ this.name,
3690
+ _.JSONEncode(this['props']),
3691
+ this.expire_days,
3692
+ this.cross_subdomain,
3693
+ this.secure,
3694
+ this.cross_site,
3695
+ this.cookie_domain
3696
+ );
3697
+ };
3701
3698
 
3702
- return null;
3703
- }
3699
+ MixpanelPersistence.prototype.remove = function() {
3700
+ // remove both domain and subdomain cookies
3701
+ this.storage.remove(this.name, false, this.cookie_domain);
3702
+ this.storage.remove(this.name, true, this.cookie_domain);
3703
+ };
3704
3704
 
3705
- function evaluateString(op, properties) {
3706
- if (!op['operator'] || op['operator'] !== STRING_OPERATOR || !op['children'] || op['children'].length !== 1) {
3707
- throw ('Invalid cast operator: string ' + op);
3708
- }
3705
+ // removes the storage entry and deletes all loaded data
3706
+ // forced name for tests
3707
+ MixpanelPersistence.prototype.clear = function() {
3708
+ this.remove();
3709
+ this['props'] = {};
3710
+ };
3709
3711
 
3710
- var v = evaluateSelector(op['children'][0], properties);
3711
- switch (typeof(v)) {
3712
- case 'object':
3713
- if (_.isDate(v)) {
3714
- return v.toJSON();
3715
- }
3716
- return JSON.stringify(v);
3717
- }
3718
- return String(v);
3719
- }
3720
-
3721
- // Operators
3722
- function evaluateAnd(op, properties) {
3723
- if (!op['operator'] || op['operator'] !== AND_OPERATOR || !op['children'] || op['children'].length !== 2) {
3724
- throw ('Invalid operator: AND ' + op);
3725
- }
3726
-
3727
- return toBoolean(evaluateSelector(op['children'][0], properties)) && toBoolean(evaluateSelector(op['children'][1], properties));
3728
- }
3729
-
3730
- function evaluateOr(op, properties) {
3731
- if (!op['operator'] || op['operator'] !== OR_OPERATOR || !op['children'] || op['children'].length !== 2) {
3732
- throw ('Invalid operator: OR ' + op);
3733
- }
3734
-
3735
- return toBoolean(evaluateSelector(op['children'][0], properties)) || toBoolean(evaluateSelector(op['children'][1], properties));
3736
- }
3737
-
3738
- function evaluateIn(op, properties) {
3739
- if (!op['operator'] || [IN_OPERATOR, NOT_IN_OPERATOR].indexOf(op['operator']) === -1 || !op['children'] || op['children'].length !== 2) {
3740
- throw ('Invalid operator: IN/NOT IN ' + op);
3741
- }
3742
- var leftValue = evaluateSelector(op['children'][0], properties);
3743
- var rightValue = evaluateSelector(op['children'][1], properties);
3744
-
3745
- if (!_.isArray(rightValue) && !_.isString(rightValue)) {
3746
- throw ('Invalid operand for operator IN: invalid type' + rightValue);
3747
- }
3748
-
3749
- var v = rightValue.indexOf(leftValue) > -1;
3750
- if (op['operator'] === NOT_IN_OPERATOR) {
3751
- return !v;
3752
- }
3753
- return v;
3754
- }
3755
-
3756
- function evaluatePlus(op, properties) {
3757
- if (!op['operator'] || op['operator'] !== PLUS_OPERATOR || !op['children'] || op['children'].length < 2) {
3758
- throw ('Invalid operator: PLUS ' + op);
3759
- }
3760
- var l = evaluateSelector(op['children'][0], properties);
3761
- var r = evaluateSelector(op['children'][1], properties);
3762
-
3763
- if (typeof l === 'number' && typeof r === 'number') {
3764
- return l + r;
3765
- }
3766
- if (typeof l === 'string' && typeof r === 'string') {
3767
- return l + r;
3768
- }
3769
- return null;
3770
- }
3771
-
3772
- function evaluateArithmetic(op, properties) {
3773
- if (!op['operator'] || [MINUS_OPERATOR, MUL_OPERATOR, DIV_OPERATOR, MOD_OPERATOR].indexOf(op['operator']) === -1 ||
3774
- !op['children'] || op['children'].length < 2) {
3775
- throw ('Invalid arithmetic operator ' + op);
3776
- }
3777
-
3778
- var l = evaluateSelector(op['children'][0], properties);
3779
- var r = evaluateSelector(op['children'][1], properties);
3780
-
3781
- if (typeof l === 'number' && typeof r === 'number') {
3782
- switch (op['operator']) {
3783
- case MINUS_OPERATOR:
3784
- return l - r;
3785
- case MUL_OPERATOR:
3786
- return l * r;
3787
- case DIV_OPERATOR:
3788
- if (r !== 0) {
3789
- return l / r;
3790
- }
3791
- return null;
3792
- case MOD_OPERATOR:
3793
- if (r === 0) {
3794
- return null;
3795
- }
3796
- if (l === 0) {
3797
- return 0;
3798
- }
3799
- if ((l < 0 && r > 0) || (l > 0 && r < 0)) {
3800
- /* Mimic python modulo - result takes sign of the divisor
3801
- * if one operand is negative. */
3802
- return -(Math.floor(l / r) * r - l);
3803
- }
3804
- return l % r;
3805
- default:
3806
- throw('Unknown operator: ' + op['operator']);
3807
- }
3808
- }
3809
-
3810
- return null;
3811
- }
3812
-
3813
- function _isArrayEqual(l, r) {
3814
- if (l === r) return true;
3815
- if (l === null || r === null) return false;
3816
- if (l.length !== r.length) return false;
3712
+ /**
3713
+ * @param {Object} props
3714
+ * @param {*=} default_value
3715
+ * @param {number=} days
3716
+ */
3717
+ MixpanelPersistence.prototype.register_once = function(props, default_value, days) {
3718
+ if (_.isObject(props)) {
3719
+ if (typeof(default_value) === 'undefined') { default_value = 'None'; }
3720
+ this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days;
3817
3721
 
3818
- for (var i = 0; i < l.length; i++) {
3819
- if (l[i] !== r[i]) {
3820
- return false;
3821
- }
3822
- }
3722
+ _.each(props, function(val, prop) {
3723
+ if (!this['props'].hasOwnProperty(prop) || this['props'][prop] === default_value) {
3724
+ this['props'][prop] = val;
3725
+ }
3726
+ }, this);
3823
3727
 
3824
- return true;
3825
- }
3728
+ this.save();
3826
3729
 
3827
- function _isEqual(l, r) {
3828
- if ( l === null && l === r ) {
3829
3730
  return true;
3830
3731
  }
3831
- if (typeof l === typeof r) {
3832
- switch (typeof l) {
3833
- case 'number':
3834
- case 'string':
3835
- case 'boolean':
3836
- return l === r;
3837
- case 'object':
3838
- if (_.isArray(l) && _.isArray(r)) {
3839
- return _isArrayEqual(l, r);
3840
- }
3841
- if (_.isDate(l) && _.isDate(r)) {
3842
- return l.getTime() === r.getTime();
3843
- }
3844
- if (_.isObject(l) && _.isObject(r)) {
3845
- return JSON.stringify(l) === JSON.stringify(r);
3846
- }
3847
- }
3848
- }
3849
3732
  return false;
3850
- }
3851
-
3852
- function evaluateEquality(op, properties) {
3853
- if (!op['operator'] || [EQUALS_OPERATOR, NOT_EQUALS_OPERATOR].indexOf(op['operator']) === -1 || !op['children'] || op['children'].length !== 2) {
3854
- throw ('Invalid equality operator ' + op);
3855
- }
3856
-
3857
- var v = _isEqual(evaluateSelector(op['children'][0], properties), evaluateSelector(op['children'][1], properties));
3858
-
3859
- switch (op['operator']) {
3860
- case EQUALS_OPERATOR:
3861
- return v;
3862
- case NOT_EQUALS_OPERATOR:
3863
- return !v;
3864
- }
3865
- }
3866
-
3867
- function evaluateComparison(op, properties) {
3868
- if (!op['operator'] ||
3869
- [GREATER_OPERATOR, GREATER_EQUAL_OPERATOR, LESS_OPERATOR, LESS_EQUAL_OPERATOR].indexOf(op['operator']) === -1 ||
3870
- !op['children'] || op['children'].length !== 2) {
3871
- throw ('Invalid comparison operator ' + op);
3872
- }
3873
- var l = evaluateSelector(op['children'][0], properties);
3874
- var r = evaluateSelector(op['children'][1], properties);
3875
-
3876
- if (typeof(l) === typeof(r)) {
3877
- if (typeof(r) === 'number' || _.isDate(r)) {
3878
- l = toNumber(l);
3879
- r = toNumber(r);
3880
- switch (op['operator']) {
3881
- case GREATER_OPERATOR:
3882
- return l > r;
3883
- case GREATER_EQUAL_OPERATOR:
3884
- return l >= r;
3885
- case LESS_OPERATOR:
3886
- return l < r;
3887
- case LESS_EQUAL_OPERATOR:
3888
- return l <= r;
3889
- }
3890
- } else if (typeof(r) === 'string') {
3891
- var compare = l.localeCompare(r);
3892
- switch (op['operator']) {
3893
- case GREATER_OPERATOR:
3894
- return compare > 0;
3895
- case GREATER_EQUAL_OPERATOR:
3896
- return compare >= 0;
3897
- case LESS_OPERATOR:
3898
- return compare < 0;
3899
- case LESS_EQUAL_OPERATOR:
3900
- return compare <= 0;
3901
- }
3902
- }
3903
- }
3904
-
3905
- return null;
3906
- }
3907
-
3908
- function evaluateDefined(op, properties) {
3909
- if (!op['operator'] || [DEFINED_OPERATOR, NOT_DEFINED_OPERATOR].indexOf(op['operator']) === -1 ||
3910
- !op['children'] || op['children'].length !== 1) {
3911
- throw ('Invalid defined/not defined operator: ' + op);
3912
- }
3733
+ };
3913
3734
 
3914
- var b = evaluateSelector(op['children'][0], properties) !== null;
3915
- if (op['operator'] === NOT_DEFINED_OPERATOR) {
3916
- return !b;
3917
- }
3735
+ /**
3736
+ * @param {Object} props
3737
+ * @param {number=} days
3738
+ */
3739
+ MixpanelPersistence.prototype.register = function(props, days) {
3740
+ if (_.isObject(props)) {
3741
+ this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days;
3918
3742
 
3919
- return b;
3920
- }
3743
+ _.extend(this['props'], props);
3921
3744
 
3922
- function evaluateNot(op, properties) {
3923
- if (!op['operator'] || op['operator'] !== NOT_OPERATOR || !op['children'] || op['children'].length !== 1) {
3924
- throw ('Invalid not operator: ' + op);
3925
- }
3745
+ this.save();
3926
3746
 
3927
- var v = evaluateSelector(op['children'][0], properties);
3928
- if (v === null) {
3929
3747
  return true;
3930
3748
  }
3749
+ return false;
3750
+ };
3931
3751
 
3932
- if (typeof(v) === 'boolean') {
3933
- return !v;
3934
- }
3935
-
3936
- return null;
3937
- }
3938
-
3939
- function evaluateOperator(op, properties) {
3940
- if (!op['operator']) {
3941
- throw ('Invalid operator: operator key missing ' + op);
3942
- }
3943
-
3944
- switch (op['operator']) {
3945
- case AND_OPERATOR:
3946
- return evaluateAnd(op, properties);
3947
- case OR_OPERATOR:
3948
- return evaluateOr(op, properties);
3949
- case IN_OPERATOR:
3950
- case NOT_IN_OPERATOR:
3951
- return evaluateIn(op, properties);
3952
- case PLUS_OPERATOR:
3953
- return evaluatePlus(op, properties);
3954
- case MINUS_OPERATOR:
3955
- case MUL_OPERATOR:
3956
- case DIV_OPERATOR:
3957
- case MOD_OPERATOR:
3958
- return evaluateArithmetic(op, properties);
3959
- case EQUALS_OPERATOR:
3960
- case NOT_EQUALS_OPERATOR:
3961
- return evaluateEquality(op, properties);
3962
- case GREATER_OPERATOR:
3963
- case LESS_OPERATOR:
3964
- case GREATER_EQUAL_OPERATOR:
3965
- case LESS_EQUAL_OPERATOR:
3966
- return evaluateComparison(op, properties);
3967
- case BOOLEAN_OPERATOR:
3968
- return evaluateBoolean(op, properties);
3969
- case DATETIME_OPERATOR:
3970
- return evaluateDateTime(op, properties);
3971
- case LIST_OPERATOR:
3972
- return evaluateList(op, properties);
3973
- case NUMBER_OPERATOR:
3974
- return evaluateNumber(op, properties);
3975
- case STRING_OPERATOR:
3976
- return evaluateString(op, properties);
3977
- case DEFINED_OPERATOR:
3978
- case NOT_DEFINED_OPERATOR:
3979
- return evaluateDefined(op, properties);
3980
- case NOT_OPERATOR:
3981
- return evaluateNot(op, properties);
3982
- }
3983
- }
3984
-
3985
- function evaluateWindow(value) {
3986
- var win = value[WINDOW_KEY];
3987
- if (!win || !win[UNIT_KEY] || !win[VALUE_KEY]) {
3988
- throw('Invalid window: missing required keys ' + JSON.stringify(value));
3989
- }
3990
- var out = new Date();
3991
- switch (win[UNIT_KEY]) {
3992
- case HOUR_KEY:
3993
- out.setTime(out.getTime() + (win[VALUE_KEY]*-1*60*60*1000));
3994
- break;
3995
- case DAY_KEY:
3996
- out.setTime(out.getTime() + (win[VALUE_KEY]*-1*24*60*60*1000));
3997
- break;
3998
- case WEEK_KEY:
3999
- out.setTime(out.getTime() + (win[VALUE_KEY]*-1*7*24*60*60*1000));
4000
- break;
4001
- case MONTH_KEY:
4002
- out.setTime(out.getTime() + (win[VALUE_KEY]*-1*30*24*60*60*1000));
4003
- break;
4004
- default:
4005
- throw('Invalid unit: ' + win[UNIT_KEY]);
4006
- }
4007
-
4008
- return out;
4009
- }
4010
-
4011
- function evaluateOperand(op, properties) {
4012
- if (!op['property'] || !op['value']) {
4013
- throw('Invalid operand: missing required keys ' + op);
4014
- }
4015
- switch (op['property']) {
4016
- case EVENT_PROPERTY:
4017
- if (properties[op['value']] !== undefined) {
4018
- return properties[op['value']];
4019
- }
4020
- return null;
4021
- case LITERAL_PROPERTY:
4022
- if (op['value'] === NOW_LITERAL) {
4023
- return new Date();
4024
- }
4025
- if (typeof(op['value']) === 'object') {
4026
- return evaluateWindow(op['value']);
4027
- }
4028
- return op['value'];
4029
- default:
4030
- throw('Invalid operand: Invalid property type ' + op['property']);
4031
- }
4032
- }
4033
-
4034
- function evaluateSelector(filters, properties) {
4035
- if (filters[PROPERTY_KEY]) {
4036
- return evaluateOperand(filters, properties);
4037
- }
4038
- if (filters[OPERATOR_KEY]) {
4039
- return evaluateOperator(filters, properties);
4040
- }
4041
- }
4042
-
4043
- // Internal class for notification display
4044
-
4045
- var MixpanelNotification = function(notif_data, mixpanel_instance) {
4046
- _.bind_instance_methods(this);
4047
-
4048
- this.mixpanel = mixpanel_instance;
4049
- this.persistence = this.mixpanel['persistence'];
4050
- this.resource_protocol = this.mixpanel.get_config('inapp_protocol');
4051
- this.cdn_host = this.mixpanel.get_config('cdn');
4052
-
4053
- this.campaign_id = _.escapeHTML(notif_data['id']);
4054
- this.message_id = _.escapeHTML(notif_data['message_id']);
4055
-
4056
- this.body = (_.escapeHTML(notif_data['body']) || '').replace(/\n/g, '<br/>');
4057
- this.cta = _.escapeHTML(notif_data['cta']) || 'Close';
4058
- this.notif_type = _.escapeHTML(notif_data['type']) || 'takeover';
4059
- this.style = _.escapeHTML(notif_data['style']) || 'light';
4060
- this.title = _.escapeHTML(notif_data['title']) || '';
4061
- this.video_width = MixpanelNotification.VIDEO_WIDTH;
4062
- this.video_height = MixpanelNotification.VIDEO_HEIGHT;
4063
-
4064
- this.display_triggers = notif_data['display_triggers'] || [];
4065
-
4066
- // These fields are url-sanitized in the backend already.
4067
- this.dest_url = notif_data['cta_url'] || null;
4068
- this.image_url = notif_data['image_url'] || null;
4069
- this.thumb_image_url = notif_data['thumb_image_url'] || null;
4070
- this.video_url = notif_data['video_url'] || null;
4071
-
4072
- if (this.thumb_image_url && this.thumb_image_url.indexOf('//') === 0) {
4073
- this.thumb_image_url = this.thumb_image_url.replace('//', this.resource_protocol);
4074
- }
4075
-
4076
- this.clickthrough = true;
4077
- if (!this.dest_url) {
4078
- this.dest_url = '#dismiss';
4079
- this.clickthrough = false;
3752
+ MixpanelPersistence.prototype.unregister = function(prop) {
3753
+ if (prop in this['props']) {
3754
+ delete this['props'][prop];
3755
+ this.save();
4080
3756
  }
3757
+ };
4081
3758
 
4082
- this.mini = this.notif_type === 'mini';
4083
- if (!this.mini) {
4084
- this.notif_type = 'takeover';
3759
+ MixpanelPersistence.prototype.update_campaign_params = function() {
3760
+ if (!this.campaign_params_saved) {
3761
+ this.register_once(_.info.campaignParams());
3762
+ this.campaign_params_saved = true;
4085
3763
  }
4086
- this.notif_width = !this.mini ? MixpanelNotification.NOTIF_WIDTH : MixpanelNotification.NOTIF_WIDTH_MINI;
4087
-
4088
- this._set_client_config();
4089
- this.imgs_to_preload = this._init_image_html();
4090
- this._init_video();
4091
3764
  };
4092
3765
 
4093
- MixpanelNotification.ANIM_TIME = 200;
4094
- MixpanelNotification.MARKUP_PREFIX = 'mixpanel-notification';
4095
- MixpanelNotification.BG_OPACITY = 0.6;
4096
- MixpanelNotification.NOTIF_TOP = 25;
4097
- MixpanelNotification.NOTIF_START_TOP = 200;
4098
- MixpanelNotification.NOTIF_WIDTH = 388;
4099
- MixpanelNotification.NOTIF_WIDTH_MINI = 420;
4100
- MixpanelNotification.NOTIF_HEIGHT_MINI = 85;
4101
- MixpanelNotification.THUMB_BORDER_SIZE = 5;
4102
- MixpanelNotification.THUMB_IMG_SIZE = 60;
4103
- MixpanelNotification.THUMB_OFFSET = Math.round(MixpanelNotification.THUMB_IMG_SIZE / 2);
4104
- MixpanelNotification.VIDEO_WIDTH = 595;
4105
- MixpanelNotification.VIDEO_HEIGHT = 334;
3766
+ MixpanelPersistence.prototype.update_search_keyword = function(referrer) {
3767
+ this.register(_.info.searchInfo(referrer));
3768
+ };
4106
3769
 
4107
- MixpanelNotification.prototype.show = function() {
4108
- var self = this;
4109
- this._set_client_config();
3770
+ // EXPORTED METHOD, we test this directly.
3771
+ MixpanelPersistence.prototype.update_referrer_info = function(referrer) {
3772
+ // If referrer doesn't exist, we want to note the fact that it was type-in traffic.
3773
+ this.register_once({
3774
+ '$initial_referrer': referrer || '$direct',
3775
+ '$initial_referring_domain': _.info.referringDomain(referrer) || '$direct'
3776
+ }, '');
3777
+ };
4110
3778
 
4111
- // don't display until HTML body exists
4112
- if (!this.body_el) {
4113
- setTimeout(function() { self.show(); }, 300);
4114
- return;
4115
- }
3779
+ MixpanelPersistence.prototype.get_referrer_info = function() {
3780
+ return _.strip_empty_properties({
3781
+ '$initial_referrer': this['props']['$initial_referrer'],
3782
+ '$initial_referring_domain': this['props']['$initial_referring_domain']
3783
+ });
3784
+ };
4116
3785
 
4117
- this._init_styles();
4118
- this._init_notification_el();
3786
+ // safely fills the passed in object with stored properties,
3787
+ // does not override any properties defined in both
3788
+ // returns the passed in object
3789
+ MixpanelPersistence.prototype.safe_merge = function(props) {
3790
+ _.each(this['props'], function(val, prop) {
3791
+ if (!(prop in props)) {
3792
+ props[prop] = val;
3793
+ }
3794
+ });
4119
3795
 
4120
- // wait for any images to load before showing notification
4121
- this._preload_images(this._attach_and_animate);
3796
+ return props;
4122
3797
  };
4123
3798
 
4124
- MixpanelNotification.prototype.dismiss = _.safewrap(function() {
4125
- if (!this.marked_as_shown) {
4126
- // unexpected condition: user interacted with notif even though we didn't consider it
4127
- // visible (see _mark_as_shown()); send tracking signals to mark delivery
4128
- this._mark_delivery({'invisible': true});
4129
- }
3799
+ MixpanelPersistence.prototype.update_config = function(config) {
3800
+ this.default_expiry = this.expire_days = config['cookie_expiration'];
3801
+ this.set_disabled(config['disable_persistence']);
3802
+ this.set_cookie_domain(config['cookie_domain']);
3803
+ this.set_cross_site(config['cross_site_cookie']);
3804
+ this.set_cross_subdomain(config['cross_subdomain_cookie']);
3805
+ this.set_secure(config['secure_cookie']);
3806
+ };
4130
3807
 
4131
- var exiting_el = this.showing_video ? this._get_el('video') : this._get_notification_display_el();
4132
- if (this.use_transitions) {
4133
- this._remove_class('bg', 'visible');
4134
- this._add_class(exiting_el, 'exiting');
4135
- setTimeout(this._remove_notification_el, MixpanelNotification.ANIM_TIME);
3808
+ MixpanelPersistence.prototype.set_disabled = function(disabled) {
3809
+ this.disabled = disabled;
3810
+ if (this.disabled) {
3811
+ this.remove();
4136
3812
  } else {
4137
- var notif_attr, notif_start, notif_goal;
4138
- if (this.mini) {
4139
- notif_attr = 'right';
4140
- notif_start = 20;
4141
- notif_goal = -100;
4142
- } else {
4143
- notif_attr = 'top';
4144
- notif_start = MixpanelNotification.NOTIF_TOP;
4145
- notif_goal = MixpanelNotification.NOTIF_START_TOP + MixpanelNotification.NOTIF_TOP;
4146
- }
4147
- this._animate_els([
4148
- {
4149
- el: this._get_el('bg'),
4150
- attr: 'opacity',
4151
- start: MixpanelNotification.BG_OPACITY,
4152
- goal: 0.0
4153
- },
4154
- {
4155
- el: exiting_el,
4156
- attr: 'opacity',
4157
- start: 1.0,
4158
- goal: 0.0
4159
- },
4160
- {
4161
- el: exiting_el,
4162
- attr: notif_attr,
4163
- start: notif_start,
4164
- goal: notif_goal
4165
- }
4166
- ], MixpanelNotification.ANIM_TIME, this._remove_notification_el);
4167
- }
4168
- });
4169
-
4170
- MixpanelNotification.prototype._add_class = _.safewrap(function(el, class_name) {
4171
- class_name = MixpanelNotification.MARKUP_PREFIX + '-' + class_name;
4172
- if (typeof el === 'string') {
4173
- el = this._get_el(el);
4174
- }
4175
- if (!el.className) {
4176
- el.className = class_name;
4177
- } else if (!~(' ' + el.className + ' ').indexOf(' ' + class_name + ' ')) {
4178
- el.className += ' ' + class_name;
4179
- }
4180
- });
4181
- MixpanelNotification.prototype._remove_class = _.safewrap(function(el, class_name) {
4182
- class_name = MixpanelNotification.MARKUP_PREFIX + '-' + class_name;
4183
- if (typeof el === 'string') {
4184
- el = this._get_el(el);
4185
- }
4186
- if (el.className) {
4187
- el.className = (' ' + el.className + ' ')
4188
- .replace(' ' + class_name + ' ', '')
4189
- .replace(/^[\s\xA0]+/, '')
4190
- .replace(/[\s\xA0]+$/, '');
3813
+ this.save();
4191
3814
  }
4192
- });
4193
-
4194
- MixpanelNotification.prototype._animate_els = _.safewrap(function(anims, mss, done_cb, start_time) {
4195
- var self = this,
4196
- in_progress = false,
4197
- ai, anim,
4198
- cur_time = 1 * new Date(), time_diff;
4199
-
4200
- start_time = start_time || cur_time;
4201
- time_diff = cur_time - start_time;
3815
+ };
4202
3816
 
4203
- for (ai = 0; ai < anims.length; ai++) {
4204
- anim = anims[ai];
4205
- if (typeof anim.val === 'undefined') {
4206
- anim.val = anim.start;
4207
- }
4208
- if (anim.val !== anim.goal) {
4209
- in_progress = true;
4210
- var anim_diff = anim.goal - anim.start,
4211
- anim_dir = anim.goal >= anim.start ? 1 : -1;
4212
- anim.val = anim.start + anim_diff * time_diff / mss;
4213
- if (anim.attr !== 'opacity') {
4214
- anim.val = Math.round(anim.val);
4215
- }
4216
- if ((anim_dir > 0 && anim.val >= anim.goal) || (anim_dir < 0 && anim.val <= anim.goal)) {
4217
- anim.val = anim.goal;
4218
- }
4219
- }
4220
- }
4221
- if (!in_progress) {
4222
- if (done_cb) {
4223
- done_cb();
4224
- }
4225
- return;
3817
+ MixpanelPersistence.prototype.set_cookie_domain = function(cookie_domain) {
3818
+ if (cookie_domain !== this.cookie_domain) {
3819
+ this.remove();
3820
+ this.cookie_domain = cookie_domain;
3821
+ this.save();
4226
3822
  }
3823
+ };
4227
3824
 
4228
- for (ai = 0; ai < anims.length; ai++) {
4229
- anim = anims[ai];
4230
- if (anim.el) {
4231
- var suffix = anim.attr === 'opacity' ? '' : 'px';
4232
- anim.el.style[anim.attr] = String(anim.val) + suffix;
4233
- }
3825
+ MixpanelPersistence.prototype.set_cross_site = function(cross_site) {
3826
+ if (cross_site !== this.cross_site) {
3827
+ this.cross_site = cross_site;
3828
+ this.remove();
3829
+ this.save();
4234
3830
  }
4235
- setTimeout(function() { self._animate_els(anims, mss, done_cb, start_time); }, 10);
4236
- });
4237
-
4238
- MixpanelNotification.prototype._attach_and_animate = _.safewrap(function() {
4239
- var self = this;
3831
+ };
4240
3832
 
4241
- // no possibility to double-display
4242
- if (this.shown || this._get_shown_campaigns()[this.campaign_id]) {
4243
- return;
3833
+ MixpanelPersistence.prototype.set_cross_subdomain = function(cross_subdomain) {
3834
+ if (cross_subdomain !== this.cross_subdomain) {
3835
+ this.cross_subdomain = cross_subdomain;
3836
+ this.remove();
3837
+ this.save();
4244
3838
  }
4245
- this.shown = true;
4246
-
4247
- this.body_el.appendChild(this.notification_el);
4248
- setTimeout(function() {
4249
- var notif_el = self._get_notification_display_el();
4250
- if (self.use_transitions) {
4251
- if (!self.mini) {
4252
- self._add_class('bg', 'visible');
4253
- }
4254
- self._add_class(notif_el, 'visible');
4255
- self._mark_as_shown();
4256
- } else {
4257
- var notif_attr, notif_start, notif_goal;
4258
- if (self.mini) {
4259
- notif_attr = 'right';
4260
- notif_start = -100;
4261
- notif_goal = 20;
4262
- } else {
4263
- notif_attr = 'top';
4264
- notif_start = MixpanelNotification.NOTIF_START_TOP + MixpanelNotification.NOTIF_TOP;
4265
- notif_goal = MixpanelNotification.NOTIF_TOP;
4266
- }
4267
- self._animate_els([
4268
- {
4269
- el: self._get_el('bg'),
4270
- attr: 'opacity',
4271
- start: 0.0,
4272
- goal: MixpanelNotification.BG_OPACITY
4273
- },
4274
- {
4275
- el: notif_el,
4276
- attr: 'opacity',
4277
- start: 0.0,
4278
- goal: 1.0
4279
- },
4280
- {
4281
- el: notif_el,
4282
- attr: notif_attr,
4283
- start: notif_start,
4284
- goal: notif_goal
4285
- }
4286
- ], MixpanelNotification.ANIM_TIME, self._mark_as_shown);
4287
- }
4288
- }, 100);
4289
- _.register_event(self._get_el('cancel'), 'click', function(e) {
4290
- e.preventDefault();
4291
- self.dismiss();
4292
- });
4293
- var click_el = self._get_el('button') ||
4294
- self._get_el('mini-content');
4295
- _.register_event(click_el, 'click', function(e) {
4296
- e.preventDefault();
4297
- if (self.show_video) {
4298
- self._track_event('$campaign_open', {'$resource_type': 'video'});
4299
- self._switch_to_video();
4300
- } else {
4301
- self.dismiss();
4302
- if (self.clickthrough) {
4303
- var tracking_cb = null;
4304
- if (self.mixpanel.get_config('inapp_link_new_window')) {
4305
- window.open(self.dest_url);
4306
- } else {
4307
- tracking_cb = function() {
4308
- window.location.href = self.dest_url;
4309
- };
4310
- }
4311
- self._track_event('$campaign_open', {'$resource_type': 'link'}, tracking_cb);
4312
- }
4313
- }
4314
- });
4315
- });
4316
-
4317
- MixpanelNotification.prototype._get_el = function(id) {
4318
- return document.getElementById(MixpanelNotification.MARKUP_PREFIX + '-' + id);
4319
3839
  };
4320
3840
 
4321
- MixpanelNotification.prototype._get_notification_display_el = function() {
4322
- return this._get_el(this.notif_type);
3841
+ MixpanelPersistence.prototype.get_cross_subdomain = function() {
3842
+ return this.cross_subdomain;
4323
3843
  };
4324
3844
 
4325
- MixpanelNotification.prototype._get_shown_campaigns = function() {
4326
- return this.persistence['props'][CAMPAIGN_IDS_KEY] || (this.persistence['props'][CAMPAIGN_IDS_KEY] = {});
3845
+ MixpanelPersistence.prototype.set_secure = function(secure) {
3846
+ if (secure !== this.secure) {
3847
+ this.secure = secure ? true : false;
3848
+ this.remove();
3849
+ this.save();
3850
+ }
4327
3851
  };
4328
3852
 
4329
- MixpanelNotification.prototype._matches_event_data = _.safewrap(function(event_data) {
4330
- var event_name = event_data['event'] || '';
4331
- for (var i = 0; i < this.display_triggers.length; i++) {
4332
- var display_trigger = this.display_triggers[i];
4333
- var match_event = display_trigger['event'] || '';
4334
- if (match_event === '$any_event' || event_name === display_trigger['event']) {
4335
- if (display_trigger['selector'] && !_.isEmptyObject(display_trigger['selector'])) {
4336
- if (evaluateSelector(display_trigger['selector'], event_data['properties'])) {
4337
- return true;
4338
- }
4339
- } else {
4340
- return true;
4341
- }
4342
- }
4343
- }
4344
- return false;
4345
- });
4346
-
4347
-
4348
- MixpanelNotification.prototype._browser_lte = function(browser, version) {
4349
- return this.browser_versions[browser] && this.browser_versions[browser] <= version;
4350
- };
4351
-
4352
- MixpanelNotification.prototype._init_image_html = function() {
4353
- var imgs_to_preload = [];
4354
-
4355
- if (!this.mini) {
4356
- if (this.image_url) {
4357
- imgs_to_preload.push(this.image_url);
4358
- this.img_html = '<img id="img" src="' + this.image_url + '"/>';
4359
- } else {
4360
- this.img_html = '';
4361
- }
4362
- if (this.thumb_image_url) {
4363
- imgs_to_preload.push(this.thumb_image_url);
4364
- this.thumb_img_html =
4365
- '<div id="thumbborder-wrapper"><div id="thumbborder"></div></div>' +
4366
- '<img id="thumbnail"' +
4367
- ' src="' + this.thumb_image_url + '"' +
4368
- ' width="' + MixpanelNotification.THUMB_IMG_SIZE + '"' +
4369
- ' height="' + MixpanelNotification.THUMB_IMG_SIZE + '"' +
4370
- '/>' +
4371
- '<div id="thumbspacer"></div>';
4372
- } else {
4373
- this.thumb_img_html = '';
4374
- }
4375
- } else {
4376
- this.thumb_image_url = this.thumb_image_url || (this.cdn_host + '/site_media/images/icons/notifications/mini-news-dark.png');
4377
- imgs_to_preload.push(this.thumb_image_url);
4378
- }
4379
-
4380
- return imgs_to_preload;
4381
- };
4382
-
4383
- MixpanelNotification.prototype._init_notification_el = function() {
4384
- var notification_html = '';
4385
- var video_src = '';
4386
- var video_html = '';
4387
- var cancel_html = '<div id="cancel">' +
4388
- '<div id="cancel-icon"></div>' +
4389
- '</div>';
4390
-
4391
- this.notification_el = document.createElement('div');
4392
- this.notification_el.id = MixpanelNotification.MARKUP_PREFIX + '-wrapper';
4393
- if (!this.mini) {
4394
- // TAKEOVER notification
4395
- var close_html = (this.clickthrough || this.show_video) ? '' : '<div id="button-close"></div>',
4396
- play_html = this.show_video ? '<div id="button-play"></div>' : '';
4397
- if (this._browser_lte('ie', 7)) {
4398
- close_html = '';
4399
- play_html = '';
4400
- }
4401
- notification_html =
4402
- '<div id="takeover">' +
4403
- this.thumb_img_html +
4404
- '<div id="mainbox">' +
4405
- cancel_html +
4406
- '<div id="content">' +
4407
- this.img_html +
4408
- '<div id="title">' + this.title + '</div>' +
4409
- '<div id="body">' + this.body + '</div>' +
4410
- '<div id="tagline">' +
4411
- '<a href="http://mixpanel.com?from=inapp" target="_blank">POWERED BY MIXPANEL</a>' +
4412
- '</div>' +
4413
- '</div>' +
4414
- '<div id="button">' +
4415
- close_html +
4416
- '<a id="button-link" href="' + this.dest_url + '">' + this.cta + '</a>' +
4417
- play_html +
4418
- '</div>' +
4419
- '</div>' +
4420
- '</div>';
4421
- } else {
4422
- // MINI notification
4423
- notification_html =
4424
- '<div id="mini">' +
4425
- '<div id="mainbox">' +
4426
- cancel_html +
4427
- '<div id="mini-content">' +
4428
- '<div id="mini-icon">' +
4429
- '<div id="mini-icon-img"></div>' +
4430
- '</div>' +
4431
- '<div id="body">' +
4432
- '<div id="body-text"><div>' + this.body + '</div></div>' +
4433
- '</div>' +
4434
- '</div>' +
4435
- '</div>' +
4436
- '<div id="mini-border"></div>' +
4437
- '</div>';
4438
- }
4439
- if (this.youtube_video) {
4440
- video_src = this.resource_protocol + 'www.youtube.com/embed/' + this.youtube_video +
4441
- '?wmode=transparent&showinfo=0&modestbranding=0&rel=0&autoplay=1&loop=0&vq=hd1080';
4442
- if (this.yt_custom) {
4443
- video_src += '&enablejsapi=1&html5=1&controls=0';
4444
- video_html =
4445
- '<div id="video-controls">' +
4446
- '<div id="video-progress" class="video-progress-el">' +
4447
- '<div id="video-progress-total" class="video-progress-el"></div>' +
4448
- '<div id="video-elapsed" class="video-progress-el"></div>' +
4449
- '</div>' +
4450
- '<div id="video-time" class="video-progress-el"></div>' +
4451
- '</div>';
4452
- }
4453
- } else if (this.vimeo_video) {
4454
- video_src = this.resource_protocol + 'player.vimeo.com/video/' + this.vimeo_video + '?autoplay=1&title=0&byline=0&portrait=0';
4455
- }
4456
- if (this.show_video) {
4457
- this.video_iframe =
4458
- '<iframe id="' + MixpanelNotification.MARKUP_PREFIX + '-video-frame" ' +
4459
- 'width="' + this.video_width + '" height="' + this.video_height + '" ' +
4460
- ' src="' + video_src + '"' +
4461
- ' frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen="1" scrolling="no"' +
4462
- '></iframe>';
4463
- video_html =
4464
- '<div id="video-' + (this.flip_animate ? '' : 'no') + 'flip">' +
4465
- '<div id="video">' +
4466
- '<div id="video-holder"></div>' +
4467
- video_html +
4468
- '</div>' +
4469
- '</div>';
4470
- }
4471
- var main_html = video_html + notification_html;
4472
- if (this.flip_animate) {
4473
- main_html =
4474
- (this.mini ? notification_html : '') +
4475
- '<div id="flipcontainer"><div id="flipper">' +
4476
- (this.mini ? video_html : main_html) +
4477
- '</div></div>';
4478
- }
4479
-
4480
- this.notification_el.innerHTML =
4481
- ('<div id="overlay" class="' + this.notif_type + '">' +
4482
- '<div id="campaignid-' + this.campaign_id + '">' +
4483
- '<div id="bgwrapper">' +
4484
- '<div id="bg"></div>' +
4485
- main_html +
4486
- '</div>' +
4487
- '</div>' +
4488
- '</div>')
4489
- .replace(/class="/g, 'class="' + MixpanelNotification.MARKUP_PREFIX + '-')
4490
- .replace(/id="/g, 'id="' + MixpanelNotification.MARKUP_PREFIX + '-');
4491
- };
4492
-
4493
- MixpanelNotification.prototype._init_styles = function() {
4494
- if (this.style === 'dark') {
4495
- this.style_vals = {
4496
- bg: '#1d1f25',
4497
- bg_actions: '#282b32',
4498
- bg_hover: '#3a4147',
4499
- bg_light: '#4a5157',
4500
- border_gray: '#32353c',
4501
- cancel_opacity: '0.4',
4502
- mini_hover: '#2a3137',
4503
- text_title: '#fff',
4504
- text_main: '#9498a3',
4505
- text_tagline: '#464851',
4506
- text_hover: '#ddd'
4507
- };
4508
- } else {
4509
- this.style_vals = {
4510
- bg: '#fff',
4511
- bg_actions: '#e7eaee',
4512
- bg_hover: '#eceff3',
4513
- bg_light: '#f5f5f5',
4514
- border_gray: '#e4ecf2',
4515
- cancel_opacity: '1.0',
4516
- mini_hover: '#fafafa',
4517
- text_title: '#5c6578',
4518
- text_main: '#8b949b',
4519
- text_tagline: '#ced9e6',
4520
- text_hover: '#7c8598'
4521
- };
4522
- }
4523
- var shadow = '0px 0px 35px 0px rgba(45, 49, 56, 0.7)',
4524
- video_shadow = shadow,
4525
- mini_shadow = shadow,
4526
- thumb_total_size = MixpanelNotification.THUMB_IMG_SIZE + MixpanelNotification.THUMB_BORDER_SIZE * 2,
4527
- anim_seconds = (MixpanelNotification.ANIM_TIME / 1000) + 's';
4528
- if (this.mini) {
4529
- shadow = 'none';
4530
- }
4531
-
4532
- // don't display on small viewports
4533
- var notif_media_queries = {},
4534
- min_width = MixpanelNotification.NOTIF_WIDTH_MINI + 20;
4535
- notif_media_queries['@media only screen and (max-width: ' + (min_width - 1) + 'px)'] = {
4536
- '#overlay': {
4537
- 'display': 'none'
4538
- }
4539
- };
4540
- var notif_styles = {
4541
- '.flipped': {
4542
- 'transform': 'rotateY(180deg)'
4543
- },
4544
- '#overlay': {
4545
- 'position': 'fixed',
4546
- 'top': '0',
4547
- 'left': '0',
4548
- 'width': '100%',
4549
- 'height': '100%',
4550
- 'overflow': 'auto',
4551
- 'text-align': 'center',
4552
- 'z-index': '10000',
4553
- 'font-family': '"Helvetica", "Arial", sans-serif',
4554
- '-webkit-font-smoothing': 'antialiased',
4555
- '-moz-osx-font-smoothing': 'grayscale'
4556
- },
4557
- '#overlay.mini': {
4558
- 'height': '0',
4559
- 'overflow': 'visible'
4560
- },
4561
- '#overlay a': {
4562
- 'width': 'initial',
4563
- 'padding': '0',
4564
- 'text-decoration': 'none',
4565
- 'text-transform': 'none',
4566
- 'color': 'inherit'
4567
- },
4568
- '#bgwrapper': {
4569
- 'position': 'relative',
4570
- 'width': '100%',
4571
- 'height': '100%'
4572
- },
4573
- '#bg': {
4574
- 'position': 'fixed',
4575
- 'top': '0',
4576
- 'left': '0',
4577
- 'width': '100%',
4578
- 'height': '100%',
4579
- 'min-width': this.doc_width * 4 + 'px',
4580
- 'min-height': this.doc_height * 4 + 'px',
4581
- 'background-color': 'black',
4582
- 'opacity': '0.0',
4583
- '-ms-filter': 'progid:DXImageTransform.Microsoft.Alpha(Opacity=60)', // IE8
4584
- 'filter': 'alpha(opacity=60)', // IE5-7
4585
- 'transition': 'opacity ' + anim_seconds
4586
- },
4587
- '#bg.visible': {
4588
- 'opacity': MixpanelNotification.BG_OPACITY
4589
- },
4590
- '.mini #bg': {
4591
- 'width': '0',
4592
- 'height': '0',
4593
- 'min-width': '0'
4594
- },
4595
- '#flipcontainer': {
4596
- 'perspective': '1000px',
4597
- 'position': 'absolute',
4598
- 'width': '100%'
4599
- },
4600
- '#flipper': {
4601
- 'position': 'relative',
4602
- 'transform-style': 'preserve-3d',
4603
- 'transition': '0.3s'
4604
- },
4605
- '#takeover': {
4606
- 'position': 'absolute',
4607
- 'left': '50%',
4608
- 'width': MixpanelNotification.NOTIF_WIDTH + 'px',
4609
- 'margin-left': Math.round(-MixpanelNotification.NOTIF_WIDTH / 2) + 'px',
4610
- 'backface-visibility': 'hidden',
4611
- 'transform': 'rotateY(0deg)',
4612
- 'opacity': '0.0',
4613
- 'top': MixpanelNotification.NOTIF_START_TOP + 'px',
4614
- 'transition': 'opacity ' + anim_seconds + ', top ' + anim_seconds
4615
- },
4616
- '#takeover.visible': {
4617
- 'opacity': '1.0',
4618
- 'top': MixpanelNotification.NOTIF_TOP + 'px'
4619
- },
4620
- '#takeover.exiting': {
4621
- 'opacity': '0.0',
4622
- 'top': MixpanelNotification.NOTIF_START_TOP + 'px'
4623
- },
4624
- '#thumbspacer': {
4625
- 'height': MixpanelNotification.THUMB_OFFSET + 'px'
4626
- },
4627
- '#thumbborder-wrapper': {
4628
- 'position': 'absolute',
4629
- 'top': (-MixpanelNotification.THUMB_BORDER_SIZE) + 'px',
4630
- 'left': (MixpanelNotification.NOTIF_WIDTH / 2 - MixpanelNotification.THUMB_OFFSET - MixpanelNotification.THUMB_BORDER_SIZE) + 'px',
4631
- 'width': thumb_total_size + 'px',
4632
- 'height': (thumb_total_size / 2) + 'px',
4633
- 'overflow': 'hidden'
4634
- },
4635
- '#thumbborder': {
4636
- 'position': 'absolute',
4637
- 'width': thumb_total_size + 'px',
4638
- 'height': thumb_total_size + 'px',
4639
- 'border-radius': thumb_total_size + 'px',
4640
- 'background-color': this.style_vals.bg_actions,
4641
- 'opacity': '0.5'
4642
- },
4643
- '#thumbnail': {
4644
- 'position': 'absolute',
4645
- 'top': '0px',
4646
- 'left': (MixpanelNotification.NOTIF_WIDTH / 2 - MixpanelNotification.THUMB_OFFSET) + 'px',
4647
- 'width': MixpanelNotification.THUMB_IMG_SIZE + 'px',
4648
- 'height': MixpanelNotification.THUMB_IMG_SIZE + 'px',
4649
- 'overflow': 'hidden',
4650
- 'z-index': '100',
4651
- 'border-radius': MixpanelNotification.THUMB_IMG_SIZE + 'px'
4652
- },
4653
- '#mini': {
4654
- 'position': 'absolute',
4655
- 'right': '20px',
4656
- 'top': MixpanelNotification.NOTIF_TOP + 'px',
4657
- 'width': this.notif_width + 'px',
4658
- 'height': MixpanelNotification.NOTIF_HEIGHT_MINI * 2 + 'px',
4659
- 'margin-top': 20 - MixpanelNotification.NOTIF_HEIGHT_MINI + 'px',
4660
- 'backface-visibility': 'hidden',
4661
- 'opacity': '0.0',
4662
- 'transform': 'rotateX(90deg)',
4663
- 'transition': 'opacity 0.3s, transform 0.3s, right 0.3s'
4664
- },
4665
- '#mini.visible': {
4666
- 'opacity': '1.0',
4667
- 'transform': 'rotateX(0deg)'
4668
- },
4669
- '#mini.exiting': {
4670
- 'opacity': '0.0',
4671
- 'right': '-150px'
4672
- },
4673
- '#mainbox': {
4674
- 'border-radius': '4px',
4675
- 'box-shadow': shadow,
4676
- 'text-align': 'center',
4677
- 'background-color': this.style_vals.bg,
4678
- 'font-size': '14px',
4679
- 'color': this.style_vals.text_main
4680
- },
4681
- '#mini #mainbox': {
4682
- 'height': MixpanelNotification.NOTIF_HEIGHT_MINI + 'px',
4683
- 'margin-top': MixpanelNotification.NOTIF_HEIGHT_MINI + 'px',
4684
- 'border-radius': '3px',
4685
- 'transition': 'background-color ' + anim_seconds
4686
- },
4687
- '#mini-border': {
4688
- 'height': (MixpanelNotification.NOTIF_HEIGHT_MINI + 6) + 'px',
4689
- 'width': (MixpanelNotification.NOTIF_WIDTH_MINI + 6) + 'px',
4690
- 'position': 'absolute',
4691
- 'top': '-3px',
4692
- 'left': '-3px',
4693
- 'margin-top': MixpanelNotification.NOTIF_HEIGHT_MINI + 'px',
4694
- 'border-radius': '6px',
4695
- 'opacity': '0.25',
4696
- 'background-color': '#fff',
4697
- 'z-index': '-1',
4698
- 'box-shadow': mini_shadow
4699
- },
4700
- '#mini-icon': {
4701
- 'position': 'relative',
4702
- 'display': 'inline-block',
4703
- 'width': '75px',
4704
- 'height': MixpanelNotification.NOTIF_HEIGHT_MINI + 'px',
4705
- 'border-radius': '3px 0 0 3px',
4706
- 'background-color': this.style_vals.bg_actions,
4707
- 'background': 'linear-gradient(135deg, ' + this.style_vals.bg_light + ' 0%, ' + this.style_vals.bg_actions + ' 100%)',
4708
- 'transition': 'background-color ' + anim_seconds
4709
- },
4710
- '#mini:hover #mini-icon': {
4711
- 'background-color': this.style_vals.mini_hover
4712
- },
4713
- '#mini:hover #mainbox': {
4714
- 'background-color': this.style_vals.mini_hover
4715
- },
4716
- '#mini-icon-img': {
4717
- 'position': 'absolute',
4718
- 'background-image': 'url(' + this.thumb_image_url + ')',
4719
- 'width': '48px',
4720
- 'height': '48px',
4721
- 'top': '20px',
4722
- 'left': '12px'
4723
- },
4724
- '#content': {
4725
- 'padding': '30px 20px 0px 20px'
4726
- },
4727
- '#mini-content': {
4728
- 'text-align': 'left',
4729
- 'height': MixpanelNotification.NOTIF_HEIGHT_MINI + 'px',
4730
- 'cursor': 'pointer'
4731
- },
4732
- '#img': {
4733
- 'width': '328px',
4734
- 'margin-top': '30px',
4735
- 'border-radius': '5px'
4736
- },
4737
- '#title': {
4738
- 'max-height': '600px',
4739
- 'overflow': 'hidden',
4740
- 'word-wrap': 'break-word',
4741
- 'padding': '25px 0px 20px 0px',
4742
- 'font-size': '19px',
4743
- 'font-weight': 'bold',
4744
- 'color': this.style_vals.text_title
4745
- },
4746
- '#body': {
4747
- 'max-height': '600px',
4748
- 'margin-bottom': '25px',
4749
- 'overflow': 'hidden',
4750
- 'word-wrap': 'break-word',
4751
- 'line-height': '21px',
4752
- 'font-size': '15px',
4753
- 'font-weight': 'normal',
4754
- 'text-align': 'left'
4755
- },
4756
- '#mini #body': {
4757
- 'display': 'inline-block',
4758
- 'max-width': '250px',
4759
- 'margin': '0 0 0 30px',
4760
- 'height': MixpanelNotification.NOTIF_HEIGHT_MINI + 'px',
4761
- 'font-size': '16px',
4762
- 'letter-spacing': '0.8px',
4763
- 'color': this.style_vals.text_title
4764
- },
4765
- '#mini #body-text': {
4766
- 'display': 'table',
4767
- 'height': MixpanelNotification.NOTIF_HEIGHT_MINI + 'px'
4768
- },
4769
- '#mini #body-text div': {
4770
- 'display': 'table-cell',
4771
- 'vertical-align': 'middle'
4772
- },
4773
- '#tagline': {
4774
- 'margin-bottom': '15px',
4775
- 'font-size': '10px',
4776
- 'font-weight': '600',
4777
- 'letter-spacing': '0.8px',
4778
- 'color': '#ccd7e0',
4779
- 'text-align': 'left'
4780
- },
4781
- '#tagline a': {
4782
- 'color': this.style_vals.text_tagline,
4783
- 'transition': 'color ' + anim_seconds
4784
- },
4785
- '#tagline a:hover': {
4786
- 'color': this.style_vals.text_hover
4787
- },
4788
- '#cancel': {
4789
- 'position': 'absolute',
4790
- 'right': '0',
4791
- 'width': '8px',
4792
- 'height': '8px',
4793
- 'padding': '10px',
4794
- 'border-radius': '20px',
4795
- 'margin': '12px 12px 0 0',
4796
- 'box-sizing': 'content-box',
4797
- 'cursor': 'pointer',
4798
- 'transition': 'background-color ' + anim_seconds
4799
- },
4800
- '#mini #cancel': {
4801
- 'margin': '7px 7px 0 0'
4802
- },
4803
- '#cancel-icon': {
4804
- 'width': '8px',
4805
- 'height': '8px',
4806
- 'overflow': 'hidden',
4807
- 'background-image': 'url(' + this.cdn_host + '/site_media/images/icons/notifications/cancel-x.png)',
4808
- 'opacity': this.style_vals.cancel_opacity
4809
- },
4810
- '#cancel:hover': {
4811
- 'background-color': this.style_vals.bg_hover
4812
- },
4813
- '#button': {
4814
- 'display': 'block',
4815
- 'height': '60px',
4816
- 'line-height': '60px',
4817
- 'text-align': 'center',
4818
- 'background-color': this.style_vals.bg_actions,
4819
- 'border-radius': '0 0 4px 4px',
4820
- 'overflow': 'hidden',
4821
- 'cursor': 'pointer',
4822
- 'transition': 'background-color ' + anim_seconds
4823
- },
4824
- '#button-close': {
4825
- 'display': 'inline-block',
4826
- 'width': '9px',
4827
- 'height': '60px',
4828
- 'margin-right': '8px',
4829
- 'vertical-align': 'top',
4830
- 'background-image': 'url(' + this.cdn_host + '/site_media/images/icons/notifications/close-x-' + this.style + '.png)',
4831
- 'background-repeat': 'no-repeat',
4832
- 'background-position': '0px 25px'
4833
- },
4834
- '#button-play': {
4835
- 'display': 'inline-block',
4836
- 'width': '30px',
4837
- 'height': '60px',
4838
- 'margin-left': '15px',
4839
- 'background-image': 'url(' + this.cdn_host + '/site_media/images/icons/notifications/play-' + this.style + '-small.png)',
4840
- 'background-repeat': 'no-repeat',
4841
- 'background-position': '0px 15px'
4842
- },
4843
- 'a#button-link': {
4844
- 'display': 'inline-block',
4845
- 'vertical-align': 'top',
4846
- 'text-align': 'center',
4847
- 'font-size': '17px',
4848
- 'font-weight': 'bold',
4849
- 'overflow': 'hidden',
4850
- 'word-wrap': 'break-word',
4851
- 'color': this.style_vals.text_title,
4852
- 'transition': 'color ' + anim_seconds
4853
- },
4854
- '#button:hover': {
4855
- 'background-color': this.style_vals.bg_hover,
4856
- 'color': this.style_vals.text_hover
4857
- },
4858
- '#button:hover a': {
4859
- 'color': this.style_vals.text_hover
4860
- },
4861
-
4862
- '#video-noflip': {
4863
- 'position': 'relative',
4864
- 'top': (-this.video_height * 2) + 'px'
4865
- },
4866
- '#video-flip': {
4867
- 'backface-visibility': 'hidden',
4868
- 'transform': 'rotateY(180deg)'
4869
- },
4870
- '#video': {
4871
- 'position': 'absolute',
4872
- 'width': (this.video_width - 1) + 'px',
4873
- 'height': this.video_height + 'px',
4874
- 'top': MixpanelNotification.NOTIF_TOP + 'px',
4875
- 'margin-top': '100px',
4876
- 'left': '50%',
4877
- 'margin-left': Math.round(-this.video_width / 2) + 'px',
4878
- 'overflow': 'hidden',
4879
- 'border-radius': '5px',
4880
- 'box-shadow': video_shadow,
4881
- 'transform': 'translateZ(1px)', // webkit rendering bug http://stackoverflow.com/questions/18167981/clickable-link-area-unexpectedly-smaller-after-css-transform
4882
- 'transition': 'opacity ' + anim_seconds + ', top ' + anim_seconds
4883
- },
4884
- '#video.exiting': {
4885
- 'opacity': '0.0',
4886
- 'top': this.video_height + 'px'
4887
- },
4888
- '#video-holder': {
4889
- 'position': 'absolute',
4890
- 'width': (this.video_width - 1) + 'px',
4891
- 'height': this.video_height + 'px',
4892
- 'overflow': 'hidden',
4893
- 'border-radius': '5px'
4894
- },
4895
- '#video-frame': {
4896
- 'margin-left': '-1px',
4897
- 'width': this.video_width + 'px'
4898
- },
4899
- '#video-controls': {
4900
- 'opacity': '0',
4901
- 'transition': 'opacity 0.5s'
4902
- },
4903
- '#video:hover #video-controls': {
4904
- 'opacity': '1.0'
4905
- },
4906
- '#video .video-progress-el': {
4907
- 'position': 'absolute',
4908
- 'bottom': '0',
4909
- 'height': '25px',
4910
- 'border-radius': '0 0 0 5px'
4911
- },
4912
- '#video-progress': {
4913
- 'width': '90%'
4914
- },
4915
- '#video-progress-total': {
4916
- 'width': '100%',
4917
- 'background-color': this.style_vals.bg,
4918
- 'opacity': '0.7'
4919
- },
4920
- '#video-elapsed': {
4921
- 'width': '0',
4922
- 'background-color': '#6cb6f5',
4923
- 'opacity': '0.9'
4924
- },
4925
- '#video #video-time': {
4926
- 'width': '10%',
4927
- 'right': '0',
4928
- 'font-size': '11px',
4929
- 'line-height': '25px',
4930
- 'color': this.style_vals.text_main,
4931
- 'background-color': '#666',
4932
- 'border-radius': '0 0 5px 0'
4933
- }
4934
- };
4935
-
4936
- // IE hacks
4937
- if (this._browser_lte('ie', 8)) {
4938
- _.extend(notif_styles, {
4939
- '* html #overlay': {
4940
- 'position': 'absolute'
4941
- },
4942
- '* html #bg': {
4943
- 'position': 'absolute'
4944
- },
4945
- 'html, body': {
4946
- 'height': '100%'
4947
- }
4948
- });
4949
- }
4950
- if (this._browser_lte('ie', 7)) {
4951
- _.extend(notif_styles, {
4952
- '#mini #body': {
4953
- 'display': 'inline',
4954
- 'zoom': '1',
4955
- 'border': '1px solid ' + this.style_vals.bg_hover
4956
- },
4957
- '#mini #body-text': {
4958
- 'padding': '20px'
4959
- },
4960
- '#mini #mini-icon': {
4961
- 'display': 'none'
4962
- }
4963
- });
4964
- }
4965
-
4966
- // add vendor-prefixed style rules
4967
- var VENDOR_STYLES = [
4968
- 'backface-visibility', 'border-radius', 'box-shadow', 'opacity',
4969
- 'perspective', 'transform', 'transform-style', 'transition'
4970
- ],
4971
- VENDOR_PREFIXES = ['khtml', 'moz', 'ms', 'o', 'webkit'];
4972
- for (var selector in notif_styles) {
4973
- for (var si = 0; si < VENDOR_STYLES.length; si++) {
4974
- var prop = VENDOR_STYLES[si];
4975
- if (prop in notif_styles[selector]) {
4976
- var val = notif_styles[selector][prop];
4977
- for (var pi = 0; pi < VENDOR_PREFIXES.length; pi++) {
4978
- notif_styles[selector]['-' + VENDOR_PREFIXES[pi] + '-' + prop] = val;
4979
- }
4980
- }
4981
- }
4982
- }
4983
-
4984
- var inject_styles = function(styles, media_queries) {
4985
- var create_style_text = function(style_defs) {
4986
- var st = '';
4987
- for (var selector in style_defs) {
4988
- var mp_selector = selector
4989
- .replace(/#/g, '#' + MixpanelNotification.MARKUP_PREFIX + '-')
4990
- .replace(/\./g, '.' + MixpanelNotification.MARKUP_PREFIX + '-');
4991
- st += '\n' + mp_selector + ' {';
4992
- var props = style_defs[selector];
4993
- for (var k in props) {
4994
- st += k + ':' + props[k] + ';';
4995
- }
4996
- st += '}';
4997
- }
4998
- return st;
4999
- };
5000
- var create_media_query_text = function(mq_defs) {
5001
- var mqt = '';
5002
- for (var mq in mq_defs) {
5003
- mqt += '\n' + mq + ' {' + create_style_text(mq_defs[mq]) + '\n}';
5004
- }
5005
- return mqt;
5006
- };
5007
-
5008
- var style_text = create_style_text(styles) + create_media_query_text(media_queries),
5009
- head_el = document.head || document.getElementsByTagName('head')[0] || document.documentElement,
5010
- style_el = document.createElement('style');
5011
- head_el.appendChild(style_el);
5012
- style_el.setAttribute('type', 'text/css');
5013
- if (style_el.styleSheet) { // IE
5014
- style_el.styleSheet.cssText = style_text;
5015
- } else {
5016
- style_el.textContent = style_text;
5017
- }
5018
- };
5019
- inject_styles(notif_styles, notif_media_queries);
5020
- };
5021
-
5022
- MixpanelNotification.prototype._init_video = _.safewrap(function() {
5023
- if (!this.video_url) {
5024
- return;
5025
- }
5026
- var self = this;
5027
-
5028
- // Youtube iframe API compatibility
5029
- self.yt_custom = 'postMessage' in window;
5030
-
5031
- self.dest_url = self.video_url;
5032
- var youtube_match = self.video_url.match(
5033
- // http://stackoverflow.com/questions/2936467/parse-youtube-video-id-using-preg-match
5034
- /(?:youtube(?:-nocookie)?\.com\/(?:[^/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?/ ]{11})/i
5035
- ),
5036
- vimeo_match = self.video_url.match(
5037
- /vimeo\.com\/.*?(\d+)/i
5038
- );
5039
- if (youtube_match) {
5040
- self.show_video = true;
5041
- self.youtube_video = youtube_match[1];
5042
-
5043
- if (self.yt_custom) {
5044
- window['onYouTubeIframeAPIReady'] = function() {
5045
- if (self._get_el('video-frame')) {
5046
- self._yt_video_ready();
5047
- }
5048
- };
5049
-
5050
- // load Youtube iframe API; see https://developers.google.com/youtube/iframe_api_reference
5051
- var tag = document.createElement('script');
5052
- tag.src = self.resource_protocol + 'www.youtube.com/iframe_api';
5053
- var firstScriptTag = document.getElementsByTagName('script')[0];
5054
- firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
5055
- }
5056
- } else if (vimeo_match) {
5057
- self.show_video = true;
5058
- self.vimeo_video = vimeo_match[1];
5059
- }
5060
-
5061
- // IE <= 7, FF <= 3: fall through to video link rather than embedded player
5062
- if (self._browser_lte('ie', 7) || self._browser_lte('firefox', 3)) {
5063
- self.show_video = false;
5064
- self.clickthrough = true;
5065
- }
5066
- });
5067
-
5068
- MixpanelNotification.prototype._mark_as_shown = _.safewrap(function() {
5069
- // click on background to dismiss
5070
- var self = this;
5071
- _.register_event(self._get_el('bg'), 'click', function() {
5072
- self.dismiss();
5073
- });
5074
-
5075
- var get_style = function(el, style_name) {
5076
- var styles = {};
5077
- if (document.defaultView && document.defaultView.getComputedStyle) {
5078
- styles = document.defaultView.getComputedStyle(el, null); // FF3 requires both args
5079
- } else if (el.currentStyle) { // IE
5080
- styles = el.currentStyle;
5081
- }
5082
- return styles[style_name];
5083
- };
5084
-
5085
- if (this.campaign_id) {
5086
- var notif_el = this._get_el('overlay');
5087
- if (notif_el && get_style(notif_el, 'visibility') !== 'hidden' && get_style(notif_el, 'display') !== 'none') {
5088
- this._mark_delivery();
5089
- }
5090
- }
5091
- });
5092
-
5093
- MixpanelNotification.prototype._mark_delivery = _.safewrap(function(extra_props) {
5094
- if (!this.marked_as_shown) {
5095
- this.marked_as_shown = true;
5096
-
5097
- if (this.campaign_id) {
5098
- // mark notification shown (local cache)
5099
- this._get_shown_campaigns()[this.campaign_id] = 1 * new Date();
5100
- this.persistence.save();
5101
- }
3853
+ MixpanelPersistence.prototype._add_to_people_queue = function(queue, data) {
3854
+ var q_key = this._get_queue_key(queue),
3855
+ q_data = data[queue],
3856
+ set_q = this._get_or_create_queue(SET_ACTION),
3857
+ set_once_q = this._get_or_create_queue(SET_ONCE_ACTION),
3858
+ unset_q = this._get_or_create_queue(UNSET_ACTION),
3859
+ add_q = this._get_or_create_queue(ADD_ACTION),
3860
+ union_q = this._get_or_create_queue(UNION_ACTION),
3861
+ remove_q = this._get_or_create_queue(REMOVE_ACTION, []),
3862
+ append_q = this._get_or_create_queue(APPEND_ACTION, []);
5102
3863
 
5103
- // track delivery
5104
- this._track_event('$campaign_delivery', extra_props);
5105
-
5106
- // mark notification shown (mixpanel property)
5107
- this.mixpanel['people']['append']({
5108
- '$campaigns': this.campaign_id,
5109
- '$notifications': {
5110
- 'campaign_id': this.campaign_id,
5111
- 'message_id': this.message_id,
5112
- 'type': 'web',
5113
- 'time': new Date()
3864
+ if (q_key === SET_QUEUE_KEY) {
3865
+ // Update the set queue - we can override any existing values
3866
+ _.extend(set_q, q_data);
3867
+ // if there was a pending increment, override it
3868
+ // with the set.
3869
+ this._pop_from_people_queue(ADD_ACTION, q_data);
3870
+ // if there was a pending union, override it
3871
+ // with the set.
3872
+ this._pop_from_people_queue(UNION_ACTION, q_data);
3873
+ this._pop_from_people_queue(UNSET_ACTION, q_data);
3874
+ } else if (q_key === SET_ONCE_QUEUE_KEY) {
3875
+ // only queue the data if there is not already a set_once call for it.
3876
+ _.each(q_data, function(v, k) {
3877
+ if (!(k in set_once_q)) {
3878
+ set_once_q[k] = v;
5114
3879
  }
5115
3880
  });
5116
- }
5117
- });
5118
-
5119
- MixpanelNotification.prototype._preload_images = function(all_loaded_cb) {
5120
- var self = this;
5121
- if (this.imgs_to_preload.length === 0) {
5122
- all_loaded_cb();
5123
- return;
5124
- }
5125
-
5126
- var preloaded_imgs = 0;
5127
- var img_objs = [];
5128
- var onload = function() {
5129
- preloaded_imgs++;
5130
- if (preloaded_imgs === self.imgs_to_preload.length && all_loaded_cb) {
5131
- all_loaded_cb();
5132
- all_loaded_cb = null;
5133
- }
5134
- };
5135
- for (var i = 0; i < this.imgs_to_preload.length; i++) {
5136
- var img = new Image();
5137
- img.onload = onload;
5138
- img.src = this.imgs_to_preload[i];
5139
- if (img.complete) {
5140
- onload();
5141
- }
5142
- img_objs.push(img);
5143
- }
3881
+ this._pop_from_people_queue(UNSET_ACTION, q_data);
3882
+ } else if (q_key === UNSET_QUEUE_KEY) {
3883
+ _.each(q_data, function(prop) {
5144
3884
 
5145
- // IE6/7 doesn't fire onload reliably
5146
- if (this._browser_lte('ie', 7)) {
5147
- setTimeout(function() {
5148
- var imgs_loaded = true;
5149
- for (i = 0; i < img_objs.length; i++) {
5150
- if (!img_objs[i].complete) {
5151
- imgs_loaded = false;
3885
+ // undo previously-queued actions on this key
3886
+ _.each([set_q, set_once_q, add_q, union_q], function(enqueued_obj) {
3887
+ if (prop in enqueued_obj) {
3888
+ delete enqueued_obj[prop];
5152
3889
  }
5153
- }
5154
- if (imgs_loaded && all_loaded_cb) {
5155
- all_loaded_cb();
5156
- all_loaded_cb = null;
5157
- }
5158
- }, 500);
5159
- }
5160
- };
5161
-
5162
- MixpanelNotification.prototype._remove_notification_el = _.safewrap(function() {
5163
- window.clearInterval(this._video_progress_checker);
5164
- this.notification_el.style.visibility = 'hidden';
5165
- this.body_el.removeChild(this.notification_el);
5166
- });
5167
-
5168
- MixpanelNotification.prototype._set_client_config = function() {
5169
- var get_browser_version = function(browser_ex) {
5170
- var match = navigator.userAgent.match(browser_ex);
5171
- return match && match[1];
5172
- };
5173
- this.browser_versions = {};
5174
- this.browser_versions['chrome'] = get_browser_version(/Chrome\/(\d+)/);
5175
- this.browser_versions['firefox'] = get_browser_version(/Firefox\/(\d+)/);
5176
- this.browser_versions['ie'] = get_browser_version(/MSIE (\d+).+/);
5177
- if (!this.browser_versions['ie'] && !(window.ActiveXObject) && 'ActiveXObject' in window) {
5178
- this.browser_versions['ie'] = 11;
5179
- }
5180
-
5181
- this.body_el = document.body || document.getElementsByTagName('body')[0];
5182
- if (this.body_el) {
5183
- this.doc_width = Math.max(
5184
- this.body_el.scrollWidth, document.documentElement.scrollWidth,
5185
- this.body_el.offsetWidth, document.documentElement.offsetWidth,
5186
- this.body_el.clientWidth, document.documentElement.clientWidth
5187
- );
5188
- this.doc_height = Math.max(
5189
- this.body_el.scrollHeight, document.documentElement.scrollHeight,
5190
- this.body_el.offsetHeight, document.documentElement.offsetHeight,
5191
- this.body_el.clientHeight, document.documentElement.clientHeight
5192
- );
5193
- }
5194
-
5195
- // detect CSS compatibility
5196
- var ie_ver = this.browser_versions['ie'];
5197
- var sample_styles = document.createElement('div').style,
5198
- is_css_compatible = function(rule) {
5199
- if (rule in sample_styles) {
5200
- return true;
5201
- }
5202
- if (!ie_ver) {
5203
- rule = rule[0].toUpperCase() + rule.slice(1);
5204
- var props = ['O' + rule, 'Webkit' + rule, 'Moz' + rule];
5205
- for (var i = 0; i < props.length; i++) {
5206
- if (props[i] in sample_styles) {
5207
- return true;
5208
- }
3890
+ });
3891
+ _.each(append_q, function(append_obj) {
3892
+ if (prop in append_obj) {
3893
+ delete append_obj[prop];
5209
3894
  }
5210
- }
5211
- return false;
5212
- };
5213
- this.use_transitions = this.body_el &&
5214
- is_css_compatible('transition') &&
5215
- is_css_compatible('transform');
5216
- this.flip_animate = (this.browser_versions['chrome'] >= 33 || this.browser_versions['firefox'] >= 15) &&
5217
- this.body_el &&
5218
- is_css_compatible('backfaceVisibility') &&
5219
- is_css_compatible('perspective') &&
5220
- is_css_compatible('transform');
5221
- };
5222
-
5223
- MixpanelNotification.prototype._switch_to_video = _.safewrap(function() {
5224
- var self = this,
5225
- anims = [
5226
- {
5227
- el: self._get_notification_display_el(),
5228
- attr: 'opacity',
5229
- start: 1.0,
5230
- goal: 0.0
5231
- },
5232
- {
5233
- el: self._get_notification_display_el(),
5234
- attr: 'top',
5235
- start: MixpanelNotification.NOTIF_TOP,
5236
- goal: -500
5237
- },
5238
- {
5239
- el: self._get_el('video-noflip'),
5240
- attr: 'opacity',
5241
- start: 0.0,
5242
- goal: 1.0
5243
- },
5244
- {
5245
- el: self._get_el('video-noflip'),
5246
- attr: 'top',
5247
- start: -self.video_height * 2,
5248
- goal: 0
5249
- }
5250
- ];
5251
-
5252
- if (self.mini) {
5253
- var bg = self._get_el('bg'),
5254
- overlay = self._get_el('overlay');
5255
- bg.style.width = '100%';
5256
- bg.style.height = '100%';
5257
- overlay.style.width = '100%';
5258
-
5259
- self._add_class(self._get_notification_display_el(), 'exiting');
5260
- self._add_class(bg, 'visible');
5261
-
5262
- anims.push({
5263
- el: self._get_el('bg'),
5264
- attr: 'opacity',
5265
- start: 0.0,
5266
- goal: MixpanelNotification.BG_OPACITY
5267
- });
5268
- }
5269
-
5270
- var video_el = self._get_el('video-holder');
5271
- video_el.innerHTML = self.video_iframe;
5272
-
5273
- var video_ready = function() {
5274
- if (window['YT'] && window['YT']['loaded']) {
5275
- self._yt_video_ready();
5276
- }
5277
- self.showing_video = true;
5278
- self._get_notification_display_el().style.visibility = 'hidden';
5279
- };
5280
- if (self.flip_animate) {
5281
- self._add_class('flipper', 'flipped');
5282
- setTimeout(video_ready, MixpanelNotification.ANIM_TIME);
5283
- } else {
5284
- self._animate_els(anims, MixpanelNotification.ANIM_TIME, video_ready);
5285
- }
5286
- });
5287
-
5288
- MixpanelNotification.prototype._track_event = function(event_name, properties, cb) {
5289
- if (this.campaign_id) {
5290
- properties = properties || {};
5291
- properties = _.extend(properties, {
5292
- 'campaign_id': this.campaign_id,
5293
- 'message_id': this.message_id,
5294
- 'message_type': 'web_inapp',
5295
- 'message_subtype': this.notif_type
5296
- });
5297
- this.mixpanel['track'](event_name, properties, cb);
5298
- } else if (cb) {
5299
- cb.call();
5300
- }
5301
- };
5302
-
5303
- MixpanelNotification.prototype._yt_video_ready = _.safewrap(function() {
5304
- var self = this;
5305
- if (self.video_inited) {
5306
- return;
5307
- }
5308
- self.video_inited = true;
5309
-
5310
- var progress_bar = self._get_el('video-elapsed'),
5311
- progress_time = self._get_el('video-time'),
5312
- progress_el = self._get_el('video-progress');
5313
-
5314
- new window['YT']['Player'](MixpanelNotification.MARKUP_PREFIX + '-video-frame', {
5315
- 'events': {
5316
- 'onReady': function(event) {
5317
- var ytplayer = event['target'],
5318
- video_duration = ytplayer['getDuration'](),
5319
- pad = function(i) {
5320
- return ('00' + i).slice(-2);
5321
- },
5322
- update_video_time = function(current_time) {
5323
- var secs = Math.round(video_duration - current_time),
5324
- mins = Math.floor(secs / 60),
5325
- hours = Math.floor(mins / 60);
5326
- secs -= mins * 60;
5327
- mins -= hours * 60;
5328
- progress_time.innerHTML = '-' + (hours ? hours + ':' : '') + pad(mins) + ':' + pad(secs);
5329
- };
5330
- update_video_time(0);
5331
- self._video_progress_checker = window.setInterval(function() {
5332
- var current_time = ytplayer['getCurrentTime']();
5333
- progress_bar.style.width = (current_time / video_duration * 100) + '%';
5334
- update_video_time(current_time);
5335
- }, 250);
5336
- _.register_event(progress_el, 'click', function(e) {
5337
- var clickx = Math.max(0, e.pageX - progress_el.getBoundingClientRect().left);
5338
- ytplayer['seekTo'](video_duration * clickx / progress_el.clientWidth, true);
5339
- });
5340
- }
5341
- }
5342
- });
5343
- });
5344
-
5345
- /**
5346
- * Mixpanel People Object
5347
- * @constructor
5348
- */
5349
- var MixpanelPeople = function() {};
5350
-
5351
- _.extend(MixpanelPeople.prototype, apiActions);
5352
-
5353
- MixpanelPeople.prototype._init = function(mixpanel_instance) {
5354
- this._mixpanel = mixpanel_instance;
5355
- };
5356
-
5357
- /*
5358
- * Set properties on a user record.
5359
- *
5360
- * ### Usage:
5361
- *
5362
- * mixpanel.people.set('gender', 'm');
5363
- *
5364
- * // or set multiple properties at once
5365
- * mixpanel.people.set({
5366
- * 'Company': 'Acme',
5367
- * 'Plan': 'Premium',
5368
- * 'Upgrade date': new Date()
5369
- * });
5370
- * // properties can be strings, integers, dates, or lists
5371
- *
5372
- * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
5373
- * @param {*} [to] A value to set on the given property name
5374
- * @param {Function} [callback] If provided, the callback will be called after tracking the event.
5375
- */
5376
- MixpanelPeople.prototype.set = addOptOutCheckMixpanelPeople(function(prop, to, callback) {
5377
- var data = this.set_action(prop, to);
5378
- if (_.isObject(prop)) {
5379
- callback = to;
5380
- }
5381
- // make sure that the referrer info has been updated and saved
5382
- if (this._get_config('save_referrer')) {
5383
- this._mixpanel['persistence'].update_referrer_info(document.referrer);
5384
- }
5385
-
5386
- // update $set object with default people properties
5387
- data[SET_ACTION] = _.extend(
5388
- {},
5389
- _.info.people_properties(),
5390
- this._mixpanel['persistence'].get_referrer_info(),
5391
- data[SET_ACTION]
5392
- );
5393
- return this._send_request(data, callback);
5394
- });
5395
-
5396
- /*
5397
- * Set properties on a user record, only if they do not yet exist.
5398
- * This will not overwrite previous people property values, unlike
5399
- * people.set().
5400
- *
5401
- * ### Usage:
5402
- *
5403
- * mixpanel.people.set_once('First Login Date', new Date());
5404
- *
5405
- * // or set multiple properties at once
5406
- * mixpanel.people.set_once({
5407
- * 'First Login Date': new Date(),
5408
- * 'Starting Plan': 'Premium'
5409
- * });
5410
- *
5411
- * // properties can be strings, integers or dates
5412
- *
5413
- * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
5414
- * @param {*} [to] A value to set on the given property name
5415
- * @param {Function} [callback] If provided, the callback will be called after tracking the event.
5416
- */
5417
- MixpanelPeople.prototype.set_once = addOptOutCheckMixpanelPeople(function(prop, to, callback) {
5418
- var data = this.set_once_action(prop, to);
5419
- if (_.isObject(prop)) {
5420
- callback = to;
5421
- }
5422
- return this._send_request(data, callback);
5423
- });
5424
-
5425
- /*
5426
- * Unset properties on a user record (permanently removes the properties and their values from a profile).
5427
- *
5428
- * ### Usage:
5429
- *
5430
- * mixpanel.people.unset('gender');
5431
- *
5432
- * // or unset multiple properties at once
5433
- * mixpanel.people.unset(['gender', 'Company']);
5434
- *
5435
- * @param {Array|String} prop If a string, this is the name of the property. If an array, this is a list of property names.
5436
- * @param {Function} [callback] If provided, the callback will be called after tracking the event.
5437
- */
5438
- MixpanelPeople.prototype.unset = addOptOutCheckMixpanelPeople(function(prop, callback) {
5439
- var data = this.unset_action(prop);
5440
- return this._send_request(data, callback);
5441
- });
5442
-
5443
- /*
5444
- * Increment/decrement numeric people analytics properties.
5445
- *
5446
- * ### Usage:
5447
- *
5448
- * mixpanel.people.increment('page_views', 1);
5449
- *
5450
- * // or, for convenience, if you're just incrementing a counter by
5451
- * // 1, you can simply do
5452
- * mixpanel.people.increment('page_views');
5453
- *
5454
- * // to decrement a counter, pass a negative number
5455
- * mixpanel.people.increment('credits_left', -1);
5456
- *
5457
- * // like mixpanel.people.set(), you can increment multiple
5458
- * // properties at once:
5459
- * mixpanel.people.increment({
5460
- * counter1: 1,
5461
- * counter2: 6
5462
- * });
5463
- *
5464
- * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and numeric values.
5465
- * @param {Number} [by] An amount to increment the given property
5466
- * @param {Function} [callback] If provided, the callback will be called after tracking the event.
5467
- */
5468
- MixpanelPeople.prototype.increment = addOptOutCheckMixpanelPeople(function(prop, by, callback) {
5469
- var data = {};
5470
- var $add = {};
5471
- if (_.isObject(prop)) {
5472
- _.each(prop, function(v, k) {
5473
- if (!this._is_reserved_property(k)) {
5474
- if (isNaN(parseFloat(v))) {
5475
- console.error('Invalid increment value passed to mixpanel.people.increment - must be a number');
5476
- return;
5477
- } else {
5478
- $add[k] = v;
5479
- }
5480
- }
5481
- }, this);
5482
- callback = by;
5483
- } else {
5484
- // convenience: mixpanel.people.increment('property'); will
5485
- // increment 'property' by 1
5486
- if (_.isUndefined(by)) {
5487
- by = 1;
5488
- }
5489
- $add[prop] = by;
5490
- }
5491
- data[ADD_ACTION] = $add;
5492
-
5493
- return this._send_request(data, callback);
5494
- });
5495
-
5496
- /*
5497
- * Append a value to a list-valued people analytics property.
5498
- *
5499
- * ### Usage:
5500
- *
5501
- * // append a value to a list, creating it if needed
5502
- * mixpanel.people.append('pages_visited', 'homepage');
5503
- *
5504
- * // like mixpanel.people.set(), you can append multiple
5505
- * // properties at once:
5506
- * mixpanel.people.append({
5507
- * list1: 'bob',
5508
- * list2: 123
5509
- * });
5510
- *
5511
- * @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values.
5512
- * @param {*} [value] value An item to append to the list
5513
- * @param {Function} [callback] If provided, the callback will be called after tracking the event.
5514
- */
5515
- MixpanelPeople.prototype.append = addOptOutCheckMixpanelPeople(function(list_name, value, callback) {
5516
- if (_.isObject(list_name)) {
5517
- callback = value;
5518
- }
5519
- var data = this.append_action(list_name, value);
5520
- return this._send_request(data, callback);
5521
- });
5522
-
5523
- /*
5524
- * Remove a value from a list-valued people analytics property.
5525
- *
5526
- * ### Usage:
5527
- *
5528
- * mixpanel.people.remove('School', 'UCB');
5529
- *
5530
- * @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values.
5531
- * @param {*} [value] value Item to remove from the list
5532
- * @param {Function} [callback] If provided, the callback will be called after tracking the event.
5533
- */
5534
- MixpanelPeople.prototype.remove = addOptOutCheckMixpanelPeople(function(list_name, value, callback) {
5535
- if (_.isObject(list_name)) {
5536
- callback = value;
5537
- }
5538
- var data = this.remove_action(list_name, value);
5539
- return this._send_request(data, callback);
5540
- });
5541
-
5542
- /*
5543
- * Merge a given list with a list-valued people analytics property,
5544
- * excluding duplicate values.
5545
- *
5546
- * ### Usage:
5547
- *
5548
- * // merge a value to a list, creating it if needed
5549
- * mixpanel.people.union('pages_visited', 'homepage');
5550
- *
5551
- * // like mixpanel.people.set(), you can append multiple
5552
- * // properties at once:
5553
- * mixpanel.people.union({
5554
- * list1: 'bob',
5555
- * list2: 123
5556
- * });
5557
- *
5558
- * // like mixpanel.people.append(), you can append multiple
5559
- * // values to the same list:
5560
- * mixpanel.people.union({
5561
- * list1: ['bob', 'billy']
5562
- * });
5563
- *
5564
- * @param {Object|String} list_name If a string, this is the name of the property. If an object, this is an associative array of names and values.
5565
- * @param {*} [value] Value / values to merge with the given property
5566
- * @param {Function} [callback] If provided, the callback will be called after tracking the event.
5567
- */
5568
- MixpanelPeople.prototype.union = addOptOutCheckMixpanelPeople(function(list_name, values, callback) {
5569
- if (_.isObject(list_name)) {
5570
- callback = values;
5571
- }
5572
- var data = this.union_action(list_name, values);
5573
- return this._send_request(data, callback);
5574
- });
5575
-
5576
- /*
5577
- * Record that you have charged the current user a certain amount
5578
- * of money. Charges recorded with track_charge() will appear in the
5579
- * Mixpanel revenue report.
5580
- *
5581
- * ### Usage:
5582
- *
5583
- * // charge a user $50
5584
- * mixpanel.people.track_charge(50);
5585
- *
5586
- * // charge a user $30.50 on the 2nd of january
5587
- * mixpanel.people.track_charge(30.50, {
5588
- * '$time': new Date('jan 1 2012')
5589
- * });
5590
- *
5591
- * @param {Number} amount The amount of money charged to the current user
5592
- * @param {Object} [properties] An associative array of properties associated with the charge
5593
- * @param {Function} [callback] If provided, the callback will be called when the server responds
5594
- */
5595
- MixpanelPeople.prototype.track_charge = addOptOutCheckMixpanelPeople(function(amount, properties, callback) {
5596
- if (!_.isNumber(amount)) {
5597
- amount = parseFloat(amount);
5598
- if (isNaN(amount)) {
5599
- console.error('Invalid value passed to mixpanel.people.track_charge - must be a number');
5600
- return;
5601
- }
5602
- }
5603
-
5604
- return this.append('$transactions', _.extend({
5605
- '$amount': amount
5606
- }, properties), callback);
5607
- });
5608
-
5609
- /*
5610
- * Permanently clear all revenue report transactions from the
5611
- * current user's people analytics profile.
5612
- *
5613
- * ### Usage:
5614
- *
5615
- * mixpanel.people.clear_charges();
5616
- *
5617
- * @param {Function} [callback] If provided, the callback will be called after tracking the event.
5618
- */
5619
- MixpanelPeople.prototype.clear_charges = function(callback) {
5620
- return this.set('$transactions', [], callback);
5621
- };
5622
-
5623
- /*
5624
- * Permanently deletes the current people analytics profile from
5625
- * Mixpanel (using the current distinct_id).
5626
- *
5627
- * ### Usage:
5628
- *
5629
- * // remove the all data you have stored about the current user
5630
- * mixpanel.people.delete_user();
5631
- *
5632
- */
5633
- MixpanelPeople.prototype.delete_user = function() {
5634
- if (!this._identify_called()) {
5635
- console.error('mixpanel.people.delete_user() requires you to call identify() first');
5636
- return;
5637
- }
5638
- var data = {'$delete': this._mixpanel.get_distinct_id()};
5639
- return this._send_request(data);
5640
- };
5641
-
5642
- MixpanelPeople.prototype.toString = function() {
5643
- return this._mixpanel.toString() + '.people';
5644
- };
5645
-
5646
- MixpanelPeople.prototype._send_request = function(data, callback) {
5647
- data['$token'] = this._get_config('token');
5648
- data['$distinct_id'] = this._mixpanel.get_distinct_id();
5649
- var device_id = this._mixpanel.get_property('$device_id');
5650
- var user_id = this._mixpanel.get_property('$user_id');
5651
- var had_persisted_distinct_id = this._mixpanel.get_property('$had_persisted_distinct_id');
5652
- if (device_id) {
5653
- data['$device_id'] = device_id;
5654
- }
5655
- if (user_id) {
5656
- data['$user_id'] = user_id;
5657
- }
5658
- if (had_persisted_distinct_id) {
5659
- data['$had_persisted_distinct_id'] = had_persisted_distinct_id;
5660
- }
3895
+ });
5661
3896
 
5662
- var date_encoded_data = _.encodeDates(data);
3897
+ unset_q[prop] = true;
5663
3898
 
5664
- if (!this._identify_called()) {
5665
- this._enqueue(data);
5666
- if (!_.isUndefined(callback)) {
5667
- if (this._get_config('verbose')) {
5668
- callback({status: -1, error: null});
3899
+ });
3900
+ } else if (q_key === ADD_QUEUE_KEY) {
3901
+ _.each(q_data, function(v, k) {
3902
+ // If it exists in the set queue, increment
3903
+ // the value
3904
+ if (k in set_q) {
3905
+ set_q[k] += v;
5669
3906
  } else {
5670
- callback(-1);
3907
+ // If it doesn't exist, update the add
3908
+ // queue
3909
+ if (!(k in add_q)) {
3910
+ add_q[k] = 0;
3911
+ }
3912
+ add_q[k] += v;
5671
3913
  }
5672
- }
5673
- return _.truncate(date_encoded_data, 255);
3914
+ }, this);
3915
+ this._pop_from_people_queue(UNSET_ACTION, q_data);
3916
+ } else if (q_key === UNION_QUEUE_KEY) {
3917
+ _.each(q_data, function(v, k) {
3918
+ if (_.isArray(v)) {
3919
+ if (!(k in union_q)) {
3920
+ union_q[k] = [];
3921
+ }
3922
+ // We may send duplicates, the server will dedup them.
3923
+ union_q[k] = union_q[k].concat(v);
3924
+ }
3925
+ });
3926
+ this._pop_from_people_queue(UNSET_ACTION, q_data);
3927
+ } else if (q_key === REMOVE_QUEUE_KEY) {
3928
+ remove_q.push(q_data);
3929
+ this._pop_from_people_queue(APPEND_ACTION, q_data);
3930
+ } else if (q_key === APPEND_QUEUE_KEY) {
3931
+ append_q.push(q_data);
3932
+ this._pop_from_people_queue(UNSET_ACTION, q_data);
5674
3933
  }
5675
3934
 
5676
- return this._mixpanel._track_or_batch({
5677
- type: 'people',
5678
- data: date_encoded_data,
5679
- endpoint: this._get_config('api_host') + '/engage/',
5680
- batcher: this._mixpanel.request_batchers.people
5681
- }, callback);
5682
- };
3935
+ console.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):');
3936
+ console.log(data);
5683
3937
 
5684
- MixpanelPeople.prototype._get_config = function(conf_var) {
5685
- return this._mixpanel.get_config(conf_var);
3938
+ this.save();
5686
3939
  };
5687
3940
 
5688
- MixpanelPeople.prototype._identify_called = function() {
5689
- return this._mixpanel._flags.identify_called === true;
5690
- };
3941
+ MixpanelPersistence.prototype._pop_from_people_queue = function(queue, data) {
3942
+ var q = this._get_queue(queue);
3943
+ if (!_.isUndefined(q)) {
3944
+ _.each(data, function(v, k) {
3945
+ if (queue === APPEND_ACTION || queue === REMOVE_ACTION) {
3946
+ // list actions: only remove if both k+v match
3947
+ // e.g. remove should not override append in a case like
3948
+ // append({foo: 'bar'}); remove({foo: 'qux'})
3949
+ _.each(q, function(queued_action) {
3950
+ if (queued_action[k] === v) {
3951
+ delete queued_action[k];
3952
+ }
3953
+ });
3954
+ } else {
3955
+ delete q[k];
3956
+ }
3957
+ }, this);
5691
3958
 
5692
- // Queue up engage operations if identify hasn't been called yet.
5693
- MixpanelPeople.prototype._enqueue = function(data) {
5694
- if (SET_ACTION in data) {
5695
- this._mixpanel['persistence']._add_to_people_queue(SET_ACTION, data);
5696
- } else if (SET_ONCE_ACTION in data) {
5697
- this._mixpanel['persistence']._add_to_people_queue(SET_ONCE_ACTION, data);
5698
- } else if (UNSET_ACTION in data) {
5699
- this._mixpanel['persistence']._add_to_people_queue(UNSET_ACTION, data);
5700
- } else if (ADD_ACTION in data) {
5701
- this._mixpanel['persistence']._add_to_people_queue(ADD_ACTION, data);
5702
- } else if (APPEND_ACTION in data) {
5703
- this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, data);
5704
- } else if (REMOVE_ACTION in data) {
5705
- this._mixpanel['persistence']._add_to_people_queue(REMOVE_ACTION, data);
5706
- } else if (UNION_ACTION in data) {
5707
- this._mixpanel['persistence']._add_to_people_queue(UNION_ACTION, data);
5708
- } else {
5709
- console.error('Invalid call to _enqueue():', data);
3959
+ this.save();
5710
3960
  }
5711
3961
  };
5712
3962
 
5713
- MixpanelPeople.prototype._flush_one_queue = function(action, action_method, callback, queue_to_params_fn) {
5714
- var _this = this;
5715
- var queued_data = _.extend({}, this._mixpanel['persistence']._get_queue(action));
5716
- var action_params = queued_data;
5717
-
5718
- if (!_.isUndefined(queued_data) && _.isObject(queued_data) && !_.isEmptyObject(queued_data)) {
5719
- _this._mixpanel['persistence']._pop_from_people_queue(action, queued_data);
5720
- if (queue_to_params_fn) {
5721
- action_params = queue_to_params_fn(queued_data);
5722
- }
5723
- action_method.call(_this, action_params, function(response, data) {
5724
- // on bad response, we want to add it back to the queue
5725
- if (response === 0) {
5726
- _this._mixpanel['persistence']._add_to_people_queue(action, queued_data);
5727
- }
5728
- if (!_.isUndefined(callback)) {
5729
- callback(response, data);
5730
- }
5731
- });
3963
+ MixpanelPersistence.prototype._get_queue_key = function(queue) {
3964
+ if (queue === SET_ACTION) {
3965
+ return SET_QUEUE_KEY;
3966
+ } else if (queue === SET_ONCE_ACTION) {
3967
+ return SET_ONCE_QUEUE_KEY;
3968
+ } else if (queue === UNSET_ACTION) {
3969
+ return UNSET_QUEUE_KEY;
3970
+ } else if (queue === ADD_ACTION) {
3971
+ return ADD_QUEUE_KEY;
3972
+ } else if (queue === APPEND_ACTION) {
3973
+ return APPEND_QUEUE_KEY;
3974
+ } else if (queue === REMOVE_ACTION) {
3975
+ return REMOVE_QUEUE_KEY;
3976
+ } else if (queue === UNION_ACTION) {
3977
+ return UNION_QUEUE_KEY;
3978
+ } else {
3979
+ console.error('Invalid queue:', queue);
5732
3980
  }
5733
3981
  };
5734
3982
 
5735
- // Flush queued engage operations - order does not matter,
5736
- // and there are network level race conditions anyway
5737
- MixpanelPeople.prototype._flush = function(
5738
- _set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback
5739
- ) {
5740
- var _this = this;
5741
- var $append_queue = this._mixpanel['persistence']._get_queue(APPEND_ACTION);
5742
- var $remove_queue = this._mixpanel['persistence']._get_queue(REMOVE_ACTION);
5743
-
5744
- this._flush_one_queue(SET_ACTION, this.set, _set_callback);
5745
- this._flush_one_queue(SET_ONCE_ACTION, this.set_once, _set_once_callback);
5746
- this._flush_one_queue(UNSET_ACTION, this.unset, _unset_callback, function(queue) { return _.keys(queue); });
5747
- this._flush_one_queue(ADD_ACTION, this.increment, _add_callback);
5748
- this._flush_one_queue(UNION_ACTION, this.union, _union_callback);
5749
-
5750
- // we have to fire off each $append individually since there is
5751
- // no concat method server side
5752
- if (!_.isUndefined($append_queue) && _.isArray($append_queue) && $append_queue.length) {
5753
- var $append_item;
5754
- var append_callback = function(response, data) {
5755
- if (response === 0) {
5756
- _this._mixpanel['persistence']._add_to_people_queue(APPEND_ACTION, $append_item);
5757
- }
5758
- if (!_.isUndefined(_append_callback)) {
5759
- _append_callback(response, data);
5760
- }
5761
- };
5762
- for (var i = $append_queue.length - 1; i >= 0; i--) {
5763
- $append_item = $append_queue.pop();
5764
- if (!_.isEmptyObject($append_item)) {
5765
- _this.append($append_item, append_callback);
5766
- }
5767
- }
5768
- // Save the shortened append queue
5769
- _this._mixpanel['persistence'].save();
5770
- }
3983
+ MixpanelPersistence.prototype._get_queue = function(queue) {
3984
+ return this['props'][this._get_queue_key(queue)];
3985
+ };
3986
+ MixpanelPersistence.prototype._get_or_create_queue = function(queue, default_val) {
3987
+ var key = this._get_queue_key(queue);
3988
+ default_val = _.isUndefined(default_val) ? {} : default_val;
5771
3989
 
5772
- // same for $remove
5773
- if (!_.isUndefined($remove_queue) && _.isArray($remove_queue) && $remove_queue.length) {
5774
- var $remove_item;
5775
- var remove_callback = function(response, data) {
5776
- if (response === 0) {
5777
- _this._mixpanel['persistence']._add_to_people_queue(REMOVE_ACTION, $remove_item);
5778
- }
5779
- if (!_.isUndefined(_remove_callback)) {
5780
- _remove_callback(response, data);
5781
- }
5782
- };
5783
- for (var j = $remove_queue.length - 1; j >= 0; j--) {
5784
- $remove_item = $remove_queue.pop();
5785
- if (!_.isEmptyObject($remove_item)) {
5786
- _this.remove($remove_item, remove_callback);
5787
- }
5788
- }
5789
- _this._mixpanel['persistence'].save();
5790
- }
3990
+ return this['props'][key] || (this['props'][key] = default_val);
5791
3991
  };
5792
3992
 
5793
- MixpanelPeople.prototype._is_reserved_property = function(prop) {
5794
- return prop === '$distinct_id' || prop === '$token' || prop === '$device_id' || prop === '$user_id' || prop === '$had_persisted_distinct_id';
3993
+ MixpanelPersistence.prototype.set_event_timer = function(event_name, timestamp) {
3994
+ var timers = this['props'][EVENT_TIMERS_KEY] || {};
3995
+ timers[event_name] = timestamp;
3996
+ this['props'][EVENT_TIMERS_KEY] = timers;
3997
+ this.save();
5795
3998
  };
5796
3999
 
5797
- // MixpanelPeople Exports
5798
- MixpanelPeople.prototype['set'] = MixpanelPeople.prototype.set;
5799
- MixpanelPeople.prototype['set_once'] = MixpanelPeople.prototype.set_once;
5800
- MixpanelPeople.prototype['unset'] = MixpanelPeople.prototype.unset;
5801
- MixpanelPeople.prototype['increment'] = MixpanelPeople.prototype.increment;
5802
- MixpanelPeople.prototype['append'] = MixpanelPeople.prototype.append;
5803
- MixpanelPeople.prototype['remove'] = MixpanelPeople.prototype.remove;
5804
- MixpanelPeople.prototype['union'] = MixpanelPeople.prototype.union;
5805
- MixpanelPeople.prototype['track_charge'] = MixpanelPeople.prototype.track_charge;
5806
- MixpanelPeople.prototype['clear_charges'] = MixpanelPeople.prototype.clear_charges;
5807
- MixpanelPeople.prototype['delete_user'] = MixpanelPeople.prototype.delete_user;
5808
- MixpanelPeople.prototype['toString'] = MixpanelPeople.prototype.toString;
4000
+ MixpanelPersistence.prototype.remove_event_timer = function(event_name) {
4001
+ var timers = this['props'][EVENT_TIMERS_KEY] || {};
4002
+ var timestamp = timers[event_name];
4003
+ if (!_.isUndefined(timestamp)) {
4004
+ delete this['props'][EVENT_TIMERS_KEY][event_name];
4005
+ this.save();
4006
+ }
4007
+ return timestamp;
4008
+ };
5809
4009
 
5810
4010
  /*
5811
4011
  * Mixpanel JS Library
@@ -5861,10 +4061,10 @@ define(function () { 'use strict';
5861
4061
 
5862
4062
  // save reference to navigator.sendBeacon so it can be minified
5863
4063
  var sendBeacon = null;
5864
- if (navigator$1['sendBeacon']) {
4064
+ if (navigator['sendBeacon']) {
5865
4065
  sendBeacon = function() {
5866
4066
  // late reference to navigator.sendBeacon to allow patching/spying
5867
- return navigator$1['sendBeacon'].apply(navigator$1, arguments);
4067
+ return navigator['sendBeacon'].apply(navigator, arguments);
5868
4068
  };
5869
4069
  }
5870
4070
 
@@ -5880,6 +4080,7 @@ define(function () { 'use strict';
5880
4080
  'cdn': 'https://cdn.mxpnl.com',
5881
4081
  'cross_site_cookie': false,
5882
4082
  'cross_subdomain_cookie': true,
4083
+ 'error_reporter': NOOP_FUNC,
5883
4084
  'persistence': 'cookie',
5884
4085
  'persistence_name': '',
5885
4086
  'cookie_domain': '',
@@ -5904,8 +4105,6 @@ define(function () { 'use strict';
5904
4105
  'opt_out_tracking_cookie_prefix': null,
5905
4106
  'property_blacklist': [],
5906
4107
  'xhr_headers': {}, // { header: value, header2: value }
5907
- 'inapp_protocol': '//',
5908
- 'inapp_link_new_window': false,
5909
4108
  'ignore_dnt': false,
5910
4109
  'batch_requests': true,
5911
4110
  'batch_size': 50,
@@ -5947,8 +4146,6 @@ define(function () { 'use strict';
5947
4146
  }
5948
4147
 
5949
4148
  instance._cached_groups = {}; // cache groups in a pool
5950
- instance._user_decide_check_complete = false;
5951
- instance._events_tracked_before_user_decide_check_complete = [];
5952
4149
 
5953
4150
  instance._init(token, config, name);
5954
4151
 
@@ -5991,11 +4188,11 @@ define(function () { 'use strict';
5991
4188
  */
5992
4189
  MixpanelLib.prototype.init = function (token, config, name) {
5993
4190
  if (_.isUndefined(name)) {
5994
- console.error('You must name your new library: init(token, config, name)');
4191
+ this.report_error('You must name your new library: init(token, config, name)');
5995
4192
  return;
5996
4193
  }
5997
4194
  if (name === PRIMARY_INSTANCE_NAME) {
5998
- console.error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet');
4195
+ this.report_error('You must initialize the main mixpanel object right after you include the Mixpanel js snippet');
5999
4196
  return;
6000
4197
  }
6001
4198
 
@@ -6018,7 +4215,6 @@ define(function () { 'use strict';
6018
4215
 
6019
4216
  this['__loaded'] = true;
6020
4217
  this['config'] = {};
6021
- this['_triggered_notifs'] = [];
6022
4218
 
6023
4219
  var variable_features = {};
6024
4220
 
@@ -6137,7 +4333,7 @@ define(function () { 'use strict';
6137
4333
 
6138
4334
  MixpanelLib.prototype._track_dom = function(DomClass, args) {
6139
4335
  if (this.get_config('img')) {
6140
- console.error('You can\'t use DOM tracking functions with img = true.');
4336
+ this.report_error('You can\'t use DOM tracking functions with img = true.');
6141
4337
  return false;
6142
4338
  }
6143
4339
 
@@ -6239,6 +4435,7 @@ define(function () { 'use strict';
6239
4435
 
6240
4436
  url += '?' + _.HTTPBuildQuery(data);
6241
4437
 
4438
+ var lib = this;
6242
4439
  if ('img' in data) {
6243
4440
  var img = document$1.createElement('img');
6244
4441
  img.src = url;
@@ -6247,7 +4444,7 @@ define(function () { 'use strict';
6247
4444
  try {
6248
4445
  succeeded = sendBeacon(url, body_data);
6249
4446
  } catch (e) {
6250
- console.error(e);
4447
+ lib.report_error(e);
6251
4448
  succeeded = false;
6252
4449
  }
6253
4450
  try {
@@ -6255,7 +4452,7 @@ define(function () { 'use strict';
6255
4452
  callback(succeeded ? 1 : 0);
6256
4453
  }
6257
4454
  } catch (e) {
6258
- console.error(e);
4455
+ lib.report_error(e);
6259
4456
  }
6260
4457
  } else if (USE_XHR) {
6261
4458
  try {
@@ -6287,7 +4484,7 @@ define(function () { 'use strict';
6287
4484
  try {
6288
4485
  response = _.JSONDecode(req.responseText);
6289
4486
  } catch (e) {
6290
- console.error(e);
4487
+ lib.report_error(e);
6291
4488
  if (options.ignore_json_errors) {
6292
4489
  response = req.responseText;
6293
4490
  } else {
@@ -6310,7 +4507,7 @@ define(function () { 'use strict';
6310
4507
  } else {
6311
4508
  error = 'Bad HTTP status: ' + req.status + ' ' + req.statusText;
6312
4509
  }
6313
- console.error(error);
4510
+ lib.report_error(error);
6314
4511
  if (callback) {
6315
4512
  if (verbose_mode) {
6316
4513
  callback({status: 0, error: error, xhr_req: req});
@@ -6323,7 +4520,7 @@ define(function () { 'use strict';
6323
4520
  };
6324
4521
  req.send(body_data);
6325
4522
  } catch (e) {
6326
- console.error(e);
4523
+ lib.report_error(e);
6327
4524
  succeeded = false;
6328
4525
  }
6329
4526
  } else {
@@ -6413,7 +4610,9 @@ define(function () { 'use strict';
6413
4610
  }, this),
6414
4611
  beforeSendHook: _.bind(function(item) {
6415
4612
  return this._run_hook('before_send_' + attrs.type, item);
6416
- }, this)
4613
+ }, this),
4614
+ errorReporter: this.get_config('error_reporter'),
4615
+ stopAllBatchingFunc: _.bind(this.stop_batch_senders, this)
6417
4616
  }
6418
4617
  );
6419
4618
  }, this);
@@ -6570,7 +4769,7 @@ define(function () { 'use strict';
6570
4769
  }
6571
4770
 
6572
4771
  if (_.isUndefined(event_name)) {
6573
- console.error('No event name provided to mixpanel.track');
4772
+ this.report_error('No event name provided to mixpanel.track');
6574
4773
  return;
6575
4774
  }
6576
4775
 
@@ -6611,7 +4810,7 @@ define(function () { 'use strict';
6611
4810
  delete properties[blacklisted_prop];
6612
4811
  });
6613
4812
  } else {
6614
- console.error('Invalid value for property_blacklist config: ' + property_blacklist);
4813
+ this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
6615
4814
  }
6616
4815
 
6617
4816
  var data = {
@@ -6627,8 +4826,6 @@ define(function () { 'use strict';
6627
4826
  send_request_options: options
6628
4827
  }, callback);
6629
4828
 
6630
- this._check_and_handle_triggered_notifications(data);
6631
-
6632
4829
  return ret;
6633
4830
  });
6634
4831
 
@@ -6856,7 +5053,7 @@ define(function () { 'use strict';
6856
5053
  */
6857
5054
  MixpanelLib.prototype.time_event = function(event_name) {
6858
5055
  if (_.isUndefined(event_name)) {
6859
- console.error('No event name provided to mixpanel.time_event');
5056
+ this.report_error('No event name provided to mixpanel.time_event');
6860
5057
  return;
6861
5058
  }
6862
5059
 
@@ -7039,7 +5236,6 @@ define(function () { 'use strict';
7039
5236
  this.unregister(ALIAS_ID_KEY);
7040
5237
  this.register({'distinct_id': new_distinct_id});
7041
5238
  }
7042
- this._check_and_handle_notifications(this.get_distinct_id());
7043
5239
  this._flags.identify_called = true;
7044
5240
  // Flush any queued up people requests
7045
5241
  this['people']._flush(_set_callback, _add_callback, _append_callback, _set_once_callback, _union_callback, _unset_callback, _remove_callback);
@@ -7129,7 +5325,7 @@ define(function () { 'use strict';
7129
5325
  // mixpanel.people.identify() call made for this user. It is VERY BAD to make an alias with
7130
5326
  // this ID, as it will duplicate users.
7131
5327
  if (alias === this.get_property(PEOPLE_DISTINCT_ID_KEY)) {
7132
- console.critical('Attempting to create alias for existing People user - aborting.');
5328
+ this.report_error('Attempting to create alias for existing People user - aborting.');
7133
5329
  return -2;
7134
5330
  }
7135
5331
 
@@ -7149,7 +5345,7 @@ define(function () { 'use strict';
7149
5345
  _this.identify(alias);
7150
5346
  });
7151
5347
  } else {
7152
- console.error('alias matches current distinct_id - skipping api call.');
5348
+ this.report_error('alias matches current distinct_id - skipping api call.');
7153
5349
  this.identify(alias);
7154
5350
  return -1;
7155
5351
  }
@@ -7276,14 +5472,6 @@ define(function () { 'use strict';
7276
5472
  * // the format {'Header-Name': value}
7277
5473
  * xhr_headers: {}
7278
5474
  *
7279
- * // protocol for fetching in-app message resources, e.g.
7280
- * // 'https://' or 'http://'; defaults to '//' (which defers to the
7281
- * // current page's protocol)
7282
- * inapp_protocol: '//'
7283
- *
7284
- * // whether to open in-app message link in new tab/window
7285
- * inapp_link_new_window: false
7286
- *
7287
5475
  * // whether to ignore or respect the web browser's Do Not Track setting
7288
5476
  * ignore_dnt: false
7289
5477
  * }
@@ -7332,7 +5520,7 @@ define(function () { 'use strict';
7332
5520
  MixpanelLib.prototype._run_hook = function(hook_name) {
7333
5521
  var ret = (this['config']['hooks'][hook_name] || IDENTITY_FUNC).apply(this, slice.call(arguments, 1));
7334
5522
  if (typeof ret === 'undefined') {
7335
- console.error(hook_name + ' hook did not return a value');
5523
+ this.report_error(hook_name + ' hook did not return a value');
7336
5524
  ret = null;
7337
5525
  }
7338
5526
  return ret;
@@ -7374,75 +5562,6 @@ define(function () { 'use strict';
7374
5562
  _.include(this.__disabled_events, event_name);
7375
5563
  };
7376
5564
 
7377
- MixpanelLib.prototype._check_and_handle_triggered_notifications = addOptOutCheckMixpanelLib(function(event_data) {
7378
- if (!this._user_decide_check_complete) {
7379
- this._events_tracked_before_user_decide_check_complete.push(event_data);
7380
- } else {
7381
- var arr = this['_triggered_notifs'];
7382
- for (var i = 0; i < arr.length; i++) {
7383
- var notif = new MixpanelNotification(arr[i], this);
7384
- if (notif._matches_event_data(event_data)) {
7385
- this._show_notification(arr[i]);
7386
- return;
7387
- }
7388
- }
7389
- }
7390
- });
7391
-
7392
- MixpanelLib.prototype._check_and_handle_notifications = addOptOutCheckMixpanelLib(function(distinct_id) {
7393
- if (
7394
- !distinct_id ||
7395
- this._flags.identify_called ||
7396
- this.get_config('disable_notifications')
7397
- ) {
7398
- return;
7399
- }
7400
-
7401
- console.log('MIXPANEL NOTIFICATION CHECK');
7402
-
7403
- var data = {
7404
- 'verbose': true,
7405
- 'version': '3',
7406
- 'lib': 'web',
7407
- 'token': this.get_config('token'),
7408
- 'distinct_id': distinct_id
7409
- };
7410
- this._send_request(
7411
- this.get_config('api_host') + '/decide/',
7412
- data,
7413
- {method: 'GET', transport: 'XHR'},
7414
- this._prepare_callback(_.bind(function(result) {
7415
- if (result['notifications'] && result['notifications'].length > 0) {
7416
- this['_triggered_notifs'] = [];
7417
- var notifications = [];
7418
- _.each(result['notifications'], function(notif) {
7419
- (notif['display_triggers'] && notif['display_triggers'].length > 0 ? this['_triggered_notifs'] : notifications).push(notif);
7420
- }, this);
7421
- if (notifications.length > 0) {
7422
- this._show_notification.call(this, notifications[0]);
7423
- }
7424
- }
7425
- this._handle_user_decide_check_complete();
7426
- }, this))
7427
- );
7428
- });
7429
-
7430
- MixpanelLib.prototype._handle_user_decide_check_complete = function() {
7431
- this._user_decide_check_complete = true;
7432
-
7433
- // check notifications against events that were tracked before decide call completed
7434
- var events = this._events_tracked_before_user_decide_check_complete;
7435
- while (events.length > 0) {
7436
- var data = events.shift(); // replay in the same order they came in
7437
- this._check_and_handle_triggered_notifications(data);
7438
- }
7439
- };
7440
-
7441
- MixpanelLib.prototype._show_notification = function(notif_data) {
7442
- var notification = new MixpanelNotification(notif_data, this);
7443
- notification.show();
7444
- };
7445
-
7446
5565
  // perform some housekeeping around GDPR opt-in/out state
7447
5566
  MixpanelLib.prototype._gdpr_init = function() {
7448
5567
  var is_localStorage_requested = this.get_config('opt_out_tracking_persistence_type') === 'localStorage';
@@ -7688,6 +5807,18 @@ define(function () { 'use strict';
7688
5807
  this._gdpr_update_persistence(options);
7689
5808
  };
7690
5809
 
5810
+ MixpanelLib.prototype.report_error = function(msg, err) {
5811
+ console.error.apply(console.error, arguments);
5812
+ try {
5813
+ if (!err && !(msg instanceof Error)) {
5814
+ msg = new Error(msg);
5815
+ }
5816
+ this.get_config('error_reporter')(msg, err);
5817
+ } catch(err) {
5818
+ console.error(err);
5819
+ }
5820
+ };
5821
+
7691
5822
  // EXPORTS (for closure compiler)
7692
5823
 
7693
5824
  // MixpanelLib Exports
@@ -7710,9 +5841,6 @@ define(function () { 'use strict';
7710
5841
  MixpanelLib.prototype['get_property'] = MixpanelLib.prototype.get_property;
7711
5842
  MixpanelLib.prototype['get_distinct_id'] = MixpanelLib.prototype.get_distinct_id;
7712
5843
  MixpanelLib.prototype['toString'] = MixpanelLib.prototype.toString;
7713
- MixpanelLib.prototype['_check_and_handle_notifications'] = MixpanelLib.prototype._check_and_handle_notifications;
7714
- MixpanelLib.prototype['_handle_user_decide_check_complete'] = MixpanelLib.prototype._handle_user_decide_check_complete;
7715
- MixpanelLib.prototype['_show_notification'] = MixpanelLib.prototype._show_notification;
7716
5844
  MixpanelLib.prototype['opt_out_tracking'] = MixpanelLib.prototype.opt_out_tracking;
7717
5845
  MixpanelLib.prototype['opt_in_tracking'] = MixpanelLib.prototype.opt_in_tracking;
7718
5846
  MixpanelLib.prototype['has_opted_out_tracking'] = MixpanelLib.prototype.has_opted_out_tracking;
@@ -7733,8 +5861,6 @@ define(function () { 'use strict';
7733
5861
  MixpanelPersistence.prototype['get_cross_subdomain'] = MixpanelPersistence.prototype.get_cross_subdomain;
7734
5862
  MixpanelPersistence.prototype['clear'] = MixpanelPersistence.prototype.clear;
7735
5863
 
7736
- _.safewrap_class(MixpanelLib, ['identify', '_check_and_handle_notifications', '_show_notification']);
7737
-
7738
5864
 
7739
5865
  var instances = {};
7740
5866
  var extend_mp = function() {