mixpanel-browser 2.53.0 → 2.54.1

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.1'
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
- }
2257
+ if (!this.usePersistence) {
2253
2258
  if (cb) {
2254
- cb(succeeded);
2259
+ cb(true);
2255
2260
  }
2256
- }, this), _.bind(function lockFailure(err) {
2257
- this.reportError('Error acquiring storage lock', err);
2258
- if (cb) {
2259
- cb(false);
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
  /**
@@ -2393,7 +2423,11 @@
2393
2423
  RequestBatcher.prototype.scheduleFlush = function(flushMS) {
2394
2424
  this.flushInterval = flushMS;
2395
2425
  if (!this.stopped) { // don't schedule anymore if batching has been stopped
2396
- this.timeoutID = setTimeout(_.bind(this.flush, this), this.flushInterval);
2426
+ this.timeoutID = setTimeout(_.bind(function() {
2427
+ if (!this.stopped) {
2428
+ this.flush();
2429
+ }
2430
+ }, this), this.flushInterval);
2397
2431
  }
2398
2432
  };
2399
2433
 
@@ -2420,6 +2454,9 @@
2420
2454
  var startTime = new Date().getTime();
2421
2455
  var currentBatchSize = this.batchSize;
2422
2456
  var batch = this.queue.fillBatch(currentBatchSize);
2457
+ // if there's more items in the queue than the batch size, attempt
2458
+ // to flush again after the current batch is done.
2459
+ var attemptSecondaryFlush = batch.length === currentBatchSize;
2423
2460
  var dataForRequest = [];
2424
2461
  var transformedItems = {};
2425
2462
  _.each(batch, function(item) {
@@ -2487,22 +2524,17 @@
2487
2524
  this.flush();
2488
2525
  } else if (
2489
2526
  _.isObject(res) &&
2490
- res.xhr_req &&
2491
- (res.xhr_req['status'] >= 500 || res.xhr_req['status'] === 429 || res.error === 'timeout')
2527
+ (res.httpStatusCode >= 500 || res.httpStatusCode === 429 || res.error === 'timeout')
2492
2528
  ) {
2493
2529
  // network or API error, or 429 Too Many Requests, retry
2494
2530
  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
- }
2531
+ if (res.retryAfter) {
2532
+ retryMS = (parseInt(res.retryAfter, 10) * 1000) || retryMS;
2501
2533
  }
2502
2534
  retryMS = Math.min(MAX_RETRY_INTERVAL_MS, retryMS);
2503
2535
  this.reportError('Error; retry in ' + retryMS + ' ms');
2504
2536
  this.scheduleFlush(retryMS);
2505
- } else if (_.isObject(res) && res.xhr_req && res.xhr_req['status'] === 413) {
2537
+ } else if (_.isObject(res) && res.httpStatusCode === 413) {
2506
2538
  // 413 Payload Too Large
2507
2539
  if (batch.length > 1) {
2508
2540
  var halvedBatchSize = Math.max(1, Math.floor(currentBatchSize / 2));
@@ -2526,7 +2558,11 @@
2526
2558
  _.bind(function(succeeded) {
2527
2559
  if (succeeded) {
2528
2560
  this.consecutiveRemovalFailures = 0;
2529
- this.flush(); // handle next batch if the queue isn't empty
2561
+ if (this.flushOnlyOnInterval && !attemptSecondaryFlush) {
2562
+ this.resetFlush(); // schedule next batch with a delay
2563
+ } else {
2564
+ this.flush(); // handle next batch if the queue isn't empty
2565
+ }
2530
2566
  } else {
2531
2567
  this.reportError('Failed to remove items from queue');
2532
2568
  if (++this.consecutiveRemovalFailures > 5) {
@@ -2574,7 +2610,6 @@
2574
2610
  }
2575
2611
  logger.log('MIXPANEL REQUEST:', dataForRequest);
2576
2612
  this.sendRequest(dataForRequest, requestOptions, batchSendCallback);
2577
-
2578
2613
  } catch(err) {
2579
2614
  this.reportError('Error flushing request queue', err);
2580
2615
  this.resetFlush();
@@ -4108,6 +4143,12 @@
4108
4143
  */
4109
4144
 
4110
4145
  var init_type; // MODULE or SNIPPET loader
4146
+ // allow bundlers to specify how extra code (recorder bundle) should be loaded
4147
+ // eslint-disable-next-line no-unused-vars
4148
+ var load_extra_bundle = function(src, _onload) {
4149
+ throw new Error(src + ' not available in this build.');
4150
+ };
4151
+
4111
4152
  var mixpanel_master; // main mixpanel instance / object
4112
4153
  var INIT_MODULE = 0;
4113
4154
  var INIT_SNIPPET = 1;
@@ -4201,7 +4242,9 @@
4201
4242
  'hooks': {},
4202
4243
  'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
4203
4244
  'record_block_selector': 'img, video',
4245
+ 'record_collect_fonts': false,
4204
4246
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
4247
+ 'record_inline_images': false,
4205
4248
  'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
4206
4249
  'record_mask_text_selector': '*',
4207
4250
  'record_max_ms': MAX_RECORDING_MS,
@@ -4437,12 +4480,7 @@
4437
4480
  }, this);
4438
4481
 
4439
4482
  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);
