replaySegments.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import {EventType} from '@sentry-internal/rrweb';
  2. import {serializedNodeWithId} from '@sentry-internal/rrweb-snapshot';
  3. type FullSnapshotEvent = {
  4. data: {
  5. initialOffset: {
  6. left: number;
  7. top: number;
  8. };
  9. node: serializedNodeWithId;
  10. };
  11. timestamp: number;
  12. type: EventType.FullSnapshot;
  13. };
  14. type BaseReplayProps = {
  15. timestamp: Date;
  16. };
  17. export function ReplaySegmentInit({
  18. height = 600,
  19. href = 'http://localhost/',
  20. timestamp = new Date(),
  21. width = 800,
  22. }: BaseReplayProps & {
  23. height: number;
  24. href: string;
  25. width: number;
  26. }) {
  27. return [
  28. {
  29. type: EventType.DomContentLoaded,
  30. timestamp: timestamp.getTime(), // rrweb timestamps are in ms
  31. },
  32. {
  33. type: EventType.Load,
  34. timestamp: timestamp.getTime(), // rrweb timestamps are in ms
  35. },
  36. {
  37. type: EventType.Meta,
  38. data: {href, width, height},
  39. timestamp: timestamp.getTime(), // rrweb timestamps are in ms
  40. },
  41. ];
  42. }
  43. export function ReplaySegmentFullsnapshot({
  44. timestamp,
  45. childNodes,
  46. }: BaseReplayProps & {childNodes: serializedNodeWithId[]}): [FullSnapshotEvent] {
  47. return [
  48. {
  49. type: EventType.FullSnapshot,
  50. timestamp: timestamp.getTime(),
  51. data: {
  52. initialOffset: {
  53. top: 0,
  54. left: 0,
  55. },
  56. node: {
  57. type: 0, // NodeType.DocumentType
  58. id: 0,
  59. childNodes: [
  60. ReplayRRWebNode({
  61. tagName: 'body',
  62. attributes: {
  63. style:
  64. 'margin:0; font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu;',
  65. },
  66. childNodes,
  67. }),
  68. ],
  69. },
  70. },
  71. },
  72. ];
  73. }
  74. export function ReplaySegmentConsole({timestamp = new Date()}: BaseReplayProps) {
  75. return ReplaySegmentBreadcrumb({
  76. timestamp,
  77. payload: {
  78. timestamp: timestamp.getTime() / 1000, // sentry data inside rrweb is in seconds
  79. type: 'default',
  80. category: 'console',
  81. data: {
  82. arguments: [
  83. './src/pages/template/Header.js\n Line 14: The href attribute requires a valid value to be accessible. Provide a valid, navigable address as the href value.',
  84. ],
  85. logger: 'console',
  86. },
  87. level: 'warning',
  88. message:
  89. './src/pages/template/Header.js\n Line 14: The href attribute requires a valid value to be accessible. Provide a valid, navigable address as the href value.',
  90. },
  91. });
  92. }
  93. export function ReplaySegmentNavigation({
  94. timestamp = new Date(),
  95. hrefFrom = '/',
  96. hrefTo = '/profile/',
  97. }: BaseReplayProps & {hrefFrom: string; hrefTo: string}) {
  98. return ReplaySegmentBreadcrumb({
  99. timestamp,
  100. payload: {
  101. timestamp: timestamp.getTime() / 1000, // sentry data inside rrweb is in seconds
  102. type: 'default',
  103. category: 'navigation',
  104. data: {
  105. from: hrefFrom,
  106. to: hrefTo,
  107. },
  108. },
  109. });
  110. }
  111. export function ReplaySegmentBreadcrumb({
  112. timestamp = new Date(),
  113. payload,
  114. }: BaseReplayProps & {payload: any}) {
  115. return [
  116. {
  117. type: EventType.Custom,
  118. timestamp: timestamp.getTime(), // rrweb timestamps are in ms
  119. data: {
  120. tag: 'breadcrumb',
  121. payload,
  122. },
  123. },
  124. ];
  125. }
  126. const nextRRWebId = (function () {
  127. let __rrwebID = 0;
  128. return () => ++__rrwebID;
  129. })();
  130. export function ReplayRRWebNode({
  131. id,
  132. tagName,
  133. attributes,
  134. childNodes,
  135. textContent,
  136. }: {
  137. attributes?: Record<string, string>;
  138. childNodes?: serializedNodeWithId[];
  139. id?: number;
  140. tagName?: string;
  141. textContent?: string;
  142. }): serializedNodeWithId {
  143. id = id ?? nextRRWebId();
  144. if (tagName) {
  145. return {
  146. type: 2, // NodeType.Element
  147. id,
  148. tagName,
  149. attributes: attributes ?? {},
  150. childNodes: childNodes ?? [],
  151. };
  152. }
  153. return {
  154. type: 3, // NodeType.Text
  155. id,
  156. textContent: textContent ?? '',
  157. };
  158. }
  159. export function ReplayRRWebDivHelloWorld() {
  160. return ReplayRRWebNode({
  161. tagName: 'div',
  162. childNodes: [
  163. ReplayRRWebNode({
  164. tagName: 'h1',
  165. attributes: {style: 'text-align: center;'},
  166. childNodes: [
  167. ReplayRRWebNode({
  168. textContent: 'Hello World',
  169. }),
  170. ],
  171. }),
  172. ],
  173. });
  174. }