@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/cjs/index.js CHANGED
@@ -2097,7 +2097,10 @@ function visit(
2097
2097
  const [memoize, unmemoize] = memo;
2098
2098
 
2099
2099
  // Get the simple cases out of the way first
2100
- if (value === null || (['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))) {
2100
+ if (
2101
+ value == null || // this matches null and undefined -> eqeq not eqeqeq
2102
+ (['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))
2103
+ ) {
2101
2104
  return value ;
2102
2105
  }
2103
2106
 
@@ -2235,11 +2238,6 @@ function stringifyValue(
2235
2238
  return '[NaN]';
2236
2239
  }
2237
2240
 
2238
- // this catches `undefined` (but not `null`, which is a primitive and can be serialized on its own)
2239
- if (value === void 0) {
2240
- return '[undefined]';
2241
- }
2242
-
2243
2241
  if (typeof value === 'function') {
2244
2242
  return `[Function: ${getFunctionName(value)}]`;
2245
2243
  }
@@ -5822,7 +5820,7 @@ function getEventForEnvelopeItem(item, type) {
5822
5820
  return Array.isArray(item) ? (item )[1] : undefined;
5823
5821
  }
5824
5822
 
5825
- const SDK_VERSION = '7.50.0';
5823
+ const SDK_VERSION = '7.52.1';
5826
5824
 
5827
5825
  let originalFunctionToString;
5828
5826
 
@@ -6003,8 +6001,9 @@ function _getPossibleEventMessages(event) {
6003
6001
  return [event.message];
6004
6002
  }
6005
6003
  if (event.exception) {
6004
+ const { values } = event.exception;
6006
6005
  try {
6007
- const { type = '', value = '' } = (event.exception.values && event.exception.values[0]) || {};
6006
+ const { type = '', value = '' } = (values && values[values.length - 1]) || {};
6008
6007
  return [`${value}`, `${type}: ${value}`];
6009
6008
  } catch (oO) {
6010
6009
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(`Cannot extract message for event ${getEventDescription(event)}`);
@@ -8361,7 +8360,7 @@ function maskInputValue({ input, maskInputSelector, unmaskInputSelector, maskInp
8361
8360
  if (unmaskInputSelector && input.matches(unmaskInputSelector)) {
8362
8361
  return text;
8363
8362
  }
8364
- if (input.hasAttribute('rr_is_password')) {
8363
+ if (input.hasAttribute('data-rr-is-password')) {
8365
8364
  type = 'password';
8366
8365
  }
8367
8366
  if (isInputTypeMasked({ maskInputOptions, tagName, type }) ||
@@ -8394,6 +8393,21 @@ function is2DCanvasBlank(canvas) {
8394
8393
  }
8395
8394
  return true;
8396
8395
  }
8396
+ function getInputType(element) {
8397
+ const type = element.type;
8398
+ return element.hasAttribute('data-rr-is-password')
8399
+ ? 'password'
8400
+ : type
8401
+ ? type.toLowerCase()
8402
+ : null;
8403
+ }
8404
+ function getInputValue(el, tagName, type) {
8405
+ typeof type === 'string' ? type.toLowerCase() : '';
8406
+ if (tagName === 'INPUT' && (type === 'radio' || type === 'checkbox')) {
8407
+ return el.getAttribute('value') || '';
8408
+ }
8409
+ return el.value;
8410
+ }
8397
8411
 
8398
8412
  let _id = 1;
8399
8413
  const tagNameRegex = new RegExp('[^a-z0-9-_:]');
@@ -8432,6 +8446,13 @@ function getCssRuleString(rule) {
8432
8446
  catch (_a) {
8433
8447
  }
8434
8448
  }
8449
+ return validateStringifiedCssRule(cssStringified);
8450
+ }
8451
+ function validateStringifiedCssRule(cssStringified) {
8452
+ if (cssStringified.indexOf(':') > -1) {
8453
+ const regex = /(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;
8454
+ return cssStringified.replace(regex, '$1\\$2');
8455
+ }
8435
8456
  return cssStringified;
8436
8457
  }
8437
8458
  function isCSSImportRule(rule) {
@@ -8440,7 +8461,7 @@ function isCSSImportRule(rule) {
8440
8461
  function stringifyStyleSheet(sheet) {
8441
8462
  return sheet.cssRules
8442
8463
  ? Array.from(sheet.cssRules)
8443
- .map((rule) => rule.cssText || '')
8464
+ .map((rule) => rule.cssText ? validateStringifiedCssRule(rule.cssText) : '')
8444
8465
  .join('')
8445
8466
  : '';
8446
8467
  }
@@ -8775,14 +8796,15 @@ function serializeNode(n, options) {
8775
8796
  tagName === 'select' ||
8776
8797
  tagName === 'option') {
8777
8798
  const el = n;
8778
- const value = getInputValue(tagName, el, attributes);
8799
+ const type = getInputType(el);
8800
+ const value = getInputValue(el, tagName.toUpperCase(), type);
8779
8801
  const checked = n.checked;
8780
- if (attributes.type !== 'submit' &&
8781
- attributes.type !== 'button' &&
8802
+ if (type !== 'submit' &&
8803
+ type !== 'button' &&
8782
8804
  value) {
8783
8805
  attributes.value = maskInputValue({
8784
8806
  input: el,
8785
- type: attributes.type,
8807
+ type,
8786
8808
  tagName,
8787
8809
  value,
8788
8810
  maskInputSelector,
@@ -9255,15 +9277,8 @@ function snapshot(n, options) {
9255
9277
  function skipAttribute(tagName, attributeName, value) {
9256
9278
  return ((tagName === 'video' || tagName === 'audio') && attributeName === 'autoplay');
9257
9279
  }
9258
- function getInputValue(tagName, el, attributes) {
9259
- if (tagName === 'input' &&
9260
- (attributes.type === 'radio' || attributes.type === 'checkbox')) {
9261
- return el.getAttribute('value') || '';
9262
- }
9263
- return el.value;
9264
- }
9265
9280
 
9266
- var EventType;
9281
+ var EventType$1;
9267
9282
  (function (EventType) {
9268
9283
  EventType[EventType["DomContentLoaded"] = 0] = "DomContentLoaded";
9269
9284
  EventType[EventType["Load"] = 1] = "Load";
@@ -9272,7 +9287,7 @@ var EventType;
9272
9287
  EventType[EventType["Meta"] = 4] = "Meta";
9273
9288
  EventType[EventType["Custom"] = 5] = "Custom";
9274
9289
  EventType[EventType["Plugin"] = 6] = "Plugin";
9275
- })(EventType || (EventType = {}));
9290
+ })(EventType$1 || (EventType$1 = {}));
9276
9291
  var IncrementalSource;
9277
9292
  (function (IncrementalSource) {
9278
9293
  IncrementalSource[IncrementalSource["Mutation"] = 0] = "Mutation";
@@ -9887,9 +9902,9 @@ class MutationBuffer {
9887
9902
  this.attributes.push(item);
9888
9903
  }
9889
9904
  if (m.attributeName === 'type' &&
9890
- m.target.tagName === 'INPUT' &&
9905
+ target.tagName === 'INPUT' &&
9891
9906
  (m.oldValue || '').toLowerCase() === 'password') {
9892
- m.target.setAttribute('rr_is_password', 'true');
9907
+ target.setAttribute('data-rr-is-password', 'true');
9893
9908
  }
9894
9909
  if (m.attributeName === 'style') {
9895
9910
  const old = this.doc.createElement('span');
@@ -10286,27 +10301,25 @@ function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, un
10286
10301
  isBlocked(target, blockClass, blockSelector, unblockSelector)) {
10287
10302
  return;
10288
10303
  }
10289
- let type = target.type;
10290
- if (target.classList.contains(ignoreClass) ||
10291
- (ignoreSelector && target.matches(ignoreSelector))) {
10304
+ const el = target;
10305
+ const type = getInputType(el);
10306
+ if (el.classList.contains(ignoreClass) ||
10307
+ (ignoreSelector && el.matches(ignoreSelector))) {
10292
10308
  return;
10293
10309
  }
10294
- let text = target.value;
10310
+ let text = getInputValue(el, tagName, type);
10295
10311
  let isChecked = false;
10296
- if (target.hasAttribute('rr_is_password')) {
10297
- type = 'password';
10298
- }
10299
10312
  if (type === 'radio' || type === 'checkbox') {
10300
10313
  isChecked = target.checked;
10301
10314
  }
10302
- else if (hasInputMaskOptions({
10315
+ if (hasInputMaskOptions({
10303
10316
  maskInputOptions,
10304
10317
  maskInputSelector,
10305
10318
  tagName,
10306
10319
  type,
10307
10320
  })) {
10308
10321
  text = maskInputValue({
10309
- input: target,
10322
+ input: el,
10310
10323
  maskInputOptions,
10311
10324
  maskInputSelector,
10312
10325
  unmaskInputSelector,
@@ -10323,8 +10336,18 @@ function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, un
10323
10336
  .querySelectorAll(`input[type="radio"][name="${name}"]`)
10324
10337
  .forEach((el) => {
10325
10338
  if (el !== target) {
10339
+ const text = maskInputValue({
10340
+ input: el,
10341
+ maskInputOptions,
10342
+ maskInputSelector,
10343
+ unmaskInputSelector,
10344
+ tagName,
10345
+ type,
10346
+ value: getInputValue(el, tagName, type),
10347
+ maskInputFn,
10348
+ });
10326
10349
  cbWithDedup(el, callbackWrapper(wrapEventWithUserTriggeredFlag)({
10327
- text: el.value,
10350
+ text,
10328
10351
  isChecked: !isChecked,
10329
10352
  userTriggered: false,
10330
10353
  }, userTriggeredOnInput));
@@ -11237,17 +11260,17 @@ function record(options = {}) {
11237
11260
  wrappedEmit = (e, isCheckout) => {
11238
11261
  var _a;
11239
11262
  if (((_a = mutationBuffers[0]) === null || _a === void 0 ? void 0 : _a.isFrozen()) &&
11240
- e.type !== EventType.FullSnapshot &&
11241
- !(e.type === EventType.IncrementalSnapshot &&
11263
+ e.type !== EventType$1.FullSnapshot &&
11264
+ !(e.type === EventType$1.IncrementalSnapshot &&
11242
11265
  e.data.source === IncrementalSource.Mutation)) {
11243
11266
  mutationBuffers.forEach((buf) => buf.unfreeze());
11244
11267
  }
11245
11268
  emit(eventProcessor(e), isCheckout);
11246
- if (e.type === EventType.FullSnapshot) {
11269
+ if (e.type === EventType$1.FullSnapshot) {
11247
11270
  lastFullSnapshotEvent = e;
11248
11271
  incrementalSnapshotCount = 0;
11249
11272
  }
11250
- else if (e.type === EventType.IncrementalSnapshot) {
11273
+ else if (e.type === EventType$1.IncrementalSnapshot) {
11251
11274
  if (e.data.source === IncrementalSource.Mutation &&
11252
11275
  e.data.isAttachIframe) {
11253
11276
  return;
@@ -11263,16 +11286,16 @@ function record(options = {}) {
11263
11286
  };
11264
11287
  const wrappedMutationEmit = (m) => {
11265
11288
  wrappedEmit(wrapEvent({
11266
- type: EventType.IncrementalSnapshot,
11289
+ type: EventType$1.IncrementalSnapshot,
11267
11290
  data: Object.assign({ source: IncrementalSource.Mutation }, m),
11268
11291
  }));
11269
11292
  };
11270
11293
  const wrappedScrollEmit = (p) => wrappedEmit(wrapEvent({
11271
- type: EventType.IncrementalSnapshot,
11294
+ type: EventType$1.IncrementalSnapshot,
11272
11295
  data: Object.assign({ source: IncrementalSource.Scroll }, p),
11273
11296
  }));
11274
11297
  const wrappedCanvasMutationEmit = (p) => wrappedEmit(wrapEvent({
11275
- type: EventType.IncrementalSnapshot,
11298
+ type: EventType$1.IncrementalSnapshot,
11276
11299
  data: Object.assign({ source: IncrementalSource.CanvasMutation }, p),
11277
11300
  }));
11278
11301
  const iframeManager = new IframeManager({
@@ -11317,7 +11340,7 @@ function record(options = {}) {
11317
11340
  takeFullSnapshot = (isCheckout = false) => {
11318
11341
  var _a, _b, _c, _d;
11319
11342
  wrappedEmit(wrapEvent({
11320
- type: EventType.Meta,
11343
+ type: EventType$1.Meta,
11321
11344
  data: {
11322
11345
  href: window.location.href,
11323
11346
  width: getWindowWidth(),
@@ -11360,7 +11383,7 @@ function record(options = {}) {
11360
11383
  }
11361
11384
  mirror.map = idNodeMap;
11362
11385
  wrappedEmit(wrapEvent({
11363
- type: EventType.FullSnapshot,
11386
+ type: EventType$1.FullSnapshot,
11364
11387
  data: {
11365
11388
  node,
11366
11389
  initialOffset: {
@@ -11385,7 +11408,7 @@ function record(options = {}) {
11385
11408
  const handlers = [];
11386
11409
  handlers.push(on$1('DOMContentLoaded', () => {
11387
11410
  wrappedEmit(wrapEvent({
11388
- type: EventType.DomContentLoaded,
11411
+ type: EventType$1.DomContentLoaded,
11389
11412
  data: {},
11390
11413
  }));
11391
11414
  }));
@@ -11395,40 +11418,40 @@ function record(options = {}) {
11395
11418
  onMutation,
11396
11419
  mutationCb: wrappedMutationEmit,
11397
11420
  mousemoveCb: (positions, source) => wrappedEmit(wrapEvent({
11398
- type: EventType.IncrementalSnapshot,
11421
+ type: EventType$1.IncrementalSnapshot,
11399
11422
  data: {
11400
11423
  source,
11401
11424
  positions,
11402
11425
  },
11403
11426
  })),
11404
11427
  mouseInteractionCb: (d) => wrappedEmit(wrapEvent({
11405
- type: EventType.IncrementalSnapshot,
11428
+ type: EventType$1.IncrementalSnapshot,
11406
11429
  data: Object.assign({ source: IncrementalSource.MouseInteraction }, d),
11407
11430
  })),
11408
11431
  scrollCb: wrappedScrollEmit,
11409
11432
  viewportResizeCb: (d) => wrappedEmit(wrapEvent({
11410
- type: EventType.IncrementalSnapshot,
11433
+ type: EventType$1.IncrementalSnapshot,
11411
11434
  data: Object.assign({ source: IncrementalSource.ViewportResize }, d),
11412
11435
  })),
11413
11436
  inputCb: (v) => wrappedEmit(wrapEvent({
11414
- type: EventType.IncrementalSnapshot,
11437
+ type: EventType$1.IncrementalSnapshot,
11415
11438
  data: Object.assign({ source: IncrementalSource.Input }, v),
11416
11439
  })),
11417
11440
  mediaInteractionCb: (p) => wrappedEmit(wrapEvent({
11418
- type: EventType.IncrementalSnapshot,
11441
+ type: EventType$1.IncrementalSnapshot,
11419
11442
  data: Object.assign({ source: IncrementalSource.MediaInteraction }, p),
11420
11443
  })),
11421
11444
  styleSheetRuleCb: (r) => wrappedEmit(wrapEvent({
11422
- type: EventType.IncrementalSnapshot,
11445
+ type: EventType$1.IncrementalSnapshot,
11423
11446
  data: Object.assign({ source: IncrementalSource.StyleSheetRule }, r),
11424
11447
  })),
11425
11448
  styleDeclarationCb: (r) => wrappedEmit(wrapEvent({
11426
- type: EventType.IncrementalSnapshot,
11449
+ type: EventType$1.IncrementalSnapshot,
11427
11450
  data: Object.assign({ source: IncrementalSource.StyleDeclaration }, r),
11428
11451
  })),
11429
11452
  canvasMutationCb: wrappedCanvasMutationEmit,
11430
11453
  fontCb: (p) => wrappedEmit(wrapEvent({
11431
- type: EventType.IncrementalSnapshot,
11454
+ type: EventType$1.IncrementalSnapshot,
11432
11455
  data: Object.assign({ source: IncrementalSource.Font }, p),
11433
11456
  })),
11434
11457
  blockClass,
@@ -11461,7 +11484,7 @@ function record(options = {}) {
11461
11484
  observer: p.observer,
11462
11485
  options: p.options,
11463
11486
  callback: (payload) => wrappedEmit(wrapEvent({
11464
- type: EventType.Plugin,
11487
+ type: EventType$1.Plugin,
11465
11488
  data: {
11466
11489
  plugin: p.name,
11467
11490
  payload,
@@ -11489,7 +11512,7 @@ function record(options = {}) {
11489
11512
  else {
11490
11513
  handlers.push(on$1('load', () => {
11491
11514
  wrappedEmit(wrapEvent({
11492
- type: EventType.Load,
11515
+ type: EventType$1.Load,
11493
11516
  data: {},
11494
11517
  }));
11495
11518
  init();
@@ -11508,7 +11531,7 @@ record.addCustomEvent = (tag, payload) => {
11508
11531
  throw new Error('please add custom event after start recording');
11509
11532
  }
11510
11533
  wrappedEmit(wrapEvent({
11511
- type: EventType.Custom,
11534
+ type: EventType$1.Custom,
11512
11535
  data: {
11513
11536
  tag,
11514
11537
  payload,
@@ -11526,6 +11549,475 @@ record.takeFullSnapshot = (isCheckout) => {
11526
11549
  };
11527
11550
  record.mirror = mirror;
11528
11551
 
11552
+ /**
11553
+ * Create a breadcrumb for a replay.
11554
+ */
11555
+ function createBreadcrumb(
11556
+ breadcrumb,
11557
+ ) {
11558
+ return {
11559
+ timestamp: Date.now() / 1000,
11560
+ type: 'default',
11561
+ ...breadcrumb,
11562
+ };
11563
+ }
11564
+
11565
+ var NodeType;
11566
+ (function (NodeType) {
11567
+ NodeType[NodeType["Document"] = 0] = "Document";
11568
+ NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
11569
+ NodeType[NodeType["Element"] = 2] = "Element";
11570
+ NodeType[NodeType["Text"] = 3] = "Text";
11571
+ NodeType[NodeType["CDATA"] = 4] = "CDATA";
11572
+ NodeType[NodeType["Comment"] = 5] = "Comment";
11573
+ })(NodeType || (NodeType = {}));
11574
+
11575
+ /**
11576
+ * Converts a timestamp to ms, if it was in s, or keeps it as ms.
11577
+ */
11578
+ function timestampToMs(timestamp) {
11579
+ const isMs = timestamp > 9999999999;
11580
+ return isMs ? timestamp : timestamp * 1000;
11581
+ }
11582
+
11583
+ /**
11584
+ * Add an event to the event buffer.
11585
+ * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
11586
+ */
11587
+ async function addEvent(
11588
+ replay,
11589
+ event,
11590
+ isCheckout,
11591
+ ) {
11592
+ if (!replay.eventBuffer) {
11593
+ // This implies that `_isEnabled` is false
11594
+ return null;
11595
+ }
11596
+
11597
+ if (replay.isPaused()) {
11598
+ // Do not add to event buffer when recording is paused
11599
+ return null;
11600
+ }
11601
+
11602
+ const timestampInMs = timestampToMs(event.timestamp);
11603
+
11604
+ // Throw out events that happen more than 5 minutes ago. This can happen if
11605
+ // page has been left open and idle for a long period of time and user
11606
+ // comes back to trigger a new session. The performance entries rely on
11607
+ // `performance.timeOrigin`, which is when the page first opened.
11608
+ if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
11609
+ return null;
11610
+ }
11611
+
11612
+ try {
11613
+ if (isCheckout) {
11614
+ replay.eventBuffer.clear();
11615
+ }
11616
+
11617
+ return await replay.eventBuffer.addEvent(event);
11618
+ } catch (error) {
11619
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
11620
+ await replay.stop('addEvent');
11621
+
11622
+ const client = getCurrentHub().getClient();
11623
+
11624
+ if (client) {
11625
+ client.recordDroppedEvent('internal_sdk_error', 'replay');
11626
+ }
11627
+ }
11628
+ }
11629
+
11630
+ /**
11631
+ * Add a breadcrumb event to replay.
11632
+ */
11633
+ function addBreadcrumbEvent(replay, breadcrumb) {
11634
+ if (breadcrumb.category === 'sentry.transaction') {
11635
+ return;
11636
+ }
11637
+
11638
+ if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
11639
+ replay.triggerUserActivity();
11640
+ } else {
11641
+ replay.checkAndHandleExpiredSession();
11642
+ }
11643
+
11644
+ replay.addUpdate(() => {
11645
+ void addEvent(replay, {
11646
+ type: EventType$1.Custom,
11647
+ // TODO: We were converting from ms to seconds for breadcrumbs, spans,
11648
+ // but maybe we should just keep them as milliseconds
11649
+ timestamp: (breadcrumb.timestamp || 0) * 1000,
11650
+ data: {
11651
+ tag: 'breadcrumb',
11652
+ // normalize to max. 10 depth and 1_000 properties per object
11653
+ payload: normalize(breadcrumb, 10, 1000),
11654
+ },
11655
+ });
11656
+
11657
+ // Do not flush after console log messages
11658
+ return breadcrumb.category === 'console';
11659
+ });
11660
+ }
11661
+
11662
+ /**
11663
+ * Detect a slow click on a button/a tag,
11664
+ * and potentially create a corresponding breadcrumb.
11665
+ */
11666
+ function detectSlowClick(
11667
+ replay,
11668
+ config,
11669
+ clickBreadcrumb,
11670
+ node,
11671
+ ) {
11672
+ if (ignoreElement(node, config)) {
11673
+ return;
11674
+ }
11675
+
11676
+ /*
11677
+ We consider a slow click a click on a button/a, which does not trigger one of:
11678
+ - DOM mutation
11679
+ - Scroll (within 100ms)
11680
+ Within the given threshold time.
11681
+ After time timeout time, we stop listening and mark it as a slow click anyhow.
11682
+ */
11683
+
11684
+ let cleanup = () => {
11685
+ // replaced further down
11686
+ };
11687
+
11688
+ // After timeout time, def. consider this a slow click, and stop watching for mutations
11689
+ const timeout = setTimeout(() => {
11690
+ handleSlowClick(replay, clickBreadcrumb, config.timeout, 'timeout');
11691
+ cleanup();
11692
+ }, config.timeout);
11693
+
11694
+ const mutationHandler = () => {
11695
+ maybeHandleSlowClick(replay, clickBreadcrumb, config.threshold, config.timeout, 'mutation');
11696
+ cleanup();
11697
+ };
11698
+
11699
+ const scrollHandler = () => {
11700
+ maybeHandleSlowClick(replay, clickBreadcrumb, config.scrollTimeout, config.timeout, 'scroll');
11701
+ cleanup();
11702
+ };
11703
+
11704
+ const obs = new MutationObserver(mutationHandler);
11705
+
11706
+ obs.observe(WINDOW.document.documentElement, {
11707
+ attributes: true,
11708
+ characterData: true,
11709
+ childList: true,
11710
+ subtree: true,
11711
+ });
11712
+
11713
+ WINDOW.addEventListener('scroll', scrollHandler);
11714
+
11715
+ // Stop listening to scroll timeouts early
11716
+ const scrollTimeout = setTimeout(() => {
11717
+ WINDOW.removeEventListener('scroll', scrollHandler);
11718
+ }, config.scrollTimeout);
11719
+
11720
+ cleanup = () => {
11721
+ clearTimeout(timeout);
11722
+ clearTimeout(scrollTimeout);
11723
+ obs.disconnect();
11724
+ WINDOW.removeEventListener('scroll', scrollHandler);
11725
+ };
11726
+ }
11727
+
11728
+ function maybeHandleSlowClick(
11729
+ replay,
11730
+ clickBreadcrumb,
11731
+ threshold,
11732
+ timeout,
11733
+ endReason,
11734
+ ) {
11735
+ const now = Date.now();
11736
+ const timeAfterClickMs = now - clickBreadcrumb.timestamp * 1000;
11737
+
11738
+ if (timeAfterClickMs > threshold) {
11739
+ handleSlowClick(replay, clickBreadcrumb, Math.min(timeAfterClickMs, timeout), endReason);
11740
+ return true;
11741
+ }
11742
+
11743
+ return false;
11744
+ }
11745
+
11746
+ function handleSlowClick(
11747
+ replay,
11748
+ clickBreadcrumb,
11749
+ timeAfterClickMs,
11750
+ endReason,
11751
+ ) {
11752
+ const breadcrumb = {
11753
+ message: clickBreadcrumb.message,
11754
+ timestamp: clickBreadcrumb.timestamp,
11755
+ category: 'ui.slowClickDetected',
11756
+ data: {
11757
+ ...clickBreadcrumb.data,
11758
+ url: WINDOW.location.href,
11759
+ // TODO FN: add parametrized route, when possible
11760
+ timeAfterClickMs,
11761
+ endReason,
11762
+ },
11763
+ };
11764
+
11765
+ addBreadcrumbEvent(replay, breadcrumb);
11766
+ }
11767
+
11768
+ const SLOW_CLICK_IGNORE_TAGS = ['SELECT', 'OPTION'];
11769
+
11770
+ function ignoreElement(node, config) {
11771
+ // If <input> tag, we only want to consider input[type='submit'] & input[type='button']
11772
+ if (node.tagName === 'INPUT' && !['submit', 'button'].includes(node.getAttribute('type') || '')) {
11773
+ return true;
11774
+ }
11775
+
11776
+ if (SLOW_CLICK_IGNORE_TAGS.includes(node.tagName)) {
11777
+ return true;
11778
+ }
11779
+
11780
+ // If <a> tag, detect special variants that may not lead to an action
11781
+ // If target !== _self, we may open the link somewhere else, which would lead to no action
11782
+ // Also, when downloading a file, we may not leave the page, but still not trigger an action
11783
+ if (
11784
+ node.tagName === 'A' &&
11785
+ (node.hasAttribute('download') || (node.hasAttribute('target') && node.getAttribute('target') !== '_self'))
11786
+ ) {
11787
+ return true;
11788
+ }
11789
+
11790
+ if (config.ignoreSelector && node.matches(config.ignoreSelector)) {
11791
+ return true;
11792
+ }
11793
+
11794
+ return false;
11795
+ }
11796
+
11797
+ // Note that these are the serialized attributes and not attributes directly on
11798
+ // the DOM Node. Attributes we are interested in:
11799
+ const ATTRIBUTES_TO_RECORD = new Set([
11800
+ 'id',
11801
+ 'class',
11802
+ 'aria-label',
11803
+ 'role',
11804
+ 'name',
11805
+ 'alt',
11806
+ 'title',
11807
+ 'data-test-id',
11808
+ 'data-testid',
11809
+ ]);
11810
+
11811
+ /**
11812
+ * Inclusion list of attributes that we want to record from the DOM element
11813
+ */
11814
+ function getAttributesToRecord(attributes) {
11815
+ const obj = {};
11816
+ for (const key in attributes) {
11817
+ if (ATTRIBUTES_TO_RECORD.has(key)) {
11818
+ let normalizedKey = key;
11819
+
11820
+ if (key === 'data-testid' || key === 'data-test-id') {
11821
+ normalizedKey = 'testId';
11822
+ }
11823
+
11824
+ obj[normalizedKey] = attributes[key];
11825
+ }
11826
+ }
11827
+
11828
+ return obj;
11829
+ }
11830
+
11831
+ const handleDomListener = (
11832
+ replay,
11833
+ ) => {
11834
+ const slowClickExperiment = replay.getOptions()._experiments.slowClicks;
11835
+
11836
+ const slowClickConfig = slowClickExperiment
11837
+ ? {
11838
+ threshold: slowClickExperiment.threshold,
11839
+ timeout: slowClickExperiment.timeout,
11840
+ scrollTimeout: slowClickExperiment.scrollTimeout,
11841
+ ignoreSelector: slowClickExperiment.ignoreSelectors ? slowClickExperiment.ignoreSelectors.join(',') : '',
11842
+ }
11843
+ : undefined;
11844
+
11845
+ return (handlerData) => {
11846
+ if (!replay.isEnabled()) {
11847
+ return;
11848
+ }
11849
+
11850
+ const result = handleDom(handlerData);
11851
+
11852
+ if (!result) {
11853
+ return;
11854
+ }
11855
+
11856
+ const isClick = handlerData.name === 'click';
11857
+ const event = isClick && (handlerData.event );
11858
+ // Ignore clicks if ctrl/alt/meta keys are held down as they alter behavior of clicks (e.g. open in new tab)
11859
+ if (isClick && slowClickConfig && event && !event.altKey && !event.metaKey && !event.ctrlKey) {
11860
+ detectSlowClick(
11861
+ replay,
11862
+ slowClickConfig,
11863
+ result ,
11864
+ getClickTargetNode(handlerData.event) ,
11865
+ );
11866
+ }
11867
+
11868
+ addBreadcrumbEvent(replay, result);
11869
+ };
11870
+ };
11871
+
11872
+ /** Get the base DOM breadcrumb. */
11873
+ function getBaseDomBreadcrumb(target, message) {
11874
+ // `__sn` property is the serialized node created by rrweb
11875
+ const serializedNode = target && isRrwebNode(target) && target.__sn.type === NodeType.Element ? target.__sn : null;
11876
+
11877
+ return {
11878
+ message,
11879
+ data: serializedNode
11880
+ ? {
11881
+ nodeId: serializedNode.id,
11882
+ node: {
11883
+ id: serializedNode.id,
11884
+ tagName: serializedNode.tagName,
11885
+ textContent: target
11886
+ ? Array.from(target.childNodes)
11887
+ .map(
11888
+ (node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
11889
+ )
11890
+ .filter(Boolean) // filter out empty values
11891
+ .map(text => (text ).trim())
11892
+ .join('')
11893
+ : '',
11894
+ attributes: getAttributesToRecord(serializedNode.attributes),
11895
+ },
11896
+ }
11897
+ : {},
11898
+ };
11899
+ }
11900
+
11901
+ /**
11902
+ * An event handler to react to DOM events.
11903
+ * Exported for tests.
11904
+ */
11905
+ function handleDom(handlerData) {
11906
+ const { target, message } = getDomTarget(handlerData);
11907
+
11908
+ return createBreadcrumb({
11909
+ category: `ui.${handlerData.name}`,
11910
+ ...getBaseDomBreadcrumb(target, message),
11911
+ });
11912
+ }
11913
+
11914
+ function getDomTarget(handlerData) {
11915
+ const isClick = handlerData.name === 'click';
11916
+
11917
+ let message;
11918
+ let target = null;
11919
+
11920
+ // Accessing event.target can throw (see getsentry/raven-js#838, #768)
11921
+ try {
11922
+ target = isClick ? getClickTargetNode(handlerData.event) : getTargetNode(handlerData.event);
11923
+ message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
11924
+ } catch (e) {
11925
+ message = '<unknown>';
11926
+ }
11927
+
11928
+ return { target, message };
11929
+ }
11930
+
11931
+ function isRrwebNode(node) {
11932
+ return '__sn' in node;
11933
+ }
11934
+
11935
+ function getTargetNode(event) {
11936
+ if (isEventWithTarget(event)) {
11937
+ return event.target ;
11938
+ }
11939
+
11940
+ return event;
11941
+ }
11942
+
11943
+ const INTERACTIVE_SELECTOR = 'button,a';
11944
+
11945
+ // For clicks, we check if the target is inside of a button or link
11946
+ // If so, we use this as the target instead
11947
+ // This is useful because if you click on the image in <button><img></button>,
11948
+ // The target will be the image, not the button, which we don't want here
11949
+ function getClickTargetNode(event) {
11950
+ const target = getTargetNode(event);
11951
+
11952
+ if (!target || !(target instanceof Element)) {
11953
+ return target;
11954
+ }
11955
+
11956
+ const closestInteractive = target.closest(INTERACTIVE_SELECTOR);
11957
+ return closestInteractive || target;
11958
+ }
11959
+
11960
+ function isEventWithTarget(event) {
11961
+ return typeof event === 'object' && !!event && 'target' in event;
11962
+ }
11963
+
11964
+ /** Handle keyboard events & create breadcrumbs. */
11965
+ function handleKeyboardEvent(replay, event) {
11966
+ if (!replay.isEnabled()) {
11967
+ return;
11968
+ }
11969
+
11970
+ replay.triggerUserActivity();
11971
+
11972
+ const breadcrumb = getKeyboardBreadcrumb(event);
11973
+
11974
+ if (!breadcrumb) {
11975
+ return;
11976
+ }
11977
+
11978
+ addBreadcrumbEvent(replay, breadcrumb);
11979
+ }
11980
+
11981
+ /** exported only for tests */
11982
+ function getKeyboardBreadcrumb(event) {
11983
+ const { metaKey, shiftKey, ctrlKey, altKey, key, target } = event;
11984
+
11985
+ // never capture for input fields
11986
+ if (!target || isInputElement(target )) {
11987
+ return null;
11988
+ }
11989
+
11990
+ // Note: We do not consider shift here, as that means "uppercase"
11991
+ const hasModifierKey = metaKey || ctrlKey || altKey;
11992
+ const isCharacterKey = key.length === 1; // other keys like Escape, Tab, etc have a longer length
11993
+
11994
+ // Do not capture breadcrumb if only a word key is pressed
11995
+ // This could leak e.g. user input
11996
+ if (!hasModifierKey && isCharacterKey) {
11997
+ return null;
11998
+ }
11999
+
12000
+ const message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
12001
+ const baseBreadcrumb = getBaseDomBreadcrumb(target , message);
12002
+
12003
+ return createBreadcrumb({
12004
+ category: 'ui.keyDown',
12005
+ message,
12006
+ data: {
12007
+ ...baseBreadcrumb.data,
12008
+ metaKey,
12009
+ shiftKey,
12010
+ ctrlKey,
12011
+ altKey,
12012
+ key,
12013
+ },
12014
+ });
12015
+ }
12016
+
12017
+ function isInputElement(target) {
12018
+ return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
12019
+ }
12020
+
11529
12021
  const NAVIGATION_ENTRY_KEYS = [
11530
12022
  'name',
11531
12023
  'type',
@@ -11679,20 +12171,19 @@ class EventBufferArray {
11679
12171
  return this.events.length > 0;
11680
12172
  }
11681
12173
 
12174
+ /** @inheritdoc */
12175
+ get type() {
12176
+ return 'sync';
12177
+ }
12178
+
11682
12179
  /** @inheritdoc */
11683
12180
  destroy() {
11684
12181
  this.events = [];
11685
12182
  }
11686
12183
 
11687
12184
  /** @inheritdoc */
11688
- async addEvent(event, isCheckout) {
11689
- if (isCheckout) {
11690
- this.events = [event];
11691
- return;
11692
- }
11693
-
12185
+ async addEvent(event) {
11694
12186
  this.events.push(event);
11695
- return;
11696
12187
  }
11697
12188
 
11698
12189
  /** @inheritdoc */
@@ -11706,6 +12197,22 @@ class EventBufferArray {
11706
12197
  resolve(JSON.stringify(eventsRet));
11707
12198
  });
11708
12199
  }
12200
+
12201
+ /** @inheritdoc */
12202
+ clear() {
12203
+ this.events = [];
12204
+ }
12205
+
12206
+ /** @inheritdoc */
12207
+ getEarliestTimestamp() {
12208
+ const timestamp = this.events.map(event => event.timestamp).sort()[0];
12209
+
12210
+ if (!timestamp) {
12211
+ return null;
12212
+ }
12213
+
12214
+ return timestampToMs(timestamp);
12215
+ }
11709
12216
  }
11710
12217
 
11711
12218
  /**
@@ -11813,11 +12320,20 @@ class WorkerHandler {
11813
12320
  * Exported only for testing.
11814
12321
  */
11815
12322
  class EventBufferCompressionWorker {
11816
- /** @inheritdoc */
11817
12323
 
11818
12324
  constructor(worker) {
11819
12325
  this._worker = new WorkerHandler(worker);
11820
- this.hasEvents = false;
12326
+ this._earliestTimestamp = null;
12327
+ }
12328
+
12329
+ /** @inheritdoc */
12330
+ get hasEvents() {
12331
+ return !!this._earliestTimestamp;
12332
+ }
12333
+
12334
+ /** @inheritdoc */
12335
+ get type() {
12336
+ return 'worker';
11821
12337
  }
11822
12338
 
11823
12339
  /**
@@ -11840,13 +12356,10 @@ class EventBufferCompressionWorker {
11840
12356
  *
11841
12357
  * Returns true if event was successfuly received and processed by worker.
11842
12358
  */
11843
- async addEvent(event, isCheckout) {
11844
- this.hasEvents = true;
11845
-
11846
- if (isCheckout) {
11847
- // This event is a checkout, make sure worker buffer is cleared before
11848
- // proceeding.
11849
- await this._clear();
12359
+ addEvent(event) {
12360
+ const timestamp = timestampToMs(event.timestamp);
12361
+ if (!this._earliestTimestamp || timestamp < this._earliestTimestamp) {
12362
+ this._earliestTimestamp = timestamp;
11850
12363
  }
11851
12364
 
11852
12365
  return this._sendEventToWorker(event);
@@ -11859,6 +12372,18 @@ class EventBufferCompressionWorker {
11859
12372
  return this._finishRequest();
11860
12373
  }
11861
12374
 
12375
+ /** @inheritdoc */
12376
+ clear() {
12377
+ this._earliestTimestamp = null;
12378
+ // We do not wait on this, as we assume the order of messages is consistent for the worker
12379
+ void this._worker.postMessage('clear');
12380
+ }
12381
+
12382
+ /** @inheritdoc */
12383
+ getEarliestTimestamp() {
12384
+ return this._earliestTimestamp;
12385
+ }
12386
+
11862
12387
  /**
11863
12388
  * Send the event to the worker.
11864
12389
  */
@@ -11872,15 +12397,10 @@ class EventBufferCompressionWorker {
11872
12397
  async _finishRequest() {
11873
12398
  const response = await this._worker.postMessage('finish');
11874
12399
 
11875
- this.hasEvents = false;
12400
+ this._earliestTimestamp = null;
11876
12401
 
11877
12402
  return response;
11878
12403
  }
11879
-
11880
- /** Clear any pending events from the worker. */
11881
- _clear() {
11882
- return this._worker.postMessage('clear');
11883
- }
11884
12404
  }
11885
12405
 
11886
12406
  /**
@@ -11898,6 +12418,11 @@ class EventBufferProxy {
11898
12418
  this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded();
11899
12419
  }
11900
12420
 
12421
+ /** @inheritdoc */
12422
+ get type() {
12423
+ return this._used.type;
12424
+ }
12425
+
11901
12426
  /** @inheritDoc */
11902
12427
  get hasEvents() {
11903
12428
  return this._used.hasEvents;
@@ -11909,13 +12434,23 @@ class EventBufferProxy {
11909
12434
  this._compression.destroy();
11910
12435
  }
11911
12436
 
12437
+ /** @inheritdoc */
12438
+ clear() {
12439
+ return this._used.clear();
12440
+ }
12441
+
12442
+ /** @inheritdoc */
12443
+ getEarliestTimestamp() {
12444
+ return this._used.getEarliestTimestamp();
12445
+ }
12446
+
11912
12447
  /**
11913
12448
  * Add an event to the event buffer.
11914
12449
  *
11915
12450
  * Returns true if event was successfully added.
11916
12451
  */
11917
- addEvent(event, isCheckout) {
11918
- return this._used.addEvent(event, isCheckout);
12452
+ addEvent(event) {
12453
+ return this._used.addEvent(event);
11919
12454
  }
11920
12455
 
11921
12456
  /** @inheritDoc */
@@ -12197,59 +12732,6 @@ function getSession({
12197
12732
  return { type: 'new', session: newSession };
12198
12733
  }
12199
12734
 
12200
- /**
12201
- * Add an event to the event buffer.
12202
- * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
12203
- */
12204
- async function addEvent(
12205
- replay,
12206
- event,
12207
- isCheckout,
12208
- ) {
12209
- if (!replay.eventBuffer) {
12210
- // This implies that `_isEnabled` is false
12211
- return null;
12212
- }
12213
-
12214
- if (replay.isPaused()) {
12215
- // Do not add to event buffer when recording is paused
12216
- return null;
12217
- }
12218
-
12219
- // TODO: sadness -- we will want to normalize timestamps to be in ms -
12220
- // requires coordination with frontend
12221
- const isMs = event.timestamp > 9999999999;
12222
- const timestampInMs = isMs ? event.timestamp : event.timestamp * 1000;
12223
-
12224
- // Throw out events that happen more than 5 minutes ago. This can happen if
12225
- // page has been left open and idle for a long period of time and user
12226
- // comes back to trigger a new session. The performance entries rely on
12227
- // `performance.timeOrigin`, which is when the page first opened.
12228
- if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
12229
- return null;
12230
- }
12231
-
12232
- // Only record earliest event if a new session was created, otherwise it
12233
- // shouldn't be relevant
12234
- const earliestEvent = replay.getContext().earliestEvent;
12235
- if (replay.session && replay.session.segmentId === 0 && (!earliestEvent || timestampInMs < earliestEvent)) {
12236
- replay.getContext().earliestEvent = timestampInMs;
12237
- }
12238
-
12239
- try {
12240
- return await replay.eventBuffer.addEvent(event, isCheckout);
12241
- } catch (error) {
12242
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
12243
- await replay.stop('addEvent');
12244
-
12245
- const client = getCurrentHub().getClient();
12246
-
12247
- if (client) {
12248
- client.recordDroppedEvent('internal_sdk_error', 'replay');
12249
- }
12250
- }
12251
- }
12252
-
12253
12735
  /** If the event is an error event */
12254
12736
  function isErrorEvent(event) {
12255
12737
  return !event.type;
@@ -12299,22 +12781,18 @@ function handleAfterSendEvent(replay) {
12299
12781
  return;
12300
12782
  }
12301
12783
 
12302
- // Add error to list of errorIds of replay
12784
+ // Add error to list of errorIds of replay. This is ok to do even if not
12785
+ // sampled because context will get reset at next checkout.
12786
+ // XXX: There is also a race condition where it's possible to capture an
12787
+ // error to Sentry before Replay SDK has loaded, but response returns after
12788
+ // it was loaded, and this gets called.
12303
12789
  if (event.event_id) {
12304
12790
  replay.getContext().errorIds.add(event.event_id);
12305
12791
  }
12306
12792
 
12307
- // Trigger error recording
12793
+ // If error event is tagged with replay id it means it was sampled (when in buffer mode)
12308
12794
  // Need to be very careful that this does not cause an infinite loop
12309
- if (
12310
- replay.recordingMode === 'buffer' &&
12311
- event.exception &&
12312
- event.message !== UNABLE_TO_SEND_REPLAY // ignore this error because otherwise we could loop indefinitely with trying to capture replay and failing
12313
- ) {
12314
- if (!isSampled(replay.getOptions().errorSampleRate)) {
12315
- return;
12316
- }
12317
-
12795
+ if (replay.recordingMode === 'buffer' && event.tags && event.tags.replayId) {
12318
12796
  setTimeout(() => {
12319
12797
  // Capture current event buffer as new replay
12320
12798
  void replay.sendBufferedReplayOrFlush();
@@ -12339,167 +12817,6 @@ function isBaseTransportSend() {
12339
12817
  );
12340
12818
  }
12341
12819
 
12342
- var NodeType;
12343
- (function (NodeType) {
12344
- NodeType[NodeType["Document"] = 0] = "Document";
12345
- NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
12346
- NodeType[NodeType["Element"] = 2] = "Element";
12347
- NodeType[NodeType["Text"] = 3] = "Text";
12348
- NodeType[NodeType["CDATA"] = 4] = "CDATA";
12349
- NodeType[NodeType["Comment"] = 5] = "Comment";
12350
- })(NodeType || (NodeType = {}));
12351
-
12352
- /**
12353
- * Create a breadcrumb for a replay.
12354
- */
12355
- function createBreadcrumb(
12356
- breadcrumb,
12357
- ) {
12358
- return {
12359
- timestamp: Date.now() / 1000,
12360
- type: 'default',
12361
- ...breadcrumb,
12362
- };
12363
- }
12364
-
12365
- /**
12366
- * Add a breadcrumb event to replay.
12367
- */
12368
- function addBreadcrumbEvent(replay, breadcrumb) {
12369
- if (breadcrumb.category === 'sentry.transaction') {
12370
- return;
12371
- }
12372
-
12373
- if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
12374
- replay.triggerUserActivity();
12375
- } else {
12376
- replay.checkAndHandleExpiredSession();
12377
- }
12378
-
12379
- replay.addUpdate(() => {
12380
- void addEvent(replay, {
12381
- type: EventType.Custom,
12382
- // TODO: We were converting from ms to seconds for breadcrumbs, spans,
12383
- // but maybe we should just keep them as milliseconds
12384
- timestamp: (breadcrumb.timestamp || 0) * 1000,
12385
- data: {
12386
- tag: 'breadcrumb',
12387
- // normalize to max. 10 depth and 1_000 properties per object
12388
- payload: normalize(breadcrumb, 10, 1000),
12389
- },
12390
- });
12391
-
12392
- // Do not flush after console log messages
12393
- return breadcrumb.category === 'console';
12394
- });
12395
- }
12396
-
12397
- // Note that these are the serialized attributes and not attributes directly on
12398
- // the DOM Node. Attributes we are interested in:
12399
- const ATTRIBUTES_TO_RECORD = new Set([
12400
- 'id',
12401
- 'class',
12402
- 'aria-label',
12403
- 'role',
12404
- 'name',
12405
- 'alt',
12406
- 'title',
12407
- 'data-test-id',
12408
- 'data-testid',
12409
- ]);
12410
-
12411
- /**
12412
- * Inclusion list of attributes that we want to record from the DOM element
12413
- */
12414
- function getAttributesToRecord(attributes) {
12415
- const obj = {};
12416
- for (const key in attributes) {
12417
- if (ATTRIBUTES_TO_RECORD.has(key)) {
12418
- let normalizedKey = key;
12419
-
12420
- if (key === 'data-testid' || key === 'data-test-id') {
12421
- normalizedKey = 'testId';
12422
- }
12423
-
12424
- obj[normalizedKey] = attributes[key];
12425
- }
12426
- }
12427
-
12428
- return obj;
12429
- }
12430
-
12431
- const handleDomListener =
12432
- (replay) =>
12433
- (handlerData) => {
12434
- if (!replay.isEnabled()) {
12435
- return;
12436
- }
12437
-
12438
- const result = handleDom(handlerData);
12439
-
12440
- if (!result) {
12441
- return;
12442
- }
12443
-
12444
- addBreadcrumbEvent(replay, result);
12445
- };
12446
-
12447
- /**
12448
- * An event handler to react to DOM events.
12449
- */
12450
- function handleDom(handlerData) {
12451
- let target;
12452
- let targetNode;
12453
-
12454
- // Accessing event.target can throw (see getsentry/raven-js#838, #768)
12455
- try {
12456
- targetNode = getTargetNode(handlerData);
12457
- target = htmlTreeAsString(targetNode);
12458
- } catch (e) {
12459
- target = '<unknown>';
12460
- }
12461
-
12462
- // `__sn` property is the serialized node created by rrweb
12463
- const serializedNode =
12464
- targetNode && '__sn' in targetNode && targetNode.__sn.type === NodeType.Element ? targetNode.__sn : null;
12465
-
12466
- return createBreadcrumb({
12467
- category: `ui.${handlerData.name}`,
12468
- message: target,
12469
- data: serializedNode
12470
- ? {
12471
- nodeId: serializedNode.id,
12472
- node: {
12473
- id: serializedNode.id,
12474
- tagName: serializedNode.tagName,
12475
- textContent: targetNode
12476
- ? Array.from(targetNode.childNodes)
12477
- .map(
12478
- (node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
12479
- )
12480
- .filter(Boolean) // filter out empty values
12481
- .map(text => (text ).trim())
12482
- .join('')
12483
- : '',
12484
- attributes: getAttributesToRecord(serializedNode.attributes),
12485
- },
12486
- }
12487
- : {},
12488
- });
12489
- }
12490
-
12491
- function getTargetNode(handlerData) {
12492
- if (isEventWithTarget(handlerData.event)) {
12493
- return handlerData.event.target;
12494
- }
12495
-
12496
- return handlerData.event;
12497
- }
12498
-
12499
- function isEventWithTarget(event) {
12500
- return !!(event ).target;
12501
- }
12502
-
12503
12820
  /**
12504
12821
  * Returns true if we think the given event is an error originating inside of rrweb.
12505
12822
  */
@@ -12523,6 +12840,30 @@ function isRrwebError(event, hint) {
12523
12840
  });
12524
12841
  }
12525
12842
 
12843
+ /**
12844
+ * Determine if event should be sampled (only applies in buffer mode).
12845
+ * When an event is captured by `hanldleGlobalEvent`, when in buffer mode
12846
+ * we determine if we want to sample the error or not.
12847
+ */
12848
+ function shouldSampleForBufferEvent(replay, event) {
12849
+ if (replay.recordingMode !== 'buffer') {
12850
+ return false;
12851
+ }
12852
+
12853
+ // ignore this error because otherwise we could loop indefinitely with
12854
+ // trying to capture replay and failing
12855
+ if (event.message === UNABLE_TO_SEND_REPLAY) {
12856
+ return false;
12857
+ }
12858
+
12859
+ // Require the event to be an error event & to have an exception
12860
+ if (!event.exception || event.type) {
12861
+ return false;
12862
+ }
12863
+
12864
+ return isSampled(replay.getOptions().errorSampleRate);
12865
+ }
12866
+
12526
12867
  /**
12527
12868
  * Returns a listener to be added to `addGlobalEventProcessor(listener)`.
12528
12869
  */
@@ -12552,8 +12893,16 @@ function handleGlobalEventListener(
12552
12893
  return null;
12553
12894
  }
12554
12895
 
12555
- // Only tag transactions with replayId if not waiting for an error
12556
- if (isErrorEvent(event) || (isTransactionEvent(event) && replay.recordingMode === 'session')) {
12896
+ // When in buffer mode, we decide to sample here.
12897
+ // Later, in `handleAfterSendEvent`, if the replayId is set, we know that we sampled
12898
+ // And convert the buffer session to a full session
12899
+ const isErrorEventSampled = shouldSampleForBufferEvent(replay, event);
12900
+
12901
+ // Tag errors if it has been sampled in buffer mode, or if it is session mode
12902
+ // Only tag transactions if in session mode
12903
+ const shouldTagReplayId = isErrorEventSampled || replay.recordingMode === 'session';
12904
+
12905
+ if (shouldTagReplayId) {
12557
12906
  event.tags = { ...event.tags, replayId: replay.getSessionId() };
12558
12907
  }
12559
12908
 
@@ -12603,7 +12952,7 @@ function createPerformanceSpans(
12603
12952
  ) {
12604
12953
  return entries.map(({ type, start, end, name, data }) =>
12605
12954
  addEvent(replay, {
12606
- type: EventType.Custom,
12955
+ type: EventType$1.Custom,
12607
12956
  timestamp: start,
12608
12957
  data: {
12609
12958
  tag: 'performanceSpan',
@@ -13385,7 +13734,32 @@ function _strIsProbablyJson(str) {
13385
13734
 
13386
13735
  /** Match an URL against a list of strings/Regex. */
13387
13736
  function urlMatches(url, urls) {
13388
- return stringMatchesSomePattern(url, urls);
13737
+ const fullUrl = getFullUrl(url);
13738
+
13739
+ return stringMatchesSomePattern(fullUrl, urls);
13740
+ }
13741
+
13742
+ /** exported for tests */
13743
+ function getFullUrl(url, baseURI = WINDOW.document.baseURI) {
13744
+ // Short circuit for common cases:
13745
+ if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith(WINDOW.location.origin)) {
13746
+ return url;
13747
+ }
13748
+ const fixedUrl = new URL(url, baseURI);
13749
+
13750
+ // If these do not match, we are not dealing with a relative URL, so just return it
13751
+ if (fixedUrl.origin !== new URL(baseURI).origin) {
13752
+ return url;
13753
+ }
13754
+
13755
+ const fullUrl = fixedUrl.href;
13756
+
13757
+ // Remove trailing slashes, if they don't match the original URL
13758
+ if (!url.endsWith('/') && fullUrl.endsWith('/')) {
13759
+ return fullUrl.slice(0, -1);
13760
+ }
13761
+
13762
+ return fullUrl;
13389
13763
  }
13390
13764
 
13391
13765
  /**
@@ -13935,7 +14309,8 @@ function addGlobalListeners(replay) {
13935
14309
  client.on('afterSendEvent', handleAfterSendEvent(replay));
13936
14310
  client.on('createDsc', (dsc) => {
13937
14311
  const replayId = replay.getSessionId();
13938
- if (replayId && replay.isEnabled()) {
14312
+ // We do not want to set the DSC when in buffer mode, as that means the replay has not been sent (yet)
14313
+ if (replayId && replay.isEnabled() && replay.recordingMode === 'session') {
13939
14314
  dsc.replay_id = replayId;
13940
14315
  }
13941
14316
  });
@@ -14215,6 +14590,23 @@ function debounce(func, wait, options) {
14215
14590
  return debounced;
14216
14591
  }
14217
14592
 
14593
+ /* eslint-disable @typescript-eslint/naming-convention */
14594
+
14595
+ var EventType; (function (EventType) {
14596
+ const DomContentLoaded = 0; EventType[EventType["DomContentLoaded"] = DomContentLoaded] = "DomContentLoaded";
14597
+ const Load = 1; EventType[EventType["Load"] = Load] = "Load";
14598
+ const FullSnapshot = 2; EventType[EventType["FullSnapshot"] = FullSnapshot] = "FullSnapshot";
14599
+ const IncrementalSnapshot = 3; EventType[EventType["IncrementalSnapshot"] = IncrementalSnapshot] = "IncrementalSnapshot";
14600
+ const Meta = 4; EventType[EventType["Meta"] = Meta] = "Meta";
14601
+ const Custom = 5; EventType[EventType["Custom"] = Custom] = "Custom";
14602
+ const Plugin = 6; EventType[EventType["Plugin"] = Plugin] = "Plugin";
14603
+ })(EventType || (EventType = {}));
14604
+
14605
+ /**
14606
+ * This is a partial copy of rrweb's eventWithTime type which only contains the properties
14607
+ * we specifcally need in the SDK.
14608
+ */
14609
+
14218
14610
  /**
14219
14611
  * Handler for recording events.
14220
14612
  *
@@ -14257,6 +14649,14 @@ function getHandleRecordingEmit(replay) {
14257
14649
  return false;
14258
14650
  }
14259
14651
 
14652
+ // Additionally, create a meta event that will capture certain SDK settings.
14653
+ // In order to handle buffer mode, this needs to either be done when we
14654
+ // receive checkout events or at flush time.
14655
+ //
14656
+ // `isCheckout` is always true, but want to be explicit that it should
14657
+ // only be added for checkouts
14658
+ void addSettingsEvent(replay, isCheckout);
14659
+
14260
14660
  // If there is a previousSessionId after a full snapshot occurs, then
14261
14661
  // the replay session was started due to session expiration. The new session
14262
14662
  // is started before triggering a new checkout and contains the id
@@ -14267,10 +14667,10 @@ function getHandleRecordingEmit(replay) {
14267
14667
  return true;
14268
14668
  }
14269
14669
 
14270
- // See note above re: session start needs to reflect the most recent
14271
- // checkout.
14272
- if (replay.recordingMode === 'buffer' && replay.session) {
14273
- const { earliestEvent } = replay.getContext();
14670
+ // When in buffer mode, make sure we adjust the session started date to the current earliest event of the buffer
14671
+ // this should usually be the timestamp of the checkout event, but to be safe...
14672
+ if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) {
14673
+ const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
14274
14674
  if (earliestEvent) {
14275
14675
  replay.session.started = earliestEvent;
14276
14676
 
@@ -14294,6 +14694,46 @@ function getHandleRecordingEmit(replay) {
14294
14694
  };
14295
14695
  }
14296
14696
 
14697
+ /**
14698
+ * Exported for tests
14699
+ */
14700
+ function createOptionsEvent(replay) {
14701
+ const options = replay.getOptions();
14702
+ return {
14703
+ type: EventType.Custom,
14704
+ timestamp: Date.now(),
14705
+ data: {
14706
+ tag: 'options',
14707
+ payload: {
14708
+ sessionSampleRate: options.sessionSampleRate,
14709
+ errorSampleRate: options.errorSampleRate,
14710
+ useCompressionOption: options.useCompression,
14711
+ blockAllMedia: options.blockAllMedia,
14712
+ maskAllText: options.maskAllText,
14713
+ maskAllInputs: options.maskAllInputs,
14714
+ useCompression: replay.eventBuffer ? replay.eventBuffer.type === 'worker' : false,
14715
+ networkDetailHasUrls: options.networkDetailAllowUrls.length > 0,
14716
+ networkCaptureBodies: options.networkCaptureBodies,
14717
+ networkRequestHasHeaders: options.networkRequestHeaders.length > 0,
14718
+ networkResponseHasHeaders: options.networkResponseHeaders.length > 0,
14719
+ },
14720
+ },
14721
+ };
14722
+ }
14723
+
14724
+ /**
14725
+ * Add a "meta" event that contains a simplified view on current configuration
14726
+ * options. This should only be included on the first segment of a recording.
14727
+ */
14728
+ function addSettingsEvent(replay, isCheckout) {
14729
+ // Only need to add this event when sending the first segment
14730
+ if (!isCheckout || !replay.session || replay.session.segmentId !== 0) {
14731
+ return Promise.resolve(null);
14732
+ }
14733
+
14734
+ return addEvent(replay, createOptionsEvent(replay), false);
14735
+ }
14736
+
14297
14737
  /**
14298
14738
  * Create a replay envelope ready to be sent.
14299
14739
  * This includes both the replay event, as well as the recording data.
@@ -14408,7 +14848,6 @@ async function sendReplayRequest({
14408
14848
  eventContext,
14409
14849
  timestamp,
14410
14850
  session,
14411
- options,
14412
14851
  }) {
14413
14852
  const preparedRecordingData = prepareRecordingData({
14414
14853
  recordingData,
@@ -14450,15 +14889,6 @@ async function sendReplayRequest({
14450
14889
  return;
14451
14890
  }
14452
14891
 
14453
- replayEvent.contexts = {
14454
- ...replayEvent.contexts,
14455
- replay: {
14456
- ...(replayEvent.contexts && replayEvent.contexts.replay),
14457
- session_sample_rate: options.sessionSampleRate,
14458
- error_sample_rate: options.errorSampleRate,
14459
- },
14460
- };
14461
-
14462
14892
  /*
14463
14893
  For reference, the fully built event looks something like this:
14464
14894
  {
@@ -14489,10 +14919,6 @@ async function sendReplayRequest({
14489
14919
  },
14490
14920
  "sdkProcessingMetadata": {},
14491
14921
  "contexts": {
14492
- "replay": {
14493
- "session_sample_rate": 1,
14494
- "error_sample_rate": 0,
14495
- },
14496
14922
  },
14497
14923
  }
14498
14924
  */
@@ -14676,7 +15102,6 @@ class ReplayContainer {
14676
15102
  errorIds: new Set(),
14677
15103
  traceIds: new Set(),
14678
15104
  urls: [],
14679
- earliestEvent: null,
14680
15105
  initialTimestamp: Date.now(),
14681
15106
  initialUrl: '',
14682
15107
  };}
@@ -14686,7 +15111,7 @@ class ReplayContainer {
14686
15111
  recordingOptions,
14687
15112
  }
14688
15113
 
14689
- ) {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);
15114
+ ) {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);
14690
15115
  this._recordingOptions = recordingOptions;
14691
15116
  this._options = options;
14692
15117
 
@@ -15178,6 +15603,7 @@ class ReplayContainer {
15178
15603
  WINDOW.document.addEventListener('visibilitychange', this._handleVisibilityChange);
15179
15604
  WINDOW.addEventListener('blur', this._handleWindowBlur);
15180
15605
  WINDOW.addEventListener('focus', this._handleWindowFocus);
15606
+ WINDOW.addEventListener('keydown', this._handleKeyboardEvent);
15181
15607
 
15182
15608
  // There is no way to remove these listeners, so ensure they are only added once
15183
15609
  if (!this._hasInitializedCoreListeners) {
@@ -15206,6 +15632,7 @@ class ReplayContainer {
15206
15632
 
15207
15633
  WINDOW.removeEventListener('blur', this._handleWindowBlur);
15208
15634
  WINDOW.removeEventListener('focus', this._handleWindowFocus);
15635
+ WINDOW.removeEventListener('keydown', this._handleKeyboardEvent);
15209
15636
 
15210
15637
  if (this._performanceObserver) {
15211
15638
  this._performanceObserver.disconnect();
@@ -15256,6 +15683,11 @@ class ReplayContainer {
15256
15683
  this._doChangeToForegroundTasks(breadcrumb);
15257
15684
  };}
15258
15685
 
15686
+ /** Ensure page remains active when a key is pressed. */
15687
+ __init16() {this._handleKeyboardEvent = (event) => {
15688
+ handleKeyboardEvent(this, event);
15689
+ };}
15690
+
15259
15691
  /**
15260
15692
  * Tasks to run when we consider a page to be hidden (via blurring and/or visibility)
15261
15693
  */
@@ -15335,7 +15767,7 @@ class ReplayContainer {
15335
15767
  _createCustomBreadcrumb(breadcrumb) {
15336
15768
  this.addUpdate(() => {
15337
15769
  void addEvent(this, {
15338
- type: EventType.Custom,
15770
+ type: EventType$1.Custom,
15339
15771
  timestamp: breadcrumb.timestamp || 0,
15340
15772
  data: {
15341
15773
  tag: 'breadcrumb',
@@ -15376,22 +15808,35 @@ class ReplayContainer {
15376
15808
  this._context.errorIds.clear();
15377
15809
  this._context.traceIds.clear();
15378
15810
  this._context.urls = [];
15379
- this._context.earliestEvent = null;
15811
+ }
15812
+
15813
+ /** Update the initial timestamp based on the buffer content. */
15814
+ _updateInitialTimestampFromEventBuffer() {
15815
+ const { session, eventBuffer } = this;
15816
+ if (!session || !eventBuffer) {
15817
+ return;
15818
+ }
15819
+
15820
+ // we only ever update this on the initial segment
15821
+ if (session.segmentId) {
15822
+ return;
15823
+ }
15824
+
15825
+ const earliestEvent = eventBuffer.getEarliestTimestamp();
15826
+ if (earliestEvent && earliestEvent < this._context.initialTimestamp) {
15827
+ this._context.initialTimestamp = earliestEvent;
15828
+ }
15380
15829
  }
15381
15830
 
15382
15831
  /**
15383
15832
  * Return and clear _context
15384
15833
  */
15385
15834
  _popEventContext() {
15386
- if (this._context.earliestEvent && this._context.earliestEvent < this._context.initialTimestamp) {
15387
- this._context.initialTimestamp = this._context.earliestEvent;
15388
- }
15389
-
15390
15835
  const _context = {
15391
15836
  initialTimestamp: this._context.initialTimestamp,
15392
15837
  initialUrl: this._context.initialUrl,
15393
- errorIds: Array.from(this._context.errorIds).filter(Boolean),
15394
- traceIds: Array.from(this._context.traceIds).filter(Boolean),
15838
+ errorIds: Array.from(this._context.errorIds),
15839
+ traceIds: Array.from(this._context.traceIds),
15395
15840
  urls: this._context.urls,
15396
15841
  };
15397
15842
 
@@ -15430,6 +15875,9 @@ class ReplayContainer {
15430
15875
  }
15431
15876
 
15432
15877
  try {
15878
+ // This uses the data from the eventBuffer, so we need to call this before `finish()
15879
+ this._updateInitialTimestampFromEventBuffer();
15880
+
15433
15881
  // Note this empties the event buffer regardless of outcome of sending replay
15434
15882
  const recordingData = await this.eventBuffer.finish();
15435
15883
 
@@ -15470,7 +15918,7 @@ class ReplayContainer {
15470
15918
  * Flush recording data to Sentry. Creates a lock so that only a single flush
15471
15919
  * can be active at a time. Do not call this directly.
15472
15920
  */
15473
- __init16() {this._flush = async ({
15921
+ __init17() {this._flush = async ({
15474
15922
  force = false,
15475
15923
  }
15476
15924
 
@@ -15525,7 +15973,7 @@ class ReplayContainer {
15525
15973
  }
15526
15974
 
15527
15975
  /** Handler for rrweb.record.onMutation */
15528
- __init17() {this._onMutationHandler = (mutations) => {
15976
+ __init18() {this._onMutationHandler = (mutations) => {
15529
15977
  const count = mutations.length;
15530
15978
 
15531
15979
  const mutationLimit = this._options._experiments.mutationLimit || 0;
@@ -15764,6 +16212,8 @@ class Replay {
15764
16212
  errorSampleRate,
15765
16213
  useCompression,
15766
16214
  blockAllMedia,
16215
+ maskAllInputs,
16216
+ maskAllText,
15767
16217
  networkDetailAllowUrls,
15768
16218
  networkCaptureBodies,
15769
16219
  networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
@@ -24991,6 +25441,7 @@ var ACTIONS$7;
24991
25441
  ACTIONS["SET_MEDIA_RECORDER"] = "SET_MEDIA_RECORDER";
24992
25442
  ACTIONS["SET_MIC_ERROR"] = "SET_MIC_ERROR";
24993
25443
  ACTIONS["SET_PERMISSION_LISTENER"] = "SET_PERMISSION_LISTENER";
25444
+ ACTIONS["SET_ACTIVELY_STOPPED"] = "SET_ACTIVELY_STOPPED";
24994
25445
  })(ACTIONS$7 || (ACTIONS$7 = {}));
24995
25446
  var EVENTS$6;
24996
25447
  (function (EVENTS) {
@@ -43541,6 +43992,7 @@ const initialState = {
43541
43992
  isSafeMode: false,
43542
43993
  isAutoStart: false,
43543
43994
  isMicError: false,
43995
+ isActivelyStopped: false,
43544
43996
  };
43545
43997
  const recorderMachineV2 = createMachine({
43546
43998
  predictableActionArguments: true,
@@ -43759,6 +44211,7 @@ const recorderMachineV2 = createMachine({
43759
44211
  on: {
43760
44212
  [EVENTS$6.STOP_RECORDING]: {
43761
44213
  actions: [
44214
+ ACTIONS$7.SET_ACTIVELY_STOPPED,
43762
44215
  ACTIONS$7.STOP_COUNT_DOWN_ACTOR,
43763
44216
  ACTIONS$7.STOP_MEDIA_RECORDER,
43764
44217
  ACTIONS$7.STOP_MICROPHONE_MACHINE,
@@ -43971,7 +44424,7 @@ const recorderMachineV2 = createMachine({
43971
44424
  webm = new File([blob], Date.now().toString(), {
43972
44425
  type: mimeType,
43973
44426
  });
43974
- callback({ type: 'SEND_CURRENT_TAKE', data: { webm, videoLength } });
44427
+ callback({ type: EVENTS$6.SEND_CURRENT_TAKE, data: { webm, videoLength } });
43975
44428
  })
43976
44429
  .catch((error) => {
43977
44430
  console.error('fixVideoMetadata', error);
@@ -44020,6 +44473,7 @@ const recorderMachineV2 = createMachine({
44020
44473
  [ACTIONS$7.INIT_COUNT_DOWN_ACTOR]: assign$2({
44021
44474
  countDownRef: (context, event, meta) => spawn(counterMachine.withContext(Object.assign(Object.assign({}, counterMachine.context), { callback: meta.action.data.callback })), { name: 'countDownRef' }),
44022
44475
  }),
44476
+ [ACTIONS$7.SET_ACTIVELY_STOPPED]: assign$2((context) => ({ isActivelyStopped: true })),
44023
44477
  [ACTIONS$7.STOP_COUNT_DOWN_ACTOR]: assign$2((context) => {
44024
44478
  context.countDownRef.stop();
44025
44479
  return {
@@ -44090,7 +44544,7 @@ const recorderMachineV2 = createMachine({
44090
44544
  // send success event to the parent, to be able to proceed to the next step
44091
44545
  [ACTIONS$7.SEND_SUCCESS_TO_PARENT]: sendParent$1((context, event) => (Object.assign(Object.assign({}, event), { type: EVENTS$5.READY_TO_START_RECORDING }))),
44092
44546
  // on stop recording send webm file to storage machine
44093
- [ACTIONS$7.SEND_CURRENT_TAKE]: sendParent$1((context, event) => (Object.assign(Object.assign({}, event), { type: EVENTS$5.STOP_RECORDING }))),
44547
+ [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 }) }))),
44094
44548
  // send null param to mic machine to be able to stop sound detecting
44095
44549
  [ACTIONS$7.STOP_MICROPHONE_MACHINE]: send$2((_, _event) => ({ type: EVENTS$3.ON_SET_MEDIA_STREAM, data: null }), { to: (context) => context.microphoneRef }),
44096
44550
  // clean mic error, this is really sensitive, !! a lot of user can't start recording coz of it
@@ -44619,7 +45073,7 @@ const MAPPED_EVENT_TYPES = {
44619
45073
  PRACTICE: EVENT_TYPES.TIMES_UP,
44620
45074
  },
44621
45075
  };
44622
- const questionEventFormatter = (questionActionType) => pure_1(({ recordingType, questions, currentQuestion, widgetConfig, currentTake }) => {
45076
+ const questionEventFormatter = (questionActionType) => pure_1(({ recordingType, questions, currentQuestion, widgetConfig, currentTake }, event) => {
44623
45077
  var _a, _b;
44624
45078
  const isQuestionMode = recordingType === TAKE_TYPES.QUESTION;
44625
45079
  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;
@@ -44627,10 +45081,11 @@ const questionEventFormatter = (questionActionType) => pure_1(({ recordingType,
44627
45081
  const isAssessment = currentQuestionObj.answerType && currentQuestionObj.answerType !== ANSWER_TYPES.VIDEO;
44628
45082
  return {
44629
45083
  type: ACTIONS$6.EMIT_TRACKING_EVENT,
44630
- 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 && {
45084
+ 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 && {
44631
45085
  currentTake,
44632
45086
  numberOfTakes: currentQuestionObj.numOfRetakes,
44633
- })), (isAssessment && {})), { questionType: currentQuestionObj.videoQuestion ? 'video' : 'text', answerType: currentQuestionObj.answerType || ANSWER_TYPES.VIDEO }) })),
45087
+ isActivelyStopped: (event.data || {}).isActivelyStopped,
45088
+ })), { questionType: currentQuestionObj.videoQuestion ? 'video' : 'text', answerType: currentQuestionObj.answerType || ANSWER_TYPES.VIDEO }) })),
44634
45089
  };
44635
45090
  });
44636
45091
  const accWidgetMachine = createMachine({