mixpanel-browser 2.74.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 (61) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.github/workflows/integration-tests.yml +2 -2
  3. package/.github/workflows/unit-tests.yml +3 -3
  4. package/CHANGELOG.md +15 -0
  5. package/README.md +2 -2
  6. package/build.sh +10 -8
  7. package/dist/async-modules/mixpanel-recorder-bIS4LMGd.js +23595 -0
  8. package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js +2 -0
  9. package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js.map +1 -0
  10. package/dist/async-modules/mixpanel-targeting-BcAPS-Mz.js +2520 -0
  11. package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js +2 -0
  12. package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js.map +1 -0
  13. package/dist/mixpanel-core.cjs.d.ts +68 -0
  14. package/dist/mixpanel-core.cjs.js +802 -337
  15. package/dist/mixpanel-recorder.js +828 -40
  16. package/dist/mixpanel-recorder.min.js +1 -1
  17. package/dist/mixpanel-recorder.min.js.map +1 -1
  18. package/dist/mixpanel-targeting.js +2520 -0
  19. package/dist/mixpanel-targeting.min.js +2 -0
  20. package/dist/mixpanel-targeting.min.js.map +1 -0
  21. package/dist/mixpanel-with-async-modules.cjs.d.ts +590 -0
  22. package/dist/mixpanel-with-async-modules.cjs.js +9867 -0
  23. package/dist/mixpanel-with-async-recorder.cjs.d.ts +68 -0
  24. package/dist/mixpanel-with-async-recorder.cjs.js +802 -337
  25. package/dist/mixpanel-with-recorder.d.ts +68 -0
  26. package/dist/mixpanel-with-recorder.js +1591 -343
  27. package/dist/mixpanel-with-recorder.min.d.ts +68 -0
  28. package/dist/mixpanel-with-recorder.min.js +1 -1
  29. package/dist/mixpanel.amd.d.ts +68 -0
  30. package/dist/mixpanel.amd.js +2124 -345
  31. package/dist/mixpanel.cjs.d.ts +68 -0
  32. package/dist/mixpanel.cjs.js +2124 -345
  33. package/dist/mixpanel.globals.js +802 -337
  34. package/dist/mixpanel.min.js +185 -175
  35. package/dist/mixpanel.module.d.ts +68 -0
  36. package/dist/mixpanel.module.js +2124 -345
  37. package/dist/mixpanel.umd.d.ts +68 -0
  38. package/dist/mixpanel.umd.js +2124 -345
  39. package/dist/rrweb-bundled.js +119 -5
  40. package/dist/rrweb-compiled.js +116 -5
  41. package/logo.svg +5 -0
  42. package/package.json +5 -3
  43. package/rollup.config.mjs +189 -40
  44. package/src/autocapture/index.js +10 -27
  45. package/src/config.js +9 -3
  46. package/src/flags/index.js +269 -9
  47. package/src/index.d.ts +68 -0
  48. package/src/loaders/loader-module.js +1 -0
  49. package/src/mixpanel-core.js +83 -109
  50. package/src/recorder/index.js +2 -1
  51. package/src/recorder/recorder.js +5 -1
  52. package/src/recorder/rrweb-network-plugin.js +649 -0
  53. package/src/recorder/session-recording.js +31 -11
  54. package/src/recorder-manager.js +216 -0
  55. package/src/request-batcher.js +1 -1
  56. package/src/targeting/event-matcher.js +42 -0
  57. package/src/targeting/index.js +11 -0
  58. package/src/targeting/loader.js +36 -0
  59. package/src/utils.js +14 -9
  60. package/testServer.js +55 -0
  61. /package/src/loaders/{loader-module-with-async-recorder.js → loader-module-with-async-modules.js} +0 -0
@@ -1,10 +1,10 @@
1
1
  /* eslint camelcase: "off" */
2
- import Config from './config';
2
+ import {Config, TARGETING_FILENAME} from './config';
3
3
  import { MAX_RECORDING_MS, _, console, userAgent, document, navigator, slice, NOOP_FUNC, JSONStringify } from './utils';
4
- import { isRecordingExpired } from './recorder/utils';
5
4
  import { window } from './window';
6
5
  import { Autocapture } from './autocapture';
7
6
  import { FeatureFlagManager } from './flags';
7
+ import { RecorderManager } from './recorder-manager';
8
8
  import { FormTracker, LinkTracker } from './dom-trackers';
