@sentry/replay 7.53.1 → 7.55.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 (46) hide show
  1. package/cjs/index.js +344 -196
  2. package/cjs/index.js.map +1 -1
  3. package/esm/index.js +324 -176
  4. package/esm/index.js.map +1 -1
  5. package/package.json +5 -5
  6. package/types/constants.d.ts +2 -0
  7. package/types/constants.d.ts.map +1 -1
  8. package/types/coreHandlers/handleDom.d.ts.map +1 -1
  9. package/types/coreHandlers/handleGlobalEvent.d.ts.map +1 -1
  10. package/types/coreHandlers/handleKeyboardEvent.d.ts.map +1 -1
  11. package/types/coreHandlers/handleScope.d.ts +4 -1
  12. package/types/coreHandlers/handleScope.d.ts.map +1 -1
  13. package/types/coreHandlers/handleSlowClick.d.ts +2 -0
  14. package/types/coreHandlers/handleSlowClick.d.ts.map +1 -1
  15. package/types/coreHandlers/util/addBreadcrumbEvent.d.ts.map +1 -1
  16. package/types/coreHandlers/util/getAttributesToRecord.d.ts.map +1 -1
  17. package/types/index.d.ts +5 -0
  18. package/types/index.d.ts.map +1 -1
  19. package/types/integration.d.ts +1 -1
  20. package/types/integration.d.ts.map +1 -1
  21. package/types/replay.d.ts +28 -2
  22. package/types/replay.d.ts.map +1 -1
  23. package/types/session/getSession.d.ts.map +1 -1
  24. package/types/types/index.d.ts +6 -0
  25. package/types/types/index.d.ts.map +1 -0
  26. package/types/types/performance.d.ts +124 -0
  27. package/types/types/performance.d.ts.map +1 -0
  28. package/types/{types.d.ts → types/replay.d.ts} +37 -146
  29. package/types/types/replay.d.ts.map +1 -0
  30. package/types/types/replayFrame.d.ts +160 -0
  31. package/types/types/replayFrame.d.ts.map +1 -0
  32. package/types/types/request.d.ts +15 -0
  33. package/types/types/request.d.ts.map +1 -0
  34. package/types/types/rrweb.d.ts +1 -9
  35. package/types/types/rrweb.d.ts.map +1 -1
  36. package/types/util/addEvent.d.ts.map +1 -1
  37. package/types/util/addGlobalListeners.d.ts.map +1 -1
  38. package/types/util/createBreadcrumb.d.ts +2 -4
  39. package/types/util/createBreadcrumb.d.ts.map +1 -1
  40. package/types/util/createPerformanceSpans.d.ts +1 -1
  41. package/types/util/createPerformanceSpans.d.ts.map +1 -1
  42. package/types/util/handleRecordingEmit.d.ts +2 -2
  43. package/types/util/handleRecordingEmit.d.ts.map +1 -1
  44. package/types/util/throttle.d.ts +12 -0
  45. package/types/util/throttle.d.ts.map +1 -0
  46. package/types/types.d.ts.map +0 -1
package/cjs/index.js CHANGED
@@ -40,7 +40,12 @@ const NETWORK_BODY_MAX_SIZE = 150000;
40
40
  /* The max size of a single console arg that is captured. Any arg larger than this will be truncated. */
41
41
  const CONSOLE_ARG_MAX_SIZE = 5000;
42
42
 
43
- var NodeType$1;
43
+ /* Min. time to wait before we consider something a slow click. */
44
+ const SLOW_CLICK_THRESHOLD = 3000;
45
+ /* For scroll actions after a click, we only look for a very short time period to detect programmatic scrolling. */
46
+ const SLOW_CLICK_SCROLL_TIMEOUT = 300;
47
+
48
+ var NodeType;
44
49
  (function (NodeType) {
45
50
  NodeType[NodeType["Document"] = 0] = "Document";
46
51
  NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
@@ -48,7 +53,7 @@ var NodeType$1;
48
53
  NodeType[NodeType["Text"] = 3] = "Text";
49
54
  NodeType[NodeType["CDATA"] = 4] = "CDATA";
50
55
  NodeType[NodeType["Comment"] = 5] = "Comment";
51
- })(NodeType$1 || (NodeType$1 = {}));
56
+ })(NodeType || (NodeType = {}));
52
57
 
