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