mixpanel-browser 2.75.0 → 2.76.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.
Files changed (57) hide show
  1. package/.claude/settings.local.json +14 -0
  2. package/.github/workflows/integration-tests.yml +2 -2
  3. package/.github/workflows/unit-tests.yml +2 -2
  4. package/CHANGELOG.md +10 -0
  5. package/build.sh +10 -8
  6. package/dist/async-modules/mixpanel-recorder-bIS4LMGd.js +23595 -0
  7. package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js +2 -0
  8. package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js.map +1 -0
  9. package/dist/async-modules/mixpanel-targeting-BcAPS-Mz.js +2520 -0
  10. package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js +2 -0
  11. package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js.map +1 -0
  12. package/dist/mixpanel-core.cjs.d.ts +68 -0
  13. package/dist/mixpanel-core.cjs.js +550 -383
  14. package/dist/mixpanel-recorder.js +708 -32
  15. package/dist/mixpanel-recorder.min.js +1 -1
  16. package/dist/mixpanel-recorder.min.js.map +1 -1
  17. package/dist/mixpanel-targeting.js +6 -62
  18. package/dist/mixpanel-targeting.min.js +1 -1
  19. package/dist/mixpanel-targeting.min.js.map +1 -1
  20. package/dist/mixpanel-with-async-modules.cjs.d.ts +68 -0
  21. package/dist/mixpanel-with-async-modules.cjs.js +550 -383
  22. package/dist/mixpanel-with-async-recorder.cjs.d.ts +68 -0
  23. package/dist/mixpanel-with-async-recorder.cjs.js +550 -383
  24. package/dist/mixpanel-with-recorder.d.ts +68 -0
  25. package/dist/mixpanel-with-recorder.js +1036 -197
  26. package/dist/mixpanel-with-recorder.min.d.ts +68 -0
  27. package/dist/mixpanel-with-recorder.min.js +1 -1
  28. package/dist/mixpanel.amd.d.ts +68 -0
  29. package/dist/mixpanel.amd.js +1038 -251
  30. package/dist/mixpanel.cjs.d.ts +68 -0
  31. package/dist/mixpanel.cjs.js +1038 -251
  32. package/dist/mixpanel.globals.js +550 -383
  33. package/dist/mixpanel.min.js +184 -181
  34. package/dist/mixpanel.module.d.ts +68 -0
  35. package/dist/mixpanel.module.js +1038 -251
  36. package/dist/mixpanel.umd.d.ts +68 -0
  37. package/dist/mixpanel.umd.js +1038 -251
  38. package/logo.svg +5 -0
  39. package/package.json +2 -1
  40. package/rollup.config.mjs +163 -46
  41. package/src/autocapture/index.js +10 -27
  42. package/src/config.js +9 -3
  43. package/src/flags/index.js +1 -2
  44. package/src/index.d.ts +68 -0
  45. package/src/mixpanel-core.js +76 -111
  46. package/src/recorder/index.js +1 -1
  47. package/src/recorder/recorder.js +5 -1
  48. package/src/recorder/rrweb-network-plugin.js +649 -0
  49. package/src/recorder/session-recording.js +31 -11
  50. package/src/recorder-manager.js +216 -0
  51. package/src/request-batcher.js +1 -1
  52. package/src/targeting/event-matcher.js +2 -57
  53. package/src/targeting/index.js +1 -1
  54. package/src/targeting/loader.js +1 -1
  55. package/src/utils.js +13 -1
  56. package/testServer.js +55 -0
  57. package/src/globals.js +0 -14
@@ -3,9 +3,17 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.75.0'
6
+ LIB_VERSION: '2.76.0'
7
7
  };
8
8
 
9
+ // Window global names for async modules
10
+ var TARGETING_GLOBAL_NAME = '__mp_targeting';
11
+ var RECORDER_GLOBAL_NAME = '__mp_recorder';
12
+
13
+ // Constants that are injected at build-time for the names of async modules.
14
+ var RECORDER_FILENAME = 'mixpanel-recorder-bIS4LMGd.js';
15
+ var TARGETING_FILENAME = 'mixpanel-targeting-BcAPS-Mz.js';
16
+
9
17
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
10
18
  var win;