53
58
  function isElement(n) {
54
59
  return n.nodeType === n.ELEMENT_NODE;
@@ -452,7 +457,7 @@ function serializeNode(n, options) {
452
457
  case n.DOCUMENT_NODE:
453
458
  if (n.compatMode !== 'CSS1Compat') {
454
459
  return {
455
- type: NodeType$1.Document,
460
+ type: NodeType.Document,
456
461
  childNodes: [],
457
462
  compatMode: n.compatMode,
458
463
  rootId,
@@ -460,14 +465,14 @@ function serializeNode(n, options) {
460
465
  }
461
466
  else {
462
467
  return {
463
- type: NodeType$1.Document,
468
+ type: NodeType.Document,
464
469
  childNodes: [],
465
470
  rootId,
466
471
  };
467
472
  }
468
473
  case n.DOCUMENT_TYPE_NODE:
469
474
  return {
470
- type: NodeType$1.DocumentType,
475
+ type: NodeType.DocumentType,
471
476
  name: n.name,
472
477
  publicId: n.publicId,
473
478
  systemId: n.systemId,
@@ -611,7 +616,7 @@ function serializeNode(n, options) {
611
616
  delete attributes.src;
612
617
  }
613
618
  return {
614
- type: NodeType$1.Element,
619
+ type: NodeType.Element,
615
620
  tagName,
616
621
  attributes,
617
622
  childNodes: [],
@@ -665,20 +670,20 @@ function serializeNode(n, options) {
665
670
  : defaultMaskFn(textContent);
666
671
  }
667
672
  return {
668
- type: NodeType$1.Text,
673
+ type: NodeType.Text,
669
674
  textContent: textContent || '',
670
675
  isStyle,
671
676
  rootId,
672
677
  };
673
678
  case n.CDATA_SECTION_NODE:
674
679
  return {
675
- type: NodeType$1.CDATA,
680
+ type: NodeType.CDATA,
676
681
  textContent: '',
677
682
  rootId,
678
683
  };
679
684
  case n.COMMENT_NODE:
680
685
  return {
681
- type: NodeType$1.Comment,
686
+ type: NodeType.Comment,
682
687
  textContent: n.textContent || '',
683
688
  rootId,
684
689
  };
@@ -695,10 +700,10 @@ function lowerIfExists(maybeAttr) {
695
700
  }
696
701
  }
697
702
  function slimDOMExcluded(sn, slimDOMOptions) {
698
- if (slimDOMOptions.comment && sn.type === NodeType$1.Comment) {
703
+ if (slimDOMOptions.comment && sn.type === NodeType.Comment) {
699
704
  return true;
700
705
  }
701
- else if (sn.type === NodeType$1.Element) {
706
+ else if (sn.type === NodeType.Element) {
702
707
  if (slimDOMOptions.script &&
703
708
  (sn.tagName === 'script' ||
704
709
  (sn.tagName === 'link' &&
@@ -799,7 +804,7 @@ function serializeNodeWithId(n, options) {
799
804
  }
800
805
  else if (slimDOMExcluded(_serializedNode, slimDOMOptions) ||
801
806
  (!preserveWhiteSpace &&
802
- _serializedNode.type === NodeType$1.Text &&
807
+ _serializedNode.type === NodeType.Text &&
803
808
  !_serializedNode.isStyle &&
804
809
  !_serializedNode.textContent.replace(/^\s+|\s+$/gm, '').length)) {
805
810
  id = IGNORED_NODE;
@@ -817,17 +822,17 @@ function serializeNodeWithId(n, options) {
817
822
  onSerialize(n);
818
823
  }
819
824
  let recordChild = !skipChild;
820
- if (serializedNode.type === NodeType$1.Element) {
825
+ if (serializedNode.type === NodeType.Element) {
821
826
  recordChild = recordChild && !serializedNode.needBlock;
822
827
  delete serializedNode.needBlock;
823
828
  if (n.shadowRoot)
824
829
  serializedNode.isShadowHost = true;
825
830
  }
826
- if ((serializedNode.type === NodeType$1.Document ||
827
- serializedNode.type === NodeType$1.Element) &&
831
+ if ((serializedNode.type === NodeType.Document ||
832
+ serializedNode.type === NodeType.Element) &&
828
833
  recordChild) {
829
834
  if (slimDOMOptions.headWhitespace &&
830
- _serializedNode.type === NodeType$1.Element &&
835
+ _serializedNode.type === NodeType.Element &&
831
836
  _serializedNode.tagName === 'head') {
832
837
  preserveWhiteSpace = false;
833
838
  }
@@ -877,7 +882,7 @@ function serializeNodeWithId(n, options) {
877
882
  if (n.parentNode && isShadowRoot(n.parentNode)) {
878
883
  serializedNode.isShadow = true;
879
884
  }
880
- if (serializedNode.type === NodeType$1.Element &&
885
+ if (serializedNode.type === NodeType.Element &&
881
886
  serializedNode.tagName === 'iframe') {
882
887
  onceIframeLoaded(n, () => {
883
888
  const iframeDoc = n.contentDocument;
@@ -993,7 +998,7 @@ function skipAttribute(tagName, attributeName, value) {
993
998
  return ((tagName === 'video' || tagName === 'audio') && attributeName === 'autoplay');
994
999
  }
995
1000
 
996
- var EventType$1;
1001
+ exports.EventType = void 0;
997
1002
  (function (EventType) {
998
1003
  EventType[EventType["DomContentLoaded"] = 0] = "DomContentLoaded";
999
1004
  EventType[EventType["Load"] = 1] = "Load";
@@ -1002,7 +1007,7 @@ var EventType$1;
1002
1007
  EventType[EventType["Meta"] = 4] = "Meta";
1003
1008
  EventType[EventType["Custom"] = 5] = "Custom";
1004
1009
  EventType[EventType["Plugin"] = 6] = "Plugin";
1005
- })(EventType$1 || (EventType$1 = {}));
1010
+ })(exports.EventType || (exports.EventType = {}));
1006
1011
  var IncrementalSource;
1007
1012
  (function (IncrementalSource) {
1008
1013
  IncrementalSource[IncrementalSource["Mutation"] = 0] = "Mutation";
@@ -1135,7 +1140,7 @@ if (typeof window !== 'undefined' && window.Proxy && window.Reflect) {
1135
1140
  },
1136
1141
  });
1137
1142
  }
1138
- function throttle(func, wait, options = {}) {
1143
+ function throttle$1(func, wait, options = {}) {
1139
1144
  let timeout = null;
1140
1145
  let previous = 0;
1141
1146
  return function (arg) {
@@ -1302,7 +1307,7 @@ function polyfill(win = window) {
1302
1307
  }
1303
1308
  function isIframeINode(node) {
1304
1309
  if ('__sn' in node) {
1305
- return (node.__sn.type === NodeType$1.Element && node.__sn.tagName === 'iframe');
1310
+ return (node.__sn.type === NodeType.Element && node.__sn.tagName === 'iframe');
1306
1311
  }
1307
1312
  return false;
1308
1313
  }
@@ -1870,7 +1875,7 @@ function initMoveObserver({ mousemoveCb, sampling, doc, mirror, }) {
1870
1875
  : 500;
1871
1876
  let positions = [];
1872
1877
  let timeBaseline;
1873
- const wrappedCb = throttle((source) => {
1878
+ const wrappedCb = throttle$1((source) => {
1874
1879
  const totalOffset = Date.now() - timeBaseline;
1875
1880
  callbackWrapper(mousemoveCb)(positions.map((p) => {
1876
1881
  p.timeOffset -= totalOffset;
@@ -1879,7 +1884,7 @@ function initMoveObserver({ mousemoveCb, sampling, doc, mirror, }) {
1879
1884
  positions = [];
1880
1885
  timeBaseline = null;
1881
1886
  }, callbackThreshold);
1882
- const updatePosition = throttle((evt) => {
1887
+ const updatePosition = throttle$1((evt) => {
1883
1888
  const target = getEventTarget(evt);
1884
1889
  const { clientX, clientY } = isTouchEvent(evt)
1885
1890
  ? evt.changedTouches[0]
@@ -1953,7 +1958,7 @@ function initMouseInteractionObserver({ mouseInteractionCb, doc, mirror, blockCl
1953
1958
  });
1954
1959
  }
1955
1960
  function initScrollObserver({ scrollCb, doc, mirror, blockClass, blockSelector, unblockSelector, sampling, }) {
1956
- const updatePosition = throttle((evt) => {
1961
+ const updatePosition = throttle$1((evt) => {
1957
1962
  const target = getEventTarget(evt);
1958
1963
  if (!target ||
1959
1964
  isBlocked(target, blockClass, blockSelector, unblockSelector)) {
@@ -1981,7 +1986,7 @@ function initScrollObserver({ scrollCb, doc, mirror, blockClass, blockSelector,
1981
1986
  function initViewportResizeObserver({ viewportResizeCb, }) {
1982
1987
  let lastH = -1;
1983
1988
  let lastW = -1;
1984
- const updateDimension = throttle(() => {
1989
+ const updateDimension = throttle$1(() => {
1985
1990
  const height = getWindowHeight();
1986
1991
  const width = getWindowWidth();
1987
1992
  if (lastH !== height || lastW !== width) {
@@ -2270,7 +2275,7 @@ function initStyleDeclarationObserver({ styleDeclarationCb, mirror }, { win }) {
2270
2275
  });
2271
2276
  }
2272
2277
  function initMediaInteractionObserver({ mediaInteractionCb, blockClass, blockSelector, unblockSelector, mirror, sampling, }) {
2273
- const handler = (type) => throttle(callbackWrapper((event) => {
2278
+ const handler = (type) => throttle$1(callbackWrapper((event) => {
2274
2279
  const target = getEventTarget(event);
2275
2280
  if (!target ||
2276
2281
  isBlocked(target, blockClass, blockSelector, unblockSelector)) {
@@ -2975,17 +2980,17 @@ function record(options = {}) {
2975
2980
  wrappedEmit = (e, isCheckout) => {
2976
2981
  var _a;
2977
2982
  if (((_a = mutationBuffers[0]) === null || _a === void 0 ? void 0 : _a.isFrozen()) &&
2978
- e.type !== EventType$1.FullSnapshot &&
2979
- !(e.type === EventType$1.IncrementalSnapshot &&
2983
+ e.type !== exports.EventType.FullSnapshot &&
2984
+ !(e.type === exports.EventType.IncrementalSnapshot &&
2980
2985
  e.data.source === IncrementalSource.Mutation)) {
2981
2986
  mutationBuffers.forEach((buf) => buf.unfreeze());
2982
2987
  }
2983
2988
  emit(eventProcessor(e), isCheckout);
2984
- if (e.type === EventType$1.FullSnapshot) {
2989
+ if (e.type === exports.EventType.FullSnapshot) {
2985
2990
  lastFullSnapshotEvent = e;
2986
2991
  incrementalSnapshotCount = 0;
2987
2992
  }
2988
- else if (e.type === EventType$1.IncrementalSnapshot) {
2993
+ else if (e.type === exports.EventType.IncrementalSnapshot) {
2989
2994
  if (e.data.source === IncrementalSource.Mutation &&
2990
2995
  e.data.isAttachIframe) {
2991
2996
  return;
@@ -3001,16 +3006,16 @@ function record(options = {}) {
3001
3006
  };
3002
3007
  const wrappedMutationEmit = (m) => {
3003
3008
  wrappedEmit(wrapEvent({
3004
- type: EventType$1.IncrementalSnapshot,
3009
+ type: exports.EventType.IncrementalSnapshot,
3005
3010
  data: Object.assign({ source: IncrementalSource.Mutation }, m),
3006
3011
  }));
3007
3012
  };
3008
3013
  const wrappedScrollEmit = (p) => wrappedEmit(wrapEvent({
3009
- type: EventType$1.IncrementalSnapshot,
3014
+ type: exports.EventType.IncrementalSnapshot,
3010
3015
  data: Object.assign({ source: IncrementalSource.Scroll }, p),
3011
3016
  }));
3012
3017
  const wrappedCanvasMutationEmit = (p) => wrappedEmit(wrapEvent({
3013
- type: EventType$1.IncrementalSnapshot,
3018
+ type: exports.EventType.IncrementalSnapshot,
3014
3019
  data: Object.assign({ source: IncrementalSource.CanvasMutation }, p),
3015
3020
  }));
3016
3021
  const iframeManager = new IframeManager({
@@ -3055,7 +3060,7 @@ function record(options = {}) {
3055
3060
  takeFullSnapshot = (isCheckout = false) => {
3056
3061
  var _a, _b, _c, _d;
3057
3062
  wrappedEmit(wrapEvent({
3058
- type: EventType$1.Meta,
3063
+ type: exports.EventType.Meta,
3059
3064
  data: {
3060
3065
  href: window.location.href,
3061
3066
  width: getWindowWidth(),
@@ -3098,7 +3103,7 @@ function record(options = {}) {
3098
3103
  }
3099
3104
  mirror.map = idNodeMap;
3100
3105
  wrappedEmit(wrapEvent({
3101
- type: EventType$1.FullSnapshot,
3106
+ type: exports.EventType.FullSnapshot,
3102
3107
  data: {
3103
3108
  node,
3104
3109
  initialOffset: {
@@ -3123,7 +3128,7 @@ function record(options = {}) {
3123
3128
  const handlers = [];
3124
3129
  handlers.push(on('DOMContentLoaded', () => {
3125
3130
  wrappedEmit(wrapEvent({
3126
- type: EventType$1.DomContentLoaded,
3131
+ type: exports.EventType.DomContentLoaded,
3127
3132
  data: {},
3128
3133
  }));
3129
3134
  }));
@@ -3133,40 +3138,40 @@ function record(options = {}) {
3133
3138
  onMutation,
3134
3139
  mutationCb: wrappedMutationEmit,
3135
3140
  mousemoveCb: (positions, source) => wrappedEmit(wrapEvent({
3136
- type: EventType$1.IncrementalSnapshot,
3141
+ type: exports.EventType.IncrementalSnapshot,
3137
3142
  data: {
3138
3143
  source,
3139
3144
  positions,
3140
3145
  },
3141
3146
  })),
3142
3147
  mouseInteractionCb: (d) => wrappedEmit(wrapEvent({
3143
- type: EventType$1.IncrementalSnapshot,
3148
+ type: exports.EventType.IncrementalSnapshot,
3144
3149
  data: Object.assign({ source: IncrementalSource.MouseInteraction }, d),
3145
3150
  })),
3146
3151
  scrollCb: wrappedScrollEmit,
3147
3152
  viewportResizeCb: (d) => wrappedEmit(wrapEvent({
3148
- type: EventType$1.IncrementalSnapshot,
3153
+ type: exports.EventType.IncrementalSnapshot,
3149
3154
  data: Object.assign({ source: IncrementalSource.ViewportResize }, d),
3150
3155
  })),
3151
3156
  inputCb: (v) => wrappedEmit(wrapEvent({
3152
- type: EventType$1.IncrementalSnapshot,
3157
+ type: exports.EventType.IncrementalSnapshot,
3153
3158
  data: Object.assign({ source: IncrementalSource.Input }, v),
3154
3159
  })),
3155
3160
  mediaInteractionCb: (p) => wrappedEmit(wrapEvent({
3156
- type: EventType$1.IncrementalSnapshot,
3161
+ type: exports.EventType.IncrementalSnapshot,
3157
3162
  data: Object.assign({ source: IncrementalSource.MediaInteraction }, p),
3158
3163
  })),
3159
3164
  styleSheetRuleCb: (r) => wrappedEmit(wrapEvent({
3160
- type: EventType$1.IncrementalSnapshot,
3165
+ type: exports.EventType.IncrementalSnapshot,
3161
3166
  data: Object.assign({ source: IncrementalSource.StyleSheetRule }, r),
3162
3167
  })),
3163
3168
  styleDeclarationCb: (r) => wrappedEmit(wrapEvent({
3164
- type: EventType$1.IncrementalSnapshot,
3169
+ type: exports.EventType.IncrementalSnapshot,
3165
3170
  data: Object.assign({ source: IncrementalSource.StyleDeclaration }, r),
3166
3171
  })),
3167
3172
  canvasMutationCb: wrappedCanvasMutationEmit,
3168
3173
  fontCb: (p) => wrappedEmit(wrapEvent({
3169
- type: EventType$1.IncrementalSnapshot,
3174
+ type: exports.EventType.IncrementalSnapshot,
3170
3175
  data: Object.assign({ source: IncrementalSource.Font }, p),
3171
3176
  })),
3172
3177
  blockClass,
@@ -3199,7 +3204,7 @@ function record(options = {}) {
3199
3204
  observer: p.observer,
3200
3205
  options: p.options,
3201
3206
  callback: (payload) => wrappedEmit(wrapEvent({
3202
- type: EventType$1.Plugin,
3207
+ type: exports.EventType.Plugin,
3203
3208
  data: {
3204
3209
  plugin: p.name,
3205
3210
  payload,
@@ -3227,7 +3232,7 @@ function record(options = {}) {
3227
3232
  else {
3228
3233
  handlers.push(on('load', () => {
3229
3234
  wrappedEmit(wrapEvent({
3230
- type: EventType$1.Load,
3235
+ type: exports.EventType.Load,
3231
3236
  data: {},
3232
3237
  }));
3233
3238
  init();
@@ -3246,7 +3251,7 @@ record.addCustomEvent = (tag, payload) => {
3246
3251
  throw new Error('please add custom event after start recording');
3247
3252
  }
3248
3253
  wrappedEmit(wrapEvent({
3249
- type: EventType$1.Custom,
3254
+ type: exports.EventType.Custom,
3250
3255
  data: {
3251
3256
  tag,
3252
3257
  payload,
@@ -3277,7 +3282,7 @@ function createBreadcrumb(
3277
3282
  };
3278
3283
  }
3279
3284
 
3280
- var NodeType;
3285
+ exports.NodeType = void 0;
3281
3286
  (function (NodeType) {
3282
3287
  NodeType[NodeType["Document"] = 0] = "Document";
3283
3288
  NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
@@ -3285,90 +3290,7 @@ var NodeType;
3285
3290
  NodeType[NodeType["Text"] = 3] = "Text";
3286
3291
  NodeType[NodeType["CDATA"] = 4] = "CDATA";
3287
3292
  NodeType[NodeType["Comment"] = 5] = "Comment";
3288
- })(NodeType || (NodeType = {}));
3289
-
3290
- /* eslint-disable @typescript-eslint/naming-convention */
3291
-
3292
- var EventType; (function (EventType) {
3293
- const DomContentLoaded = 0; EventType[EventType["DomContentLoaded"] = DomContentLoaded] = "DomContentLoaded";
3294
- const Load = 1; EventType[EventType["Load"] = Load] = "Load";
3295
- const FullSnapshot = 2; EventType[EventType["FullSnapshot"] = FullSnapshot] = "FullSnapshot";
3296
- const IncrementalSnapshot = 3; EventType[EventType["IncrementalSnapshot"] = IncrementalSnapshot] = "IncrementalSnapshot";
3297
- const Meta = 4; EventType[EventType["Meta"] = Meta] = "Meta";
3298
- const Custom = 5; EventType[EventType["Custom"] = Custom] = "Custom";
3299
- const Plugin = 6; EventType[EventType["Plugin"] = Plugin] = "Plugin";
3300
- })(EventType || (EventType = {}));
3301
-
3302
- /**
3303
- * This is a partial copy of rrweb's eventWithTime type which only contains the properties
3304
- * we specifcally need in the SDK.
3305
- */
3306
-
3307
- /**
3308
- * Converts a timestamp to ms, if it was in s, or keeps it as ms.
3309
- */
3310
- function timestampToMs(timestamp) {
3311
- const isMs = timestamp > 9999999999;
3312
- return isMs ? timestamp : timestamp * 1000;
3313
- }
3314
-
3315
- /**
3316
- * Add an event to the event buffer.
3317
- * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
3318
- */
3319
- async function addEvent(
3320
- replay,
3321
- event,
3322
- isCheckout,
3323
- ) {
3324
- if (!replay.eventBuffer) {
3325
- // This implies that `_isEnabled` is false
3326
- return null;
3327
- }
3328
-
3329
- if (replay.isPaused()) {
3330
- // Do not add to event buffer when recording is paused
3331
- return null;
3332
- }
3333
-
3334
- const timestampInMs = timestampToMs(event.timestamp);
3335
-
3336
- // Throw out events that happen more than 5 minutes ago. This can happen if
3337
- // page has been left open and idle for a long period of time and user
3338
- // comes back to trigger a new session. The performance entries rely on
3339
- // `performance.timeOrigin`, which is when the page first opened.
3340
- if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
3341
- return null;
3342
- }
3343
-
3344
- try {
3345
- if (isCheckout) {
3346
- replay.eventBuffer.clear();
3347
- }
3348
-
3349
- const replayOptions = replay.getOptions();
3350
-
3351
- const eventAfterPossibleCallback =
3352
- typeof replayOptions.beforeAddRecordingEvent === 'function' && event.type === EventType.Custom
3353
- ? replayOptions.beforeAddRecordingEvent(event)
3354
- : event;
3355
-
3356
- if (!eventAfterPossibleCallback) {
3357
- return;
3358
- }
3359
-
3360
- return await replay.eventBuffer.addEvent(eventAfterPossibleCallback);
3361
- } catch (error) {
3362
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.error(error);
3363
- await replay.stop('addEvent');
3364
-
3365
- const client = core.getCurrentHub().getClient();
3366
-
3367
- if (client) {
3368
- client.recordDroppedEvent('internal_sdk_error', 'replay');
3369
- }
3370
- }
3371
- }
3293
+ })(exports.NodeType || (exports.NodeType = {}));
3372
3294
 
3373
3295
  /**
3374
3296
  * Add a breadcrumb event to replay.
@@ -3385,8 +3307,8 @@ function addBreadcrumbEvent(replay, breadcrumb) {
3385
3307
  }
3386
3308
 
3387
3309
  replay.addUpdate(() => {
3388
- void addEvent(replay, {
3389
- type: EventType$1.Custom,
3310
+ void replay.throttledAddEvent({
3311
+ type: exports.EventType.Custom,
3390
3312
  // TODO: We were converting from ms to seconds for breadcrumbs, spans,
3391
3313
  // but maybe we should just keep them as milliseconds
3392
3314
  timestamp: (breadcrumb.timestamp || 0) * 1000,
@@ -3508,15 +3430,16 @@ function handleSlowClick(
3508
3430
  addBreadcrumbEvent(replay, breadcrumb);
3509
3431
  }
3510
3432
 
3511
- const SLOW_CLICK_IGNORE_TAGS = ['SELECT', 'OPTION'];
3433
+ const SLOW_CLICK_TAGS = ['A', 'BUTTON', 'INPUT'];
3512
3434
 
3435
+ /** exported for tests only */
3513
3436
  function ignoreElement(node, config) {
3514
- // If <input> tag, we only want to consider input[type='submit'] & input[type='button']
3515
- if (node.tagName === 'INPUT' && !['submit', 'button'].includes(node.getAttribute('type') || '')) {
3437
+ if (!SLOW_CLICK_TAGS.includes(node.tagName)) {
3516
3438
  return true;
3517
3439
  }
3518
3440
 
3519
- if (SLOW_CLICK_IGNORE_TAGS.includes(node.tagName)) {
3441
+ // If <input> tag, we only want to consider input[type='submit'] & input[type='button']
3442
+ if (node.tagName === 'INPUT' && !['submit', 'button'].includes(node.getAttribute('type') || '')) {
3520
3443
  return true;
3521
3444
  }
3522
3445
 
@@ -3549,6 +3472,8 @@ const ATTRIBUTES_TO_RECORD = new Set([
3549
3472
  'title',
3550
3473
  'data-test-id',
3551
3474
  'data-testid',
3475
+ 'disabled',
3476
+ 'aria-disabled',
3552
3477
  ]);
3553
3478
 
3554
3479
  /**
@@ -3574,14 +3499,14 @@ function getAttributesToRecord(attributes) {
3574
3499
  const handleDomListener = (
3575
3500
  replay,
3576
3501
  ) => {
3577
- const slowClickExperiment = replay.getOptions()._experiments.slowClicks;
3502
+ const { slowClickTimeout, slowClickIgnoreSelectors } = replay.getOptions();
3578
3503
 
3579
- const slowClickConfig = slowClickExperiment
3504
+ const slowClickConfig = slowClickTimeout
3580
3505
  ? {
3581
- threshold: slowClickExperiment.threshold,
3582
- timeout: slowClickExperiment.timeout,
3583
- scrollTimeout: slowClickExperiment.scrollTimeout,
3584
- ignoreSelector: slowClickExperiment.ignoreSelectors ? slowClickExperiment.ignoreSelectors.join(',') : '',
3506
+ threshold: Math.min(SLOW_CLICK_THRESHOLD, slowClickTimeout),
3507
+ timeout: slowClickTimeout,
3508
+ scrollTimeout: SLOW_CLICK_SCROLL_TIMEOUT,
3509
+ ignoreSelector: slowClickIgnoreSelectors ? slowClickIgnoreSelectors.join(',') : '',
3585
3510
  }
3586
3511
  : undefined;
3587
3512
 
@@ -3615,7 +3540,7 @@ const handleDomListener = (
3615
3540
  /** Get the base DOM breadcrumb. */
3616
3541
  function getBaseDomBreadcrumb(target, message) {
3617
3542
  // `__sn` property is the serialized node created by rrweb
3618
- const serializedNode = target && isRrwebNode(target) && target.__sn.type === NodeType.Element ? target.__sn : null;
3543
+ const serializedNode = target && isRrwebNode(target) && target.__sn.type === exports.NodeType.Element ? target.__sn : null;
3619
3544
 
3620
3545
  return {
3621
3546
  message,
@@ -3628,7 +3553,7 @@ function getBaseDomBreadcrumb(target, message) {
3628
3553
  textContent: target
3629
3554
  ? Array.from(target.childNodes)
3630
3555
  .map(
3631
- (node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
3556
+ (node) => '__sn' in node && node.__sn.type === exports.NodeType.Text && node.__sn.textContent,
3632
3557
  )
3633
3558
  .filter(Boolean) // filter out empty values
3634
3559
  .map(text => (text ).trim())
@@ -3710,7 +3635,10 @@ function handleKeyboardEvent(replay, event) {
3710
3635
  return;
3711
3636
  }
3712
3637
 
3713
- replay.triggerUserActivity();
3638
+ // Update user activity, but do not restart recording as it can create
3639
+ // noisy/low-value replays (e.g. user comes back from idle, hits alt-tab, new
3640
+ // session with a single "keydown" breadcrumb is created)
3641
+ replay.updateUserActivity();
3714
3642
 
3715
3643
  const breadcrumb = getKeyboardBreadcrumb(event);
3716
3644
 
@@ -3726,7 +3654,7 @@ function getKeyboardBreadcrumb(event) {
3726
3654
  const { metaKey, shiftKey, ctrlKey, altKey, key, target } = event;
3727
3655
 
3728
3656
  // never capture for input fields
3729
- if (!target || isInputElement(target )) {
3657
+ if (!target || isInputElement(target ) || !key) {
3730
3658
  return null;
3731
3659
  }
3732
3660
 
@@ -3898,6 +3826,14 @@ function t(t){let e=t.length;for(;--e>=0;)t[e]=0}const e=new Uint8Array([0,0,0,0
3898
3826
 
3899
3827
  function e(){const e=new Blob([r]);return URL.createObjectURL(e)}
3900
3828
 
3829
+ /**
3830
+ * Converts a timestamp to ms, if it was in s, or keeps it as ms.
3831
+ */
3832
+ function timestampToMs(timestamp) {
3833
+ const isMs = timestamp > 9999999999;
3834
+ return isMs ? timestamp : timestamp * 1000;
3835
+ }
3836
+
3901
3837
  /**
3902
3838
  * A basic event buffer that does not do any compression.
3903
3839
  * Used as fallback if the compression worker cannot be loaded or is disabled.
@@ -4453,11 +4389,13 @@ function getSession({
4453
4389
  // within "max session time").
4454
4390
  const isExpired = isSessionExpired(session, timeouts);
4455
4391
 
4456
- if (!isExpired) {
4392
+ if (!isExpired || (allowBuffering && session.shouldRefresh)) {
4457
4393
  return { type: 'saved', session };
4458
4394
  } else if (!session.shouldRefresh) {
4459
- // In this case, stop
4460
- // This is the case if we have an error session that is completed (=triggered an error)
4395
+ // This is the case if we have an error session that is completed
4396
+ // (=triggered an error). Session will continue as session-based replay,
4397
+ // and when this session is expired, it will not be renewed until user
4398
+ // reloads.
4461
4399
  const discardedSession = makeSession({ sampled: false });
4462
4400
  return { type: 'new', session: discardedSession };
4463
4401
  } else {
@@ -4475,6 +4413,68 @@ function getSession({
4475
4413
  return { type: 'new', session: newSession };
4476
4414
  }
4477
4415
 
4416
+ function isCustomEvent(event) {
4417
+ return event.type === exports.EventType.Custom;
4418
+ }
4419
+
4420
+ /**
4421
+ * Add an event to the event buffer.
4422
+ * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
4423
+ */
4424
+ async function addEvent(
4425
+ replay,
4426
+ event,
4427
+ isCheckout,
4428
+ ) {
4429
+ if (!replay.eventBuffer) {
4430
+ // This implies that `_isEnabled` is false
4431
+ return null;
4432
+ }
4433
+
4434
+ if (replay.isPaused()) {
4435
+ // Do not add to event buffer when recording is paused
4436
+ return null;
4437
+ }
4438
+
4439
+ const timestampInMs = timestampToMs(event.timestamp);
4440
+
4441
+ // Throw out events that happen more than 5 minutes ago. This can happen if
4442
+ // page has been left open and idle for a long period of time and user
4443
+ // comes back to trigger a new session. The performance entries rely on
4444
+ // `performance.timeOrigin`, which is when the page first opened.
4445
+ if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
4446
+ return null;
4447
+ }
4448
+
4449
+ try {
4450
+ if (isCheckout) {
4451
+ replay.eventBuffer.clear();
4452
+ }
4453
+
4454
+ const replayOptions = replay.getOptions();
4455
+
4456
+ const eventAfterPossibleCallback =
4457
+ typeof replayOptions.beforeAddRecordingEvent === 'function' && isCustomEvent(event)
4458
+ ? replayOptions.beforeAddRecordingEvent(event)
4459
+ : event;
4460
+
4461
+ if (!eventAfterPossibleCallback) {
4462
+ return;
4463
+ }
4464
+
4465
+ return await replay.eventBuffer.addEvent(eventAfterPossibleCallback);
4466
+ } catch (error) {
4467
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.error(error);
4468
+ await replay.stop('addEvent');
4469
+
4470
+ const client = core.getCurrentHub().getClient();
4471
+
4472
+ if (client) {
4473
+ client.recordDroppedEvent('internal_sdk_error', 'replay');
4474
+ }
4475
+ }
4476
+ }
4477
+
4478
4478
  /** If the event is an error event */
4479
4479
  function isErrorEvent(event) {
4480
4480
  return !event.type;
@@ -4649,13 +4649,6 @@ function handleGlobalEventListener(
4649
4649
  event.tags = { ...event.tags, replayId: replay.getSessionId() };
4650
4650
  }
4651
4651
 
4652
- if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && replay.getOptions()._experiments.traceInternals && isErrorEvent(event)) {
4653
- const exc = getEventExceptionValues(event);
4654
- addInternalBreadcrumb({
4655
- message: `Tagging event (${event.event_id}) - ${event.message} - ${exc.type}: ${exc.value}`,
4656
- });
4657
- }
4658
-
4659
4652
  // In cases where a custom client is used that does not support the new hooks (yet),
4660
4653
  // we manually call this hook method here
4661
4654
  if (afterSendHandler) {
@@ -4667,35 +4660,16 @@ function handleGlobalEventListener(
4667
4660
  };
4668
4661
  }
4669
4662
 
4670
- function addInternalBreadcrumb(arg) {
4671
- const { category, level, message, ...rest } = arg;
4672
-
4673
- core.addBreadcrumb({
4674
- category: category || 'console',
4675
- level: level || 'debug',
4676
- message: `[debug]: ${message}`,
4677
- ...rest,
4678
- });
4679
- }
4680
-
4681
- function getEventExceptionValues(event) {
4682
- return {
4683
- type: 'Unknown',
4684
- value: 'n/a',
4685
- ...(event.exception && event.exception.values && event.exception.values[0]),
4686
- };
4687
- }
4688
-
4689
4663
  /**
4690
- * Create a "span" for each performance entry. The parent transaction is `this.replayEvent`.
4664
+ * Create a "span" for each performance entry.
4691
4665
  */
4692
4666
  function createPerformanceSpans(
4693
4667
  replay,
4694
4668
  entries,
4695
4669
  ) {
4696
- return entries.map(({ type, start, end, name, data }) =>
4697
- addEvent(replay, {
4698
- type: EventType$1.Custom,
4670
+ return entries.map(({ type, start, end, name, data }) => {
4671
+ const response = replay.throttledAddEvent({
4672
+ type: exports.EventType.Custom,
4699
4673
  timestamp: start,
4700
4674
  data: {
4701
4675
  tag: 'performanceSpan',
@@ -4707,8 +4681,11 @@ function createPerformanceSpans(
4707
4681
  data,
4708
4682
  },
4709
4683
  },
4710
- }),
4711
- );
4684
+ });
4685
+
4686
+ // If response is a string, it means its either THROTTLED or SKIPPED
4687
+ return typeof response === 'string' ? Promise.resolve(null) : response;
4688
+ });
4712
4689
  }
4713
4690
 
4714
4691
  function handleHistory(handlerData) {
@@ -5926,6 +5903,10 @@ function _isFetchHint(hint) {
5926
5903
 
5927
5904
  let _LAST_BREADCRUMB = null;
5928
5905
 
5906
+ function isBreadcrumbWithCategory(breadcrumb) {
5907
+ return !!breadcrumb.category;
5908
+ }
5909
+
5929
5910
  const handleScopeListener =
5930
5911
  (replay) =>
5931
5912
  (scope) => {
@@ -5961,9 +5942,9 @@ function handleScope(scope) {
5961
5942
  _LAST_BREADCRUMB = newBreadcrumb;
5962
5943
 
5963
5944
  if (
5964
- newBreadcrumb.category &&
5965
- (['fetch', 'xhr', 'sentry.event', 'sentry.transaction'].includes(newBreadcrumb.category) ||
5966
- newBreadcrumb.category.startsWith('ui.'))
5945
+ !isBreadcrumbWithCategory(newBreadcrumb) ||
5946
+ ['fetch', 'xhr', 'sentry.event', 'sentry.transaction'].includes(newBreadcrumb.category) ||
5947
+ newBreadcrumb.category.startsWith('ui.')
5967
5948
  ) {
5968
5949
  return null;
5969
5950
  }
@@ -5976,7 +5957,9 @@ function handleScope(scope) {
5976
5957
  }
5977
5958
 
5978
5959
  /** exported for tests only */
5979
- function normalizeConsoleBreadcrumb(breadcrumb) {
5960
+ function normalizeConsoleBreadcrumb(
5961
+ breadcrumb,
5962
+ ) {
5980
5963
  const args = breadcrumb.data && breadcrumb.data.arguments;
5981
5964
 
5982
5965
  if (!Array.isArray(args) || args.length === 0) {
@@ -6057,6 +6040,16 @@ function addGlobalListeners(replay) {
6057
6040
  dsc.replay_id = replayId;
6058
6041
  }
6059
6042
  });
6043
+
6044
+ client.on('startTransaction', transaction => {
6045
+ replay.lastTransaction = transaction;
6046
+ });
6047
+
6048
+ // We may be missing the initial startTransaction due to timing issues,
6049
+ // so we capture it on finish again.
6050
+ client.on('finishTransaction', transaction => {
6051
+ replay.lastTransaction = transaction;
6052
+ });
6060
6053
  }
6061
6054
  }
6062
6055
 
@@ -6415,6 +6408,10 @@ function getHandleRecordingEmit(replay) {
6415
6408
  // a previous session ID. In this case, we want to buffer events
6416
6409
  // for a set amount of time before flushing. This can help avoid
6417
6410
  // capturing replays of users that immediately close the window.
6411
+ // TODO: We should check `recordingMode` here and do nothing if it's
6412
+ // buffer, instead of checking inside of timeout, this will make our
6413
+ // tests a bit cleaner as we will need to wait on the delay in order to
6414
+ // do nothing.
6418
6415
  setTimeout(() => replay.conditionalFlush(), options._experiments.delayFlushOnCheckout);
6419
6416
 
6420
6417
  // Cancel any previously debounced flushes to ensure there are no [near]
@@ -6450,7 +6447,7 @@ function getHandleRecordingEmit(replay) {
6450
6447
  function createOptionsEvent(replay) {
6451
6448
  const options = replay.getOptions();
6452
6449
  return {
6453
- type: EventType.Custom,
6450
+ type: exports.EventType.Custom,
6454
6451
  timestamp: Date.now(),
6455
6452
  data: {
6456
6453
  tag: 'options',
@@ -6780,6 +6777,62 @@ async function sendReplay(
6780
6777
  }
6781
6778
  }
6782
6779
 
6780
+ const THROTTLED = '__THROTTLED';
6781
+ const SKIPPED = '__SKIPPED';
6782
+
6783
+ /**
6784
+ * Create a throttled function off a given function.
6785
+ * When calling the throttled function, it will call the original function only
6786
+ * if it hasn't been called more than `maxCount` times in the last `durationSeconds`.
6787
+ *
6788
+ * Returns `THROTTLED` if throttled for the first time, after that `SKIPPED`,
6789
+ * or else the return value of the original function.
6790
+ */
6791
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6792
+ function throttle(
6793
+ fn,
6794
+ maxCount,
6795
+ durationSeconds,
6796
+ ) {
6797
+ const counter = new Map();
6798
+
6799
+ const _cleanup = (now) => {
6800
+ const threshold = now - durationSeconds;
6801
+ counter.forEach((_value, key) => {
6802
+ if (key < threshold) {
6803
+ counter.delete(key);
6804
+ }
6805
+ });
6806
+ };
6807
+
6808
+ const _getTotalCount = () => {
6809
+ return [...counter.values()].reduce((a, b) => a + b, 0);
6810
+ };
6811
+
6812
+ let isThrottled = false;
6813
+
6814
+ return (...rest) => {
6815
+ // Date in second-precision, which we use as basis for the throttling
6816
+ const now = Math.floor(Date.now() / 1000);
6817
+
6818
+ // First, make sure to delete any old entries
6819
+ _cleanup(now);
6820
+
6821
+ // If already over limit, do nothing
6822
+ if (_getTotalCount() >= maxCount) {
6823
+ const wasThrottled = isThrottled;
6824
+ isThrottled = true;
6825
+ return wasThrottled ? SKIPPED : THROTTLED;
6826
+ }
6827
+
6828
+ isThrottled = false;
6829
+ const count = counter.get(now) || 0;
6830
+ counter.set(now, count + 1);
6831
+
6832
+ return fn(...rest);
6833
+ };
6834
+ }
6835
+
6783
6836
  /* eslint-disable max-lines */ // TODO: We might want to split this file up
6784
6837
 
6785
6838
  /**
@@ -6802,6 +6855,11 @@ class ReplayContainer {
6802
6855
  */
6803
6856
  __init3() {this.recordingMode = 'session';}
6804
6857
 
6858
+ /**
6859
+ * The current or last active transcation.
6860
+ * This is only available when performance is enabled.
6861
+ */
6862
+
6805
6863
  /**
6806
6864
  * These are here so we can overwrite them in tests etc.
6807
6865
  * @hidden
@@ -6868,6 +6926,14 @@ class ReplayContainer {
6868
6926
  this._debouncedFlush = debounce(() => this._flush(), this._options.flushMinDelay, {
6869
6927
  maxWait: this._options.flushMaxDelay,
6870
6928
  });
6929
+
6930
+ this._throttledAddEvent = throttle(
6931
+ (event, isCheckout) => addEvent(this, event, isCheckout),
6932
+ // Max 300 events...
6933
+ 300,
6934
+ // ... per 5s
6935
+ 5,
6936
+ );
6871
6937
  }
6872
6938
 
6873
6939
  /** Get the event context. */
@@ -7112,6 +7178,8 @@ class ReplayContainer {
7112
7178
  return this.flushImmediate();
7113
7179
  }
7114
7180
 
7181
+ const activityTime = Date.now();
7182
+
7115
7183
  // Allow flush to complete before resuming as a session recording, otherwise
7116
7184
  // the checkout from `startRecording` may be included in the payload.
7117
7185
  // Prefer to keep the error replay as a separate (and smaller) segment
@@ -7133,6 +7201,18 @@ class ReplayContainer {
7133
7201
  // Once this session ends, we do not want to refresh it
7134
7202
  if (this.session) {
7135
7203
  this.session.shouldRefresh = false;
7204
+
7205
+ // It's possible that the session lifespan is > max session lifespan
7206
+ // because we have been buffering beyond max session lifespan (we ignore
7207
+ // expiration given that `shouldRefresh` is true). Since we flip
7208
+ // `shouldRefresh`, the session could be considered expired due to
7209
+ // lifespan, which is not what we want. Update session start date to be
7210
+ // the current timestamp, so that session is not considered to be
7211
+ // expired. This means that max replay duration can be MAX_SESSION_LIFE +
7212
+ // (length of buffer), which we are ok with.
7213
+ this._updateUserActivity(activityTime);
7214
+ this._updateSessionActivity(activityTime);
7215
+ this.session.started = activityTime;
7136
7216
  this._maybeSaveSession();
7137
7217
  }
7138
7218
 
@@ -7196,6 +7276,18 @@ class ReplayContainer {
7196
7276
  this._updateSessionActivity();
7197
7277
  }
7198
7278
 
7279
+ /**
7280
+ * Updates the user activity timestamp *without* resuming
7281
+ * recording. Some user events (e.g. keydown) can be create
7282
+ * low-value replays that only contain the keypress as a
7283
+ * breadcrumb. Instead this would require other events to
7284
+ * create a new replay after a session has expired.
7285
+ */
7286
+ updateUserActivity() {
7287
+ this._updateUserActivity();
7288
+ this._updateSessionActivity();
7289
+ }
7290
+
7199
7291
  /**
7200
7292
  * Only flush if `this.recordingMode === 'session'`
7201
7293
  */
@@ -7297,6 +7389,52 @@ class ReplayContainer {
7297
7389
  this._context.urls.push(url);
7298
7390
  }
7299
7391
 
7392
+ /**
7393
+ * Add a breadcrumb event, that may be throttled.
7394
+ * If it was throttled, we add a custom breadcrumb to indicate that.
7395
+ */
7396
+ throttledAddEvent(
7397
+ event,
7398
+ isCheckout,
7399
+ ) {
7400
+ const res = this._throttledAddEvent(event, isCheckout);
7401
+
7402
+ // If this is THROTTLED, it means we have throttled the event for the first time
7403
+ // In this case, we want to add a breadcrumb indicating that something was skipped
7404
+ if (res === THROTTLED) {
7405
+ const breadcrumb = createBreadcrumb({
7406
+ category: 'replay.throttled',
7407
+ });
7408
+
7409
+ this.addUpdate(() => {
7410
+ void addEvent(this, {
7411
+ type: exports.EventType.Custom,
7412
+ timestamp: breadcrumb.timestamp || 0,
7413
+ data: {
7414
+ tag: 'breadcrumb',
7415
+ payload: breadcrumb,
7416
+ metric: true,
7417
+ },
7418
+ });
7419
+ });
7420
+ }
7421
+
7422
+ return res;
7423
+ }
7424
+
7425
+ /**
7426
+ * This will get the parametrized route name of the current page.
7427
+ * This is only available if performance is enabled, and if an instrumented router is used.
7428
+ */
7429
+ getCurrentRoute() {
7430
+ const lastTransaction = this.lastTransaction || core.getCurrentHub().getScope().getTransaction();
7431
+ if (!lastTransaction || !['route', 'custom'].includes(lastTransaction.metadata.source)) {
7432
+ return undefined;
7433
+ }
7434
+
7435
+ return lastTransaction.name;
7436
+ }
7437
+
7300
7438
  /**
7301
7439
  * Initialize and start all listeners to varying events (DOM,
7302
7440
  * Performance Observer, Recording, Sentry SDK, etc)
@@ -7340,7 +7478,7 @@ class ReplayContainer {
7340
7478
  stickySession: Boolean(this._options.stickySession),
7341
7479
  currentSession: this.session,
7342
7480
  sessionSampleRate: this._options.sessionSampleRate,
7343
- allowBuffering: this._options.errorSampleRate > 0,
7481
+ allowBuffering: this._options.errorSampleRate > 0 || this.recordingMode === 'buffer',
7344
7482
  });
7345
7483
 
7346
7484
  // If session was newly created (i.e. was not loaded from storage), then
@@ -7535,8 +7673,8 @@ class ReplayContainer {
7535
7673
  */
7536
7674
  _createCustomBreadcrumb(breadcrumb) {
7537
7675
  this.addUpdate(() => {
7538
- void addEvent(this, {
7539
- type: EventType$1.Custom,
7676
+ void this.throttledAddEvent({
7677
+ type: exports.EventType.Custom,
7540
7678
  timestamp: breadcrumb.timestamp || 0,
7541
7679
  data: {
7542
7680
  tag: 'breadcrumb',
@@ -7734,8 +7872,8 @@ class ReplayContainer {
7734
7872
  __init18() {this._onMutationHandler = (mutations) => {
7735
7873
  const count = mutations.length;
7736
7874
 
7737
- const mutationLimit = this._options._experiments.mutationLimit || 0;
7738
- const mutationBreadcrumbLimit = this._options._experiments.mutationBreadcrumbLimit || 1000;
7875
+ const mutationLimit = this._options.mutationLimit;
7876
+ const mutationBreadcrumbLimit = this._options.mutationBreadcrumbLimit;
7739
7877
  const overMutationLimit = mutationLimit && count > mutationLimit;
7740
7878
 
7741
7879
  // Create a breadcrumb if a lot of mutations happen at the same time
@@ -7745,15 +7883,15 @@ class ReplayContainer {
7745
7883
  category: 'replay.mutations',
7746
7884
  data: {
7747
7885
  count,
7886
+ limit: overMutationLimit,
7748
7887
  },
7749
7888
  });
7750
7889
  this._createCustomBreadcrumb(breadcrumb);
7751
7890
  }
7752
7891
 
7892
+ // Stop replay if over the mutation limit
7753
7893
  if (overMutationLimit) {
7754
- // We want to skip doing an incremental snapshot if there are too many mutations
7755
- // Instead, we do a full snapshot
7756
- this._triggerFullSnapshot(false);
7894
+ void this.stop('mutationLimit');
7757
7895
  return false;
7758
7896
  }
7759
7897
 
@@ -7907,6 +8045,12 @@ class Replay {
7907
8045
  maskAllInputs = true,
7908
8046
  blockAllMedia = true,
7909
8047
 
8048
+ mutationBreadcrumbLimit = 750,
8049
+ mutationLimit = 10000,
8050
+
8051
+ slowClickTimeout = 7000,
8052
+ slowClickIgnoreSelectors = [],
8053
+
7910
8054
  networkDetailAllowUrls = [],
7911
8055
  networkCaptureBodies = true,
7912
8056
  networkRequestHeaders = [],
@@ -7974,6 +8118,10 @@ class Replay {
7974
8118
  blockAllMedia,
7975
8119
  maskAllInputs,
7976
8120
  maskAllText,
8121
+ mutationBreadcrumbLimit,
8122
+ mutationLimit,
8123
+ slowClickTimeout,
8124
+ slowClickIgnoreSelectors,
7977
8125
  networkDetailAllowUrls,
7978
8126
  networkCaptureBodies,
7979
8127
  networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),