replayReader.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import memoize from 'lodash/memoize';
  2. import type {eventWithTime} from 'rrweb/typings/types';
  3. import type {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
  4. import type {RawCrumb} from 'sentry/types/breadcrumbs';
  5. import type {Event, EventTransaction} from 'sentry/types/event';
  6. import {EntryType} from 'sentry/types/event';
  7. import mergeBreadcrumbEntries from 'sentry/utils/replays/mergeBreadcrumbEntries';
  8. import mergeSpanEntries from 'sentry/utils/replays/mergeSpanEntries';
  9. function last<T>(arr: T[]): T {
  10. return arr[arr.length - 1];
  11. }
  12. export default class ReplayReader {
  13. static factory(
  14. event: EventTransaction | undefined,
  15. rrwebEvents: eventWithTime[] | undefined,
  16. replayEvents: Event[] | undefined
  17. ) {
  18. if (!event || !rrwebEvents || !replayEvents) {
  19. return null;
  20. }
  21. return new ReplayReader(event, rrwebEvents, replayEvents);
  22. }
  23. private constructor(
  24. /**
  25. * The root Replay event, created at the start of the browser session.
  26. */
  27. private _event: EventTransaction,
  28. /**
  29. * The captured data from rrweb.
  30. * Saved as N attachments that belong to the root Replay event.
  31. */
  32. private _rrwebEvents: eventWithTime[],
  33. /**
  34. * Regular Sentry SDK events that occurred during the rrweb session.
  35. */
  36. private _replayEvents: Event[]
  37. ) {}
  38. getEvent = memoize(() => {
  39. const breadcrumbs = this.getEntryType(EntryType.BREADCRUMBS);
  40. const spans = this.getEntryType(EntryType.SPANS);
  41. const lastRRweb = last(this._rrwebEvents);
  42. const lastBreadcrumb = last(breadcrumbs?.data.values as RawCrumb[]);
  43. const lastSpan = last(spans?.data as RawSpanType[]);
  44. // The original `this._event.startTimestamp` and `this._event.endTimestamp`
  45. // are the same. It's because the root replay event is re-purposing the
  46. // `transaction` type, but it is not a real span occuring over time.
  47. // So we need to figure out the real end time (in seconds).
  48. const endTimestamp =
  49. Math.max(
  50. lastRRweb.timestamp,
  51. +new Date(lastBreadcrumb.timestamp || 0),
  52. lastSpan.timestamp * 1000
  53. ) / 1000;
  54. return {
  55. ...this._event,
  56. entries: [breadcrumbs, spans],
  57. endTimestamp,
  58. } as EventTransaction;
  59. });
  60. getRRWebEvents() {
  61. return this._rrwebEvents;
  62. }
  63. getEntryType = memoize((type: EntryType) => {
  64. switch (type) {
  65. case EntryType.BREADCRUMBS:
  66. return mergeBreadcrumbEntries(this._replayEvents);
  67. case EntryType.SPANS:
  68. return mergeSpanEntries(this._replayEvents);
  69. default:
  70. throw new Error(
  71. `ReplayReader is unable to prepare EntryType ${type}. Type not supported.`
  72. );
  73. }
  74. });
  75. }