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