mixpanel-browser 2.53.0 → 2.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.53.0'
6
+ LIB_VERSION: '2.54.0'
7
7
  };
8
8
 
9
9
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -2052,6 +2052,7 @@
2052
2052
  this.reportError = options.errorReporter || _.bind(logger$1.error, logger$1);
2053
2053
  this.lock = new SharedLock(storageKey, {storage: this.storage});
2054
2054
 
2055
+ this.usePersistence = options.usePersistence;
2055
2056
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
2056
2057
 
2057
2058
  this.memQueue = [];
@@ -2076,29 +2077,36 @@
2076
2077
  'payload': item
2077
2078
  };
2078
2079
 
2079
- this.lock.withLock(_.bind(function lockAcquired() {
2080
- var succeeded;
2081
- try {
2082
- var storedQueue = this.readFromStorage();
2083
- storedQueue.push(queueEntry);
2084
- succeeded = this.saveToStorage(storedQueue);
2085
- if (succeeded) {
2086
- // only add to in-memory queue when storage succeeds
2087
- this.memQueue.push(queueEntry);
2088
- }
2089
- } catch(err) {
2090
- this.reportError('Error enqueueing item', item);
2091
- succeeded = false;
2092
- }
2093
- if (cb) {
2094
- cb(succeeded);
2095
- }
2096
- }, this), _.bind(function lockFailure(err) {
2097
- this.reportError('Error acquiring storage lock', err);
2080
+ if (!this.usePersistence) {
2081
+ this.memQueue.push(queueEntry);
2098
2082
  if (cb) {
2099
- cb(false);
2083
+ cb(true);
2100
2084
  }
2101
- }, this), this.pid);
2085
+ } else {
2086
+ this.lock.withLock(_.bind(function lockAcquired() {
2087
+ var succeeded;
2088
+ try {
2089
+ var storedQueue = this.readFromStorage();
2090
+ storedQueue.push(queueEntry);
2091
+ succeeded = this.saveToStorage(storedQueue);
2092
+ if (succeeded) {
2093
+ // only add to in-memory queue when storage succeeds
2094
+ this.memQueue.push(queueEntry);
2095
+ }
2096
+ } catch(err) {
2097
+ this.reportError('Error enqueueing item', item);
2098
+ succeeded = false;
2099
+ }
2100
+ if (cb) {
2101
+ cb(succeeded);
2102
+ }
2103
+ }, this), _.bind(function lockFailure(err) {
2104
+ this.reportError('Error acquiring storage lock', err);
2105
+ if (cb) {
2106
+ cb(false);
2107
+ }
2108
+ }, this), this.pid);
2109
+ }
2102
2110
  };
2103
2111
 
2104
2112
  /**
@@ -2109,7 +2117,7 @@
2109
2117
  */
