mixpanel-browser 2.71.0 → 2.72.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 (39) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.github/workflows/tests.yml +1 -0
  3. package/CHANGELOG.md +10 -0
  4. package/dist/mixpanel-core.cjs.d.ts +47 -10
  5. package/dist/mixpanel-core.cjs.js +57 -21
  6. package/dist/mixpanel-recorder.js +681 -113
  7. package/dist/mixpanel-recorder.min.js +1 -1
  8. package/dist/mixpanel-recorder.min.js.map +1 -1
  9. package/dist/mixpanel-with-async-recorder.cjs.d.ts +47 -10
  10. package/dist/mixpanel-with-async-recorder.cjs.js +57 -21
  11. package/dist/mixpanel-with-recorder.d.ts +47 -10
  12. package/dist/mixpanel-with-recorder.js +737 -133
  13. package/dist/mixpanel-with-recorder.min.d.ts +47 -10
  14. package/dist/mixpanel-with-recorder.min.js +1 -1
  15. package/dist/mixpanel.amd.d.ts +47 -10
  16. package/dist/mixpanel.amd.js +737 -133
  17. package/dist/mixpanel.cjs.d.ts +47 -10
  18. package/dist/mixpanel.cjs.js +737 -133
  19. package/dist/mixpanel.globals.js +57 -21
  20. package/dist/mixpanel.min.js +149 -149
  21. package/dist/mixpanel.module.d.ts +47 -10
  22. package/dist/mixpanel.module.js +737 -133
  23. package/dist/mixpanel.umd.d.ts +47 -10
  24. package/dist/mixpanel.umd.js +737 -133
  25. package/dist/rrweb-bundled.js +12760 -0
  26. package/dist/rrweb-compiled.js +2496 -7176
  27. package/package.json +3 -2
  28. package/rollup.config.mjs +15 -4
  29. package/src/autocapture/index.js +7 -5
  30. package/src/autocapture/rageclick.js +20 -1
  31. package/src/autocapture/shadow-dom-observer.js +3 -15
  32. package/src/autocapture/utils.js +30 -0
  33. package/src/config.js +1 -1
  34. package/src/index.d.ts +47 -10
  35. package/src/mixpanel-core.js +1 -0
  36. package/src/recorder/recorder.js +1 -1
  37. package/src/recorder/rrweb-entrypoint.js +6 -0
  38. package/src/recorder/session-recording.js +69 -12
  39. package/src/utils.js +24 -0
@@ -43,20 +43,22 @@ export interface OutTrackingOptions extends ClearOptOutInOutOptions {
43
43
  export type RageClickConfig =
44
44
  | boolean
45
45
  | {
46
- /** Distance threshold in pixels for clicks to be considered within the same area (default: 30) */
47
- threshold_px?: number;
48
- /** Time window in milliseconds for clicks to be considered rapid (default: 1000) */
49
- timeout_ms?: number;
50
- /** Number of clicks required to trigger a rage click event (default: 3) */
51
- click_count?: number;
52
- };
46
+ /** Distance threshold in pixels for clicks to be considered within the same area (default: 30) */
47
+ threshold_px?: number;
48
+ /** Time window in milliseconds for clicks to be considered rapid (default: 1000) */
49
+ timeout_ms?: number;
50
+ /** Number of clicks required to trigger a rage click event (default: 3) */
51
+ click_count?: number;
52
+ /** Whether to only track rage clicks on interactive elements like buttons, links, inputs (default: false) */
53
+ interactive_elements_only?: boolean;
54
+ };
53
55
 
54
56
  export type DeadClickConfig =
55
57
  | boolean
56
58
  | {
57
- /** Time in milliseconds to wait after a click before qualifying it as dead (default: 500) */
58
- timeout_ms?: number;
59
- };
59
+ /** Time in milliseconds to wait after a click before qualifying it as dead (default: 500) */
60
+ timeout_ms?: number;
61
+ };
60
62
 