9
9
  import { RequestBatcher } from './request-batcher';
10
10
  import { MixpanelGroup } from './mixpanel-group';
@@ -22,7 +22,6 @@ import {
22
22
  clearOptInOut,
23
23
  addOptOutCheckMixpanelLib
24
24
  } from './gdpr-utils';
25
- import { IDBStorageWrapper, RECORDING_REGISTRY_STORE_NAME } from './storage/indexed-db';
26
25
 
27
26
  /*
28
27
  * Mixpanel JS Library
@@ -156,12 +155,17 @@ var DEFAULT_CONFIG = {
156
155
  'record_collect_fonts': false,
157
156
  'record_console': true,
158
157
  'record_heatmap_data': false,
158
+ 'recording_event_triggers': {},
159
159
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
160
160
  'record_mask_inputs': true,
161
161
  'record_max_ms': MAX_RECORDING_MS,
162
162
  'record_min_ms': 0,
163
+ 'record_network': false,
164
+ 'record_network_options': {},
163
165
  'record_sessions_percent': 0,
164
- 'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js',
166
+ 'recorder_src': null,
167
+ 'targeting_src': null,
168
+ 'lib_base_path': 'https://cdn.mxpnl.com/libs/',
165
169
  'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
166
170
  };
167
171
 
@@ -315,6 +319,19 @@ MixpanelLib.prototype._init = function(token, config, name) {
315
319
  'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
316
320
  }));
317
321
 
322
+ this.recorderManager = new RecorderManager({
323
+ mixpanelInstance: this,
324
+ getConfigFunc: _.bind(this.get_config, this),
325
+ setConfigFunc: _.bind(this.set_config, this),
326
+ getTabIdFunc: _.bind(this.get_tab_id, this),
327
+ reportErrorFunc: _.bind(this.report_error, this),
328
+ getDistinctIdFunc: _.bind(this.get_distinct_id, this),
329
+ recorderSrc: this.get_config('recorder_src'),
330
+ targetingSrc: this.get_config('targeting_src'),
331
+ libBasePath: this.get_config('lib_base_path'),
332
+ loadExtraBundle: load_extra_bundle
333
+ });
334
+
318
335
  this['_jsc'] = NOOP_FUNC;
319
336
 
320
337
  this.__dom_loaded_queue = [];
@@ -391,7 +408,9 @@ MixpanelLib.prototype._init = function(token, config, name) {
391
408
  getConfigFunc: _.bind(this.get_config, this),
392
409
  setConfigFunc: _.bind(this.set_config, this),
393
410
  getPropertyFunc: _.bind(this.get_property, this),
394
- trackingFunc: _.bind(this.track, this)
411
+ trackingFunc: _.bind(this.track, this),
412
+ loadExtraBundle: load_extra_bundle,
413
+ targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
395
414
  });
396
415
  this.flags.init();
397
416
  this['flags'] = this.flags;
@@ -404,11 +423,11 @@ MixpanelLib.prototype._init = function(token, config, name) {
404
423
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
405
424
  var mode = this.get_config('remote_settings_mode');
406
425
  if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
407
- this._fetch_remote_settings(mode).then(_.bind(function() {
408
- this._check_and_start_session_recording();
426
+ this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
427
+ return this._check_and_start_session_recording();
409
428
  }, this));
410
429
  } else {
411
- this._check_and_start_session_recording();
430
+ this.__session_recording_init_promise = this._check_and_start_session_recording();
412
431
  }
413
432
  };
414
433
 
@@ -452,132 +471,50 @@ MixpanelLib.prototype.get_tab_id = function () {
452
471
  return this.tab_id || null;
453
472
  };
454
473
 
455
- MixpanelLib.prototype._should_load_recorder = function () {
456
- if (this.get_config('disable_persistence')) {
457
- console.log('Load recorder check skipped due to disable_persistence config');
458
- return Promise.resolve(false);
459
- }
460
-
461
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
462
- var tab_id = this.get_tab_id();
463
- return recording_registry_idb.init()
464
- .then(function () {
465
- return recording_registry_idb.getAll();
466
- })
467
- .then(function (recordings) {
468
- for (var i = 0; i < recordings.length; i++) {
469
- // if there are expired recordings in the registry, we should load the recorder to flush them
470
- // if there's a recording for this tab id, we should load the recorder to continue the recording
471
- if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
472
- return true;
473
- }
474
- }
475
- return false;
476
- })
477
- .catch(_.bind(function (err) {
478
- this.report_error('Error checking recording registry', err);
479
- }, this));
480
- };
481
-
482
474
  MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
483
- if (!window['MutationObserver']) {
484
- console.critical('Browser does not support MutationObserver; skipping session recording');
485
- return;
486
- }
487
-
488
- var loadRecorder = _.bind(function(startNewIfInactive) {
489
- var handleLoadedRecorder = _.bind(function() {
490
- this._recorder = this._recorder || new window['__mp_recorder'](this);
491
- this._recorder['resumeRecording'](startNewIfInactive);
492
- }, this);
493
-
494
- if (_.isUndefined(window['__mp_recorder'])) {
495
- load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
496
- } else {
497
- handleLoadedRecorder();
498
- }
499
- }, this);
500
-
501
- /**
502
- * If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
503
- * 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.
504
- */
505
- var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
506
- if (force_start || is_sampled) {
507
- loadRecorder(true);
508
- } else {
509
- this._should_load_recorder()
510
- .then(function (shouldLoad) {
511
- if (shouldLoad) {
512
- loadRecorder(false);
513
- }
514
- });
515
- }
475
+ return this.recorderManager.checkAndStartSessionRecording(force_start);
516
476
  });
