Skip to content

Commit 9ed5cfd

Browse files
authored
feat(replay): Improve types for replay recording events (#8224)
More specific types for our replay recording events. These are exported so that our UI can use them as well. ![image](https://github.com/getsentry/sentry-javascript/assets/79684/8b5d3ff1-ac44-4f30-b1b2-0057b98ec4b7)
1 parent b532311 commit 9ed5cfd

14 files changed

+391
-192
lines changed

packages/replay/src/coreHandlers/handleScope.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@ import { normalize } from '@sentry/utils';
33

44
import { CONSOLE_ARG_MAX_SIZE } from '../constants';
55
import type { ReplayContainer } from '../types';
6+
import type { ReplayFrame } from '../types/replayFrame';
67
import { createBreadcrumb } from '../util/createBreadcrumb';
78
import { fixJson } from '../util/truncateJson/fixJson';
89
import { addBreadcrumbEvent } from './util/addBreadcrumbEvent';
910

1011
let _LAST_BREADCRUMB: null | Breadcrumb = null;
1112

13+
type BreadcrumbWithCategory = Required<Pick<Breadcrumb, 'category'>>;
14+
15+
function isBreadcrumbWithCategory(breadcrumb: Breadcrumb): breadcrumb is BreadcrumbWithCategory {
16+
return !!breadcrumb.category;
17+
}
18+
1219
export const handleScopeListener: (replay: ReplayContainer) => (scope: Scope) => void =
1320
(replay: ReplayContainer) =>
1421
(scope: Scope): void => {
@@ -44,9 +51,9 @@ export function handleScope(scope: Scope): Breadcrumb | null {
4451
_LAST_BREADCRUMB = newBreadcrumb;
4552

4653
if (
47-
newBreadcrumb.category &&
48-
(['fetch', 'xhr', 'sentry.event', 'sentry.transaction'].includes(newBreadcrumb.category) ||
49-
newBreadcrumb.category.startsWith('ui.'))
54+
!isBreadcrumbWithCategory(newBreadcrumb) ||
55+
['fetch', 'xhr', 'sentry.event', 'sentry.transaction'].includes(newBreadcrumb.category) ||
56+
newBreadcrumb.category.startsWith('ui.')
5057
) {
5158
return null;
5259
}
@@ -59,7 +66,9 @@ export function handleScope(scope: Scope): Breadcrumb | null {
5966
}
6067

6168
/** exported for tests only */
62-
export function normalizeConsoleBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb {
69+
export function normalizeConsoleBreadcrumb(
70+
breadcrumb: Omit<Breadcrumb, 'category'> & BreadcrumbWithCategory,
71+
): ReplayFrame {
6372
const args = breadcrumb.data && breadcrumb.data.arguments;
6473

6574
if (!Array.isArray(args) || args.length === 0) {

packages/replay/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
11
export { Replay } from './integration';
2+
export type {
3+
BreadcrumbFrame,
4+
BreadcrumbFrameEvent,
5+
ReplayFrame,
6+
ReplayFrameEvent,
7+
SpanFrame,
8+
SpanFrameEvent,
9+
} from './types/replayFrame';

packages/replay/src/replay.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable max-lines */ // TODO: We might want to split this file up
22
import { EventType, record } from '@sentry-internal/rrweb';
33
import { captureException, getCurrentHub } from '@sentry/core';
4-
import type { Breadcrumb, ReplayRecordingMode, Transaction } from '@sentry/types';
4+
import type { ReplayRecordingMode, Transaction } from '@sentry/types';
55
import { logger } from '@sentry/utils';
66

77
import {
@@ -21,6 +21,7 @@ import type {
2121
AddEventResult,
2222
AddUpdateCallback,
2323
AllPerformanceEntry,
24+
BreadcrumbFrame,
2425
EventBuffer,
2526
InternalEventContext,
2627
PopEventContext,
@@ -808,7 +809,7 @@ export class ReplayContainer implements ReplayContainerInterface {
808809
/**
809810
* Tasks to run when we consider a page to be hidden (via blurring and/or visibility)
810811
*/
811-
private _doChangeToBackgroundTasks(breadcrumb?: Breadcrumb): void {
812+
private _doChangeToBackgroundTasks(breadcrumb?: BreadcrumbFrame): void {
812813
if (!this.session) {
813814
return;
814815
}
@@ -828,7 +829,7 @@ export class ReplayContainer implements ReplayContainerInterface {
828829
/**
829830
* Tasks to run when we consider a page to be visible (via focus and/or visibility)
830831
*/
831-
private _doChangeToForegroundTasks(breadcrumb?: Breadcrumb): void {
832+
private _doChangeToForegroundTasks(breadcrumb?: BreadcrumbFrame): void {
832833
if (!this.session) {
833834
return;
834835
}
@@ -881,7 +882,7 @@ export class ReplayContainer implements ReplayContainerInterface {
881882
/**
882883
* Helper to create (and buffer) a replay breadcrumb from a core SDK breadcrumb
883884
*/
884-
private _createCustomBreadcrumb(breadcrumb: Breadcrumb): void {
885+
private _createCustomBreadcrumb(breadcrumb: BreadcrumbFrame): void {
885886
this.addUpdate(() => {
886887
void this.throttledAddEvent({
887888
type: EventType.Custom,

packages/replay/src/types/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './performance';
2+
export * from './replay';
3+
export * from './replayFrame';
4+
export * from './rrweb';
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
export type AllPerformanceEntry = PerformancePaintTiming | PerformanceResourceTiming | PerformanceNavigationTiming;
2+
3+
// PerformancePaintTiming and PerformanceNavigationTiming are only available with TS 4.4 and newer
4+
// Therefore, we're exporting them here to make them available in older TS versions
5+
export type PerformancePaintTiming = PerformanceEntry;
6+
export type PerformanceNavigationTiming = PerformanceEntry &
7+
PerformanceResourceTiming & {
8+
type: string;
9+
transferSize: number;
10+
11+
/**
12+
* A DOMHighResTimeStamp representing the time immediately before the user agent
13+
* sets the document's readyState to "interactive".
14+
*/
15+
domInteractive: number;
16+
17+
/**
18+
* A DOMHighResTimeStamp representing the time immediately before the current
19+
* document's DOMContentLoaded event handler starts.
20+
*/
21+
domContentLoadedEventStart: number;
22+
/**
23+
* A DOMHighResTimeStamp representing the time immediately after the current
24+
* document's DOMContentLoaded event handler completes.
25+
*/
26+
domContentLoadedEventEnd: number;
27+
28+
/**
29+
* A DOMHighResTimeStamp representing the time immediately before the current
30+
* document's load event handler starts.
31+
*/
32+
loadEventStart: number;
33+
34+
/**
35+
* A DOMHighResTimeStamp representing the time immediately after the current
36+
* document's load event handler completes.
37+
*/
38+
loadEventEnd: number;
39+
40+
/**
41+
* A DOMHighResTimeStamp representing the time immediately before the user agent
42+
* sets the document's readyState to "complete".
43+
*/
44+
domComplete: number;
45+
46+
/**
47+
* A number representing the number of redirects since the last non-redirect
48+
* navigation in the current browsing context.
49+
*/
50+
redirectCount: number;
51+
};
52+
export type ExperimentalPerformanceResourceTiming = PerformanceResourceTiming & {
53+
// Experimental, see: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/responseStatus
54+
// Requires Chrome 109
55+
responseStatus?: number;
56+
};
57+
58+
export type PaintData = undefined;
59+
60+
/**
61+
* See https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming
62+
*
63+
* Note `navigation.push` will not have any data
64+
*/
65+
export type NavigationData = Partial<
66+
Pick<
67+
PerformanceNavigationTiming,
68+
| 'decodedBodySize'
69+
| 'encodedBodySize'
70+
| 'duration'
71+
| 'domInteractive'
72+
| 'domContentLoadedEventEnd'
73+
| 'domContentLoadedEventStart'
74+
| 'loadEventStart'
75+
| 'loadEventEnd'
76+
| 'domComplete'
77+
| 'redirectCount'
78+
>
79+
> & {
80+
/**
81+
* Transfer size of resource
82+
*/
83+
size?: number;
84+
};
85+
86+
export type ResourceData = Pick<PerformanceResourceTiming, 'decodedBodySize' | 'encodedBodySize'> & {
87+
/**
88+
* Transfer size of resource
89+
*/
90+
size: number;
91+
/**
92+
* HTTP status code. Note this is experimental and not available on all browsers.
93+
*/
94+
statusCode?: number;
95+
};
96+
97+
export interface LargestContentfulPaintData {
98+
/**
99+
* Render time (in ms) of the LCP
100+
*/
101+
value: number;
102+
size: number;
103+
/**
104+
* The recording id of the LCP node. -1 if not found
105+
*/
106+
nodeId?: number;
107+
}
108+
109+
/**
110+
* Entries that come from window.performance
111+
*/
112+
export type AllPerformanceEntryData = PaintData | NavigationData | ResourceData | LargestContentfulPaintData;
113+
114+
export interface MemoryData {
115+
memory: {
116+
jsHeapSizeLimit: number;
117+
totalJSHeapSize: number;
118+
usedJSHeapSize: number;
119+
};
120+
}
121+
122+
export interface NetworkRequestData {
123+
method?: string;
124+
statusCode?: number;
125+
requestBodySize?: number;
126+
responseBodySize?: number;
127+
}
128+
129+
export interface HistoryData {
130+
previous: string;
131+
}
132+
133+
export type AllEntryData = AllPerformanceEntryData | MemoryData | NetworkRequestData | HistoryData;
134+
135+
export interface ReplayPerformanceEntry<T> {
136+
/**
137+
* One of these types https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry/entryType
138+
*/
139+
type: string;
140+
141+
/**
142+
* A more specific description of the performance entry
143+
*/
144+
name: string;
145+
146+
/**
147+
* The start timestamp in seconds
148+
*/
149+
start: number;
150+
151+
/**
152+
* The end timestamp in seconds
153+
*/
154+
end: number;
155+
156+
/**
157+
* Additional unstructured data to be included
158+
*/
159+
data: T;
160+
}

0 commit comments

Comments
 (0)