mixpanel-browser 2.71.1 → 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 (38) hide show
  1. package/.github/workflows/tests.yml +1 -0
  2. package/CHANGELOG.md +7 -0
  3. package/dist/mixpanel-core.cjs.d.ts +47 -10
  4. package/dist/mixpanel-core.cjs.js +51 -17
  5. package/dist/mixpanel-recorder.js +681 -113
  6. package/dist/mixpanel-recorder.min.js +1 -1
  7. package/dist/mixpanel-recorder.min.js.map +1 -1
  8. package/dist/mixpanel-with-async-recorder.cjs.d.ts +47 -10
  9. package/dist/mixpanel-with-async-recorder.cjs.js +51 -17
  10. package/dist/mixpanel-with-recorder.d.ts +47 -10
  11. package/dist/mixpanel-with-recorder.js +731 -129
  12. package/dist/mixpanel-with-recorder.min.d.ts +47 -10
  13. package/dist/mixpanel-with-recorder.min.js +1 -1
  14. package/dist/mixpanel.amd.d.ts +47 -10
  15. package/dist/mixpanel.amd.js +731 -129
  16. package/dist/mixpanel.cjs.d.ts +47 -10
  17. package/dist/mixpanel.cjs.js +731 -129
  18. package/dist/mixpanel.globals.js +51 -17
  19. package/dist/mixpanel.min.js +144 -144
  20. package/dist/mixpanel.module.d.ts +47 -10
  21. package/dist/mixpanel.module.js +731 -129
  22. package/dist/mixpanel.umd.d.ts +47 -10
  23. package/dist/mixpanel.umd.js +731 -129
  24. package/dist/rrweb-bundled.js +12760 -0
  25. package/dist/rrweb-compiled.js +2496 -7176
  26. package/package.json +3 -2
  27. package/rollup.config.mjs +15 -4
  28. package/src/autocapture/index.js +1 -1
  29. package/src/autocapture/rageclick.js +20 -1
  30. package/src/autocapture/shadow-dom-observer.js +3 -15
  31. package/src/autocapture/utils.js +30 -0
  32. package/src/config.js +1 -1
  33. package/src/index.d.ts +47 -10
  34. package/src/mixpanel-core.js +1 -0
  35. package/src/recorder/recorder.js +1 -1
  36. package/src/recorder/rrweb-entrypoint.js +6 -0
  37. package/src/recorder/session-recording.js +69 -12
  38. package/src/utils.js +24 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixpanel-browser",
3
- "version": "2.71.1",
3
+ "version": "2.72.0",
4
4
  "description": "The official Mixpanel JavaScript browser client library",
5
5
  "main": "dist/mixpanel.cjs.js",
6
6
  "module": "dist/mixpanel.module.js",
@@ -66,6 +66,7 @@
66
66
  "webpack": "1.12.2"
67
67
  },
68
68
  "dependencies": {
69
- "@mixpanel/rrweb": "2.0.0-alpha.18.2"
69
+ "@mixpanel/rrweb": "2.0.0-alpha.18.2",
70
+ "@mixpanel/rrweb-plugin-console-record": "2.0.0-alpha.18.2"
70
71
  }
71
72
  }
package/rollup.config.mjs CHANGED
@@ -7,10 +7,11 @@ import fs from 'fs';
7
7
  import path from 'path';
8
8
 
9
9
  const COMPILED_RRWEB_PATH = 'build/rrweb-compiled.js';
10
+ const BUNDLED_RRWEB_PATH = 'build/rrweb-bundled.js';
10
11
 
11
12
  const aliasRrweb = () => alias({
12
13
  entries: [
13
- { find: '@mixpanel/rrweb', replacement: COMPILED_RRWEB_PATH },
14
+ { find: /rrweb-entrypoint(?:\.js)?$/, replacement: COMPILED_RRWEB_PATH },
14
15
  ]
15
16
  });
16
17
 
@@ -42,15 +43,25 @@ const MINIFY = process.env.MINIFY || process.env.FULL;
42
43
 
43
44
  // Main builds used to develop / iterate quickly