517
477
 
478
+ MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
479
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
480
+ };
481
+
518
482
  MixpanelLib.prototype.start_session_recording = function () {
519
- this._check_and_start_session_recording(true);
483
+ return this._check_and_start_session_recording(true);
520
484
  };
521
485
 
522
486
  MixpanelLib.prototype.stop_session_recording = function () {
523
- if (this._recorder) {
524
- return this._recorder['stopRecording']();
525
- }
526
- return Promise.resolve();
487
+ return this.recorderManager.stopSessionRecording();
527
488
  };
528
489
 
529
490
  MixpanelLib.prototype.pause_session_recording = function () {
530
- if (this._recorder) {
531
- return this._recorder['pauseRecording']();
532
- }
533
- return Promise.resolve();
491
+ return this.recorderManager.pauseSessionRecording();
534
492
  };
535
493
 
536
494
  MixpanelLib.prototype.resume_session_recording = function () {
537
- if (this._recorder) {
538
- return this._recorder['resumeRecording']();
539
- }
540
- return Promise.resolve();
495
+ return this.recorderManager.resumeSessionRecording();
541
496
  };
542
497
 
543
498
  MixpanelLib.prototype.is_recording_heatmap_data = function () {
544
- return this._get_session_replay_id() && this.get_config('record_heatmap_data');
499
+ return this.recorderManager.isRecordingHeatmapData();
545
500
  };
546
501
 
547
502
  MixpanelLib.prototype.get_session_recording_properties = function () {
548
- var props = {};
549
- var replay_id = this._get_session_replay_id();
550
- if (replay_id) {
551
- props['$mp_replay_id'] = replay_id;
552
- }
553
- return props;
503
+ return this.recorderManager.getSessionRecordingProperties();
554
504
  };
555
505
 
556
506
  MixpanelLib.prototype.get_session_replay_url = function () {
557
- var replay_url = null;
558
- var replay_id = this._get_session_replay_id();
559
- if (replay_id) {
560
- var query_params = _.HTTPBuildQuery({
561
- 'replay_id': replay_id,
562
- 'distinct_id': this.get_distinct_id(),
563
- 'token': this.get_config('token')
564
- });
565
- replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
566
- }
567
- return replay_url;
568
- };
569
-
570
- MixpanelLib.prototype._get_session_replay_id = function () {
571
- var replay_id = null;
572
- if (this._recorder) {
573
- replay_id = this._recorder['replayId'];
574
- }
575
- return replay_id || null;
507
+ return this.recorderManager.getSessionReplayUrl();
576
508
  };
577
509
 
578
510
  // "private" public method to reach into the recorder in test cases
579
511
  MixpanelLib.prototype.__get_recorder = function () {
580
- return this._recorder;
512
+ return this.recorderManager.getRecorder();
513
+ };
514
+
515
+ // "private" public method to get session recording init promise in test cases
516
+ MixpanelLib.prototype.__get_recording_init_promise = function () {
517
+ return this.__session_recording_init_promise;
581
518
  };
582
519
 
583
520
  // Private methods
@@ -835,6 +772,7 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
835
772
  };
836
773
 