4483
+ load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
4446
4484
  } else {
4447
4485
  handleLoadedRecorder();
4448
4486
  }
@@ -4741,7 +4779,8 @@
4741
4779
  lib.report_error(error);
4742
4780
  if (callback) {
4743
4781
  if (verbose_mode) {
4744
- callback({status: 0, error: error, xhr_req: req});
4782
+ var response_headers = req['responseHeaders'] || {};
4783
+ callback({status: 0, httpStatusCode: req['status'], error: error, retryAfter: response_headers['Retry-After']});
4745
4784
  } else {
4746
4785
  callback(0);
4747
4786
  }
@@ -4841,6 +4880,7 @@
4841
4880
  attrs.queue_key,
4842
4881
  {
4843
4882
  libConfig: this['config'],
4883
+ errorReporter: this.get_config('error_reporter'),
4844
4884
  sendRequestFunc: _.bind(function(data, options, cb) {
4845
4885
  this._send_request(
4846
4886
  this.get_config('api_host') + attrs.endpoint,
@@ -4852,8 +4892,8 @@
4852
4892
  beforeSendHook: _.bind(function(item) {
4853
4893
  return this._run_hook('before_send_' + attrs.type, item);
4854
4894
  }, this),
4855
- errorReporter: this.get_config('error_reporter'),
4856
- stopAllBatchingFunc: _.bind(this.stop_batch_senders, this)
4895
+ stopAllBatchingFunc: _.bind(this.stop_batch_senders, this),
4896
+ usePersistence: true
4857
4897
  }
4858
4898
  );
4859
4899
  }, this);
@@ -6302,7 +6342,8 @@
6302
6342
  _.register_event(win, 'load', dom_loaded_handler, true);
6303
6343
  };
6304
6344
 
6305
- function init_from_snippet() {
6345
+ function init_from_snippet(bundle_loader) {
6346
+ load_extra_bundle = bundle_loader;
6306
6347
  init_type = INIT_SNIPPET;
6307
6348
  mixpanel_master = win[PRIMARY_INSTANCE_NAME];
6308
6349
 
@@ -6342,8 +6383,19 @@
6342
6383
  add_dom_loaded_handler();
6343
6384
  }
6344
6385
 
6386
+ // For loading separate bundles asynchronously via script tag
6387
+ // so that we don't load them until they are needed at runtime.
6388
+ function loadAsync (src, onload) {
6389
+ var scriptEl = document.createElement('script');
6390
+ scriptEl.type = 'text/javascript';
6391
+ scriptEl.async = true;
6392
+ scriptEl.onload = onload;
6393
+ scriptEl.src = src;
6394
+ document.head.appendChild(scriptEl);
6395
+ }
6396
+
6345
6397
  /* eslint camelcase: "off" */
6346
6398
 
6347
- init_from_snippet();
6399
+ init_from_snippet(loadAsync);
6348
6400
 
6349
6401
  })();