44
45
  const MAIN_BUILDS = [
45
- // compile rrweb first to es5 with swc, we'll replace the import later on
46
46
  {
47
- 'input': '@mixpanel/rrweb',
47
+ 'input': 'src/recorder/rrweb-entrypoint.js',
48
+ 'output': [
49
+ {
50
+ file: BUNDLED_RRWEB_PATH,
51
+ format: 'es',
52
+ }
53
+ ],
54
+ plugins: [nodeResolve({browser: true})]
55
+ },
56
+ {
57
+ 'input': BUNDLED_RRWEB_PATH,
48
58
  'output': [
49
59
  {
50
60
  file: COMPILED_RRWEB_PATH,
61
+ format: 'es',
51
62
  }
52
63
  ],
53
- plugins: [nodeResolve({browser: true}), swc({swc: {jsc: {target: 'es5'}}})]
64
+ plugins: [swc({swc: {jsc: {target: 'es5'}}})]
54
65
  },
55
66
 
56
67
  // IIFE recorder bundle that is loaded asynchronously
@@ -449,7 +449,7 @@ Autocapture.prototype.initRageClickTracking = function() {
449
449
  return;
450
450
  }
451
451
 
452
- if (this._rageClickTracker.isRageClick(ev['pageX'], ev['pageY'], currentRageClickConfig)) {
452
+ if (this._rageClickTracker.isRageClick(ev, currentRageClickConfig)) {
453
453
  this.trackDomEvent(ev, MP_EV_RAGE_CLICK);
454
454
  }
455
455
  }.bind(this);
@@ -1,17 +1,36 @@
1
+ import { getClickEventTargetElement, isDefinitelyNonInteractive } from './utils';
2
+
1
3
  /** @const */ var DEFAULT_RAGE_CLICK_THRESHOLD_PX = 30;
2
4
  /** @const */ var DEFAULT_RAGE_CLICK_TIMEOUT_MS = 1000;
3
5
  /** @const */ var DEFAULT_RAGE_CLICK_CLICK_COUNT = 4;
6
+ /** @const */ var DEFAULT_RAGE_CLICK_INTERACTIVE_ELEMENTS_ONLY = false;
4
7
 
5
8
  function RageClickTracker() {
6
9
  this.clicks = [];
7
10
  }
8
11
 
9
- RageClickTracker.prototype.isRageClick = function(x, y, options) {
12
+ /**
13
+ * Determines if a click event is part of a rage click sequence.
14
+ * @param {Event} event - the original click event.
15
+ * @param {import('../index.d.ts').RageClickConfig} options - configuration options for rage click detection.
16
+ * @returns {boolean} - true if the click is considered a rage click, false otherwise.
17
+ */
18
+ RageClickTracker.prototype.isRageClick = function(event, options) {
10
19
  options = options || {};
11
20
  var thresholdPx = options['threshold_px'] || DEFAULT_RAGE_CLICK_THRESHOLD_PX;
12
21
  var timeoutMs = options['timeout_ms'] || DEFAULT_RAGE_CLICK_TIMEOUT_MS;
13
22
  var clickCount = options['click_count'] || DEFAULT_RAGE_CLICK_CLICK_COUNT;
23
+ var interactiveElementsOnly = options['interactive_elements_only'] || DEFAULT_RAGE_CLICK_INTERACTIVE_ELEMENTS_ONLY;
24
+
25
+ if (interactiveElementsOnly) {
26
+ var target = getClickEventTargetElement(event);
27
+ if (!target || isDefinitelyNonInteractive(target)) {
28
+ return false;
29
+ }
30
+ }
31
+
14
32
  var timestamp = Date.now();
33
+ var x = event['pageX'], y = event['pageY'];
15
34
 
16
35
  var lastClick = this.clicks[this.clicks.length - 1];
17
36
  if (
@@ -1,4 +1,4 @@
1
- import { logger, weakSetSupported } from './utils';
1
+ import { getClickEventComposedPath, getClickEventTargetElement, logger, weakSetSupported } from './utils';
2
2
 
3
3
  function ShadowDOMObserver(changeCallback, observerConfig) {
4
4
  this.changeCallback = changeCallback || function() {};
@@ -12,28 +12,16 @@ ShadowDOMObserver.prototype.getEventTarget = function(event) {
12
12
  if (!this.observedShadowRoots) {
13
13
  return;
14
14
  }
15
- var path = this.getComposedPath(event);
16
- if (path && path.length) {
17
- return path[0];
18
- }
19
15
 
20
- return event['target'] || event['srcElement'];
16
+ return getClickEventTargetElement(event);
21
17
  };
22
18
 
23
-
24
- ShadowDOMObserver.prototype.getComposedPath = function(event) {
25
- if ('composedPath' in event) {
26
- return event['composedPath']();
27
- }
28
-
29
- return [];
30
- };
31
19
  ShadowDOMObserver.prototype.observeFromEvent = function(event) {
32
20
  if (!this.observedShadowRoots) {
33
21
  return;
34
22
  }
35
23
 
36
- var path = this.getComposedPath(event);
24
+ var path = getClickEventComposedPath(event);
37
25
 
38
26
  // Check each element in path for shadow roots
39
27
  for (var i = 0; i < path.length; i++) {
@@ -744,9 +744,39 @@ function isDefinitelyNonInteractive(element) {
744
744
  return false;
745
745
  }
746
746
 
747
+ /**
748
+ * Get the composed path of a click event for elements embedded in shadow DOM.
749
+ * @param {Event} event - event to get the composed path from
750
+ * @returns {Array} the composed path of the click event
751
+ */
752
+ function getClickEventComposedPath(event) {
753
+ if ('composedPath' in event) {
754
+ return event['composedPath']();
755
+ }
756
+
757
+ return [];
758
+ }
759
+
760
+ /**
761
+ * Get the element from a click event, accounting for elements embedded in shadow DOM.
762
+ * @param {Event} event - event to get the target from
763
+ * @returns {Element | null} the element that was the target of the click event
764
+ */
765
+ function getClickEventTargetElement(event) {
766
+ var path = getClickEventComposedPath(event);
767
+
768
+ if (path && path.length > 0) {
769
+ return path[0];
770
+ }
771
+
772
+ return event['target'] || event['srcElement'];
773
+ }
774
+
747
775
  export {
748
776
  EV_CHANGE, EV_CLICK, EV_HASHCHANGE, EV_INPUT, EV_LOAD,EV_MP_LOCATION_CHANGE, EV_POPSTATE,
749
777
  EV_SCROLL, EV_SCROLLEND, EV_SELECT, EV_SUBMIT, EV_TOGGLE, EV_VISIBILITYCHANGE,
778
+ getClickEventComposedPath,
779
+ getClickEventTargetElement,
750
780
  getPolyfillScrollEndFunction,
751
781
  getPropsForDOMEvent,
752
782
  getSafeText,
package/src/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  var Config = {
2
2
  DEBUG: false,
3
- LIB_VERSION: '2.71.1'
3
+ LIB_VERSION: '2.72.0'
4
4
  };
5
5
 
6
6
  export default Config;
package/src/index.d.ts CHANGED
@@ -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;
@@ -152,6 +152,7 @@ var DEFAULT_CONFIG = {
152
152
  'record_block_selector': 'img, video, audio',
153
153
  'record_canvas': false,
154
154
  'record_collect_fonts': false,
155
+ 'record_console': true,
155
156
  'record_heatmap_data': false,
156
157
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
157
158
  'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
@@ -1,4 +1,4 @@
1
- import { record } from '@mixpanel/rrweb';
1
+ import { record } from './rrweb-entrypoint';
2
2
  import { Promise } from '../promise-polyfill';
3
3
  import { SessionRecording } from './session-recording';
4
4
  import { RecordingRegistry } from './recording-registry';
@@ -0,0 +1,6 @@
1
+ // this file exists as an entry point to be able to transpile rrweb packages to es5
2
+ // compatible code without needing to transpile the entire mixpanel-js codebase
3
+ import {record, EventType, IncrementalSource} from '@mixpanel/rrweb';
4
+ import {getRecordConsolePlugin} from '@mixpanel/rrweb-plugin-console-record';
5
+
6
+ export { record, EventType, IncrementalSource, getRecordConsolePlugin };
@@ -1,6 +1,6 @@
1
1
  import { window } from '../window';
2
- import { IncrementalSource, EventType } from '@mixpanel/rrweb';
3
- import { MAX_RECORDING_MS, MAX_VALUE_FOR_MIN_RECORDING_MS, console_with_prefix, NOOP_FUNC, _, localStorageSupported} from '../utils'; // eslint-disable-line camelcase
2
+ import { IncrementalSource, EventType, getRecordConsolePlugin } from './rrweb-entrypoint';
3
+ import { MAX_RECORDING_MS, MAX_VALUE_FOR_MIN_RECORDING_MS, console_with_prefix, NOOP_FUNC, _, localStorageSupported, canUseCompressionStream, navigator, userAgent, windowOpera} from '../utils'; // eslint-disable-line camelcase
4
4
  import { IDBStorageWrapper, RECORDING_EVENTS_STORE_NAME } from '../storage/indexed-db';
5
5
  import { addOptOutCheckMixpanelLib } from '../gdpr-utils';
6
6
  import { RequestBatcher } from '../request-batcher';
@@ -38,6 +38,7 @@ function isUserEvent(ev) {
38
38
  * @property {number} idleExpires
39
39
  * @property {number} maxExpires
40
40
  * @property {number} replayStartTime
41
+ * @property {number} lastEventTimestamp
41
42
  * @property {number} seqNo
42
43
  * @property {string} batchStartUrl
43
44
  * @property {string} replayId
@@ -58,6 +59,7 @@ function isUserEvent(ev) {
58
59
  * @property {number} idleExpires
59
60
  * @property {number} maxExpires
60
61
  * @property {number} replayStartTime
62
+ * @property {number} lastEventTimestamp - the unix timestamp of the last recorded event from rrweb
61
63
  * @property {number} seqNo
62
64
  * @property {string} batchStartUrl
63
65
  * @property {string} replayStartUrl
@@ -91,6 +93,7 @@ var SessionRecording = function(options) {
91
93
  this.idleExpires = options.idleExpires || null;
92
94
  this.maxExpires = options.maxExpires || null;
93
95
  this.replayStartTime = options.replayStartTime || null;
96
+ this.lastEventTimestamp = options.lastEventTimestamp || null;
94
97
  this.seqNo = options.seqNo || 0;
95
98
 
96
99
  this.idleTimeoutId = null;
@@ -150,10 +153,20 @@ SessionRecording.prototype.getUserIdInfo = function () {
150
153
 
151
154
  SessionRecording.prototype.unloadPersistedData = function () {
152
155
  this.batcher.stop();
153
- return this.batcher.flush()
154
- .then(function () {
156
+
157
+ return this.queueStorage.init().catch(function () {
158
+ this.reportError('Error initializing IndexedDB storage for unloading persisted data.');
159
+ }.bind(this)).then(function () {
160
+ // if the recording is too short, just delete any stored events without flushing
161
+ if (this.getDurationMs() < this._getRecordMinMs()) {
155
162
  return this.queueStorage.removeItem(this.batcherKey);
156
- }.bind(this));
163
+ }
164
+
165
+ return this.batcher.flush()
166
+ .then(function () {
167
+ return this.queueStorage.removeItem(this.batcherKey);
168
+ }.bind(this));
169
+ }.bind(this));
157
170
  };
158
171
 
159
172
  SessionRecording.prototype.getConfig = function(configVar) {
@@ -188,11 +201,7 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
188
201
  this.maxExpires = new Date().getTime() + this.recordMaxMs;
189
202
  }
190
203
 
191
- this.recordMinMs = this.getConfig('record_min_ms');
192
- if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
193
- this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
194
- logger.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
195
- }
204
+ this.recordMinMs = this._getRecordMinMs();
196
205
 
197
206
  if (!this.replayStartTime) {
198
207
  this.replayStartTime = new Date().getTime();
@@ -240,6 +249,11 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
240
249
  }
241
250
  // promise only used to await during tests
242
251
  this.__enqueuePromise = this.batcher.enqueue(ev);
252
+
253
+ // Capture the timestamp of the last event for duration calculation.
254
+ if (this.lastEventTimestamp === null || ev.timestamp > this.lastEventTimestamp) {
255
+ this.lastEventTimestamp = ev.timestamp;
256
+ }
243
257
  }.bind(this),
244
258
  'blockClass': this.getConfig('record_block_class'),
245
259
  'blockSelector': blockSelector,
@@ -254,7 +268,16 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
254
268
  'recordCanvas': this.getConfig('record_canvas'),
255
269
  'sampling': {
256
270
  'canvas': 15
257
- }
271
+ },
272
+ 'plugins': this.getConfig('record_console') ? [
273
+ getRecordConsolePlugin({
274
+ stringifyOptions: {
275
+ stringLengthLimit: 1000,
276
+ numOfKeysLimit: 50,
277
+ depthOfLimit: 2
278
+ }
279
+ })
280
+ ] : []
258
281
  });
259
282
  } catch (err) {
260
283
  this.reportError('Unexpected error when starting rrweb recording.', err);
@@ -339,6 +362,7 @@ SessionRecording.prototype.serialize = function () {
339
362
  'replayStartTime': this.replayStartTime,
340
363
  'batchStartUrl': this.batchStartUrl,
341
364
  'replayStartUrl': this.replayStartUrl,
365
+ 'lastEventTimestamp': this.lastEventTimestamp,
342
366
  'idleExpires': this.idleExpires,
343
367
  'maxExpires': this.maxExpires,
344
368
  'tabId': tabId,
@@ -360,6 +384,7 @@ SessionRecording.deserialize = function (serializedRecording, options) {
360
384
  idleExpires: serializedRecording['idleExpires'],
361
385
  maxExpires: serializedRecording['maxExpires'],
362
386
  replayStartTime: serializedRecording['replayStartTime'],
387
+ lastEventTimestamp: serializedRecording['lastEventTimestamp'],
363
388
  seqNo: serializedRecording['seqNo'],
364
389
  sharedLockStorage: options.sharedLockStorage,
365
390
  }));
@@ -450,7 +475,7 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
450
475
  var eventsJson = JSON.stringify(data);
451
476
  Object.assign(reqParams, this.getUserIdInfo());
452
477
 
453
- if (CompressionStream) {
478
+ if (canUseCompressionStream(userAgent, navigator.vendor, windowOpera)) {
454
479
  var jsonStream = new Blob([eventsJson], {type: 'application/json'}).stream();
455
480
  var gzipStream = jsonStream.pipeThrough(new CompressionStream('gzip'));
456
481
  new Response(gzipStream)
@@ -479,4 +504,36 @@ SessionRecording.prototype.reportError = function(msg, err) {
479
504
  }
480
505
  };
481
506
 
507
+ /**
508
+ * Calculates the duration of the recording in milliseconds, based on the start time and time of last recorded event.
509
+ * @returns {number} The duration of the recording in milliseconds. Returns 0 if recording hasn't started.
510
+ */
511
+ SessionRecording.prototype.getDurationMs = function() {
512
+ if (this.replayStartTime === null) {
513
+ return 0;
514
+ }
515
+
516
+ // If the recording has no events, assume it is in progress and use the current time as the end time.
517
+ if (this.lastEventTimestamp === null) {
518
+ return new Date().getTime() - this.replayStartTime;
519
+ }
520
+
521
+ return this.lastEventTimestamp - this.replayStartTime;
522
+ };
523
+
524
+ /**
525
+ * Lazily loads the minimum recording length config in milliseconds, respecting the maximum limit.
526
+ * @returns {number} The minimum recording length in milliseconds.
527
+ */
528
+ SessionRecording.prototype._getRecordMinMs = function() {
529
+ var configValue = this.getConfig('record_min_ms');
530
+
531
+ if (configValue > MAX_VALUE_FOR_MIN_RECORDING_MS) {
532
+ logger.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
533
+ return MAX_VALUE_FOR_MIN_RECORDING_MS;
534
+ }
535
+
536
+ return configValue;
537
+ };
538
+
482
539
  export { SessionRecording };
package/src/utils.js CHANGED
@@ -1738,6 +1738,28 @@ if (typeof JSON !== 'undefined') {
1738
1738
  JSONStringify = JSONStringify || _.JSONEncode;
1739
1739
  JSONParse = JSONParse || _.JSONDecode;
1740
1740
 
1741
+ /**
1742
+ * Determines if CompressionStream API should be used.
1743
+ * Returns false for Safari 16.4 and 16.5 which have breaking CompressionStream bugs.
1744
+ * https://bugs.webkit.org/show_bug.cgi?id=254021
1745
+ * fixed in 16.6 https://developer.apple.com/documentation/safari-release-notes/safari-16_6-release-notes
1746
+ */
1747
+ var canUseCompressionStream = function(userAgent, vendor, opera) {
1748
+ if (!window.CompressionStream) {
1749
+ return false;
1750
+ }
1751
+
1752
+ var browser = _.info.browser(userAgent, vendor, opera);
1753
+ var version = _.info.browserVersion(userAgent, vendor, opera);
1754
+ if (browser === 'Safari' || browser === 'Mobile Safari') {
1755
+ if (version >= 16.4 && version < 16.6) {
1756
+ return false;
1757
+ }
1758
+ }
1759
+
1760
+ return true;
1761
+ };
1762
+
1741
1763
  // UNMINIFIED EXPORTS (for closure compiler)
1742
1764
  _['info'] = _.info;
1743
1765
  _['info']['browser'] = _.info.browser;
@@ -1755,6 +1777,7 @@ _['NPO'] = NpoPromise;
1755
1777
  export {
1756
1778
  _,
1757
1779
  batchedThrottle,
1780
+ canUseCompressionStream,
1758
1781
  cheap_guid,
1759
1782
  console_with_prefix,
1760
1783
  console,
@@ -1773,4 +1796,5 @@ export {
1773
1796
  safewrapClass,
1774
1797
  slice,
1775
1798
  userAgent,
1799
+ windowOpera,
1776
1800
  };