837
774
  MixpanelLib.prototype._fetch_remote_settings = function(mode) {
775
+ var self = this;
838
776
  var disableRecordingIfStrict = function() {
839
777
  if (mode === 'strict') {
840
778
  self.set_config({'record_sessions_percent': 0});
@@ -855,7 +793,6 @@ MixpanelLib.prototype._fetch_remote_settings = function(mode) {
855
793
  };
856
794
  var query_string = _.HTTPBuildQuery(request_params);
857
795
  var full_url = settings_endpoint + '?' + query_string;
858
- var self = this;
859
796
 
860
797
  var abortController = new AbortController();
861
798
  var timeout_id = setTimeout(function() {
@@ -1047,6 +984,34 @@ MixpanelLib.prototype.push = function(item) {
1047
984
  this._execute_array([item]);
1048
985
  };
1049
986
 
987
+ /**
988
+ * Enables events on the Mixpanel object. If passed no arguments,
989
+ * this function enable tracking of all events. If passed an
990
+ * array of event names, those events will be enabled, but other
991
+ * existing disabled events will continue to be not tracked.
992
+ *
993
+ * @param {Array} [events] An array of event names to enable
994
+ */
995
+ MixpanelLib.prototype.enable = function(events) {
996
+ var keys, new_disabled_events, i, j;
997
+
998
+ if (typeof(events) === 'undefined') {
999
+ this._flags.disable_all_events = false;
1000
+ } else {
1001
+ keys = {};
1002
+ new_disabled_events = [];
1003
+ for (i = 0; i < events.length; i++) {
1004
+ keys[events[i]] = true;
1005
+ }
1006
+ for (j = 0; j < this.__disabled_events.length; j++) {
1007
+ if (!keys[this.__disabled_events[j]]) {
1008
+ new_disabled_events.push(this.__disabled_events[j]);
1009
+ }
1010
+ }
1011
+ this.__disabled_events = new_disabled_events;
1012
+ }
1013
+ };
1014
+
1050
1015
  /**
1051
1016
  * Disable events on the Mixpanel object. If passed no arguments,
1052
1017
  * this function disables tracking of any event. If passed an
@@ -1220,6 +1185,8 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
1220
1185
  this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
1221
1186
  }
1222
1187
 
1188
+ this._start_recording_on_event(event_name, properties);
1189
+
1223
1190
  var data = {
1224
1191
  'event': event_name,
1225
1192
  'properties': properties
@@ -1233,6 +1200,11 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
1233
1200
  send_request_options: options
1234
1201
  }, callback);
1235
1202
 
1203
+ // Check for first-time event matches
1204
+ if (this.flags && this.flags.checkFirstTimeEvents) {
1205
+ this.flags.checkFirstTimeEvents(event_name, properties);
1206
+ }
1207
+
1236
1208
  return ret;
1237
1209
  });
1238
1210
 
@@ -2423,6 +2395,7 @@ MixpanelLib.prototype.remove_hook = function(hook_name, hook_fn) {
2423
2395
  // MixpanelLib Exports
2424
2396
  MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
2425
2397
  MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
2398
+ MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
2426
2399
  MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
2427
2400
  MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
2428
2401
  MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
@@ -2466,6 +2439,7 @@ MixpanelLib.prototype['DEFAULT_API_ROUTES'] = DEFAULT_API_ROUTES
2466
2439
 
2467
2440
  // Exports intended only for testing
2468
2441
  MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
2442
+ MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
2469
2443
 
2470
2444
  // MixpanelPersistence Exports
2471
2445
  MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
@@ -1,4 +1,5 @@
1
1
  import { window } from '../window';
2
+ import { RECORDER_GLOBAL_NAME } from '../config';
2
3
  import { MixpanelRecorder } from './recorder';
3
4
 
4
- window['__mp_recorder'] = MixpanelRecorder;
5
+ window[RECORDER_GLOBAL_NAME] = MixpanelRecorder;
@@ -125,8 +125,12 @@ MixpanelRecorder.prototype.resetRecording = function () {
125
125
  this.startRecording({shouldStopBatcher: true});
126
126
  };
127
127
 
128
+ MixpanelRecorder.prototype.isRecording = function () {
129
+ return this.activeRecording && !this.activeRecording.isRrwebStopped();
130
+ };
131
+
128
132
  MixpanelRecorder.prototype.getActiveReplayId = function () {
129
- if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
133
+ if (this.isRecording()) {
130
134
  return this.activeRecording.replayId;
131
135
  } else {
132
136
  return null;