61
63
  export interface RegisterOptions {
62
64
  persistent: boolean;
@@ -149,6 +151,10 @@ export interface AutocaptureConfig {
149
151
  block_element_callback?: (element: Element, event: Event) => boolean;
150
152
  }
151
153
 
154
+ export interface FlagsConfig {
155
+ context: Dict;
156
+ }
157
+
152
158
  export interface Config {
153
159
  api_host: string;
154
160
  api_routes: {
@@ -165,6 +171,7 @@ export interface Config {
165
171
  cookie_domain: string;
166
172
  cross_site_cookie: boolean;
167
173
  cross_subdomain_cookie: boolean;
174
+ flags: boolean | FlagsConfig;
168
175
  persistence: Persistence;
169
176
  persistence_name: string;
170
177
  cookie_name: string;
@@ -277,11 +284,41 @@ export interface Group {
277
284
  unset(prop: string, callback?: Callback): void;
278
285
  }
279
286
 
287
+ export interface FlagsVariant {
288
+ key: string;
289
+ value: any;
290
+ experiment_id?: string;
291
+ is_experiment_active?: boolean;
292
+ is_qa_tester?: boolean;
293
+ }
294
+
295
+ export interface FlagsUpdateContextOptions {
296
+ replace?: boolean;
297
+ }
298
+
299
+ export interface FlagsManager {
300
+ are_flags_ready(): boolean;
301
+ get_variant(
302
+ featureName: string,
303
+ fallback: FlagsVariant
304
+ ): Promise<FlagsVariant>;
305
+ get_variant_sync(featureName: string, fallback: FlagsVariant): FlagsVariant;
306
+ get_variant_value(featureName: string, fallbackValue: any): Promise<any>;
307
+ get_variant_value_sync(featureName: string, fallbackValue: any): any;
308
+ is_enabled(featureName: string, fallbackValue?: boolean): Promise<boolean>;
309
+ is_enabled_sync(featureName: string, fallbackValue?: boolean): boolean;
310
+ update_context(
311
+ context: Dict,
312
+ options?: FlagsUpdateContextOptions
313
+ ): Promise<void>;
314
+ }
315
+
280
316
  export interface Mixpanel {
281
317
  add_group(group_key: string, group_id: string, callback?: Callback): void;
282
318
  alias(alias: string, original?: string): void;
283
319
  clear_opt_in_out_tracking(options?: Partial<ClearOptOutInOutOptions>): void;
284
320
  disable(events?: string[]): void;
321
+ flags: FlagsManager;
285
322
  get_config(prop_name?: string): any;
286
323
  get_distinct_id(): any;
287
324
  get_group(group_key: string, group_id: string): Group;
@@ -2,7 +2,7 @@
2
2
 
3
3
  var Config = {
4
4
  DEBUG: false,
5
- LIB_VERSION: '2.71.0'
5
+ LIB_VERSION: '2.72.0'
6
6
  };
7
7
 
8
8
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
@@ -2892,20 +2892,65 @@ function isDefinitelyNonInteractive(element) {
2892
2892
  return false;
2893
2893
  }
2894
2894
 
2895
+ /**
2896
+ * Get the composed path of a click event for elements embedded in shadow DOM.
2897
+ * @param {Event} event - event to get the composed path from
2898
+ * @returns {Array} the composed path of the click event
2899
+ */
2900
+ function getClickEventComposedPath(event) {
2901
+ if ('composedPath' in event) {
2902
+ return event['composedPath']();
2903
+ }
2904
+
2905
+ return [];
2906
+ }
2907
+
2908
+ /**
2909
+ * Get the element from a click event, accounting for elements embedded in shadow DOM.
2910
+ * @param {Event} event - event to get the target from
2911
+ * @returns {Element | null} the element that was the target of the click event
2912
+ */
2913
+ function getClickEventTargetElement(event) {
2914
+ var path = getClickEventComposedPath(event);
2915
+
2916
+ if (path && path.length > 0) {
2917
+ return path[0];
2918
+ }
2919
+
2920
+ return event['target'] || event['srcElement'];
2921
+ }
2922
+
2895
2923
  /** @const */ var DEFAULT_RAGE_CLICK_THRESHOLD_PX = 30;
2896
2924
  /** @const */ var DEFAULT_RAGE_CLICK_TIMEOUT_MS = 1000;
2897
2925
  /** @const */ var DEFAULT_RAGE_CLICK_CLICK_COUNT = 4;
2926
+ /** @const */ var DEFAULT_RAGE_CLICK_INTERACTIVE_ELEMENTS_ONLY = false;
2898
2927
 
2899
2928
  function RageClickTracker() {
2900
2929
  this.clicks = [];
2901
2930
  }
2902
2931
 
2903
- RageClickTracker.prototype.isRageClick = function(x, y, options) {
2932
+ /**
2933
+ * Determines if a click event is part of a rage click sequence.
2934
+ * @param {Event} event - the original click event.
2935
+ * @param {import('../index.d.ts').RageClickConfig} options - configuration options for rage click detection.
2936
+ * @returns {boolean} - true if the click is considered a rage click, false otherwise.
2937
+ */
2938
+ RageClickTracker.prototype.isRageClick = function(event, options) {
2904
2939
  options = options || {};
2905
2940
  var thresholdPx = options['threshold_px'] || DEFAULT_RAGE_CLICK_THRESHOLD_PX;
2906
2941
  var timeoutMs = options['timeout_ms'] || DEFAULT_RAGE_CLICK_TIMEOUT_MS;
2907
2942
  var clickCount = options['click_count'] || DEFAULT_RAGE_CLICK_CLICK_COUNT;
2943
+ var interactiveElementsOnly = options['interactive_elements_only'] || DEFAULT_RAGE_CLICK_INTERACTIVE_ELEMENTS_ONLY;
2944
+
2945
+ if (interactiveElementsOnly) {
2946
+ var target = getClickEventTargetElement(event);
2947
+ if (!target || isDefinitelyNonInteractive(target)) {
2948
+ return false;
2949
+ }
2950
+ }
2951
+
2908
2952
  var timestamp = Date.now();
2953
+ var x = event['pageX'], y = event['pageY'];
2909
2954
 
2910
2955
  var lastClick = this.clicks[this.clicks.length - 1];
2911
2956
  if (
@@ -2936,28 +2981,16 @@ ShadowDOMObserver.prototype.getEventTarget = function(event) {
2936
2981
  if (!this.observedShadowRoots) {
2937
2982
  return;
2938
2983
  }
2939
- var path = this.getComposedPath(event);
2940
- if (path && path.length) {
2941
- return path[0];
2942
- }
2943
2984
 
2944
- return event['target'] || event['srcElement'];
2985
+ return getClickEventTargetElement(event);
2945
2986
  };
2946
2987
 
2947
-
2948
- ShadowDOMObserver.prototype.getComposedPath = function(event) {
2949
- if ('composedPath' in event) {
2950
- return event['composedPath']();
2951
- }
2952
-
2953
- return [];
2954
- };
2955
2988
  ShadowDOMObserver.prototype.observeFromEvent = function(event) {
2956
2989
  if (!this.observedShadowRoots) {
2957
2990
  return;
2958
2991
  }
2959
2992
 
2960
- var path = this.getComposedPath(event);
2993
+ var path = getClickEventComposedPath(event);
2961
2994
 
2962
2995
  // Check each element in path for shadow roots
2963
2996
  for (var i = 0; i < path.length; i++) {
@@ -3509,6 +3542,11 @@ Autocapture.prototype._trackPageLeave = function(ev, currentUrl, currentScrollHe
3509
3542
  // User has navigated away already ending their impression.
3510
3543
  return;
3511
3544
  }
3545
+
3546
+ if (!this.getConfig(CONFIG_TRACK_PAGE_LEAVE) && !this.mp.is_recording_heatmap_data()) {
3547
+ return;
3548
+ }
3549
+
3512
3550
  this.hasTrackedScrollSession = true;
3513
3551
  var viewportHeight = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
3514
3552
  var scrollPercentage = Math.round(Math.max(this.maxScrollViewDepth - viewportHeight, 0) / (currentScrollHeight - viewportHeight) * 100);
@@ -3528,12 +3566,9 @@ Autocapture.prototype._trackPageLeave = function(ev, currentUrl, currentScrollHe
3528
3566
  '$current_url': currentUrl || _.info.currentUrl(),
3529
3567
  '$viewportHeight': viewportHeight, // This is the fold line
3530
3568
  '$viewportWidth': Math.max(document$1.documentElement.clientWidth, win.innerWidth || 0),
3569
+ '$captured_for_heatmap': this.mp.is_recording_heatmap_data()
3531
3570
  }, DEFAULT_PROPS);
3532
3571
 
3533
- if (this.mp.is_recording_heatmap_data() && !this.getConfig(CONFIG_TRACK_PAGE_LEAVE)) {
3534
- props['$captured_for_heatmap'] = true;
3535
- }
3536
-
3537
3572
  // Send with beacon transport to ensure event is sent before unload
3538
3573
  this.mp.track(MP_EV_PAGE_LEAVE, props, {transport: 'sendBeacon'});
3539
3574
  };
@@ -3707,7 +3742,7 @@ Autocapture.prototype.initRageClickTracking = function() {
3707
3742
  return;
3708
3743
  }
3709
3744
 
3710
- if (this._rageClickTracker.isRageClick(ev['pageX'], ev['pageY'], currentRageClickConfig)) {
3745
+ if (this._rageClickTracker.isRageClick(ev, currentRageClickConfig)) {
3711
3746
  this.trackDomEvent(ev, MP_EV_RAGE_CLICK);
3712
3747
  }
3713
3748
  }.bind(this);
@@ -6895,6 +6930,7 @@ var DEFAULT_CONFIG = {
6895
6930
  'record_block_selector': 'img, video, audio',
6896
6931
  'record_canvas': false,
6897
6932
  'record_collect_fonts': false,
6933
+ 'record_console': true,
6898
6934
  'record_heatmap_data': false,
6899
6935
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
6900
6936
  'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
@@ -43,20 +43,22 @@ export interface OutTrackingOptions extends ClearOptOutInOutOptions {
43
43
  export type RageClickConfig =
44
44
  | boolean
45
45
  | {
46
- /** Distance threshold in pixels for clicks to be considered within the same area (default: 30) */
47
- threshold_px?: number;
48
- /** Time window in milliseconds for clicks to be considered rapid (default: 1000) */
49
- timeout_ms?: number;
50
- /** Number of clicks required to trigger a rage click event (default: 3) */
51
- click_count?: number;
52
- };
46
+ /** Distance threshold in pixels for clicks to be considered within the same area (default: 30) */
47
+ threshold_px?: number;
48
+ /** Time window in milliseconds for clicks to be considered rapid (default: 1000) */
49
+ timeout_ms?: number;
50
+ /** Number of clicks required to trigger a rage click event (default: 3) */
51
+ click_count?: number;
52
+ /** Whether to only track rage clicks on interactive elements like buttons, links, inputs (default: false) */
53
+ interactive_elements_only?: boolean;
54
+ };
53
55
 
54
56
  export type DeadClickConfig =
55
57
  | boolean
56
58
  | {
57
- /** Time in milliseconds to wait after a click before qualifying it as dead (default: 500) */
58
- timeout_ms?: number;
59
- };
59
+ /** Time in milliseconds to wait after a click before qualifying it as dead (default: 500) */
60
+ timeout_ms?: number;
61
+ };
60
62
 
61
63
  export interface RegisterOptions {
62
64
  persistent: boolean;
@@ -149,6 +151,10 @@ export interface AutocaptureConfig {
149
151
  block_element_callback?: (element: Element, event: Event) => boolean;
150
152
  }
151
153
 
154
+ export interface FlagsConfig {
155
+ context: Dict;
156
+ }
157
+
152
158
  export interface Config {
153
159
  api_host: string;
154
160
  api_routes: {
@@ -165,6 +171,7 @@ export interface Config {
165
171
  cookie_domain: string;
166
172
  cross_site_cookie: boolean;
167
173
  cross_subdomain_cookie: boolean;
174
+ flags: boolean | FlagsConfig;
168
175
  persistence: Persistence;
169
176
  persistence_name: string;
170
177
  cookie_name: string;
@@ -277,11 +284,41 @@ export interface Group {
277
284
  unset(prop: string, callback?: Callback): void;
278
285
  }
279
286
 
287
+ export interface FlagsVariant {
288
+ key: string;
289
+ value: any;
290
+ experiment_id?: string;
291
+ is_experiment_active?: boolean;
292
+ is_qa_tester?: boolean;
293
+ }
294
+
295
+ export interface FlagsUpdateContextOptions {
296
+ replace?: boolean;
297
+ }
298
+
299
+ export interface FlagsManager {
300
+ are_flags_ready(): boolean;
301
+ get_variant(
302
+ featureName: string,
303
+ fallback: FlagsVariant
304
+ ): Promise<FlagsVariant>;
305
+ get_variant_sync(featureName: string, fallback: FlagsVariant): FlagsVariant;
306
+ get_variant_value(featureName: string, fallbackValue: any): Promise<any>;
307
+ get_variant_value_sync(featureName: string, fallbackValue: any): any;
308
+ is_enabled(featureName: string, fallbackValue?: boolean): Promise<boolean>;
309
+ is_enabled_sync(featureName: string, fallbackValue?: boolean): boolean;
310
+ update_context(
311
+ context: Dict,
312
+ options?: FlagsUpdateContextOptions
313
+ ): Promise<void>;
314
+ }
315
+
280
316
  export interface Mixpanel {
281
317
  add_group(group_key: string, group_id: string, callback?: Callback): void;
282
318
  alias(alias: string, original?: string): void;
283
319
  clear_opt_in_out_tracking(options?: Partial<ClearOptOutInOutOptions>): void;
284
320
  disable(events?: string[]): void;
321
+ flags: FlagsManager;
285
322
  get_config(prop_name?: string): any;
286
323
  get_distinct_id(): any;
287
324
  get_group(group_key: string, group_id: string): Group;