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