Skip to content

feat(replay): Improve types for replay recording events #8224

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions packages/replay/src/coreHandlers/handleScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ import { normalize } from '@sentry/utils';

import { CONSOLE_ARG_MAX_SIZE } from '../constants';
import type { ReplayContainer } from '../types';
import type { ReplayFrame } from '../types/replayFrame';
import { createBreadcrumb } from '../util/createBreadcrumb';
import { fixJson } from '../util/truncateJson/fixJson';
import { addBreadcrumbEvent } from './util/addBreadcrumbEvent';

let _LAST_BREADCRUMB: null | Breadcrumb = null;

type BreadcrumbWithCategory = Required<Pick<Breadcrumb, 'category'>>;

function isBreadcrumbWithCategory(breadcrumb: Breadcrumb): breadcrumb is BreadcrumbWithCategory {
return !!breadcrumb.category;
}

export const handleScopeListener: (replay: ReplayContainer) => (scope: Scope) => void =
(replay: ReplayContainer) =>
(scope: Scope): void => {
Expand Down Expand Up @@ -44,9 +51,9 @@ export function handleScope(scope: Scope): Breadcrumb | null {
_LAST_BREADCRUMB = newBreadcrumb;

if (
newBreadcrumb.category &&
(['fetch', 'xhr', 'sentry.event', 'sentry.transaction'].includes(newBreadcrumb.category) ||
newBreadcrumb.category.startsWith('ui.'))
!isBreadcrumbWithCategory(newBreadcrumb) ||
['fetch', 'xhr', 'sentry.event', 'sentry.transaction'].includes(newBreadcrumb.category) ||
newBreadcrumb.category.startsWith('ui.')
) {
return null;
}
Expand All @@ -59,7 +66,9 @@ export function handleScope(scope: Scope): Breadcrumb | null {
}

/** exported for tests only */
export function normalizeConsoleBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb {
export function normalizeConsoleBreadcrumb(
breadcrumb: Omit<Breadcrumb, 'category'> & BreadcrumbWithCategory,
): ReplayFrame {
const args = breadcrumb.data && breadcrumb.data.arguments;

if (!Array.isArray(args) || args.length === 0) {
Expand Down
8 changes: 8 additions & 0 deletions packages/replay/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
export { Replay } from './integration';
export type {
BreadcrumbFrame,
BreadcrumbFrameEvent,
ReplayFrame,
ReplayFrameEvent,
SpanFrame,
SpanFrameEvent,
} from './types/replayFrame';
9 changes: 5 additions & 4 deletions packages/replay/src/replay.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable max-lines */ // TODO: We might want to split this file up
import { EventType, record } from '@sentry-internal/rrweb';
import { captureException, getCurrentHub } from '@sentry/core';
import type { Breadcrumb, ReplayRecordingMode, Transaction } from '@sentry/types';
import type { ReplayRecordingMode, Transaction } from '@sentry/types';
import { logger } from '@sentry/utils';

import {
Expand All @@ -21,6 +21,7 @@ import type {
AddEventResult,
AddUpdateCallback,
AllPerformanceEntry,
BreadcrumbFrame,
EventBuffer,
InternalEventContext,
PopEventContext,
Expand Down Expand Up @@ -808,7 +809,7 @@ export class ReplayContainer implements ReplayContainerInterface {
/**
* Tasks to run when we consider a page to be hidden (via blurring and/or visibility)
*/
private _doChangeToBackgroundTasks(breadcrumb?: Breadcrumb): void {
private _doChangeToBackgroundTasks(breadcrumb?: BreadcrumbFrame): void {
if (!this.session) {
return;
}
Expand All @@ -828,7 +829,7 @@ export class ReplayContainer implements ReplayContainerInterface {
/**
* Tasks to run when we consider a page to be visible (via focus and/or visibility)
*/
private _doChangeToForegroundTasks(breadcrumb?: Breadcrumb): void {
private _doChangeToForegroundTasks(breadcrumb?: BreadcrumbFrame): void {
if (!this.session) {
return;
}
Expand Down Expand Up @@ -881,7 +882,7 @@ export class ReplayContainer implements ReplayContainerInterface {
/**
* Helper to create (and buffer) a replay breadcrumb from a core SDK breadcrumb
*/
private _createCustomBreadcrumb(breadcrumb: Breadcrumb): void {
private _createCustomBreadcrumb(breadcrumb: BreadcrumbFrame): void {
this.addUpdate(() => {
void this.throttledAddEvent({
type: EventType.Custom,
Expand Down
4 changes: 4 additions & 0 deletions packages/replay/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './performance';
export * from './replay';
export * from './replayFrame';
export * from './rrweb';
160 changes: 160 additions & 0 deletions packages/replay/src/types/performance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
export type AllPerformanceEntry = PerformancePaintTiming | PerformanceResourceTiming | PerformanceNavigationTiming;

// PerformancePaintTiming and PerformanceNavigationTiming are only available with TS 4.4 and newer
// Therefore, we're exporting them here to make them available in older TS versions
export type PerformancePaintTiming = PerformanceEntry;
export type PerformanceNavigationTiming = PerformanceEntry &
PerformanceResourceTiming & {
type: string;
transferSize: number;

/**
* A DOMHighResTimeStamp representing the time immediately before the user agent
* sets the document's readyState to "interactive".
*/
domInteractive: number;

/**
* A DOMHighResTimeStamp representing the time immediately before the current
* document's DOMContentLoaded event handler starts.
*/
domContentLoadedEventStart: number;
/**
* A DOMHighResTimeStamp representing the time immediately after the current
* document's DOMContentLoaded event handler completes.
*/
domContentLoadedEventEnd: number;

/**
* A DOMHighResTimeStamp representing the time immediately before the current
* document's load event handler starts.
*/
loadEventStart: number;

/**
* A DOMHighResTimeStamp representing the time immediately after the current
* document's load event handler completes.
*/
loadEventEnd: number;

/**
* A DOMHighResTimeStamp representing the time immediately before the user agent
* sets the document's readyState to "complete".
*/
domComplete: number;

/**
* A number representing the number of redirects since the last non-redirect
* navigation in the current browsing context.
*/
redirectCount: number;
};
export type ExperimentalPerformanceResourceTiming = PerformanceResourceTiming & {
// Experimental, see: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/responseStatus
// Requires Chrome 109
responseStatus?: number;
};

export type PaintData = undefined;

/**
* See https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming
*
* Note `navigation.push` will not have any data
*/
export type NavigationData = Partial<
Pick<
PerformanceNavigationTiming,
| 'decodedBodySize'
| 'encodedBodySize'
| 'duration'
| 'domInteractive'
| 'domContentLoadedEventEnd'
| 'domContentLoadedEventStart'
| 'loadEventStart'
| 'loadEventEnd'
| 'domComplete'
| 'redirectCount'
>
> & {
/**
* Transfer size of resource
*/
size?: number;
};

export type ResourceData = Pick<PerformanceResourceTiming, 'decodedBodySize' | 'encodedBodySize'> & {
/**
* Transfer size of resource
*/
size: number;
/**
* HTTP status code. Note this is experimental and not available on all browsers.
*/
statusCode?: number;
};

export interface LargestContentfulPaintData {
/**
* Render time (in ms) of the LCP
*/
value: number;
size: number;
/**
* The recording id of the LCP node. -1 if not found
*/
nodeId?: number;
}

/**
* Entries that come from window.performance
*/
export type AllPerformanceEntryData = PaintData | NavigationData | ResourceData | LargestContentfulPaintData;

export interface MemoryData {
memory: {
jsHeapSizeLimit: number;
totalJSHeapSize: number;
usedJSHeapSize: number;
};
}

export interface NetworkRequestData {
method?: string;
statusCode?: number;
requestBodySize?: number;
responseBodySize?: number;
}

export interface HistoryData {
previous: string;
}

export type AllEntryData = AllPerformanceEntryData | MemoryData | NetworkRequestData | HistoryData;

export interface ReplayPerformanceEntry<T> {
/**
* One of these types https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry/entryType
*/
type: string;

/**
* A more specific description of the performance entry
*/
name: string;

/**
* The start timestamp in seconds
*/
start: number;

/**
* The end timestamp in seconds
*/
end: number;

/**
* Additional unstructured data to be included
*/
data: T;
}
Loading