2110
2118
  RequestQueue.prototype.fillBatch = function(batchSize) {
2111
2119
  var batch = this.memQueue.slice(0, batchSize);
2112
- if (batch.length < batchSize) {
2120
+ if (this.usePersistence && batch.length < batchSize) {
2113
2121
  // don't need lock just to read events; localStorage is thread-safe
2114
2122
  // and the worst that could happen is a duplicate send of some
2115
2123
  // orphaned events, which will be deduplicated on the server side
@@ -2158,61 +2166,67 @@
2158
2166
  _.each(ids, function(id) { idSet[id] = true; });
2159
2167
 
2160
2168
  this.memQueue = filterOutIDsAndInvalid(this.memQueue, idSet);
2161
-
2162
- var removeFromStorage = _.bind(function() {
2163
- var succeeded;
2164
- try {
2165
- var storedQueue = this.readFromStorage();
2166
- storedQueue = filterOutIDsAndInvalid(storedQueue, idSet);
2167
- succeeded = this.saveToStorage(storedQueue);
2168
-
2169
- // an extra check: did storage report success but somehow
2170
- // the items are still there?
2171
- if (succeeded) {
2172
- storedQueue = this.readFromStorage();
2173
- for (var i = 0; i < storedQueue.length; i++) {
2174
- var item = storedQueue[i];
2175
- if (item['id'] && !!idSet[item['id']]) {
2176
- this.reportError('Item not removed from storage');
2177
- return false;
2169
+ if (!this.usePersistence) {
2170
+ if (cb) {
2171
+ cb(true);
2172
+ }
2173
+ } else {
2174
+ var removeFromStorage = _.bind(function() {
2175
+ var succeeded;
2176
+ try {
2177
+ var storedQueue = this.readFromStorage();
2178
+ storedQueue = filterOutIDsAndInvalid(storedQueue, idSet);
2179
+ succeeded = this.saveToStorage(storedQueue);
2180
+
2181
+ // an extra check: did storage report success but somehow
2182
+ // the items are still there?
2183
+ if (succeeded) {
2184
+ storedQueue = this.readFromStorage();
2185
+ for (var i = 0; i < storedQueue.length; i++) {
2186
+ var item = storedQueue[i];
2187
+ if (item['id'] && !!idSet[item['id']]) {
2188
+ this.reportError('Item not removed from storage');
2189
+ return false;
2190
+ }
2178
2191
  }
2179
2192
  }
2193
+ } catch(err) {
2194
+ this.reportError('Error removing items', ids);
2195
+ succeeded = false;
2180
2196
  }
2181
- } catch(err) {
2182
- this.reportError('Error removing items', ids);
2183
- succeeded = false;
2184
- }
2185
- return succeeded;
2186
- }, this);
2197
+ return succeeded;
2198
+ }, this);
2187
2199
 
2188
- this.lock.withLock(function lockAcquired() {
2189
- var succeeded = removeFromStorage();
2190
- if (cb) {
2191
- cb(succeeded);
2192
- }
2193
- }, _.bind(function lockFailure(err) {
2194
- var succeeded = false;
2195
- this.reportError('Error acquiring storage lock', err);
2196
- if (!localStorageSupported(this.storage, true)) {
2197
- // Looks like localStorage writes have stopped working sometime after
2198
- // initialization (probably full), and so nobody can acquire locks
2199
- // anymore. Consider it temporarily safe to remove items without the
2200
- // lock, since nobody's writing successfully anyway.
2201
- succeeded = removeFromStorage();
2202
- if (!succeeded) {
2203
- // OK, we couldn't even write out the smaller queue. Try clearing it
2204
- // entirely.
2205
- try {
2206
- this.storage.removeItem(this.storageKey);
2207
- } catch(err) {
2208
- this.reportError('Error clearing queue', err);
2200
+ this.lock.withLock(function lockAcquired() {
2201
+ var succeeded = removeFromStorage();
2202
+ if (cb) {
2203
+ cb(succeeded);
2204
+ }
2205
+ }, _.bind(function lockFailure(err) {
2206
+ var succeeded = false;
2207
+ this.reportError('Error acquiring storage lock', err);
2208
+ if (!localStorageSupported(this.storage, true)) {
2209
+ // Looks like localStorage writes have stopped working sometime after
2210
+ // initialization (probably full), and so nobody can acquire locks
2211
+ // anymore. Consider it temporarily safe to remove items without the
2212
+ // lock, since nobody's writing successfully anyway.
2213
+ succeeded = removeFromStorage();
2214
+ if (!succeeded) {
2215
+ // OK, we couldn't even write out the smaller queue. Try clearing it
2216
+ // entirely.
2217
+ try {
2218
+ this.storage.removeItem(this.storageKey);
2219
+ } catch(err) {
2220
+ this.reportError('Error clearing queue', err);
2221
+ }
2209
2222
  }
2210
2223
  }
2211
- }
2212
- if (cb) {
2213
- cb(succeeded);
2214
- }
2215
- }, this), this.pid);
2224
+ if (cb) {
2225
+ cb(succeeded);
2226
+ }
2227
+ }, this), this.pid);
2228
+ }
2229
+
2216
2230
  };
2217
2231
 
2218
2232
  // internal helper for RequestQueue.updatePayloads
@@ -2240,25 +2254,32 @@
2240
2254
  */
2241
2255
  RequestQueue.prototype.updatePayloads = function(itemsToUpdate, cb) {
2242
2256
  this.memQueue = updatePayloads(this.memQueue, itemsToUpdate);
2243
- this.lock.withLock(_.bind(function lockAcquired() {
2244
- var succeeded;
2245
- try {
2246
- var storedQueue = this.readFromStorage();
2247
- storedQueue = updatePayloads(storedQueue, itemsToUpdate);
2248
- succeeded = this.saveToStorage(storedQueue);
2249
- } catch(err) {
2250
- this.reportError('Error updating items', itemsToUpdate);
2251
- succeeded = false;
2252
- }
2253
- if (cb) {
2254
- cb(succeeded);
2255
- }
2256
- }, this), _.bind(function lockFailure(err) {
2257
- this.reportError('Error acquiring storage lock', err);
2257
+ if (!this.usePersistence) {
2258
2258
  if (cb) {
2259
- cb(false);
2259
+ cb(true);
2260
2260
  }
2261
- }, this), this.pid);
2261
+ } else {
2262
+ this.lock.withLock(_.bind(function lockAcquired() {
2263
+ var succeeded;
2264
+ try {
2265
+ var storedQueue = this.readFromStorage();
2266
+ storedQueue = updatePayloads(storedQueue, itemsToUpdate);
2267
+ succeeded = this.saveToStorage(storedQueue);
2268
+ } catch(err) {
2269
+ this.reportError('Error updating items', itemsToUpdate);
2270
+ succeeded = false;
2271
+ }
2272
+ if (cb) {
2273
+ cb(succeeded);
2274
+ }
2275
+ }, this), _.bind(function lockFailure(err) {
2276
+ this.reportError('Error acquiring storage lock', err);
2277
+ if (cb) {
2278
+ cb(false);
2279
+ }
2280
+ }, this), this.pid);
2281
+ }
2282
+
2262
2283
  };
2263
2284
 
2264
2285
  /**
@@ -2301,7 +2322,10 @@
2301
2322
  */
2302
2323
  RequestQueue.prototype.clear = function() {
2303
2324
  this.memQueue = [];
2304
- this.storage.removeItem(this.storageKey);
2325
+
2326
+ if (this.usePersistence) {
2327
+ this.storage.removeItem(this.storageKey);
2328
+ }
2305
2329
  };
2306
2330
 
2307
2331
  // maximum interval between request retries after exponential backoff
@@ -2319,7 +2343,8 @@
2319
2343
  this.errorReporter = options.errorReporter;
2320
2344
  this.queue = new RequestQueue(storageKey, {
2321
2345
  errorReporter: _.bind(this.reportError, this),
2322
- storage: options.storage
2346
+ storage: options.storage,
2347
+ usePersistence: options.usePersistence
2323
2348
  });
2324
2349
 
2325
2350
  this.libConfig = options.libConfig;
@@ -2336,6 +2361,11 @@
2336
2361
 
2337
2362
  // extra client-side dedupe
2338
2363
  this.itemIdsSentSuccessfully = {};
2364
+
2365
+ // Make the flush occur at the interval specified by flushIntervalMs, default behavior will attempt consecutive flushes
2366
+ // as long as the queue is not empty. This is useful for high-frequency events like Session Replay where we might end up
2367
+ // in a request loop and get ratelimited by the server.
2368
+ this.flushOnlyOnInterval = options.flushOnlyOnInterval || false;
2339
2369
  };
2340
2370
 
2341
2371
  /**
@@ -2420,6 +2450,9 @@
2420
2450
  var startTime = new Date().getTime();
2421
2451
  var currentBatchSize = this.batchSize;
2422
2452
  var batch = this.queue.fillBatch(currentBatchSize);
2453
+ // if there's more items in the queue than the batch size, attempt
2454
+ // to flush again after the current batch is done.
2455
+ var attemptSecondaryFlush = batch.length === currentBatchSize;
2423
2456
  var dataForRequest = [];
2424
2457
  var transformedItems = {};
2425
2458
  _.each(batch, function(item) {
@@ -2487,22 +2520,17 @@
2487
2520
  this.flush();
2488
2521
  } else if (
2489
2522
  _.isObject(res) &&
2490
- res.xhr_req &&
2491
- (res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout')
2523
+ (res.httpStatusCode >= 500 || res.httpStatusCode === 429 || res.error === 'timeout')
2492
2524
  ) {
2493
2525
  // network or API error, or 429 Too Many Requests, retry
2494
2526
  var retryMS = this.flushInterval * 2;
2495
- var headers = res.xhr_req['responseHeaders'];
2496
- if (headers) {
2497
- var retryAfter = headers['Retry-After'];
2498
- if (retryAfter) {
2499
- retryMS = (parseInt(retryAfter, 10) * 1000) || retryMS;
2500
- }
2527
+ if (res.retryAfter) {
2528
+ retryMS = (parseInt(res.retryAfter, 10) * 1000) || retryMS;
2501
2529
  }
2502
2530
  retryMS = Math.min(MAX_RETRY_INTERVAL_MS, retryMS);
2503
2531
  this.reportError('Error; retry in ' + retryMS + ' ms');
2504
2532
  this.scheduleFlush(retryMS);
2505
- } else if (_.isObject(res) && res.xhr_req && res.xhr_req['status'] === 413) {
2533
+ } else if (_.isObject(res) && res.httpStatusCode === 413) {
2506
2534
  // 413 Payload Too Large
2507
2535
  if (batch.length > 1) {
2508
2536
  var halvedBatchSize = Math.max(1, Math.floor(currentBatchSize / 2));
@@ -2526,7 +2554,11 @@
2526
2554
  _.bind(function(succeeded) {
2527
2555
  if (succeeded) {
2528
2556
  this.consecutiveRemovalFailures = 0;
2529
- this.flush(); // handle next batch if the queue isn't empty
2557
+ if (this.flushOnlyOnInterval && !attemptSecondaryFlush) {
2558
+ this.resetFlush(); // schedule next batch with a delay
2559
+ } else {
2560
+ this.flush(); // handle next batch if the queue isn't empty
2561
+ }
2530
2562
  } else {
2531
2563
  this.reportError('Failed to remove items from queue');
2532
2564
  if (++this.consecutiveRemovalFailures > 5) {
@@ -2574,7 +2606,6 @@
2574
2606
  }
2575
2607
  logger.log('MIXPANEL REQUEST:', dataForRequest);
2576
2608
  this.sendRequest(dataForRequest, requestOptions, batchSendCallback);
2577
-
2578
2609
  } catch(err) {
2579
2610
  this.reportError('Error flushing request queue', err);
2580
2611
  this.resetFlush();
@@ -4108,6 +4139,12 @@
4108
4139
  */
4109
4140
 
4110
4141
  var init_type; // MODULE or SNIPPET loader
4142
+ // allow bundlers to specify how extra code (recorder bundle) should be loaded
4143
+ // eslint-disable-next-line no-unused-vars
4144
+ var load_extra_bundle = function(src, _onload) {
4145
+ throw new Error(src + ' not available in this build.');
4146
+ };
4147
+
4111
4148
  var mixpanel_master; // main mixpanel instance / object
4112
4149
  var INIT_MODULE = 0;
4113
4150
  var INIT_SNIPPET = 1;
@@ -4201,7 +4238,9 @@
4201
4238
  'hooks': {},
4202
4239
  'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
4203
4240
  'record_block_selector': 'img, video',
4241
+ 'record_collect_fonts': false,
4204
4242
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
4243
+ 'record_inline_images': false,
4205
4244
  'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
4206
4245
  'record_mask_text_selector': '*',
4207
4246
  'record_max_ms': MAX_RECORDING_MS,
@@ -4437,12 +4476,7 @@
4437
4476
  }, this);
4438
4477
 
4439
4478
  if (_.isUndefined(win['__mp_recorder'])) {
4440
- var scriptEl = document$1.createElement('script');
4441
- scriptEl.type = 'text/javascript';
4442
- scriptEl.async = true;
4443
- scriptEl.onload = handleLoadedRecorder;
4444
- scriptEl.src = this.get_config('recorder_src');
4445
- document$1.head.appendChild(scriptEl);
4479
+ load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
4446
4480
  } else {
4447
4481
  handleLoadedRecorder();
4448
4482
  }
@@ -4741,7 +4775,8 @@
4741
4775
  lib.report_error(error);
4742
4776
  if (callback) {
4743
4777
  if (verbose_mode) {
4744
- callback({status: 0, error: error, xhr_req: req});
4778
+ var response_headers = req['responseHeaders'] || {};
4779
+ callback({status: 0, httpStatusCode: req['status'], error: error, retryAfter: response_headers['Retry-After']});
4745
4780
  } else {
4746
4781
  callback(0);
4747
4782
  }
@@ -4841,6 +4876,7 @@
4841
4876
  attrs.queue_key,
4842
4877
  {
4843
4878
  libConfig: this['config'],
4879
+ errorReporter: this.get_config('error_reporter'),
4844
4880
  sendRequestFunc: _.bind(function(data, options, cb) {
4845
4881
  this._send_request(
4846
4882
  this.get_config('api_host') + attrs.endpoint,
@@ -4852,8 +4888,8 @@
4852
4888
  beforeSendHook: _.bind(function(item) {
4853
4889
  return this._run_hook('before_send_' + attrs.type, item);
4854
4890
  }, this),
4855
- errorReporter: this.get_config('error_reporter'),
4856
- stopAllBatchingFunc: _.bind(this.stop_batch_senders, this)
4891
+ stopAllBatchingFunc: _.bind(this.stop_batch_senders, this),
4892
+ usePersistence: true
4857
4893
  }
4858
4894
  );
4859
4895
  }, this);
@@ -6302,7 +6338,8 @@
6302
6338
  _.register_event(win, 'load', dom_loaded_handler, true);
6303
6339
  };
6304
6340
 
6305
- function init_from_snippet() {
6341
+ function init_from_snippet(bundle_loader) {
6342
+ load_extra_bundle = bundle_loader;
6306
6343
  init_type = INIT_SNIPPET;
6307
6344
  mixpanel_master = win[PRIMARY_INSTANCE_NAME];
6308
6345
 
@@ -6342,8 +6379,19 @@
6342
6379
  add_dom_loaded_handler();
6343
6380
  }
6344
6381
 
6382
+ // For loading separate bundles asynchronously via script tag
6383
+ // so that we don't load them until they are needed at runtime.
6384
+ function loadAsync (src, onload) {
6385
+ var scriptEl = document.createElement('script');
6386
+ scriptEl.type = 'text/javascript';
6387
+ scriptEl.async = true;
6388
+ scriptEl.onload = onload;
6389
+ scriptEl.src = src;
6390
+ document.head.appendChild(scriptEl);
6391
+ }
6392
+
6345
6393
  /* eslint camelcase: "off" */
6346
6394
 
6347
- init_from_snippet();
6395
+ init_from_snippet(loadAsync);
6348
6396
 
6349
6397
  })();