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