11
19
  if (typeof(window) === 'undefined') {
@@ -2122,6 +2130,17 @@
2122
2130
 
2123
2131
  var NOOP_FUNC = function () {};
2124
2132
 
2133
+ var urlMatchesRegexList = function (url, regexList) {
2134
+ var matches = false;
2135
+ for (var i = 0; i < regexList.length; i++) {
2136
+ if (url.match(regexList[i])) {
2137
+ matches = true;
2138
+ break;
2139
+ }
2140
+ }
2141
+ return matches;
2142
+ };
2143
+
2125
2144
  var JSONStringify = null, JSONParse = null;
2126
2145
  if (typeof JSON !== 'undefined') {
2127
2146
  JSONStringify = JSON.stringify;
@@ -2144,25 +2163,6 @@
2144
2163
  _['toArray'] = _.toArray;
2145
2164
  _['NPO'] = NpoPromise;
2146
2165
 
2147
- /**
2148
- * @param {import('./session-recording').SerializedRecording} serializedRecording
2149
- * @returns {boolean}
2150
- */
2151
- var isRecordingExpired = function(serializedRecording) {
2152
- var now = Date.now();
2153
- return !serializedRecording || now > serializedRecording['maxExpires'] || now > serializedRecording['idleExpires'];
2154
- };
2155
-
2156
- /**
2157
- * Shared global window property names used across modules
2158
- */
2159
-
2160
- // Targeting library global (used by flags and targeting modules)
2161
- var TARGETING_GLOBAL_NAME = '__mp_targeting';
2162
-
2163
- // Recorder library global (used by recorder and mixpanel-core)
2164
- var RECORDER_GLOBAL_NAME = '__mp_recorder';
2165
-
2166
2166
  // stateless utils
2167
2167
  // mostly from https://github.com/mixpanel/mixpanel-js/blob/989ada50f518edab47b9c4fd9535f9fbd5ec5fc0/src/autotrack-utils.js
2168
2168
 
@@ -3427,27 +3427,15 @@
3427
3427
  };
3428
3428
 
3429
3429
  Autocapture.prototype.currentUrlBlocked = function() {
3430
- var i;
3431
3430
  var currentUrl = _.info.currentUrl();
3432
3431
 
3433
3432
  var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
3434
3433
  if (allowUrlRegexes.length) {
3435
3434
  // we're using an allowlist, only track if current URL matches
3436
- var allowed = false;
3437
- for (i = 0; i < allowUrlRegexes.length; i++) {
3438
- var allowRegex = allowUrlRegexes[i];
3439
- try {
3440
- if (currentUrl.match(allowRegex)) {
3441
- allowed = true;
3442
- break;
3443
- }
3444
- } catch (err) {
3445
- logger$4.critical('Error while checking block URL regex: ' + allowRegex, err);
3446
- return true;
3447
- }
3448
- }
3449
- if (!allowed) {
3450
- // wasn't allowed by any regex
3435
+ try {
3436
+ return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
3437
+ } catch (err) {
3438
+ logger$4.critical('Error while checking block URL regexes: ', err);
3451
3439
  return true;
3452
3440
  }
3453
3441
  }
@@ -3457,17 +3445,12 @@
3457
3445
  return false;
3458
3446
  }
3459
3447
 
3460
- for (i = 0; i < blockUrlRegexes.length; i++) {
3461
- try {
3462
- if (currentUrl.match(blockUrlRegexes[i])) {
3463
- return true;
3464
- }
3465
- } catch (err) {
3466
- logger$4.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
3467
- return true;
3468
- }
3448
+ try {
3449
+ return urlMatchesRegexList(currentUrl, blockUrlRegexes);
3450
+ } catch (err) {
3451
+ logger$4.critical('Error while checking block URL regexes: ', err);
3452
+ return true;
3469
3453
  }
3470
- return false;
3471
3454
  };
3472
3455
 
3473
3456
  Autocapture.prototype.pageviewTrackingConfig = function() {
@@ -4461,146 +4444,490 @@
4461
4444
  // Exports intended only for testing
4462
4445
  FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
4463
4446
 
4464
- /* eslint camelcase: "off" */
4447
+ var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
4448
+
4449
+ var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
4450
+ var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
4465
4451
 
4452
+ // note: increment the version number when adding new object stores
4453
+ var DB_VERSION = 1;
4454
+ var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
4466
4455
 
4467
4456
  /**
4468
- * DomTracker Object
4469
- * @constructor
4457
+ * @type {import('./wrapper').StorageWrapper}
4470
4458
  */
4471
- var DomTracker = function() {};
4459
+ var IDBStorageWrapper = function (storeName) {
4460
+ /**
4461
+ * @type {Promise<IDBDatabase>|null}
4462
+ */
4463
+ this.dbPromise = null;
4464
+ this.storeName = storeName;
4465
+ };
4466
+
4467
+ IDBStorageWrapper.prototype._openDb = function () {
4468
+ return new PromisePolyfill(function (resolve, reject) {
4469
+ var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
4470
+ openRequest['onerror'] = function () {
4471
+ reject(openRequest.error);
4472
+ };
4472
4473
 
4474
+ openRequest['onsuccess'] = function () {
4475
+ resolve(openRequest.result);
4476
+ };
4473
4477
 
4474
- // interface
4475
- DomTracker.prototype.create_properties = function() {};
4476
- DomTracker.prototype.event_handler = function() {};
4477
- DomTracker.prototype.after_track_handler = function() {};
4478
+ openRequest['onupgradeneeded'] = function (ev) {
4479
+ var db = ev.target.result;
4478
4480
 
4479
- DomTracker.prototype.init = function(mixpanel_instance) {
4480
- this.mp = mixpanel_instance;
4481
- return this;
4481
+ OBJECT_STORES.forEach(function (storeName) {
4482
+ db.createObjectStore(storeName);
4483
+ });
4484
+ };
4485
+ });
4482
4486
  };
4483
4487
 
4484
- /**
4485
- * @param {Object|string} query
4486
- * @param {string} event_name
4487
- * @param {Object=} properties
4488
- * @param {function=} user_callback
4489
- */
4490
- DomTracker.prototype.track = function(query, event_name, properties, user_callback) {
4491
- var that = this;
4492
- var elements = _.dom_query(query);
4493
-
4494
- if (elements.length === 0) {
4495
- console.error('The DOM query (' + query + ') returned 0 elements');
4496
- return;
4488
+ IDBStorageWrapper.prototype.init = function () {
4489
+ if (!win.indexedDB) {
4490
+ return PromisePolyfill.reject('indexedDB is not supported in this browser');
4497
4491
  }
4498
4492
 
4499
- _.each(elements, function(element) {
4500
- _.register_event(element, this.override_event, function(e) {
4501
- var options = {};
4502
- var props = that.create_properties(properties, this);
4503
- var timeout = that.mp.get_config('track_links_timeout');
4504
-
4505
- that.event_handler(e, this, options);
4506
-
4507
- // in case the mixpanel servers don't get back to us in time
4508
- window.setTimeout(that.track_callback(user_callback, props, options, true), timeout);
4493
+ if (!this.dbPromise) {
4494
+ this.dbPromise = this._openDb();
4495
+ }
4509
4496
 
4510
- // fire the tracking event
4511
- that.mp.track(event_name, props, that.track_callback(user_callback, props, options));
4497
+ return this.dbPromise
4498
+ .then(function (dbOrError) {
4499
+ if (dbOrError instanceof win['IDBDatabase']) {
4500
+ return PromisePolyfill.resolve();
4501
+ } else {
4502
+ return PromisePolyfill.reject(dbOrError);
4503
+ }
4512
4504
  });
4513
- }, this);
4505
+ };
4514
4506
 
4515
- return true;
4507
+ IDBStorageWrapper.prototype.isInitialized = function () {
4508
+ return !!this.dbPromise;
4516
4509
  };
4517
4510
 
4518
4511
  /**
4519
- * @param {function} user_callback
4520
- * @param {Object} props
4521
- * @param {boolean=} timeout_occured
4512
+ * @param {IDBTransactionMode} mode
4513
+ * @param {function(IDBObjectStore): void} storeCb
4522
4514
  */
4523
- DomTracker.prototype.track_callback = function(user_callback, props, options, timeout_occured) {
4524
- timeout_occured = timeout_occured || false;
4525
- var that = this;
4526
-
4527
- return function() {
4528
- // options is referenced from both callbacks, so we can have
4529
- // a 'lock' of sorts to ensure only one fires
4530
- if (options.callback_fired) { return; }
4531
- options.callback_fired = true;
4532
-
4533
- if (user_callback && user_callback(timeout_occured, props) === false) {
4534
- // user can prevent the default functionality by
4535
- // returning false from their callback
4536
- return;
4537
- }
4515
+ IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
4516
+ var storeName = this.storeName;
4517
+ var doTransaction = function (db) {
4518
+ return new PromisePolyfill(function (resolve, reject) {
4519
+ var transaction = db.transaction(storeName, mode);
4520
+ transaction.oncomplete = function () {
4521
+ resolve(transaction);
4522
+ };
4523
+ transaction.onabort = transaction.onerror = function () {
4524
+ reject(transaction.error);
4525
+ };
4538
4526
 
4539
- that.after_track_handler(props, options, timeout_occured);
4527
+ storeCb(transaction.objectStore(storeName));
4528
+ });
4540
4529
  };
4541
- };
4542
-
4543
- DomTracker.prototype.create_properties = function(properties, element) {
4544
- var props;
4545
4530
 
4546
- if (typeof(properties) === 'function') {
4547
- props = properties(element);
4548
- } else {
4549
- props = _.extend({}, properties);
4550
- }
4551
-
4552
- return props;
4531
+ return this.dbPromise
4532
+ .then(doTransaction)
4533
+ .catch(function (err) {
4534
+ if (err && err['name'] === 'InvalidStateError') {
4535
+ // try reopening the DB if the connection is closed
4536
+ this.dbPromise = this._openDb();
4537
+ return this.dbPromise.then(doTransaction);
4538
+ } else {
4539
+ return PromisePolyfill.reject(err);
4540
+ }
4541
+ }.bind(this));
4553
4542
  };
4554
4543
 
4555
- /**
4556
- * LinkTracker Object
4557
- * @constructor
4558
- * @extends DomTracker
4559
- */
4560
- var LinkTracker = function() {
4561
- this.override_event = 'click';
4544
+ IDBStorageWrapper.prototype.setItem = function (key, value) {
4545
+ return this.makeTransaction('readwrite', function (objectStore) {
4546
+ objectStore.put(value, key);
4547
+ });
4562
4548
  };
4563
- _.inherit(LinkTracker, DomTracker);
4564
-
4565
- LinkTracker.prototype.create_properties = function(properties, element) {
4566
- var props = LinkTracker.superclass.create_properties.apply(this, arguments);
4567
4549
 
4568
- if (element.href) { props['url'] = element.href; }
4550
+ IDBStorageWrapper.prototype.getItem = function (key) {
4551
+ var req;
4552
+ return this.makeTransaction('readonly', function (objectStore) {
4553
+ req = objectStore.get(key);
4554
+ }).then(function () {
4555
+ return req.result;
4556
+ });
4557
+ };
4569
4558
 
4570
- return props;
4559
+ IDBStorageWrapper.prototype.removeItem = function (key) {
4560
+ return this.makeTransaction('readwrite', function (objectStore) {
4561
+ objectStore.delete(key);
4562
+ });
4571
4563
  };
4572
4564
 
4573
- LinkTracker.prototype.event_handler = function(evt, element, options) {
4574
- options.new_tab = (
4575
- evt.which === 2 ||
4576
- evt.metaKey ||
4577
- evt.ctrlKey ||
4578
- element.target === '_blank'
4579
- );
4580
- options.href = element.href;
4565
+ IDBStorageWrapper.prototype.getAll = function () {
4566
+ var req;
4567
+ return this.makeTransaction('readonly', function (objectStore) {
4568
+ req = objectStore.getAll();
4569
+ }).then(function () {
4570
+ return req.result;
4571
+ });
4572
+ };
4581
4573
 
4582
- if (!options.new_tab) {
4583
- evt.preventDefault();
4584
- }
4574
+ /**
4575
+ * @param {import('./session-recording').SerializedRecording} serializedRecording
4576
+ * @returns {boolean}
4577
+ */
4578
+ var isRecordingExpired = function(serializedRecording) {
4579
+ var now = Date.now();
4580
+ return !serializedRecording || now > serializedRecording['maxExpires'] || now > serializedRecording['idleExpires'];
4585
4581
  };
4586
4582
 
4587
- LinkTracker.prototype.after_track_handler = function(props, options) {
4588
- if (options.new_tab) { return; }
4583
+ /* eslint camelcase: "off" */
4589
4584
 
4590
- setTimeout(function() {
4591
- window.location = options.href;
4592
- }, 0);
4593
- };
4594
4585
 
4595
4586
  /**
4596
- * FormTracker Object
4587
+ * RecorderManager: manages session recording initialization, lifecycle and state
4597
4588
  * @constructor
4598
- * @extends DomTracker
4599
4589
  */
4600
- var FormTracker = function() {
4601
- this.override_event = 'submit';
4590
+ var RecorderManager = function(initOptions) {
4591
+ // TODO - Passing in mixpanel instance as it is still needed for recorder creation
4592
+ // but ideally we should be able to remove this dependency.
4593
+ this.mixpanelInstance = initOptions.mixpanelInstance;
4594
+
4595
+ this.getMpConfig = initOptions.getConfigFunc;
4596
+ this.getTabId = initOptions.getTabIdFunc;
4597
+ this.reportError = initOptions.reportErrorFunc;
4598
+ this.getDistinctId = initOptions.getDistinctIdFunc;
4599
+ this.loadExtraBundle = initOptions.loadExtraBundle;
4600
+ this.recorderSrc = initOptions.recorderSrc;
4601
+ this.targetingSrc = initOptions.targetingSrc;
4602
+ this.libBasePath = initOptions.libBasePath;
4603
+
4604
+ this._recorder = null;
4602
4605
  };
4603
- _.inherit(FormTracker, DomTracker);
4606
+
4607
+ RecorderManager.prototype.shouldLoadRecorder = function() {
4608
+ if (this.getMpConfig('disable_persistence')) {
4609
+ console.log('Load recorder check skipped due to disable_persistence config');
4610
+ return PromisePolyfill.resolve(false);
4611
+ }
4612
+
4613
+ var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
4614
+ var tab_id = this.getTabId();
4615
+ return recording_registry_idb.init()
4616
+ .then(function () {
4617
+ return recording_registry_idb.getAll();
4618
+ })
4619
+ .then(function (recordings) {
4620
+ for (var i = 0; i < recordings.length; i++) {
4621
+ // if there are expired recordings in the registry, we should load the recorder to flush them
4622
+ // if there's a recording for this tab id, we should load the recorder to continue the recording
4623
+ if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
4624
+ return true;
4625
+ }
4626
+ }
4627
+ return false;
4628
+ })
4629
+ .catch(_.bind(function (err) {
4630
+ this.reportError('Error checking recording registry', err);
4631
+ return false;
4632
+ }, this));
4633
+ };
4634
+
4635
+ RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
4636
+ if (!win['MutationObserver']) {
4637
+ console.critical('Browser does not support MutationObserver; skipping session recording');
4638
+ return PromisePolyfill.resolve();
4639
+ }
4640
+
4641
+ var loadRecorder = _.bind(function(startNewIfInactive) {
4642
+ return new PromisePolyfill(_.bind(function(resolve) {
4643
+ var handleLoadedRecorder = safewrap(_.bind(function() {
4644
+ this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
4645
+ this._recorder['resumeRecording'](startNewIfInactive);
4646
+ resolve();
4647
+ }, this));
4648
+
4649
+ if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
4650
+ var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
4651
+ this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
4652
+ } else {
4653
+ handleLoadedRecorder();
4654
+ }
4655
+ }, this));
4656
+ }, this);
4657
+
4658
+ /**
4659
+ * If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
4660
+ * Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
4661
+ */
4662
+ var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
4663
+ var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
4664
+ if (force_start || is_sampled) {
4665
+ return loadRecorder(true);
4666
+ } else {
4667
+ return this.shouldLoadRecorder()
4668
+ .then(_.bind(function (shouldLoad) {
4669
+ if (shouldLoad) {
4670
+ return loadRecorder(false);
4671
+ }
4672
+ return PromisePolyfill.resolve();
4673
+ }, this));
4674
+ }
4675
+ };
4676
+
4677
+ RecorderManager.prototype.isRecording = function() {
4678
+ // Safety check: ensure isRecording method exists (older CDN builds may not have it)
4679
+ if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
4680
+ return false;
4681
+ }
4682
+ try {
4683
+ return this._recorder['isRecording']();
4684
+ } catch (e) {
4685
+ this.reportError('Error checking if recording is active', e);
4686
+ return false;
4687
+ }
4688
+ };
4689
+
4690
+ RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
4691
+ var isRecording = this.isRecording();
4692
+ var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
4693
+
4694
+ if (!isRecording && recordingTriggerEvents) {
4695
+ var trigger = recordingTriggerEvents[event_name];
4696
+ if (trigger && typeof trigger['percentage'] === 'number') {
4697
+ var newRate = trigger['percentage'];
4698
+ var propertyFilters = trigger['property_filters'];
4699
+ if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
4700
+ var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
4701
+ getTargetingPromise(this.loadExtraBundle, targetingSrc)
4702
+ .then(function(targeting) {
4703
+ try {
4704
+ var result = targeting['eventMatchesCriteria'](
4705
+ event_name,
4706
+ properties,
4707
+ {
4708
+ 'event_name': event_name,
4709
+ 'property_filters': propertyFilters
4710
+ }
4711
+ );
4712
+ if (result['matches']) {
4713
+ this.checkAndStartSessionRecording(false, newRate);
4714
+ }
4715
+ } catch (err) {
4716
+ console.critical('Could not parse recording event trigger properties logic:', err);
4717
+ }
4718
+ }.bind(this)).catch(function(err) {
4719
+ console.critical('Failed to load targeting library:', err);
4720
+ });
4721
+ } else {
4722
+ this.checkAndStartSessionRecording(false, newRate);
4723
+ }
4724
+ }
4725
+ }
4726
+ };
4727
+
4728
+ RecorderManager.prototype.stopSessionRecording = function() {
4729
+ if (this._recorder) {
4730
+ return this._recorder['stopRecording']();
4731
+ }
4732
+ return PromisePolyfill.resolve();
4733
+ };
4734
+
4735
+ RecorderManager.prototype.pauseSessionRecording = function() {
4736
+ if (this._recorder) {
4737
+ return this._recorder['pauseRecording']();
4738
+ }
4739
+ return PromisePolyfill.resolve();
4740
+ };
4741
+
4742
+ RecorderManager.prototype.resumeSessionRecording = function() {
4743
+ if (this._recorder) {
4744
+ return this._recorder['resumeRecording']();
4745
+ }
4746
+ return PromisePolyfill.resolve();
4747
+ };
4748
+
4749
+ RecorderManager.prototype.isRecordingHeatmapData = function() {
4750
+ return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
4751
+ };
4752
+
4753
+ RecorderManager.prototype.getSessionRecordingProperties = function() {
4754
+ var props = {};
4755
+ var replay_id = this.getSessionReplayId();
4756
+ if (replay_id) {
4757
+ props['$mp_replay_id'] = replay_id;
4758
+ }
4759
+ return props;
4760
+ };
4761
+
4762
+ RecorderManager.prototype.getSessionReplayUrl = function() {
4763
+ var replay_url = null;
4764
+ var replay_id = this.getSessionReplayId();
4765
+ if (replay_id) {
4766
+ var query_params = _.HTTPBuildQuery({
4767
+ 'replay_id': replay_id,
4768
+ 'distinct_id': this.getDistinctId(),
4769
+ 'token': this.getMpConfig('token')
4770
+ });
4771
+ replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
4772
+ }
4773
+ return replay_url;
4774
+ };
4775
+
4776
+ RecorderManager.prototype.getSessionReplayId = function() {
4777
+ var replay_id = null;
4778
+ if (this._recorder) {
4779
+ replay_id = this._recorder['replayId'];
4780
+ }
4781
+ return replay_id || null;
4782
+ };
4783
+
4784
+ // "private" public method to reach into the recorder in test cases
4785
+ RecorderManager.prototype.getRecorder = function() {
4786
+ return this._recorder;
4787
+ };
4788
+
4789
+ safewrapClass(RecorderManager);
4790
+
4791
+ /* eslint camelcase: "off" */
4792
+
4793
+
4794
+ /**
4795
+ * DomTracker Object
4796
+ * @constructor
4797
+ */
4798
+ var DomTracker = function() {};
4799
+
4800
+
4801
+ // interface
4802
+ DomTracker.prototype.create_properties = function() {};
4803
+ DomTracker.prototype.event_handler = function() {};
4804
+ DomTracker.prototype.after_track_handler = function() {};
4805
+
4806
+ DomTracker.prototype.init = function(mixpanel_instance) {
4807
+ this.mp = mixpanel_instance;
4808
+ return this;
4809
+ };
4810
+
4811
+ /**
4812
+ * @param {Object|string} query
4813
+ * @param {string} event_name
4814
+ * @param {Object=} properties
4815
+ * @param {function=} user_callback
4816
+ */
4817
+ DomTracker.prototype.track = function(query, event_name, properties, user_callback) {
4818
+ var that = this;
4819
+ var elements = _.dom_query(query);
4820
+
4821
+ if (elements.length === 0) {
4822
+ console.error('The DOM query (' + query + ') returned 0 elements');
4823
+ return;
4824
+ }
4825
+
4826
+ _.each(elements, function(element) {
4827
+ _.register_event(element, this.override_event, function(e) {
4828
+ var options = {};
4829
+ var props = that.create_properties(properties, this);
4830
+ var timeout = that.mp.get_config('track_links_timeout');
4831
+
4832
+ that.event_handler(e, this, options);
4833
+
4834
+ // in case the mixpanel servers don't get back to us in time
4835
+ window.setTimeout(that.track_callback(user_callback, props, options, true), timeout);
4836
+
4837
+ // fire the tracking event
4838
+ that.mp.track(event_name, props, that.track_callback(user_callback, props, options));
4839
+ });
4840
+ }, this);
4841
+
4842
+ return true;
4843
+ };
4844
+
4845
+ /**
4846
+ * @param {function} user_callback
4847
+ * @param {Object} props
4848
+ * @param {boolean=} timeout_occured
4849
+ */
4850
+ DomTracker.prototype.track_callback = function(user_callback, props, options, timeout_occured) {
4851
+ timeout_occured = timeout_occured || false;
4852
+ var that = this;
4853
+
4854
+ return function() {
4855
+ // options is referenced from both callbacks, so we can have
4856
+ // a 'lock' of sorts to ensure only one fires
4857
+ if (options.callback_fired) { return; }
4858
+ options.callback_fired = true;
4859
+
4860
+ if (user_callback && user_callback(timeout_occured, props) === false) {
4861
+ // user can prevent the default functionality by
4862
+ // returning false from their callback
4863
+ return;
4864
+ }
4865
+
4866
+ that.after_track_handler(props, options, timeout_occured);
4867
+ };
4868
+ };
4869
+
4870
+ DomTracker.prototype.create_properties = function(properties, element) {
4871
+ var props;
4872
+
4873
+ if (typeof(properties) === 'function') {
4874
+ props = properties(element);
4875
+ } else {
4876
+ props = _.extend({}, properties);
4877
+ }
4878
+
4879
+ return props;
4880
+ };
4881
+
4882
+ /**
4883
+ * LinkTracker Object
4884
+ * @constructor
4885
+ * @extends DomTracker
4886
+ */
4887
+ var LinkTracker = function() {
4888
+ this.override_event = 'click';
4889
+ };
4890
+ _.inherit(LinkTracker, DomTracker);
4891
+
4892
+ LinkTracker.prototype.create_properties = function(properties, element) {
4893
+ var props = LinkTracker.superclass.create_properties.apply(this, arguments);
4894
+
4895
+ if (element.href) { props['url'] = element.href; }
4896
+
4897
+ return props;
4898
+ };
4899
+
4900
+ LinkTracker.prototype.event_handler = function(evt, element, options) {
4901
+ options.new_tab = (
4902
+ evt.which === 2 ||
4903
+ evt.metaKey ||
4904
+ evt.ctrlKey ||
4905
+ element.target === '_blank'
4906
+ );
4907
+ options.href = element.href;
4908
+
4909
+ if (!options.new_tab) {
4910
+ evt.preventDefault();
4911
+ }
4912
+ };
4913
+
4914
+ LinkTracker.prototype.after_track_handler = function(props, options) {
4915
+ if (options.new_tab) { return; }
4916
+
4917
+ setTimeout(function() {
4918
+ window.location = options.href;
4919
+ }, 0);
4920
+ };
4921
+
4922
+ /**
4923
+ * FormTracker Object
4924
+ * @constructor
4925
+ * @extends DomTracker
4926
+ */
4927
+ var FormTracker = function() {
4928
+ this.override_event = 'submit';
4929
+ };
4930
+ _.inherit(FormTracker, DomTracker);
4604
4931
 
4605
4932
  FormTracker.prototype.event_handler = function(evt, element, options) {
4606
4933
  options.element = element;
@@ -6982,133 +7309,6 @@
6982
7309
  return timestamp;
6983
7310
  };
6984
7311
 
6985
- var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
6986
-
6987
- var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
6988
- var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
6989
-
6990
- // note: increment the version number when adding new object stores
6991
- var DB_VERSION = 1;
6992
- var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
6993
-
6994
- /**
6995
- * @type {import('./wrapper').StorageWrapper}
6996
- */
6997
- var IDBStorageWrapper = function (storeName) {
6998
- /**
6999
- * @type {Promise<IDBDatabase>|null}
7000
- */
7001
- this.dbPromise = null;
7002
- this.storeName = storeName;
7003
- };
7004
-
7005
- IDBStorageWrapper.prototype._openDb = function () {
7006
- return new PromisePolyfill(function (resolve, reject) {
7007
- var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
7008
- openRequest['onerror'] = function () {
7009
- reject(openRequest.error);
7010
- };
7011
-
7012
- openRequest['onsuccess'] = function () {
7013
- resolve(openRequest.result);
7014
- };
7015
-
7016
- openRequest['onupgradeneeded'] = function (ev) {
7017
- var db = ev.target.result;
7018
-
7019
- OBJECT_STORES.forEach(function (storeName) {
7020
- db.createObjectStore(storeName);
7021
- });
7022
- };
7023
- });
7024
- };
7025
-
7026
- IDBStorageWrapper.prototype.init = function () {
7027
- if (!win.indexedDB) {
7028
- return PromisePolyfill.reject('indexedDB is not supported in this browser');
7029
- }
7030
-
7031
- if (!this.dbPromise) {
7032
- this.dbPromise = this._openDb();
7033
- }
7034
-
7035
- return this.dbPromise
7036
- .then(function (dbOrError) {
7037
- if (dbOrError instanceof win['IDBDatabase']) {
7038
- return PromisePolyfill.resolve();
7039
- } else {
7040
- return PromisePolyfill.reject(dbOrError);
7041
- }
7042
- });
7043
- };
7044
-
7045
- IDBStorageWrapper.prototype.isInitialized = function () {
7046
- return !!this.dbPromise;
7047
- };
7048
-
7049
- /**
7050
- * @param {IDBTransactionMode} mode
7051
- * @param {function(IDBObjectStore): void} storeCb
7052
- */
7053
- IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
7054
- var storeName = this.storeName;
7055
- var doTransaction = function (db) {
7056
- return new PromisePolyfill(function (resolve, reject) {
7057
- var transaction = db.transaction(storeName, mode);
7058
- transaction.oncomplete = function () {
7059
- resolve(transaction);
7060
- };
7061
- transaction.onabort = transaction.onerror = function () {
7062
- reject(transaction.error);
7063
- };
7064
-
7065
- storeCb(transaction.objectStore(storeName));
7066
- });
7067
- };
7068
-
7069
- return this.dbPromise
7070
- .then(doTransaction)
7071
- .catch(function (err) {
7072
- if (err && err['name'] === 'InvalidStateError') {
7073
- // try reopening the DB if the connection is closed
7074
- this.dbPromise = this._openDb();
7075
- return this.dbPromise.then(doTransaction);
7076
- } else {
7077
- return PromisePolyfill.reject(err);
7078
- }
7079
- }.bind(this));
7080
- };
7081
-
7082
- IDBStorageWrapper.prototype.setItem = function (key, value) {
7083
- return this.makeTransaction('readwrite', function (objectStore) {
7084
- objectStore.put(value, key);
7085
- });
7086
- };
7087
-
7088
- IDBStorageWrapper.prototype.getItem = function (key) {
7089
- var req;
7090
- return this.makeTransaction('readonly', function (objectStore) {
7091
- req = objectStore.get(key);
7092
- }).then(function () {
7093
- return req.result;
7094
- });
7095
- };
7096
-
7097
- IDBStorageWrapper.prototype.removeItem = function (key) {
7098
- return this.makeTransaction('readwrite', function (objectStore) {
7099
- objectStore.delete(key);
7100
- });
7101
- };
7102
-
7103
- IDBStorageWrapper.prototype.getAll = function () {
7104
- var req;
7105
- return this.makeTransaction('readonly', function (objectStore) {
7106
- req = objectStore.getAll();
7107
- }).then(function () {
7108
- return req.result;
7109
- });
7110
- };
7111
-
7112
7312
  /* eslint camelcase: "off" */
7113
7313
 
7114
7314
  /*
@@ -7243,13 +7443,17 @@
7243
7443
  'record_collect_fonts': false,
7244
7444
  'record_console': true,
7245
7445
  'record_heatmap_data': false,
7446
+ 'recording_event_triggers': {},
7246
7447
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
7247
7448
  'record_mask_inputs': true,
7248
7449
  'record_max_ms': MAX_RECORDING_MS,
7249
7450
  'record_min_ms': 0,
7451
+ 'record_network': false,
7452
+ 'record_network_options': {},
7250
7453
  'record_sessions_percent': 0,
7251
- 'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js',
7252
- 'targeting_src': 'https://cdn.mxpnl.com/libs/mixpanel-targeting.min.js',
7454
+ 'recorder_src': null,
7455
+ 'targeting_src': null,
7456
+ 'lib_base_path': 'https://cdn.mxpnl.com/libs/',
7253
7457
  'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
7254
7458
  };
7255
7459
 
@@ -7403,6 +7607,19 @@
7403
7607
  'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
7404
7608
  }));
7405
7609
 
7610
+ this.recorderManager = new RecorderManager({
7611
+ mixpanelInstance: this,
7612
+ getConfigFunc: _.bind(this.get_config, this),
7613
+ setConfigFunc: _.bind(this.set_config, this),
7614
+ getTabIdFunc: _.bind(this.get_tab_id, this),
7615
+ reportErrorFunc: _.bind(this.report_error, this),
7616
+ getDistinctIdFunc: _.bind(this.get_distinct_id, this),
7617
+ recorderSrc: this.get_config('recorder_src'),
7618
+ targetingSrc: this.get_config('targeting_src'),
7619
+ libBasePath: this.get_config('lib_base_path'),
7620
+ loadExtraBundle: load_extra_bundle
7621
+ });
7622
+
7406
7623
  this['_jsc'] = NOOP_FUNC;
7407
7624
 
7408
7625
  this.__dom_loaded_queue = [];
@@ -7481,7 +7698,7 @@
7481
7698
  getPropertyFunc: _.bind(this.get_property, this),
7482
7699
  trackingFunc: _.bind(this.track, this),
7483
7700
  loadExtraBundle: load_extra_bundle,
7484
- targetingSrc: this.get_config('targeting_src')
7701
+ targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
7485
7702
  });
7486
7703
  this.flags.init();
7487
7704
  this['flags'] = this.flags;
@@ -7494,11 +7711,11 @@
7494
7711
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
7495
7712
  var mode = this.get_config('remote_settings_mode');
7496
7713
  if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
7497
- this._fetch_remote_settings(mode).then(_.bind(function() {
7498
- this._check_and_start_session_recording();
7714
+ this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
7715
+ return this._check_and_start_session_recording();
7499
7716
  }, this));
7500
7717
  } else {
7501
- this._check_and_start_session_recording();
7718
+ this.__session_recording_init_promise = this._check_and_start_session_recording();
7502
7719
  }
7503
7720
  };
7504
7721
 
@@ -7542,132 +7759,50 @@
7542
7759
  return this.tab_id || null;
7543
7760
  };
7544
7761
 
7545
- MixpanelLib.prototype._should_load_recorder = function () {
7546
- if (this.get_config('disable_persistence')) {
7547
- console.log('Load recorder check skipped due to disable_persistence config');
7548
- return Promise.resolve(false);
7549
- }
7550
-
7551
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
7552
- var tab_id = this.get_tab_id();
7553
- return recording_registry_idb.init()
7554
- .then(function () {
7555
- return recording_registry_idb.getAll();
7556
- })
7557
- .then(function (recordings) {
7558
- for (var i = 0; i < recordings.length; i++) {
7559
- // if there are expired recordings in the registry, we should load the recorder to flush them
7560
- // if there's a recording for this tab id, we should load the recorder to continue the recording
7561
- if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
7562
- return true;
7563
- }
7564
- }
7565
- return false;
7566
- })
7567
- .catch(_.bind(function (err) {
7568
- this.report_error('Error checking recording registry', err);
7569
- }, this));
7570
- };
7571
-
7572
7762
  MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
7573
- if (!win['MutationObserver']) {
7574
- console.critical('Browser does not support MutationObserver; skipping session recording');
7575
- return;
7576
- }
7577
-
7578
- var loadRecorder = _.bind(function(startNewIfInactive) {
7579
- var handleLoadedRecorder = _.bind(function() {
7580
- this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
7581
- this._recorder['resumeRecording'](startNewIfInactive);
7582
- }, this);
7583
-
7584
- if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
7585
- load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
7586
- } else {
7587
- handleLoadedRecorder();
7588
- }
7589
- }, this);
7590
-
7591
- /**
7592
- * If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
7593
- * Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
7594
- */
7595
- var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
7596
- if (force_start || is_sampled) {
7597
- loadRecorder(true);
7598
- } else {
7599
- this._should_load_recorder()
7600
- .then(function (shouldLoad) {
7601
- if (shouldLoad) {
7602
- loadRecorder(false);
7603
- }
7604
- });
7605
- }
7763
+ return this.recorderManager.checkAndStartSessionRecording(force_start);
7606
7764
  });
7607
7765
 
7766
+ MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
7767
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
7768
+ };
7769
+
7608
7770
  MixpanelLib.prototype.start_session_recording = function () {
7609
- this._check_and_start_session_recording(true);
7771
+ return this._check_and_start_session_recording(true);
7610
7772
  };
7611
7773
 
7612
7774
  MixpanelLib.prototype.stop_session_recording = function () {
7613
- if (this._recorder) {
7614
- return this._recorder['stopRecording']();
7615
- }
7616
- return Promise.resolve();
7775
+ return this.recorderManager.stopSessionRecording();
7617
7776
  };
7618
7777
 
7619
7778
  MixpanelLib.prototype.pause_session_recording = function () {
7620
- if (this._recorder) {
7621
- return this._recorder['pauseRecording']();
7622
- }
7623
- return Promise.resolve();
7779
+ return this.recorderManager.pauseSessionRecording();
7624
7780
  };
7625
7781
 
7626
7782
  MixpanelLib.prototype.resume_session_recording = function () {
7627
- if (this._recorder) {
7628
- return this._recorder['resumeRecording']();
7629
- }
7630
- return Promise.resolve();
7783
+ return this.recorderManager.resumeSessionRecording();
7631
7784
  };
7632
7785
 
7633
7786
  MixpanelLib.prototype.is_recording_heatmap_data = function () {
7634
- return this._get_session_replay_id() && this.get_config('record_heatmap_data');
7787
+ return this.recorderManager.isRecordingHeatmapData();
7635
7788
  };
7636
7789
 
7637
7790
  MixpanelLib.prototype.get_session_recording_properties = function () {
7638
- var props = {};
7639
- var replay_id = this._get_session_replay_id();
7640
- if (replay_id) {
7641
- props['$mp_replay_id'] = replay_id;
7642
- }
7643
- return props;
7791
+ return this.recorderManager.getSessionRecordingProperties();
7644
7792
  };
7645
7793
 
7646
7794
  MixpanelLib.prototype.get_session_replay_url = function () {
7647
- var replay_url = null;
7648
- var replay_id = this._get_session_replay_id();
7649
- if (replay_id) {
7650
- var query_params = _.HTTPBuildQuery({
7651
- 'replay_id': replay_id,
7652
- 'distinct_id': this.get_distinct_id(),
7653
- 'token': this.get_config('token')
7654
- });
7655
- replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
7656
- }
7657
- return replay_url;
7658
- };
7659
-
7660
- MixpanelLib.prototype._get_session_replay_id = function () {
7661
- var replay_id = null;
7662
- if (this._recorder) {
7663
- replay_id = this._recorder['replayId'];
7664
- }
7665
- return replay_id || null;
7795
+ return this.recorderManager.getSessionReplayUrl();
7666
7796
  };
7667
7797
 
7668
7798
  // "private" public method to reach into the recorder in test cases
7669
7799
  MixpanelLib.prototype.__get_recorder = function () {
7670
- return this._recorder;
7800
+ return this.recorderManager.getRecorder();
7801
+ };
7802
+
7803
+ // "private" public method to get session recording init promise in test cases
7804
+ MixpanelLib.prototype.__get_recording_init_promise = function () {
7805
+ return this.__session_recording_init_promise;
7671
7806
  };
7672
7807
 
7673
7808
  // Private methods
@@ -7925,6 +8060,7 @@
7925
8060
  };
7926
8061
 
7927
8062
  MixpanelLib.prototype._fetch_remote_settings = function(mode) {
8063
+ var self = this;
7928
8064
  var disableRecordingIfStrict = function() {
7929
8065
  if (mode === 'strict') {
7930
8066
  self.set_config({'record_sessions_percent': 0});
@@ -7945,7 +8081,6 @@
7945
8081
  };
7946
8082
  var query_string = _.HTTPBuildQuery(request_params);
7947
8083
  var full_url = settings_endpoint + '?' + query_string;
7948
- var self = this;
7949
8084
 
7950
8085
  var abortController = new AbortController();
7951
8086
  var timeout_id = setTimeout(function() {
@@ -8137,6 +8272,34 @@
8137
8272
  this._execute_array([item]);
8138
8273
  };
8139
8274
 
8275
+ /**
8276
+ * Enables events on the Mixpanel object. If passed no arguments,
8277
+ * this function enable tracking of all events. If passed an
8278
+ * array of event names, those events will be enabled, but other
8279
+ * existing disabled events will continue to be not tracked.
8280
+ *
8281
+ * @param {Array} [events] An array of event names to enable
8282
+ */
8283
+ MixpanelLib.prototype.enable = function(events) {
8284
+ var keys, new_disabled_events, i, j;
8285
+
8286
+ if (typeof(events) === 'undefined') {
8287
+ this._flags.disable_all_events = false;
8288
+ } else {
8289
+ keys = {};
8290
+ new_disabled_events = [];
8291
+ for (i = 0; i < events.length; i++) {
8292
+ keys[events[i]] = true;
8293
+ }
8294
+ for (j = 0; j < this.__disabled_events.length; j++) {
8295
+ if (!keys[this.__disabled_events[j]]) {
8296
+ new_disabled_events.push(this.__disabled_events[j]);
8297
+ }
8298
+ }
8299
+ this.__disabled_events = new_disabled_events;
8300
+ }
8301
+ };
8302
+
8140
8303
  /**
8141
8304
  * Disable events on the Mixpanel object. If passed no arguments,
8142
8305
  * this function disables tracking of any event. If passed an
@@ -8310,6 +8473,8 @@
8310
8473
  this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
8311
8474
  }
8312
8475
 
8476
+ this._start_recording_on_event(event_name, properties);
8477
+
8313
8478
  var data = {
8314
8479
  'event': event_name,
8315
8480
  'properties': properties
@@ -9518,6 +9683,7 @@
9518
9683
  // MixpanelLib Exports
9519
9684
  MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
9520
9685
  MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
9686
+ MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
9521
9687
  MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
9522
9688
  MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
9523
9689
  MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
@@ -9561,6 +9727,7 @@
9561
9727
 
9562
9728
  // Exports intended only for testing
9563
9729
  MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
9730
+ MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
9564
9731
 
9565
9732
  // MixpanelPersistence Exports
9566
9733
  MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;