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