-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathaddEvent.ts
149 lines (120 loc) · 4.49 KB
/
addEvent.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { EventType } from '@sentry-internal/rrweb';
import { getClient } from '@sentry/core';
import { DEBUG_BUILD } from '../debug-build';
import { EventBufferSizeExceededError } from '../eventBuffer/error';
import type { AddEventResult, RecordingEvent, ReplayContainer, ReplayFrameEvent, ReplayPluginOptions } from '../types';
import { logger } from './logger';
import { timestampToMs } from './timestamp';
function isCustomEvent(event: RecordingEvent): event is ReplayFrameEvent {
return event.type === EventType.Custom;
}
/**
* Add an event to the event buffer.
* In contrast to `addEvent`, this does not return a promise & does not wait for the adding of the event to succeed/fail.
* Instead this returns `true` if we tried to add the event, else false.
* It returns `false` e.g. if we are paused, disabled, or out of the max replay duration.
*
* `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
*/
export function addEventSync(replay: ReplayContainer, event: RecordingEvent, isCheckout?: boolean): boolean {
if (!shouldAddEvent(replay, event)) {
return false;
}
// This should never reject
// eslint-disable-next-line @typescript-eslint/no-floating-promises
_addEvent(replay, event, isCheckout);
return true;
}
/**
* Add an event to the event buffer.
* Resolves to `null` if no event was added, else to `void`.
*
* `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
*/
export function addEvent(
replay: ReplayContainer,
event: RecordingEvent,
isCheckout?: boolean,
): Promise<AddEventResult | null> {
if (!shouldAddEvent(replay, event)) {
return Promise.resolve(null);
}
return _addEvent(replay, event, isCheckout);
}
async function _addEvent(
replay: ReplayContainer,
event: RecordingEvent,
isCheckout?: boolean,
): Promise<AddEventResult | null> {
const { eventBuffer } = replay;
if (!eventBuffer || (eventBuffer.waitForCheckout && !isCheckout)) {
return null;
}
const isBufferMode = replay.recordingMode === 'buffer';
try {
if (isCheckout && isBufferMode) {
eventBuffer.clear();
}
if (isCheckout) {
eventBuffer.hasCheckout = true;
eventBuffer.waitForCheckout = false;
}
const replayOptions = replay.getOptions();
const eventAfterPossibleCallback = maybeApplyCallback(event, replayOptions.beforeAddRecordingEvent);
if (!eventAfterPossibleCallback) {
return;
}
return await eventBuffer.addEvent(eventAfterPossibleCallback);
} catch (error) {
const isExceeded = error && error instanceof EventBufferSizeExceededError;
const reason = isExceeded ? 'addEventSizeExceeded' : 'addEvent';
if (isExceeded && isBufferMode) {
// Clear buffer and wait for next checkout
eventBuffer.clear();
eventBuffer.waitForCheckout = true;
return null;
}
replay.handleException(error);
await replay.stop({ reason });
const client = getClient();
if (client) {
client.recordDroppedEvent('internal_sdk_error', 'replay');
}
}
}
/** Exported only for tests. */
export function shouldAddEvent(replay: ReplayContainer, event: RecordingEvent): boolean {
if (!replay.eventBuffer || replay.isPaused() || !replay.isEnabled()) {
return false;
}
const timestampInMs = timestampToMs(event.timestamp);
// Throw out events that happen more than 5 minutes ago. This can happen if
// page has been left open and idle for a long period of time and user
// comes back to trigger a new session. The performance entries rely on
// `performance.timeOrigin`, which is when the page first opened.
if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
return false;
}
// Throw out events that are +60min from the initial timestamp
if (timestampInMs > replay.getContext().initialTimestamp + replay.getOptions().maxReplayDuration) {
DEBUG_BUILD &&
logger.infoTick(`Skipping event with timestamp ${timestampInMs} because it is after maxReplayDuration`);
return false;
}
return true;
}
function maybeApplyCallback(
event: RecordingEvent,
callback: ReplayPluginOptions['beforeAddRecordingEvent'],
): RecordingEvent | null | undefined {
try {
if (typeof callback === 'function' && isCustomEvent(event)) {
return callback(event);
}
} catch (error) {
DEBUG_BUILD &&
logger.exception(error, 'An error occurred in the `beforeAddRecordingEvent` callback, skipping the event...');
return null;
}
return event;
}