UNPKG

@sentry-internal/replay

Version:
452 lines (451 loc) 16.3 kB
import { Breadcrumb, ErrorEvent, ReplayRecordingData, ReplayRecordingMode, Span } from '@sentry/core'; import { SKIPPED, THROTTLED } from '../util/throttle'; import { AllPerformanceEntry, AllPerformanceEntryData, ReplayPerformanceEntry } from './performance'; import { ReplayFrameEvent } from './replayFrame'; import { ReplayNetworkRequestOrResponse } from './request'; import { CanvasManagerInterface, CanvasManagerOptions, ReplayEventWithTime, RrwebRecordOptions } from './rrweb'; export type RecordingEvent = ReplayFrameEvent | ReplayEventWithTime; export type RecordingOptions = RrwebRecordOptions; export interface SendReplayData { recordingData: ReplayRecordingData; replayId: string; segmentId: number; eventContext: PopEventContext; timestamp: number; session: Session; onError?: (err: unknown) => void; } export interface Timeouts { sessionIdlePause: number; sessionIdleExpire: number; } /** * The request payload to worker */ export interface WorkerRequest { id: number; method: 'clear' | 'addEvent' | 'finish'; arg?: string; } /** * The response from the worker */ export interface WorkerResponse { id: number; method: string; success: boolean; response: unknown; } export type AddEventResult = void; export interface BeforeAddRecordingEvent { (event: ReplayFrameEvent): ReplayFrameEvent | null | undefined; } export interface ReplayNetworkOptions { /** * Capture request/response details for XHR/Fetch requests that match the given URLs. * The URLs can be strings or regular expressions. * When provided a string, we will match any URL that contains the given string. * You can use a Regex to handle exact matches or more complex matching. * * Only URLs matching these patterns will have bodies & additional headers captured. */ networkDetailAllowUrls: (string | RegExp)[]; /** * Deny request/response details for XHR/Fetch requests that match the given URLs. * The URLs can be strings or regular expressions. * When provided a string, we will deny any URL that contains the given string. * You can use a Regex to handle exact matches or more complex matching. * URLs matching these patterns will not have bodies & additional headers captured. */ networkDetailDenyUrls: (string | RegExp)[]; /** * If request & response bodies should be captured. * Only applies to URLs matched by `networkDetailAllowUrls` and not matched by `networkDetailDenyUrls`. * Defaults to true. */ networkCaptureBodies: boolean; /** * Capture the following request headers, in addition to the default ones. * Only applies to URLs matched by `networkDetailAllowUrls` and not matched by `networkDetailDenyUrls`. * Any headers defined here will be captured in addition to the default headers. */ networkRequestHeaders: string[]; /** * Capture the following response headers, in addition to the default ones. * Only applies to URLs matched by `networkDetailAllowUrls` and not matched by `networkDetailDenyUrls`. * Any headers defined here will be captured in addition to the default headers. */ networkResponseHeaders: string[]; } export interface ReplayPluginOptions extends ReplayNetworkOptions { /** * The sample rate for session-long replays. 1.0 will record all sessions and * 0 will record none. */ sessionSampleRate: number; /** * The sample rate for sessions that has had an error occur. This is * independent of `sessionSampleRate`. */ errorSampleRate: number; /** * If false, will create a new session per pageload. Otherwise, saves session * to Session Storage. */ stickySession: boolean; /** * The amount of time to wait before sending a replay */ flushMinDelay: number; /** * The max amount of time to wait before sending a replay */ flushMaxDelay: number; /** * Attempt to use compression when web workers are available * * (default is true) */ useCompression: boolean; /** * If defined, use this worker URL instead of the default included one for compression. * This will only be used if `useCompression` is not false. */ workerUrl?: string; /** * Block all media (e.g. images, svg, video) in recordings. */ blockAllMedia: boolean; /** * Mask all inputs in recordings */ maskAllInputs: boolean; /** * Mask all text in recordings */ maskAllText: boolean; /** * A high number of DOM mutations (in a single event loop) can cause * performance regressions in end-users' browsers. This setting will create * a breadcrumb in the recording when the limit has been reached. */ mutationBreadcrumbLimit: number; /** * A high number of DOM mutations (in a single event loop) can cause * performance regressions in end-users' browsers. This setting will cause * recording to stop when the limit has been reached. */ mutationLimit: number; /** * The max. time in ms to wait for a slow click to finish. * After this amount of time we stop waiting for actions after a click happened. * Set this to 0 to disable slow click capture. * * Default: 7000ms */ slowClickTimeout: number; /** * Ignore clicks on elements matching the given selectors for slow click detection. */ slowClickIgnoreSelectors: string[]; /** * The min. duration (in ms) a replay has to have before it is sent to Sentry. * Whenever attempting to flush a session that is shorter than this, it will not actually send it to Sentry. * Note that this is capped at max. 15s. */ minReplayDuration: number; /** * The max. duration (in ms) a replay session may be. * This is capped at max. 60min. */ maxReplayDuration: number; /** * Callback before adding a custom recording event * * Events added by the underlying DOM recording library can *not* be modified, * only custom recording events from the Replay integration will trigger the * callback listeners. This can be used to scrub certain fields in an event (e.g. URLs from navigation events). * * Returning a `null` will drop the event completely. Note, dropping a recording * event is not the same as dropping the replay, the replay will still exist and * continue to function. */ beforeAddRecordingEvent?: BeforeAddRecordingEvent; /** * An optional callback to be called before we decide to sample based on an error. * If specified, this callback will receive an error that was captured by Sentry. * Return `true` to continue sampling for this error, or `false` to ignore this error for replay sampling. * Note that returning `true` means that the `replaysOnErrorSampleRate` will be checked, * not that it will definitely be sampled. * Use this to filter out groups of errors that should def. not be sampled. */ beforeErrorSampling?: (event: ErrorEvent) => boolean; /** * Callback when an internal SDK error occurs. This can be used to debug SDK * issues. */ onError?: (err: unknown) => void; /** * _experiments allows users to enable experimental or internal features. * We don't consider such features as part of the public API and hence we don't guarantee semver for them. * Experimental features can be added, changed or removed at any time. * * Default: undefined */ _experiments: Partial<{ captureExceptions: boolean; traceInternals: boolean; continuousCheckout: number; /** * Before enabling, please read the security considerations: * https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/cross-origin-iframes.md#considerations */ recordCrossOriginIframes: boolean; autoFlushOnFeedback: boolean; }>; } /** * The options that can be set in the plugin options. `sessionSampleRate` and `errorSampleRate` are added * in the root level of the SDK options as `replaysSessionSampleRate` and `replaysOnErrorSampleRate`. */ export type InitialReplayPluginOptions = Pick<ReplayPluginOptions, Exclude<keyof ReplayPluginOptions, 'sessionSampleRate' | 'errorSampleRate'>>; type OptionalReplayPluginOptions = Partial<InitialReplayPluginOptions> & { /** * Mask element attributes that are contained in list */ maskAttributes?: string[]; }; /** * Session options that are configurable by the integration configuration */ export interface SessionOptions extends Pick<ReplayPluginOptions, 'sessionSampleRate' | 'stickySession'> { /** * Should buffer recordings to be saved later either by error sampling, or by * manually calling `flush()`. This is only a factor if not sampled for a * session-based replay. */ allowBuffering: boolean; } export interface ReplayIntegrationPrivacyOptions { /** * Mask text content for elements that match the CSS selectors in the list. */ mask?: string[]; /** * Unmask text content for elements that match the CSS selectors in the list. */ unmask?: string[]; /** * Block elements that match the CSS selectors in the list. Blocking replaces * the element with an empty placeholder with the same dimensions. */ block?: string[]; /** * Unblock elements that match the CSS selectors in the list. This is useful when using `blockAllMedia`. */ unblock?: string[]; /** * Ignore input events for elements that match the CSS selectors in the list. */ ignore?: string[]; /** * A callback function to customize how your text is masked. */ maskFn?: (s: string) => string; } export interface ReplayConfiguration extends ReplayIntegrationPrivacyOptions, OptionalReplayPluginOptions, Pick<RecordingOptions, 'maskAllText' | 'maskAllInputs'> { } interface CommonEventContext { /** * The initial URL of the session */ initialUrl: string; /** * The initial starting timestamp in ms of the session. */ initialTimestamp: number; /** * Ordered list of URLs that have been visited during a replay segment */ urls: string[]; } export interface PopEventContext extends CommonEventContext { /** * List of Sentry error ids that have occurred during a replay segment */ errorIds: Array<string>; /** * List of Sentry trace ids that have occurred during a replay segment */ traceIds: Array<string>; } /** * Additional context that will be sent w/ `replay_event` */ export interface InternalEventContext extends CommonEventContext { /** * Set of Sentry error ids that have occurred during a replay segment */ errorIds: Set<string>; /** * Set of Sentry trace ids that have occurred during a replay segment */ traceIds: Set<string>; } export type Sampled = false | 'session' | 'buffer'; export interface Session { id: string; /** * Start time of current session (in ms) */ started: number; /** * Last known activity of the session (in ms) */ lastActivity: number; /** * Segment ID for replay events */ segmentId: number; /** * The ID of the previous session. * If this is empty, there was no previous session. */ previousSessionId?: string; /** * Is the session sampled? `false` if not sampled, otherwise, `session` or `buffer` */ sampled: Sampled; } export type EventBufferType = 'sync' | 'worker'; export interface EventBuffer { /** * If any events have been added to the buffer. */ readonly hasEvents: boolean; /** * The buffer type */ readonly type: EventBufferType; /** * If the event buffer contains a checkout event. */ hasCheckout: boolean; /** * If the event buffer needs to wait for a checkout event before it * starts buffering events. */ waitForCheckout: boolean; /** * Destroy the event buffer. */ destroy(): void; /** * Clear the event buffer. */ clear(): void; /** * Add an event to the event buffer. * * Returns a promise that resolves if the event was successfully added, else rejects. */ addEvent(event: RecordingEvent): Promise<AddEventResult>; /** * Clears and returns the contents of the buffer. */ finish(): Promise<ReplayRecordingData>; /** * Get the earliest timestamp in ms of any event currently in the buffer. */ getEarliestTimestamp(): number | null; } export type AddUpdateCallback = () => boolean | void; export interface SendBufferedReplayOptions { continueRecording?: boolean; } export interface ReplayClickDetector { addListeners(): void; removeListeners(): void; /** Handle a click breadcrumb. */ handleClick(breadcrumb: Breadcrumb, node: HTMLElement): void; /** Register a mutation that happened at a given time. */ registerMutation(timestamp?: number): void; /** Register a scroll that happened at a given time. */ registerScroll(timestamp?: number): void; /** Register that a click on an element happened. */ registerClick(element: HTMLElement): void; } export interface ReplayContainer { eventBuffer: EventBuffer | null; clickDetector: ReplayClickDetector | undefined; /** * List of PerformanceEntry from PerformanceObservers. */ performanceEntries: AllPerformanceEntry[]; /** * List of already processed performance data, ready to be added to replay. */ replayPerformanceEntries: ReplayPerformanceEntry<AllPerformanceEntryData>[]; session: Session | undefined; recordingMode: ReplayRecordingMode; timeouts: Timeouts; lastActiveSpan?: Span; throttledAddEvent: (event: RecordingEvent, isCheckout?: boolean) => typeof THROTTLED | typeof SKIPPED | Promise<AddEventResult | null>; isEnabled(): boolean; isPaused(): boolean; isRecordingCanvas(): boolean; getContext(): InternalEventContext; initializeSampling(): void; start(): void; stop(options?: { reason?: string; forceflush?: boolean; }): Promise<void>; pause(): void; resume(): void; startRecording(): void; stopRecording(): boolean; sendBufferedReplayOrFlush(options?: SendBufferedReplayOptions): Promise<void>; conditionalFlush(): Promise<void>; flush(): Promise<void>; flushImmediate(): Promise<void>; cancelFlush(): void; triggerUserActivity(): void; updateUserActivity(): void; addUpdate(cb: AddUpdateCallback): void; getOptions(): ReplayPluginOptions; getSessionId(): string | undefined; checkAndHandleExpiredSession(): boolean | void; setInitialState(): void; getCurrentRoute(): string | undefined; handleException(err: unknown): void; } export type ReplayNetworkRequestData = { startTimestamp: number; endTimestamp: number; url: string; method?: string; statusCode: number; request?: ReplayNetworkRequestOrResponse; response?: ReplayNetworkRequestOrResponse; }; export interface SlowClickConfig { threshold: number; timeout: number; scrollTimeout: number; ignoreSelector: string; } export interface ReplayCanvasIntegrationOptions { enableManualSnapshot?: boolean; recordCanvas: true; getCanvasManager: (options: CanvasManagerOptions) => CanvasManagerInterface; sampling: { canvas: number; }; dataURLOptions: { type: string; quality: number; }; } export {}; //# sourceMappingURL=replay.d.ts.map