@myinterview/widget-react 1.1.22-development-2bfa0c3 → 1.1.22-development-1379a04

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.
package/dist/esm/index.js CHANGED
@@ -2071,7 +2071,10 @@ function visit(
2071
2071
  const [memoize, unmemoize] = memo;
2072
2072
 
2073
2073
  // Get the simple cases out of the way first
2074
- if (value === null || (['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))) {
2074
+ if (
2075
+ value == null || // this matches null and undefined -> eqeq not eqeqeq
2076
+ (['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))
2077
+ ) {
2075
2078
  return value ;
2076
2079
  }
2077
2080
 
@@ -2209,11 +2212,6 @@ function stringifyValue(
2209
2212
  return '[NaN]';
2210
2213
  }
2211
2214
 
2212
- // this catches `undefined` (but not `null`, which is a primitive and can be serialized on its own)
2213
- if (value === void 0) {
2214
- return '[undefined]';
2215
- }
2216
-
2217
2215
  if (typeof value === 'function') {
2218
2216
  return `[Function: ${getFunctionName(value)}]`;
2219
2217
  }
@@ -5796,7 +5794,7 @@ function getEventForEnvelopeItem(item, type) {
5796
5794
  return Array.isArray(item) ? (item )[1] : undefined;
5797
5795
  }
5798
5796
 
5799
- const SDK_VERSION = '7.50.0';
5797
+ const SDK_VERSION = '7.52.1';
5800
5798
 
5801
5799
  let originalFunctionToString;
5802
5800
 
@@ -5977,8 +5975,9 @@ function _getPossibleEventMessages(event) {
5977
5975
  return [event.message];
5978
5976
  }
5979
5977
  if (event.exception) {
5978
+ const { values } = event.exception;
5980
5979
  try {
5981
- const { type = '', value = '' } = (event.exception.values && event.exception.values[0]) || {};
5980
+ const { type = '', value = '' } = (values && values[values.length - 1]) || {};
5982
5981
  return [`${value}`, `${type}: ${value}`];
5983
5982
  } catch (oO) {
5984
5983
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(`Cannot extract message for event ${getEventDescription(event)}`);
@@ -8335,7 +8334,7 @@ function maskInputValue({ input, maskInputSelector, unmaskInputSelector, maskInp
8335
8334
  if (unmaskInputSelector && input.matches(unmaskInputSelector)) {
8336
8335
  return text;
8337
8336
  }
8338
- if (input.hasAttribute('rr_is_password')) {
8337
+ if (input.hasAttribute('data-rr-is-password')) {
8339
8338
  type = 'password';
8340
8339
  }
8341
8340
  if (isInputTypeMasked({ maskInputOptions, tagName, type }) ||
@@ -8368,6 +8367,21 @@ function is2DCanvasBlank(canvas) {
8368
8367
  }
8369
8368
  return true;
8370
8369
  }
8370
+ function getInputType(element) {
8371
+ const type = element.type;
8372
+ return element.hasAttribute('data-rr-is-password')
8373
+ ? 'password'
8374
+ : type
8375
+ ? type.toLowerCase()
8376
+ : null;
8377
+ }
8378
+ function getInputValue(el, tagName, type) {
8379
+ typeof type === 'string' ? type.toLowerCase() : '';
8380
+ if (tagName === 'INPUT' && (type === 'radio' || type === 'checkbox')) {
8381
+ return el.getAttribute('value') || '';
8382
+ }
8383
+ return el.value;
8384
+ }
8371
8385
 
8372
8386
  let _id = 1;
8373
8387
  const tagNameRegex = new RegExp('[^a-z0-9-_:]');
@@ -8406,6 +8420,13 @@ function getCssRuleString(rule) {
8406
8420
  catch (_a) {
8407
8421
  }
8408
8422
  }
8423
+ return validateStringifiedCssRule(cssStringified);
8424
+ }
8425
+ function validateStringifiedCssRule(cssStringified) {
8426
+ if (cssStringified.indexOf(':') > -1) {
8427
+ const regex = /(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;
8428
+ return cssStringified.replace(regex, '$1\\$2');
8429
+ }
8409
8430
  return cssStringified;
8410
8431
  }
8411
8432
  function isCSSImportRule(rule) {
@@ -8414,7 +8435,7 @@ function isCSSImportRule(rule) {
8414
8435
  function stringifyStyleSheet(sheet) {
8415
8436
  return sheet.cssRules
8416
8437
  ? Array.from(sheet.cssRules)
8417
- .map((rule) => rule.cssText || '')
8438
+ .map((rule) => rule.cssText ? validateStringifiedCssRule(rule.cssText) : '')
8418
8439
  .join('')
8419
8440
  : '';
8420
8441
  }
@@ -8749,14 +8770,15 @@ function serializeNode(n, options) {
8749
8770
  tagName === 'select' ||
8750
8771
  tagName === 'option') {
8751
8772
  const el = n;
8752
- const value = getInputValue(tagName, el, attributes);
8773
+ const type = getInputType(el);
8774
+ const value = getInputValue(el, tagName.toUpperCase(), type);
8753
8775
  const checked = n.checked;
8754
- if (attributes.type !== 'submit' &&
8755
- attributes.type !== 'button' &&
8776
+ if (type !== 'submit' &&
8777
+ type !== 'button' &&
8756
8778
  value) {
8757
8779
  attributes.value = maskInputValue({
8758
8780
  input: el,
8759
- type: attributes.type,
8781
+ type,
8760
8782
  tagName,
8761
8783
  value,
8762
8784
  maskInputSelector,
@@ -9229,15 +9251,8 @@ function snapshot(n, options) {
9229
9251
  function skipAttribute(tagName, attributeName, value) {
9230
9252
  return ((tagName === 'video' || tagName === 'audio') && attributeName === 'autoplay');
9231
9253
  }
9232
- function getInputValue(tagName, el, attributes) {
9233
- if (tagName === 'input' &&
9234
- (attributes.type === 'radio' || attributes.type === 'checkbox')) {
9235
- return el.getAttribute('value') || '';
9236
- }
9237
- return el.value;
9238
- }
9239
9254
 
9240
- var EventType;
9255
+ var EventType$1;
9241
9256
  (function (EventType) {
9242
9257
  EventType[EventType["DomContentLoaded"] = 0] = "DomContentLoaded";
9243
9258
  EventType[EventType["Load"] = 1] = "Load";
@@ -9246,7 +9261,7 @@ var EventType;
9246
9261
  EventType[EventType["Meta"] = 4] = "Meta";
9247
9262
  EventType[EventType["Custom"] = 5] = "Custom";
9248
9263
  EventType[EventType["Plugin"] = 6] = "Plugin";
9249
- })(EventType || (EventType = {}));
9264
+ })(EventType$1 || (EventType$1 = {}));
9250
9265
  var IncrementalSource;
9251
9266
  (function (IncrementalSource) {
9252
9267
  IncrementalSource[IncrementalSource["Mutation"] = 0] = "Mutation";
@@ -9861,9 +9876,9 @@ class MutationBuffer {
9861
9876
  this.attributes.push(item);
9862
9877
  }
9863
9878
  if (m.attributeName === 'type' &&
9864
- m.target.tagName === 'INPUT' &&
9879
+ target.tagName === 'INPUT' &&
9865
9880
  (m.oldValue || '').toLowerCase() === 'password') {
9866
- m.target.setAttribute('rr_is_password', 'true');
9881
+ target.setAttribute('data-rr-is-password', 'true');
9867
9882
  }
9868
9883
  if (m.attributeName === 'style') {
9869
9884
  const old = this.doc.createElement('span');
@@ -10260,27 +10275,25 @@ function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, un
10260
10275
  isBlocked(target, blockClass, blockSelector, unblockSelector)) {
10261
10276
  return;
10262
10277
  }
10263
- let type = target.type;
10264
- if (target.classList.contains(ignoreClass) ||
10265
- (ignoreSelector && target.matches(ignoreSelector))) {
10278
+ const el = target;
10279
+ const type = getInputType(el);
10280
+ if (el.classList.contains(ignoreClass) ||
10281
+ (ignoreSelector && el.matches(ignoreSelector))) {
10266
10282
  return;
10267
10283
  }
10268
- let text = target.value;
10284
+ let text = getInputValue(el, tagName, type);
10269
10285
  let isChecked = false;
10270
- if (target.hasAttribute('rr_is_password')) {
10271
- type = 'password';
10272
- }
10273
10286
  if (type === 'radio' || type === 'checkbox') {
10274
10287
  isChecked = target.checked;
10275
10288
  }
10276
- else if (hasInputMaskOptions({
10289
+ if (hasInputMaskOptions({
10277
10290
  maskInputOptions,
10278
10291
  maskInputSelector,
10279
10292
  tagName,
10280
10293
  type,
10281
10294
  })) {
10282
10295
  text = maskInputValue({
10283
- input: target,
10296
+ input: el,
10284
10297
  maskInputOptions,
10285
10298
  maskInputSelector,
10286
10299
  unmaskInputSelector,
@@ -10297,8 +10310,18 @@ function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, un
10297
10310
  .querySelectorAll(`input[type="radio"][name="${name}"]`)
10298
10311
  .forEach((el) => {
10299
10312
  if (el !== target) {
10313
+ const text = maskInputValue({
10314
+ input: el,
10315
+ maskInputOptions,
10316
+ maskInputSelector,
10317
+ unmaskInputSelector,
10318
+ tagName,
10319
+ type,
10320
+ value: getInputValue(el, tagName, type),
10321
+ maskInputFn,
10322
+ });
10300
10323
  cbWithDedup(el, callbackWrapper(wrapEventWithUserTriggeredFlag)({
10301
- text: el.value,
10324
+ text,
10302
10325
  isChecked: !isChecked,
10303
10326
  userTriggered: false,
10304
10327
  }, userTriggeredOnInput));
@@ -11211,17 +11234,17 @@ function record(options = {}) {
11211
11234
  wrappedEmit = (e, isCheckout) => {
11212
11235
  var _a;
11213
11236
  if (((_a = mutationBuffers[0]) === null || _a === void 0 ? void 0 : _a.isFrozen()) &&
11214
- e.type !== EventType.FullSnapshot &&
11215
- !(e.type === EventType.IncrementalSnapshot &&
11237
+ e.type !== EventType$1.FullSnapshot &&
11238
+ !(e.type === EventType$1.IncrementalSnapshot &&
11216
11239
  e.data.source === IncrementalSource.Mutation)) {
11217
11240
  mutationBuffers.forEach((buf) => buf.unfreeze());
11218
11241
  }
11219
11242
  emit(eventProcessor(e), isCheckout);
11220
- if (e.type === EventType.FullSnapshot) {
11243
+ if (e.type === EventType$1.FullSnapshot) {
11221
11244
  lastFullSnapshotEvent = e;
11222
11245
  incrementalSnapshotCount = 0;
11223
11246
  }
11224
- else if (e.type === EventType.IncrementalSnapshot) {
11247
+ else if (e.type === EventType$1.IncrementalSnapshot) {
11225
11248
  if (e.data.source === IncrementalSource.Mutation &&
11226
11249
  e.data.isAttachIframe) {
11227
11250
  return;
@@ -11237,16 +11260,16 @@ function record(options = {}) {
11237
11260
  };
11238
11261
  const wrappedMutationEmit = (m) => {
11239
11262
  wrappedEmit(wrapEvent({
11240
- type: EventType.IncrementalSnapshot,
11263
+ type: EventType$1.IncrementalSnapshot,
11241
11264
  data: Object.assign({ source: IncrementalSource.Mutation }, m),
11242
11265
  }));
11243
11266
  };
11244
11267
  const wrappedScrollEmit = (p) => wrappedEmit(wrapEvent({
11245
- type: EventType.IncrementalSnapshot,
11268
+ type: EventType$1.IncrementalSnapshot,
11246
11269
  data: Object.assign({ source: IncrementalSource.Scroll }, p),
11247
11270
  }));
11248
11271
  const wrappedCanvasMutationEmit = (p) => wrappedEmit(wrapEvent({
11249
- type: EventType.IncrementalSnapshot,
11272
+ type: EventType$1.IncrementalSnapshot,
11250
11273
  data: Object.assign({ source: IncrementalSource.CanvasMutation }, p),
11251
11274
  }));
11252
11275
  const iframeManager = new IframeManager({
@@ -11291,7 +11314,7 @@ function record(options = {}) {
11291
11314
  takeFullSnapshot = (isCheckout = false) => {
11292
11315
  var _a, _b, _c, _d;
11293
11316
  wrappedEmit(wrapEvent({
11294
- type: EventType.Meta,
11317
+ type: EventType$1.Meta,
11295
11318
  data: {
11296
11319
  href: window.location.href,
11297
11320
  width: getWindowWidth(),
@@ -11334,7 +11357,7 @@ function record(options = {}) {
11334
11357
  }
11335
11358
  mirror.map = idNodeMap;
11336
11359
  wrappedEmit(wrapEvent({
11337
- type: EventType.FullSnapshot,
11360
+ type: EventType$1.FullSnapshot,
11338
11361
  data: {
11339
11362
  node,
11340
11363
  initialOffset: {
@@ -11359,7 +11382,7 @@ function record(options = {}) {
11359
11382
  const handlers = [];
11360
11383
  handlers.push(on$1('DOMContentLoaded', () => {
11361
11384
  wrappedEmit(wrapEvent({
11362
- type: EventType.DomContentLoaded,
11385
+ type: EventType$1.DomContentLoaded,
11363
11386
  data: {},
11364
11387
  }));
11365
11388
  }));
@@ -11369,40 +11392,40 @@ function record(options = {}) {
11369
11392
  onMutation,
11370
11393
  mutationCb: wrappedMutationEmit,
11371
11394
  mousemoveCb: (positions, source) => wrappedEmit(wrapEvent({
11372
- type: EventType.IncrementalSnapshot,
11395
+ type: EventType$1.IncrementalSnapshot,
11373
11396
  data: {
11374
11397
  source,
11375
11398
  positions,
11376
11399
  },
11377
11400
  })),
11378
11401
  mouseInteractionCb: (d) => wrappedEmit(wrapEvent({
11379
- type: EventType.IncrementalSnapshot,
11402
+ type: EventType$1.IncrementalSnapshot,
11380
11403
  data: Object.assign({ source: IncrementalSource.MouseInteraction }, d),
11381
11404
  })),
11382
11405
  scrollCb: wrappedScrollEmit,
11383
11406
  viewportResizeCb: (d) => wrappedEmit(wrapEvent({
11384
- type: EventType.IncrementalSnapshot,
11407
+ type: EventType$1.IncrementalSnapshot,
11385
11408
  data: Object.assign({ source: IncrementalSource.ViewportResize }, d),
11386
11409
  })),
11387
11410
  inputCb: (v) => wrappedEmit(wrapEvent({
11388
- type: EventType.IncrementalSnapshot,
11411
+ type: EventType$1.IncrementalSnapshot,
11389
11412
  data: Object.assign({ source: IncrementalSource.Input }, v),
11390
11413
  })),
11391
11414
  mediaInteractionCb: (p) => wrappedEmit(wrapEvent({
11392
- type: EventType.IncrementalSnapshot,
11415
+ type: EventType$1.IncrementalSnapshot,
11393
11416
  data: Object.assign({ source: IncrementalSource.MediaInteraction }, p),
11394
11417
  })),
11395
11418
  styleSheetRuleCb: (r) => wrappedEmit(wrapEvent({
11396
- type: EventType.IncrementalSnapshot,
11419
+ type: EventType$1.IncrementalSnapshot,
11397
11420
  data: Object.assign({ source: IncrementalSource.StyleSheetRule }, r),
11398
11421
  })),
11399
11422
  styleDeclarationCb: (r) => wrappedEmit(wrapEvent({
11400
- type: EventType.IncrementalSnapshot,
11423
+ type: EventType$1.IncrementalSnapshot,
11401
11424
  data: Object.assign({ source: IncrementalSource.StyleDeclaration }, r),
11402
11425
  })),
11403
11426
  canvasMutationCb: wrappedCanvasMutationEmit,
11404
11427
  fontCb: (p) => wrappedEmit(wrapEvent({
11405
- type: EventType.IncrementalSnapshot,
11428
+ type: EventType$1.IncrementalSnapshot,
11406
11429
  data: Object.assign({ source: IncrementalSource.Font }, p),
11407
11430
  })),
11408
11431
  blockClass,
@@ -11435,7 +11458,7 @@ function record(options = {}) {
11435
11458
  observer: p.observer,
11436
11459
  options: p.options,
11437
11460
  callback: (payload) => wrappedEmit(wrapEvent({
11438
- type: EventType.Plugin,
11461
+ type: EventType$1.Plugin,
11439
11462
  data: {
11440
11463
  plugin: p.name,
11441
11464
  payload,
@@ -11463,7 +11486,7 @@ function record(options = {}) {
11463
11486
  else {
11464
11487
  handlers.push(on$1('load', () => {
11465
11488
  wrappedEmit(wrapEvent({
11466
- type: EventType.Load,
11489
+ type: EventType$1.Load,
11467
11490
  data: {},
11468
11491
  }));
11469
11492
  init();
@@ -11482,7 +11505,7 @@ record.addCustomEvent = (tag, payload) => {
11482
11505
  throw new Error('please add custom event after start recording');
11483
11506
  }
11484
11507
  wrappedEmit(wrapEvent({
11485
- type: EventType.Custom,
11508
+ type: EventType$1.Custom,
11486
11509
  data: {
11487
11510
  tag,
11488
11511
  payload,
@@ -11500,6 +11523,475 @@ record.takeFullSnapshot = (isCheckout) => {
11500
11523
  };
11501
11524
  record.mirror = mirror;
11502
11525
 
11526
+ /**
11527
+ * Create a breadcrumb for a replay.
11528
+ */
11529
+ function createBreadcrumb(
11530
+ breadcrumb,
11531
+ ) {
11532
+ return {
11533
+ timestamp: Date.now() / 1000,
11534
+ type: 'default',
11535
+ ...breadcrumb,
11536
+ };
11537
+ }
11538
+
11539
+ var NodeType;
11540
+ (function (NodeType) {
11541
+ NodeType[NodeType["Document"] = 0] = "Document";
11542
+ NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
11543
+ NodeType[NodeType["Element"] = 2] = "Element";
11544
+ NodeType[NodeType["Text"] = 3] = "Text";
11545
+ NodeType[NodeType["CDATA"] = 4] = "CDATA";
11546
+ NodeType[NodeType["Comment"] = 5] = "Comment";
11547
+ })(NodeType || (NodeType = {}));
11548
+
11549
+ /**
11550
+ * Converts a timestamp to ms, if it was in s, or keeps it as ms.
11551
+ */
11552
+ function timestampToMs(timestamp) {
11553
+ const isMs = timestamp > 9999999999;
11554
+ return isMs ? timestamp : timestamp * 1000;
11555
+ }
11556
+
11557
+ /**
11558
+ * Add an event to the event buffer.
11559
+ * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
11560
+ */
11561
+ async function addEvent(
11562
+ replay,
11563
+ event,
11564
+ isCheckout,
11565
+ ) {
11566
+ if (!replay.eventBuffer) {
11567
+ // This implies that `_isEnabled` is false
11568
+ return null;
11569
+ }
11570
+
11571
+ if (replay.isPaused()) {
11572
+ // Do not add to event buffer when recording is paused
11573
+ return null;
11574
+ }
11575
+
11576
+ const timestampInMs = timestampToMs(event.timestamp);
11577
+
11578
+ // Throw out events that happen more than 5 minutes ago. This can happen if
11579
+ // page has been left open and idle for a long period of time and user
11580
+ // comes back to trigger a new session. The performance entries rely on
11581
+ // `performance.timeOrigin`, which is when the page first opened.
11582
+ if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
11583
+ return null;
11584
+ }
11585
+
11586
+ try {
11587
+ if (isCheckout) {
11588
+ replay.eventBuffer.clear();
11589
+ }
11590
+
11591
+ return await replay.eventBuffer.addEvent(event);
11592
+ } catch (error) {
11593
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
11594
+ await replay.stop('addEvent');
11595
+
11596
+ const client = getCurrentHub().getClient();
11597
+
11598
+ if (client) {
11599
+ client.recordDroppedEvent('internal_sdk_error', 'replay');
11600
+ }
11601
+ }
11602
+ }
11603
+
11604
+ /**
11605
+ * Add a breadcrumb event to replay.
11606
+ */
11607
+ function addBreadcrumbEvent(replay, breadcrumb) {
11608
+ if (breadcrumb.category === 'sentry.transaction') {
11609
+ return;
11610
+ }
11611
+
11612
+ if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
11613
+ replay.triggerUserActivity();
11614
+ } else {
11615
+ replay.checkAndHandleExpiredSession();
11616
+ }
11617
+
11618
+ replay.addUpdate(() => {
11619
+ void addEvent(replay, {
11620
+ type: EventType$1.Custom,
11621
+ // TODO: We were converting from ms to seconds for breadcrumbs, spans,
11622
+ // but maybe we should just keep them as milliseconds
11623
+ timestamp: (breadcrumb.timestamp || 0) * 1000,
11624
+ data: {
11625
+ tag: 'breadcrumb',
11626
+ // normalize to max. 10 depth and 1_000 properties per object
11627
+ payload: normalize(breadcrumb, 10, 1000),
11628
+ },
11629
+ });
11630
+
11631
+ // Do not flush after console log messages
11632
+ return breadcrumb.category === 'console';
11633
+ });
11634
+ }
11635
+
11636
+ /**
11637
+ * Detect a slow click on a button/a tag,
11638
+ * and potentially create a corresponding breadcrumb.
11639
+ */
11640
+ function detectSlowClick(
11641
+ replay,
11642
+ config,
11643
+ clickBreadcrumb,
11644
+ node,
11645
+ ) {
11646
+ if (ignoreElement(node, config)) {
11647
+ return;
11648
+ }
11649
+
11650
+ /*
11651
+ We consider a slow click a click on a button/a, which does not trigger one of:
11652
+ - DOM mutation
11653
+ - Scroll (within 100ms)
11654
+ Within the given threshold time.
11655
+ After time timeout time, we stop listening and mark it as a slow click anyhow.
11656
+ */
11657
+
11658
+ let cleanup = () => {
11659
+ // replaced further down
11660
+ };
11661
+
11662
+ // After timeout time, def. consider this a slow click, and stop watching for mutations
11663
+ const timeout = setTimeout(() => {
11664
+ handleSlowClick(replay, clickBreadcrumb, config.timeout, 'timeout');
11665
+ cleanup();
11666
+ }, config.timeout);
11667
+
11668
+ const mutationHandler = () => {
11669
+ maybeHandleSlowClick(replay, clickBreadcrumb, config.threshold, config.timeout, 'mutation');
11670
+ cleanup();
11671
+ };
11672
+
11673
+ const scrollHandler = () => {
11674
+ maybeHandleSlowClick(replay, clickBreadcrumb, config.scrollTimeout, config.timeout, 'scroll');
11675
+ cleanup();
11676
+ };
11677
+
11678
+ const obs = new MutationObserver(mutationHandler);
11679
+
11680
+ obs.observe(WINDOW.document.documentElement, {
11681
+ attributes: true,
11682
+ characterData: true,
11683
+ childList: true,
11684
+ subtree: true,
11685
+ });
11686
+
11687
+ WINDOW.addEventListener('scroll', scrollHandler);
11688
+
11689
+ // Stop listening to scroll timeouts early
11690
+ const scrollTimeout = setTimeout(() => {
11691
+ WINDOW.removeEventListener('scroll', scrollHandler);
11692
+ }, config.scrollTimeout);
11693
+
11694
+ cleanup = () => {
11695
+ clearTimeout(timeout);
11696
+ clearTimeout(scrollTimeout);
11697
+ obs.disconnect();
11698
+ WINDOW.removeEventListener('scroll', scrollHandler);
11699
+ };
11700
+ }
11701
+
11702
+ function maybeHandleSlowClick(
11703
+ replay,
11704
+ clickBreadcrumb,
11705
+ threshold,
11706
+ timeout,
11707
+ endReason,
11708
+ ) {
11709
+ const now = Date.now();
11710
+ const timeAfterClickMs = now - clickBreadcrumb.timestamp * 1000;
11711
+
11712
+ if (timeAfterClickMs > threshold) {
11713
+ handleSlowClick(replay, clickBreadcrumb, Math.min(timeAfterClickMs, timeout), endReason);
11714
+ return true;
11715
+ }
11716
+
11717
+ return false;
11718
+ }
11719
+
11720
+ function handleSlowClick(
11721
+ replay,
11722
+ clickBreadcrumb,
11723
+ timeAfterClickMs,
11724
+ endReason,
11725
+ ) {
11726
+ const breadcrumb = {
11727
+ message: clickBreadcrumb.message,
11728
+ timestamp: clickBreadcrumb.timestamp,
11729
+ category: 'ui.slowClickDetected',
11730
+ data: {
11731
+ ...clickBreadcrumb.data,
11732
+ url: WINDOW.location.href,
11733
+ // TODO FN: add parametrized route, when possible
11734
+ timeAfterClickMs,
11735
+ endReason,
11736
+ },
11737
+ };
11738
+
11739
+ addBreadcrumbEvent(replay, breadcrumb);
11740
+ }
11741
+
11742
+ const SLOW_CLICK_IGNORE_TAGS = ['SELECT', 'OPTION'];
11743
+
11744
+ function ignoreElement(node, config) {
11745
+ // If <input> tag, we only want to consider input[type='submit'] & input[type='button']
11746
+ if (node.tagName === 'INPUT' && !['submit', 'button'].includes(node.getAttribute('type') || '')) {
11747
+ return true;
11748
+ }
11749
+
11750
+ if (SLOW_CLICK_IGNORE_TAGS.includes(node.tagName)) {
11751
+ return true;
11752
+ }
11753
+
11754
+ // If <a> tag, detect special variants that may not lead to an action
11755
+ // If target !== _self, we may open the link somewhere else, which would lead to no action
11756
+ // Also, when downloading a file, we may not leave the page, but still not trigger an action
11757
+ if (
11758
+ node.tagName === 'A' &&
11759
+ (node.hasAttribute('download') || (node.hasAttribute('target') && node.getAttribute('target') !== '_self'))
11760
+ ) {
11761
+ return true;
11762
+ }
11763
+
11764
+ if (config.ignoreSelector && node.matches(config.ignoreSelector)) {
11765
+ return true;
11766
+ }
11767
+
11768
+ return false;
11769
+ }
11770
+
11771
+ // Note that these are the serialized attributes and not attributes directly on
11772
+ // the DOM Node. Attributes we are interested in:
11773
+ const ATTRIBUTES_TO_RECORD = new Set([
11774
+ 'id',
11775
+ 'class',
11776
+ 'aria-label',
11777
+ 'role',
11778
+ 'name',
11779
+ 'alt',
11780
+ 'title',
11781
+ 'data-test-id',
11782
+ 'data-testid',
11783
+ ]);
11784
+
11785
+ /**
11786
+ * Inclusion list of attributes that we want to record from the DOM element
11787
+ */
11788
+ function getAttributesToRecord(attributes) {
11789
+ const obj = {};
11790
+ for (const key in attributes) {
11791
+ if (ATTRIBUTES_TO_RECORD.has(key)) {
11792
+ let normalizedKey = key;
11793
+
11794
+ if (key === 'data-testid' || key === 'data-test-id') {
11795
+ normalizedKey = 'testId';
11796
+ }
11797
+
11798
+ obj[normalizedKey] = attributes[key];
11799
+ }
11800
+ }
11801
+
11802
+ return obj;
11803
+ }
11804
+
11805
+ const handleDomListener = (
11806
+ replay,
11807
+ ) => {
11808
+ const slowClickExperiment = replay.getOptions()._experiments.slowClicks;
11809
+
11810
+ const slowClickConfig = slowClickExperiment
11811
+ ? {
11812
+ threshold: slowClickExperiment.threshold,
11813
+ timeout: slowClickExperiment.timeout,
11814
+ scrollTimeout: slowClickExperiment.scrollTimeout,
11815
+ ignoreSelector: slowClickExperiment.ignoreSelectors ? slowClickExperiment.ignoreSelectors.join(',') : '',
11816
+ }
11817
+ : undefined;
11818
+
11819
+ return (handlerData) => {
11820
+ if (!replay.isEnabled()) {
11821
+ return;
11822
+ }
11823
+
11824
+ const result = handleDom(handlerData);
11825
+
11826
+ if (!result) {
11827
+ return;
11828
+ }
11829
+
11830
+ const isClick = handlerData.name === 'click';
11831
+ const event = isClick && (handlerData.event );
11832
+ // Ignore clicks if ctrl/alt/meta keys are held down as they alter behavior of clicks (e.g. open in new tab)
11833
+ if (isClick && slowClickConfig && event && !event.altKey && !event.metaKey && !event.ctrlKey) {
11834
+ detectSlowClick(
11835
+ replay,
11836
+ slowClickConfig,
11837
+ result ,
11838
+ getClickTargetNode(handlerData.event) ,
11839
+ );
11840
+ }
11841
+
11842
+ addBreadcrumbEvent(replay, result);
11843
+ };
11844
+ };
11845
+
11846
+ /** Get the base DOM breadcrumb. */
11847
+ function getBaseDomBreadcrumb(target, message) {
11848
+ // `__sn` property is the serialized node created by rrweb
11849
+ const serializedNode = target && isRrwebNode(target) && target.__sn.type === NodeType.Element ? target.__sn : null;
11850
+
11851
+ return {
11852
+ message,
11853
+ data: serializedNode
11854
+ ? {
11855
+ nodeId: serializedNode.id,
11856
+ node: {
11857
+ id: serializedNode.id,
11858
+ tagName: serializedNode.tagName,
11859
+ textContent: target
11860
+ ? Array.from(target.childNodes)
11861
+ .map(
11862
+ (node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
11863
+ )
11864
+ .filter(Boolean) // filter out empty values
11865
+ .map(text => (text ).trim())
11866
+ .join('')
11867
+ : '',
11868
+ attributes: getAttributesToRecord(serializedNode.attributes),
11869
+ },
11870
+ }
11871
+ : {},
11872
+ };
11873
+ }
11874
+
11875
+ /**
11876
+ * An event handler to react to DOM events.
11877
+ * Exported for tests.
11878
+ */
11879
+ function handleDom(handlerData) {
11880
+ const { target, message } = getDomTarget(handlerData);
11881
+
11882
+ return createBreadcrumb({
11883
+ category: `ui.${handlerData.name}`,
11884
+ ...getBaseDomBreadcrumb(target, message),
11885
+ });
11886
+ }
11887
+
11888
+ function getDomTarget(handlerData) {
11889
+ const isClick = handlerData.name === 'click';
11890
+
11891
+ let message;
11892
+ let target = null;
11893
+
11894
+ // Accessing event.target can throw (see getsentry/raven-js#838, #768)
11895
+ try {
11896
+ target = isClick ? getClickTargetNode(handlerData.event) : getTargetNode(handlerData.event);
11897
+ message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
11898
+ } catch (e) {
11899
+ message = '<unknown>';
11900
+ }
11901
+
11902
+ return { target, message };
11903
+ }
11904
+
11905
+ function isRrwebNode(node) {
11906
+ return '__sn' in node;
11907
+ }
11908
+
11909
+ function getTargetNode(event) {
11910
+ if (isEventWithTarget(event)) {
11911
+ return event.target ;
11912
+ }
11913
+
11914
+ return event;
11915
+ }
11916
+
11917
+ const INTERACTIVE_SELECTOR = 'button,a';
11918
+
11919
+ // For clicks, we check if the target is inside of a button or link
11920
+ // If so, we use this as the target instead
11921
+ // This is useful because if you click on the image in <button><img></button>,
11922
+ // The target will be the image, not the button, which we don't want here
11923
+ function getClickTargetNode(event) {
11924
+ const target = getTargetNode(event);
11925
+
11926
+ if (!target || !(target instanceof Element)) {
11927
+ return target;
11928
+ }
11929
+
11930
+ const closestInteractive = target.closest(INTERACTIVE_SELECTOR);
11931
+ return closestInteractive || target;
11932
+ }
11933
+
11934
+ function isEventWithTarget(event) {
11935
+ return typeof event === 'object' && !!event && 'target' in event;
11936
+ }
11937
+
11938
+ /** Handle keyboard events & create breadcrumbs. */
11939
+ function handleKeyboardEvent(replay, event) {
11940
+ if (!replay.isEnabled()) {
11941
+ return;
11942
+ }
11943
+
11944
+ replay.triggerUserActivity();
11945
+
11946
+ const breadcrumb = getKeyboardBreadcrumb(event);
11947
+
11948
+ if (!breadcrumb) {
11949
+ return;
11950
+ }
11951
+
11952
+ addBreadcrumbEvent(replay, breadcrumb);
11953
+ }
11954
+
11955
+ /** exported only for tests */
11956
+ function getKeyboardBreadcrumb(event) {
11957
+ const { metaKey, shiftKey, ctrlKey, altKey, key, target } = event;
11958
+
11959
+ // never capture for input fields
11960
+ if (!target || isInputElement(target )) {
11961
+ return null;
11962
+ }
11963
+
11964
+ // Note: We do not consider shift here, as that means "uppercase"
11965
+ const hasModifierKey = metaKey || ctrlKey || altKey;
11966
+ const isCharacterKey = key.length === 1; // other keys like Escape, Tab, etc have a longer length
11967
+
11968
+ // Do not capture breadcrumb if only a word key is pressed
11969
+ // This could leak e.g. user input
11970
+ if (!hasModifierKey && isCharacterKey) {
11971
+ return null;
11972
+ }
11973
+
11974
+ const message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
11975
+ const baseBreadcrumb = getBaseDomBreadcrumb(target , message);
11976
+
11977
+ return createBreadcrumb({
11978
+ category: 'ui.keyDown',
11979
+ message,
11980
+ data: {
11981
+ ...baseBreadcrumb.data,
11982
+ metaKey,
11983
+ shiftKey,
11984
+ ctrlKey,
11985
+ altKey,
11986
+ key,
11987
+ },
11988
+ });
11989
+ }
11990
+
11991
+ function isInputElement(target) {
11992
+ return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
11993
+ }
11994
+
11503
11995
  const NAVIGATION_ENTRY_KEYS = [
11504
11996
  'name',
11505
11997
  'type',
@@ -11653,20 +12145,19 @@ class EventBufferArray {
11653
12145
  return this.events.length > 0;
11654
12146
  }
11655
12147
 
12148
+ /** @inheritdoc */
12149
+ get type() {
12150
+ return 'sync';
12151
+ }
12152
+
11656
12153
  /** @inheritdoc */
11657
12154
  destroy() {
11658
12155
  this.events = [];
11659
12156
  }
11660
12157
 
11661
12158
  /** @inheritdoc */
11662
- async addEvent(event, isCheckout) {
11663
- if (isCheckout) {
11664
- this.events = [event];
11665
- return;
11666
- }
11667
-
12159
+ async addEvent(event) {
11668
12160
  this.events.push(event);
11669
- return;
11670
12161
  }
11671
12162
 
11672
12163
  /** @inheritdoc */
@@ -11680,6 +12171,22 @@ class EventBufferArray {
11680
12171
  resolve(JSON.stringify(eventsRet));
11681
12172
  });
11682
12173
  }
12174
+
12175
+ /** @inheritdoc */
12176
+ clear() {
12177
+ this.events = [];
12178
+ }
12179
+
12180
+ /** @inheritdoc */
12181
+ getEarliestTimestamp() {
12182
+ const timestamp = this.events.map(event => event.timestamp).sort()[0];
12183
+
12184
+ if (!timestamp) {
12185
+ return null;
12186
+ }
12187
+
12188
+ return timestampToMs(timestamp);
12189
+ }
11683
12190
  }
11684
12191
 
11685
12192
  /**
@@ -11787,11 +12294,20 @@ class WorkerHandler {
11787
12294
  * Exported only for testing.
11788
12295
  */
11789
12296
  class EventBufferCompressionWorker {
11790
- /** @inheritdoc */
11791
12297
 
11792
12298
  constructor(worker) {
11793
12299
  this._worker = new WorkerHandler(worker);
11794
- this.hasEvents = false;
12300
+ this._earliestTimestamp = null;
12301
+ }
12302
+
12303
+ /** @inheritdoc */
12304
+ get hasEvents() {
12305
+ return !!this._earliestTimestamp;
12306
+ }
12307
+
12308
+ /** @inheritdoc */
12309
+ get type() {
12310
+ return 'worker';
11795
12311
  }
11796
12312
 
11797
12313
  /**
@@ -11814,13 +12330,10 @@ class EventBufferCompressionWorker {
11814
12330
  *
11815
12331
  * Returns true if event was successfuly received and processed by worker.
11816
12332
  */
11817
- async addEvent(event, isCheckout) {
11818
- this.hasEvents = true;
11819
-
11820
- if (isCheckout) {
11821
- // This event is a checkout, make sure worker buffer is cleared before
11822
- // proceeding.
11823
- await this._clear();
12333
+ addEvent(event) {
12334
+ const timestamp = timestampToMs(event.timestamp);
12335
+ if (!this._earliestTimestamp || timestamp < this._earliestTimestamp) {
12336
+ this._earliestTimestamp = timestamp;
11824
12337
  }
11825
12338
 
11826
12339
  return this._sendEventToWorker(event);
@@ -11833,6 +12346,18 @@ class EventBufferCompressionWorker {
11833
12346
  return this._finishRequest();
11834
12347
  }
11835
12348
 
12349
+ /** @inheritdoc */
12350
+ clear() {
12351
+ this._earliestTimestamp = null;
12352
+ // We do not wait on this, as we assume the order of messages is consistent for the worker
12353
+ void this._worker.postMessage('clear');
12354
+ }
12355
+
12356
+ /** @inheritdoc */
12357
+ getEarliestTimestamp() {
12358
+ return this._earliestTimestamp;
12359
+ }
12360
+
11836
12361
  /**
11837
12362
  * Send the event to the worker.
11838
12363
  */
@@ -11846,15 +12371,10 @@ class EventBufferCompressionWorker {
11846
12371
  async _finishRequest() {
11847
12372
  const response = await this._worker.postMessage('finish');
11848
12373
 
11849
- this.hasEvents = false;
12374
+ this._earliestTimestamp = null;
11850
12375
 
11851
12376
  return response;
11852
12377
  }
11853
-
11854
- /** Clear any pending events from the worker. */
11855
- _clear() {
11856
- return this._worker.postMessage('clear');
11857
- }
11858
12378
  }
11859
12379
 
11860
12380
  /**
@@ -11872,6 +12392,11 @@ class EventBufferProxy {
11872
12392
  this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded();
11873
12393
  }
11874
12394
 
12395
+ /** @inheritdoc */
12396
+ get type() {
12397
+ return this._used.type;
12398
+ }
12399
+
11875
12400
  /** @inheritDoc */
11876
12401
  get hasEvents() {
11877
12402
  return this._used.hasEvents;
@@ -11883,13 +12408,23 @@ class EventBufferProxy {
11883
12408
  this._compression.destroy();
11884
12409
  }
11885
12410
 
12411
+ /** @inheritdoc */
12412
+ clear() {
12413
+ return this._used.clear();
12414
+ }
12415
+
12416
+ /** @inheritdoc */
12417
+ getEarliestTimestamp() {
12418
+ return this._used.getEarliestTimestamp();
12419
+ }
12420
+
11886
12421
  /**
11887
12422
  * Add an event to the event buffer.
11888
12423
  *
11889
12424
  * Returns true if event was successfully added.
11890
12425
  */
11891
- addEvent(event, isCheckout) {
11892
- return this._used.addEvent(event, isCheckout);
12426
+ addEvent(event) {
12427
+ return this._used.addEvent(event);
11893
12428
  }
11894
12429
 
11895
12430
  /** @inheritDoc */
@@ -12171,59 +12706,6 @@ function getSession({
12171
12706
  return { type: 'new', session: newSession };
12172
12707
  }
12173
12708
 
12174
- /**
12175
- * Add an event to the event buffer.
12176
- * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
12177
- */
12178
- async function addEvent(
12179
- replay,
12180
- event,
12181
- isCheckout,
12182
- ) {
12183
- if (!replay.eventBuffer) {
12184
- // This implies that `_isEnabled` is false
12185
- return null;
12186
- }
12187
-
12188
- if (replay.isPaused()) {
12189
- // Do not add to event buffer when recording is paused
12190
- return null;
12191
- }
12192
-
12193
- // TODO: sadness -- we will want to normalize timestamps to be in ms -
12194
- // requires coordination with frontend
12195
- const isMs = event.timestamp > 9999999999;
12196
- const timestampInMs = isMs ? event.timestamp : event.timestamp * 1000;
12197
-
12198
- // Throw out events that happen more than 5 minutes ago. This can happen if
12199
- // page has been left open and idle for a long period of time and user
12200
- // comes back to trigger a new session. The performance entries rely on
12201
- // `performance.timeOrigin`, which is when the page first opened.
12202
- if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
12203
- return null;
12204
- }
12205
-
12206
- // Only record earliest event if a new session was created, otherwise it
12207
- // shouldn't be relevant
12208
- const earliestEvent = replay.getContext().earliestEvent;
12209
- if (replay.session && replay.session.segmentId === 0 && (!earliestEvent || timestampInMs < earliestEvent)) {
12210
- replay.getContext().earliestEvent = timestampInMs;
12211
- }
12212
-
12213
- try {
12214
- return await replay.eventBuffer.addEvent(event, isCheckout);
12215
- } catch (error) {
12216
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
12217
- await replay.stop('addEvent');
12218
-
12219
- const client = getCurrentHub().getClient();
12220
-
12221
- if (client) {
12222
- client.recordDroppedEvent('internal_sdk_error', 'replay');
12223
- }
12224
- }
12225
- }
12226
-
12227
12709
  /** If the event is an error event */
12228
12710
  function isErrorEvent(event) {
12229
12711
  return !event.type;
@@ -12273,22 +12755,18 @@ function handleAfterSendEvent(replay) {
12273
12755
  return;
12274
12756
  }
12275
12757
 
12276
- // Add error to list of errorIds of replay
12758
+ // Add error to list of errorIds of replay. This is ok to do even if not
12759
+ // sampled because context will get reset at next checkout.
12760
+ // XXX: There is also a race condition where it's possible to capture an
12761
+ // error to Sentry before Replay SDK has loaded, but response returns after
12762
+ // it was loaded, and this gets called.
12277
12763
  if (event.event_id) {
12278
12764
  replay.getContext().errorIds.add(event.event_id);
12279
12765
  }
12280
12766
 
12281
- // Trigger error recording
12767
+ // If error event is tagged with replay id it means it was sampled (when in buffer mode)
12282
12768
  // Need to be very careful that this does not cause an infinite loop
12283
- if (
12284
- replay.recordingMode === 'buffer' &&
12285
- event.exception &&
12286
- event.message !== UNABLE_TO_SEND_REPLAY // ignore this error because otherwise we could loop indefinitely with trying to capture replay and failing
12287
- ) {
12288
- if (!isSampled(replay.getOptions().errorSampleRate)) {
12289
- return;
12290
- }
12291
-
12769
+ if (replay.recordingMode === 'buffer' && event.tags && event.tags.replayId) {
12292
12770
  setTimeout(() => {
12293
12771
  // Capture current event buffer as new replay
12294
12772
  void replay.sendBufferedReplayOrFlush();
@@ -12313,167 +12791,6 @@ function isBaseTransportSend() {
12313
12791
  );
12314
12792
  }
12315
12793
 
12316
- var NodeType;
12317
- (function (NodeType) {
12318
- NodeType[NodeType["Document"] = 0] = "Document";
12319
- NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
12320
- NodeType[NodeType["Element"] = 2] = "Element";
12321
- NodeType[NodeType["Text"] = 3] = "Text";
12322
- NodeType[NodeType["CDATA"] = 4] = "CDATA";
12323
- NodeType[NodeType["Comment"] = 5] = "Comment";
12324
- })(NodeType || (NodeType = {}));
12325
-
12326
- /**
12327
- * Create a breadcrumb for a replay.
12328
- */
12329
- function createBreadcrumb(
12330
- breadcrumb,
12331
- ) {
12332
- return {
12333
- timestamp: Date.now() / 1000,
12334
- type: 'default',
12335
- ...breadcrumb,
12336
- };
12337
- }
12338
-
12339
- /**
12340
- * Add a breadcrumb event to replay.
12341
- */
12342
- function addBreadcrumbEvent(replay, breadcrumb) {
12343
- if (breadcrumb.category === 'sentry.transaction') {
12344
- return;
12345
- }
12346
-
12347
- if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
12348
- replay.triggerUserActivity();
12349
- } else {
12350
- replay.checkAndHandleExpiredSession();
12351
- }
12352
-
12353
- replay.addUpdate(() => {
12354
- void addEvent(replay, {
12355
- type: EventType.Custom,
12356
- // TODO: We were converting from ms to seconds for breadcrumbs, spans,
12357
- // but maybe we should just keep them as milliseconds
12358
- timestamp: (breadcrumb.timestamp || 0) * 1000,
12359
- data: {
12360
- tag: 'breadcrumb',
12361
- // normalize to max. 10 depth and 1_000 properties per object
12362
- payload: normalize(breadcrumb, 10, 1000),
12363
- },
12364
- });
12365
-
12366
- // Do not flush after console log messages
12367
- return breadcrumb.category === 'console';
12368
- });
12369
- }
12370
-
12371
- // Note that these are the serialized attributes and not attributes directly on
12372
- // the DOM Node. Attributes we are interested in:
12373
- const ATTRIBUTES_TO_RECORD = new Set([
12374
- 'id',
12375
- 'class',
12376
- 'aria-label',
12377
- 'role',
12378
- 'name',
12379
- 'alt',
12380
- 'title',
12381
- 'data-test-id',
12382
- 'data-testid',
12383
- ]);
12384
-
12385
- /**
12386
- * Inclusion list of attributes that we want to record from the DOM element
12387
- */
12388
- function getAttributesToRecord(attributes) {
12389
- const obj = {};
12390
- for (const key in attributes) {
12391
- if (ATTRIBUTES_TO_RECORD.has(key)) {
12392
- let normalizedKey = key;
12393
-
12394
- if (key === 'data-testid' || key === 'data-test-id') {
12395
- normalizedKey = 'testId';
12396
- }
12397
-
12398
- obj[normalizedKey] = attributes[key];
12399
- }
12400
- }
12401
-
12402
- return obj;
12403
- }
12404
-
12405
- const handleDomListener =
12406
- (replay) =>
12407
- (handlerData) => {
12408
- if (!replay.isEnabled()) {
12409
- return;
12410
- }
12411
-
12412
- const result = handleDom(handlerData);
12413
-
12414
- if (!result) {
12415
- return;
12416
- }
12417
-
12418
- addBreadcrumbEvent(replay, result);
12419
- };
12420
-
12421
- /**
12422
- * An event handler to react to DOM events.
12423
- */
12424
- function handleDom(handlerData) {
12425
- let target;
12426
- let targetNode;
12427
-
12428
- // Accessing event.target can throw (see getsentry/raven-js#838, #768)
12429
- try {
12430
- targetNode = getTargetNode(handlerData);
12431
- target = htmlTreeAsString(targetNode);
12432
- } catch (e) {
12433
- target = '<unknown>';
12434
- }
12435
-
12436
- // `__sn` property is the serialized node created by rrweb
12437
- const serializedNode =
12438
- targetNode && '__sn' in targetNode && targetNode.__sn.type === NodeType.Element ? targetNode.__sn : null;
12439
-
12440
- return createBreadcrumb({
12441
- category: `ui.${handlerData.name}`,
12442
- message: target,
12443
- data: serializedNode
12444
- ? {
12445
- nodeId: serializedNode.id,
12446
- node: {
12447
- id: serializedNode.id,
12448
- tagName: serializedNode.tagName,
12449
- textContent: targetNode
12450
- ? Array.from(targetNode.childNodes)
12451
- .map(
12452
- (node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
12453
- )
12454
- .filter(Boolean) // filter out empty values
12455
- .map(text => (text ).trim())
12456
- .join('')
12457
- : '',
12458
- attributes: getAttributesToRecord(serializedNode.attributes),
12459
- },
12460
- }
12461
- : {},
12462
- });
12463
- }
12464
-
12465
- function getTargetNode(handlerData) {
12466
- if (isEventWithTarget(handlerData.event)) {
12467
- return handlerData.event.target;
12468
- }
12469
-
12470
- return handlerData.event;
12471
- }
12472
-
12473
- function isEventWithTarget(event) {
12474
- return !!(event ).target;
12475
- }
12476
-
12477
12794
  /**
12478
12795
  * Returns true if we think the given event is an error originating inside of rrweb.
12479
12796
  */
@@ -12497,6 +12814,30 @@ function isRrwebError(event, hint) {
12497
12814
  });
12498
12815
  }
12499
12816
 
12817
+ /**
12818
+ * Determine if event should be sampled (only applies in buffer mode).
12819
+ * When an event is captured by `hanldleGlobalEvent`, when in buffer mode
12820
+ * we determine if we want to sample the error or not.
12821
+ */
12822
+ function shouldSampleForBufferEvent(replay, event) {
12823
+ if (replay.recordingMode !== 'buffer') {
12824
+ return false;
12825
+ }
12826
+
12827
+ // ignore this error because otherwise we could loop indefinitely with
12828
+ // trying to capture replay and failing
12829
+ if (event.message === UNABLE_TO_SEND_REPLAY) {
12830
+ return false;
12831
+ }
12832
+
12833
+ // Require the event to be an error event & to have an exception
12834
+ if (!event.exception || event.type) {
12835
+ return false;
12836
+ }
12837
+
12838
+ return isSampled(replay.getOptions().errorSampleRate);
12839
+ }
12840
+
12500
12841
  /**
12501
12842
  * Returns a listener to be added to `addGlobalEventProcessor(listener)`.
12502
12843
  */
@@ -12526,8 +12867,16 @@ function handleGlobalEventListener(
12526
12867
  return null;
12527
12868
  }
12528
12869
 
12529
- // Only tag transactions with replayId if not waiting for an error
12530
- if (isErrorEvent(event) || (isTransactionEvent(event) && replay.recordingMode === 'session')) {
12870
+ // When in buffer mode, we decide to sample here.
12871
+ // Later, in `handleAfterSendEvent`, if the replayId is set, we know that we sampled
12872
+ // And convert the buffer session to a full session
12873
+ const isErrorEventSampled = shouldSampleForBufferEvent(replay, event);
12874
+
12875
+ // Tag errors if it has been sampled in buffer mode, or if it is session mode
12876
+ // Only tag transactions if in session mode
12877
+ const shouldTagReplayId = isErrorEventSampled || replay.recordingMode === 'session';
12878
+
12879
+ if (shouldTagReplayId) {
12531
12880
  event.tags = { ...event.tags, replayId: replay.getSessionId() };
12532
12881
  }
12533
12882
 
@@ -12577,7 +12926,7 @@ function createPerformanceSpans(
12577
12926
  ) {
12578
12927
  return entries.map(({ type, start, end, name, data }) =>
12579
12928
  addEvent(replay, {
12580
- type: EventType.Custom,
12929
+ type: EventType$1.Custom,
12581
12930
  timestamp: start,
12582
12931
  data: {
12583
12932
  tag: 'performanceSpan',
@@ -13359,7 +13708,32 @@ function _strIsProbablyJson(str) {
13359
13708
 
13360
13709
  /** Match an URL against a list of strings/Regex. */
13361
13710
  function urlMatches(url, urls) {
13362
- return stringMatchesSomePattern(url, urls);
13711
+ const fullUrl = getFullUrl(url);
13712
+
13713
+ return stringMatchesSomePattern(fullUrl, urls);
13714
+ }
13715
+
13716
+ /** exported for tests */
13717
+ function getFullUrl(url, baseURI = WINDOW.document.baseURI) {
13718
+ // Short circuit for common cases:
13719
+ if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith(WINDOW.location.origin)) {
13720
+ return url;
13721
+ }
13722
+ const fixedUrl = new URL(url, baseURI);
13723
+
13724
+ // If these do not match, we are not dealing with a relative URL, so just return it
13725
+ if (fixedUrl.origin !== new URL(baseURI).origin) {
13726
+ return url;
13727
+ }
13728
+
13729
+ const fullUrl = fixedUrl.href;
13730
+
13731
+ // Remove trailing slashes, if they don't match the original URL
13732
+ if (!url.endsWith('/') && fullUrl.endsWith('/')) {
13733
+ return fullUrl.slice(0, -1);
13734
+ }
13735
+
13736
+ return fullUrl;
13363
13737
  }
13364
13738
 
13365
13739
  /**
@@ -13909,7 +14283,8 @@ function addGlobalListeners(replay) {
13909
14283
  client.on('afterSendEvent', handleAfterSendEvent(replay));
13910
14284
  client.on('createDsc', (dsc) => {
13911
14285
  const replayId = replay.getSessionId();
13912
- if (replayId && replay.isEnabled()) {
14286
+ // We do not want to set the DSC when in buffer mode, as that means the replay has not been sent (yet)
14287
+ if (replayId && replay.isEnabled() && replay.recordingMode === 'session') {
13913
14288
  dsc.replay_id = replayId;
13914
14289
  }
13915
14290
  });
@@ -14189,6 +14564,23 @@ function debounce(func, wait, options) {
14189
14564
  return debounced;
14190
14565
  }
14191
14566
 
14567
+ /* eslint-disable @typescript-eslint/naming-convention */
14568
+
14569
+ var EventType; (function (EventType) {
14570
+ const DomContentLoaded = 0; EventType[EventType["DomContentLoaded"] = DomContentLoaded] = "DomContentLoaded";
14571
+ const Load = 1; EventType[EventType["Load"] = Load] = "Load";
14572
+ const FullSnapshot = 2; EventType[EventType["FullSnapshot"] = FullSnapshot] = "FullSnapshot";
14573
+ const IncrementalSnapshot = 3; EventType[EventType["IncrementalSnapshot"] = IncrementalSnapshot] = "IncrementalSnapshot";
14574
+ const Meta = 4; EventType[EventType["Meta"] = Meta] = "Meta";
14575
+ const Custom = 5; EventType[EventType["Custom"] = Custom] = "Custom";
14576
+ const Plugin = 6; EventType[EventType["Plugin"] = Plugin] = "Plugin";
14577
+ })(EventType || (EventType = {}));
14578
+
14579
+ /**
14580
+ * This is a partial copy of rrweb's eventWithTime type which only contains the properties
14581
+ * we specifcally need in the SDK.
14582
+ */
14583
+
14192
14584
  /**
14193
14585
  * Handler for recording events.
14194
14586
  *
@@ -14231,6 +14623,14 @@ function getHandleRecordingEmit(replay) {
14231
14623
  return false;
14232
14624
  }
14233
14625
 
14626
+ // Additionally, create a meta event that will capture certain SDK settings.
14627
+ // In order to handle buffer mode, this needs to either be done when we
14628
+ // receive checkout events or at flush time.
14629
+ //
14630
+ // `isCheckout` is always true, but want to be explicit that it should
14631
+ // only be added for checkouts
14632
+ void addSettingsEvent(replay, isCheckout);
14633
+
14234
14634
  // If there is a previousSessionId after a full snapshot occurs, then
14235
14635
  // the replay session was started due to session expiration. The new session
14236
14636
  // is started before triggering a new checkout and contains the id
@@ -14241,10 +14641,10 @@ function getHandleRecordingEmit(replay) {
14241
14641
  return true;
14242
14642
  }
14243
14643
 
14244
- // See note above re: session start needs to reflect the most recent
14245
- // checkout.
14246
- if (replay.recordingMode === 'buffer' && replay.session) {
14247
- const { earliestEvent } = replay.getContext();
14644
+ // When in buffer mode, make sure we adjust the session started date to the current earliest event of the buffer
14645
+ // this should usually be the timestamp of the checkout event, but to be safe...
14646
+ if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) {
14647
+ const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
14248
14648
  if (earliestEvent) {
14249
14649
  replay.session.started = earliestEvent;
14250
14650
 
@@ -14268,6 +14668,46 @@ function getHandleRecordingEmit(replay) {
14268
14668
  };
14269
14669
  }
14270
14670
 
14671
+ /**
14672
+ * Exported for tests
14673
+ */
14674
+ function createOptionsEvent(replay) {
14675
+ const options = replay.getOptions();
14676
+ return {
14677
+ type: EventType.Custom,
14678
+ timestamp: Date.now(),
14679
+ data: {
14680
+ tag: 'options',
14681
+ payload: {
14682
+ sessionSampleRate: options.sessionSampleRate,
14683
+ errorSampleRate: options.errorSampleRate,
14684
+ useCompressionOption: options.useCompression,
14685
+ blockAllMedia: options.blockAllMedia,
14686
+ maskAllText: options.maskAllText,
14687
+ maskAllInputs: options.maskAllInputs,
14688
+ useCompression: replay.eventBuffer ? replay.eventBuffer.type === 'worker' : false,
14689
+ networkDetailHasUrls: options.networkDetailAllowUrls.length > 0,
14690
+ networkCaptureBodies: options.networkCaptureBodies,
14691
+ networkRequestHasHeaders: options.networkRequestHeaders.length > 0,
14692
+ networkResponseHasHeaders: options.networkResponseHeaders.length > 0,
14693
+ },
14694
+ },
14695
+ };
14696
+ }
14697
+
14698
+ /**
14699
+ * Add a "meta" event that contains a simplified view on current configuration
14700
+ * options. This should only be included on the first segment of a recording.
14701
+ */
14702
+ function addSettingsEvent(replay, isCheckout) {
14703
+ // Only need to add this event when sending the first segment
14704
+ if (!isCheckout || !replay.session || replay.session.segmentId !== 0) {
14705
+ return Promise.resolve(null);
14706
+ }
14707
+
14708
+ return addEvent(replay, createOptionsEvent(replay), false);
14709
+ }
14710
+
14271
14711
  /**
14272
14712
  * Create a replay envelope ready to be sent.
14273
14713
  * This includes both the replay event, as well as the recording data.
@@ -14382,7 +14822,6 @@ async function sendReplayRequest({
14382
14822
  eventContext,
14383
14823
  timestamp,
14384
14824
  session,
14385
- options,
14386
14825
  }) {
14387
14826
  const preparedRecordingData = prepareRecordingData({
14388
14827
  recordingData,
@@ -14424,15 +14863,6 @@ async function sendReplayRequest({
14424
14863
  return;
14425
14864
  }
14426
14865
 
14427
- replayEvent.contexts = {
14428
- ...replayEvent.contexts,
14429
- replay: {
14430
- ...(replayEvent.contexts && replayEvent.contexts.replay),
14431
- session_sample_rate: options.sessionSampleRate,
14432
- error_sample_rate: options.errorSampleRate,
14433
- },
14434
- };
14435
-
14436
14866
  /*
14437
14867
  For reference, the fully built event looks something like this:
14438
14868
  {
@@ -14463,10 +14893,6 @@ async function sendReplayRequest({
14463
14893
  },
14464
14894
  "sdkProcessingMetadata": {},
14465
14895
  "contexts": {
14466
- "replay": {
14467
- "session_sample_rate": 1,
14468
- "error_sample_rate": 0,
14469
- },
14470
14896
  },
14471
14897
  }
14472
14898
  */
@@ -14650,7 +15076,6 @@ class ReplayContainer {
14650
15076
  errorIds: new Set(),
14651
15077
  traceIds: new Set(),
14652
15078
  urls: [],
14653
- earliestEvent: null,
14654
15079
  initialTimestamp: Date.now(),
14655
15080
  initialUrl: '',
14656
15081
  };}
@@ -14660,7 +15085,7 @@ class ReplayContainer {
14660
15085
  recordingOptions,
14661
15086
  }
14662
15087
 
14663
- ) {ReplayContainer.prototype.__init.call(this);ReplayContainer.prototype.__init2.call(this);ReplayContainer.prototype.__init3.call(this);ReplayContainer.prototype.__init4.call(this);ReplayContainer.prototype.__init5.call(this);ReplayContainer.prototype.__init6.call(this);ReplayContainer.prototype.__init7.call(this);ReplayContainer.prototype.__init8.call(this);ReplayContainer.prototype.__init9.call(this);ReplayContainer.prototype.__init10.call(this);ReplayContainer.prototype.__init11.call(this);ReplayContainer.prototype.__init12.call(this);ReplayContainer.prototype.__init13.call(this);ReplayContainer.prototype.__init14.call(this);ReplayContainer.prototype.__init15.call(this);ReplayContainer.prototype.__init16.call(this);ReplayContainer.prototype.__init17.call(this);
15088
+ ) {ReplayContainer.prototype.__init.call(this);ReplayContainer.prototype.__init2.call(this);ReplayContainer.prototype.__init3.call(this);ReplayContainer.prototype.__init4.call(this);ReplayContainer.prototype.__init5.call(this);ReplayContainer.prototype.__init6.call(this);ReplayContainer.prototype.__init7.call(this);ReplayContainer.prototype.__init8.call(this);ReplayContainer.prototype.__init9.call(this);ReplayContainer.prototype.__init10.call(this);ReplayContainer.prototype.__init11.call(this);ReplayContainer.prototype.__init12.call(this);ReplayContainer.prototype.__init13.call(this);ReplayContainer.prototype.__init14.call(this);ReplayContainer.prototype.__init15.call(this);ReplayContainer.prototype.__init16.call(this);ReplayContainer.prototype.__init17.call(this);ReplayContainer.prototype.__init18.call(this);
14664
15089
  this._recordingOptions = recordingOptions;
14665
15090
  this._options = options;
14666
15091
 
@@ -15152,6 +15577,7 @@ class ReplayContainer {
15152
15577
  WINDOW.document.addEventListener('visibilitychange', this._handleVisibilityChange);
15153
15578
  WINDOW.addEventListener('blur', this._handleWindowBlur);
15154
15579
  WINDOW.addEventListener('focus', this._handleWindowFocus);
15580
+ WINDOW.addEventListener('keydown', this._handleKeyboardEvent);
15155
15581
 
15156
15582
  // There is no way to remove these listeners, so ensure they are only added once
15157
15583
  if (!this._hasInitializedCoreListeners) {
@@ -15180,6 +15606,7 @@ class ReplayContainer {
15180
15606
 
15181
15607
  WINDOW.removeEventListener('blur', this._handleWindowBlur);
15182
15608
  WINDOW.removeEventListener('focus', this._handleWindowFocus);
15609
+ WINDOW.removeEventListener('keydown', this._handleKeyboardEvent);
15183
15610
 
15184
15611
  if (this._performanceObserver) {
15185
15612
  this._performanceObserver.disconnect();
@@ -15230,6 +15657,11 @@ class ReplayContainer {
15230
15657
  this._doChangeToForegroundTasks(breadcrumb);
15231
15658
  };}
15232
15659
 
15660
+ /** Ensure page remains active when a key is pressed. */
15661
+ __init16() {this._handleKeyboardEvent = (event) => {
15662
+ handleKeyboardEvent(this, event);
15663
+ };}
15664
+
15233
15665
  /**
15234
15666
  * Tasks to run when we consider a page to be hidden (via blurring and/or visibility)
15235
15667
  */
@@ -15309,7 +15741,7 @@ class ReplayContainer {
15309
15741
  _createCustomBreadcrumb(breadcrumb) {
15310
15742
  this.addUpdate(() => {
15311
15743
  void addEvent(this, {
15312
- type: EventType.Custom,
15744
+ type: EventType$1.Custom,
15313
15745
  timestamp: breadcrumb.timestamp || 0,
15314
15746
  data: {
15315
15747
  tag: 'breadcrumb',
@@ -15350,22 +15782,35 @@ class ReplayContainer {
15350
15782
  this._context.errorIds.clear();
15351
15783
  this._context.traceIds.clear();
15352
15784
  this._context.urls = [];
15353
- this._context.earliestEvent = null;
15785
+ }
15786
+
15787
+ /** Update the initial timestamp based on the buffer content. */
15788
+ _updateInitialTimestampFromEventBuffer() {
15789
+ const { session, eventBuffer } = this;
15790
+ if (!session || !eventBuffer) {
15791
+ return;
15792
+ }
15793
+
15794
+ // we only ever update this on the initial segment
15795
+ if (session.segmentId) {
15796
+ return;
15797
+ }
15798
+
15799
+ const earliestEvent = eventBuffer.getEarliestTimestamp();
15800
+ if (earliestEvent && earliestEvent < this._context.initialTimestamp) {
15801
+ this._context.initialTimestamp = earliestEvent;
15802
+ }
15354
15803
  }
15355
15804
 
15356
15805
  /**
15357
15806
  * Return and clear _context
15358
15807
  */
15359
15808
  _popEventContext() {
15360
- if (this._context.earliestEvent && this._context.earliestEvent < this._context.initialTimestamp) {
15361
- this._context.initialTimestamp = this._context.earliestEvent;
15362
- }
15363
-
15364
15809
  const _context = {
15365
15810
  initialTimestamp: this._context.initialTimestamp,
15366
15811
  initialUrl: this._context.initialUrl,
15367
- errorIds: Array.from(this._context.errorIds).filter(Boolean),
15368
- traceIds: Array.from(this._context.traceIds).filter(Boolean),
15812
+ errorIds: Array.from(this._context.errorIds),
15813
+ traceIds: Array.from(this._context.traceIds),
15369
15814
  urls: this._context.urls,
15370
15815
  };
15371
15816
 
@@ -15404,6 +15849,9 @@ class ReplayContainer {
15404
15849
  }
15405
15850
 
15406
15851
  try {
15852
+ // This uses the data from the eventBuffer, so we need to call this before `finish()
15853
+ this._updateInitialTimestampFromEventBuffer();
15854
+
15407
15855
  // Note this empties the event buffer regardless of outcome of sending replay
15408
15856
  const recordingData = await this.eventBuffer.finish();
15409
15857
 
@@ -15444,7 +15892,7 @@ class ReplayContainer {
15444
15892
  * Flush recording data to Sentry. Creates a lock so that only a single flush
15445
15893
  * can be active at a time. Do not call this directly.
15446
15894
  */
15447
- __init16() {this._flush = async ({
15895
+ __init17() {this._flush = async ({
15448
15896
  force = false,
15449
15897
  }
15450
15898
 
@@ -15499,7 +15947,7 @@ class ReplayContainer {
15499
15947
  }
15500
15948
 
15501
15949
  /** Handler for rrweb.record.onMutation */
15502
- __init17() {this._onMutationHandler = (mutations) => {
15950
+ __init18() {this._onMutationHandler = (mutations) => {
15503
15951
  const count = mutations.length;
15504
15952
 
15505
15953
  const mutationLimit = this._options._experiments.mutationLimit || 0;
@@ -15738,6 +16186,8 @@ class Replay {
15738
16186
  errorSampleRate,
15739
16187
  useCompression,
15740
16188
  blockAllMedia,
16189
+ maskAllInputs,
16190
+ maskAllText,
15741
16191
  networkDetailAllowUrls,
15742
16192
  networkCaptureBodies,
15743
16193
  networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
@@ -24965,6 +25415,7 @@ var ACTIONS$7;
24965
25415
  ACTIONS["SET_MEDIA_RECORDER"] = "SET_MEDIA_RECORDER";
24966
25416
  ACTIONS["SET_MIC_ERROR"] = "SET_MIC_ERROR";
24967
25417
  ACTIONS["SET_PERMISSION_LISTENER"] = "SET_PERMISSION_LISTENER";
25418
+ ACTIONS["SET_ACTIVELY_STOPPED"] = "SET_ACTIVELY_STOPPED";
24968
25419
  })(ACTIONS$7 || (ACTIONS$7 = {}));
24969
25420
  var EVENTS$6;
24970
25421
  (function (EVENTS) {
@@ -43515,6 +43966,7 @@ const initialState = {
43515
43966
  isSafeMode: false,
43516
43967
  isAutoStart: false,
43517
43968
  isMicError: false,
43969
+ isActivelyStopped: false,
43518
43970
  };
43519
43971
  const recorderMachineV2 = createMachine({
43520
43972
  predictableActionArguments: true,
@@ -43733,6 +44185,7 @@ const recorderMachineV2 = createMachine({
43733
44185
  on: {
43734
44186
  [EVENTS$6.STOP_RECORDING]: {
43735
44187
  actions: [
44188
+ ACTIONS$7.SET_ACTIVELY_STOPPED,
43736
44189
  ACTIONS$7.STOP_COUNT_DOWN_ACTOR,
43737
44190
  ACTIONS$7.STOP_MEDIA_RECORDER,
43738
44191
  ACTIONS$7.STOP_MICROPHONE_MACHINE,
@@ -43945,7 +44398,7 @@ const recorderMachineV2 = createMachine({
43945
44398
  webm = new File([blob], Date.now().toString(), {
43946
44399
  type: mimeType,
43947
44400
  });
43948
- callback({ type: 'SEND_CURRENT_TAKE', data: { webm, videoLength } });
44401
+ callback({ type: EVENTS$6.SEND_CURRENT_TAKE, data: { webm, videoLength } });
43949
44402
  })
43950
44403
  .catch((error) => {
43951
44404
  console.error('fixVideoMetadata', error);
@@ -43994,6 +44447,7 @@ const recorderMachineV2 = createMachine({
43994
44447
  [ACTIONS$7.INIT_COUNT_DOWN_ACTOR]: assign$2({
43995
44448
  countDownRef: (context, event, meta) => spawn(counterMachine.withContext(Object.assign(Object.assign({}, counterMachine.context), { callback: meta.action.data.callback })), { name: 'countDownRef' }),
43996
44449
  }),
44450
+ [ACTIONS$7.SET_ACTIVELY_STOPPED]: assign$2((context) => ({ isActivelyStopped: true })),
43997
44451
  [ACTIONS$7.STOP_COUNT_DOWN_ACTOR]: assign$2((context) => {
43998
44452
  context.countDownRef.stop();
43999
44453
  return {
@@ -44064,7 +44518,7 @@ const recorderMachineV2 = createMachine({
44064
44518
  // send success event to the parent, to be able to proceed to the next step
44065
44519
  [ACTIONS$7.SEND_SUCCESS_TO_PARENT]: sendParent$1((context, event) => (Object.assign(Object.assign({}, event), { type: EVENTS$5.READY_TO_START_RECORDING }))),
44066
44520
  // on stop recording send webm file to storage machine
44067
- [ACTIONS$7.SEND_CURRENT_TAKE]: sendParent$1((context, event) => (Object.assign(Object.assign({}, event), { type: EVENTS$5.STOP_RECORDING }))),
44521
+ [ACTIONS$7.SEND_CURRENT_TAKE]: sendParent$1(({ isActivelyStopped }, event) => (Object.assign(Object.assign({}, event), { type: EVENTS$5.STOP_RECORDING, data: Object.assign(Object.assign({}, event.data), { isActivelyStopped }) }))),
44068
44522
  // send null param to mic machine to be able to stop sound detecting
44069
44523
  [ACTIONS$7.STOP_MICROPHONE_MACHINE]: send$2((_, _event) => ({ type: EVENTS$3.ON_SET_MEDIA_STREAM, data: null }), { to: (context) => context.microphoneRef }),
44070
44524
  // clean mic error, this is really sensitive, !! a lot of user can't start recording coz of it
@@ -44593,7 +45047,7 @@ const MAPPED_EVENT_TYPES = {
44593
45047
  PRACTICE: EVENT_TYPES.TIMES_UP,
44594
45048
  },
44595
45049
  };
44596
- const questionEventFormatter = (questionActionType) => pure_1(({ recordingType, questions, currentQuestion, widgetConfig, currentTake }) => {
45050
+ const questionEventFormatter = (questionActionType) => pure_1(({ recordingType, questions, currentQuestion, widgetConfig, currentTake }, event) => {
44597
45051
  var _a, _b;
44598
45052
  const isQuestionMode = recordingType === TAKE_TYPES.QUESTION;
44599
45053
  const currentQuestionFile = isQuestionMode ? (_b = (_a = widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.video) === null || _a === void 0 ? void 0 : _a.videos) === null || _b === void 0 ? void 0 : _b[currentQuestion - 1] : null;
@@ -44601,10 +45055,11 @@ const questionEventFormatter = (questionActionType) => pure_1(({ recordingType,
44601
45055
  const isAssessment = currentQuestionObj.answerType && currentQuestionObj.answerType !== ANSWER_TYPES.VIDEO;
44602
45056
  return {
44603
45057
  type: ACTIONS$6.EMIT_TRACKING_EVENT,
44604
- data: Object.assign({ eventType: isQuestionMode ? MAPPED_EVENT_TYPES[questionActionType].QUESTION : MAPPED_EVENT_TYPES[questionActionType].PRACTICE }, (currentQuestionFile && { extraData: Object.assign(Object.assign(Object.assign({ questionFilename: currentQuestionFile.filename, questionNumber: currentQuestion }, (!isAssessment && {
45058
+ data: Object.assign({ eventType: isQuestionMode ? MAPPED_EVENT_TYPES[questionActionType].QUESTION : MAPPED_EVENT_TYPES[questionActionType].PRACTICE }, (currentQuestionFile && { extraData: Object.assign(Object.assign({ questionFilename: currentQuestionFile.filename, questionNumber: currentQuestion }, (!isAssessment && {
44605
45059
  currentTake,
44606
45060
  numberOfTakes: currentQuestionObj.numOfRetakes,
44607
- })), (isAssessment && {})), { questionType: currentQuestionObj.videoQuestion ? 'video' : 'text', answerType: currentQuestionObj.answerType || ANSWER_TYPES.VIDEO }) })),
45061
+ isActivelyStopped: (event.data || {}).isActivelyStopped,
45062
+ })), { questionType: currentQuestionObj.videoQuestion ? 'video' : 'text', answerType: currentQuestionObj.answerType || ANSWER_TYPES.VIDEO }) })),
44608
45063
  };
44609
45064
  });
44610
45065
  const accWidgetMachine = createMachine({