Skip to content

Commit 00dca28

Browse files
authored
ref(replay): Extract integration to clarify public API (#6457)
1 parent 5461fb2 commit 00dca28

File tree

2 files changed

+196
-195
lines changed

2 files changed

+196
-195
lines changed

packages/replay/src/index.ts

Lines changed: 1 addition & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -1,195 +1 @@
1-
import type { BrowserClient, BrowserOptions } from '@sentry/browser';
2-
import { getCurrentHub } from '@sentry/core';
3-
import { Integration } from '@sentry/types';
4-
5-
import { DEFAULT_ERROR_SAMPLE_RATE, DEFAULT_SESSION_SAMPLE_RATE } from './constants';
6-
import { ReplayContainer } from './replay';
7-
import type { RecordingOptions, ReplayConfiguration, ReplayPluginOptions } from './types';
8-
import { captureInternalException } from './util/captureInternalException';
9-
import { isBrowser } from './util/isBrowser';
10-
11-
const MEDIA_SELECTORS = 'img,image,svg,path,rect,area,video,object,picture,embed,map,audio';
12-
13-
let _initialized = false;
14-
15-
export class Replay implements Integration {
16-
/**
17-
* @inheritDoc
18-
*/
19-
public static id: string = 'Replay';
20-
21-
/**
22-
* @inheritDoc
23-
*/
24-
public name: string = Replay.id;
25-
26-
/**
27-
* Options to pass to `rrweb.record()`
28-
*/
29-
readonly recordingOptions: RecordingOptions;
30-
31-
readonly options: ReplayPluginOptions;
32-
33-
/** In tests, this is only called the first time */
34-
protected _hasCalledSetupOnce: boolean = false;
35-
36-
private _replay?: ReplayContainer;
37-
38-
constructor({
39-
flushMinDelay = 5000,
40-
flushMaxDelay = 15000,
41-
initialFlushDelay = 5000,
42-
stickySession = true,
43-
useCompression = true,
44-
sessionSampleRate,
45-
errorSampleRate,
46-
maskAllText = true,
47-
maskAllInputs = true,
48-
blockAllMedia = true,
49-
blockClass = 'sentry-block',
50-
ignoreClass = 'sentry-ignore',
51-
maskTextClass = 'sentry-mask',
52-
blockSelector = '[data-sentry-block]',
53-
...recordingOptions
54-
}: ReplayConfiguration = {}) {
55-
this.recordingOptions = {
56-
maskAllInputs,
57-
blockClass,
58-
ignoreClass,
59-
maskTextClass,
60-
blockSelector,
61-
...recordingOptions,
62-
};
63-
64-
this.options = {
65-
flushMinDelay,
66-
flushMaxDelay,
67-
stickySession,
68-
initialFlushDelay,
69-
sessionSampleRate: DEFAULT_SESSION_SAMPLE_RATE,
70-
errorSampleRate: DEFAULT_ERROR_SAMPLE_RATE,
71-
useCompression,
72-
maskAllText,
73-
blockAllMedia,
74-
};
75-
76-
if (typeof sessionSampleRate === 'number') {
77-
// eslint-disable-next-line
78-
console.warn(
79-
`[Replay] You are passing \`sessionSampleRate\` to the Replay integration.
80-
This option is deprecated and will be removed soon.
81-
Instead, configure \`replaysSessionSampleRate\` directly in the SDK init options, e.g.:
82-
Sentry.init({ replaysSessionSampleRate: ${sessionSampleRate} })`,
83-
);
84-
85-
this.options.sessionSampleRate = sessionSampleRate;
86-
}
87-
88-
if (typeof errorSampleRate === 'number') {
89-
// eslint-disable-next-line
90-
console.warn(
91-
`[Replay] You are passing \`errorSampleRate\` to the Replay integration.
92-
This option is deprecated and will be removed soon.
93-
Instead, configure \`replaysOnErrorSampleRate\` directly in the SDK init options, e.g.:
94-
Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
95-
);
96-
97-
this.options.errorSampleRate = errorSampleRate;
98-
}
99-
100-
if (this.options.maskAllText) {
101-
// `maskAllText` is a more user friendly option to configure
102-
// `maskTextSelector`. This means that all nodes will have their text
103-
// content masked.
104-
this.recordingOptions.maskTextSelector = '*';
105-
}
106-
107-
if (this.options.blockAllMedia) {
108-
// `blockAllMedia` is a more user friendly option to configure blocking
109-
// embedded media elements
110-
this.recordingOptions.blockSelector = !this.recordingOptions.blockSelector
111-
? MEDIA_SELECTORS
112-
: `${this.recordingOptions.blockSelector},${MEDIA_SELECTORS}`;
113-
}
114-
115-
if (isBrowser() && _initialized) {
116-
const error = new Error('Multiple Sentry Session Replay instances are not supported');
117-
captureInternalException(error);
118-
throw error;
119-
}
120-
121-
_initialized = true;
122-
}
123-
124-
/**
125-
* We previously used to create a transaction in `setupOnce` and it would
126-
* potentially create a transaction before some native SDK integrations have run
127-
* and applied their own global event processor. An example is:
128-
* https://github.com/getsentry/sentry-javascript/blob/b47ceafbdac7f8b99093ce6023726ad4687edc48/packages/browser/src/integrations/useragent.ts
129-
*
130-
* So we call `replay.setup` in next event loop as a workaround to wait for other
131-
* global event processors to finish. This is no longer needed, but keeping it
132-
* here to avoid any future issues.
133-
*/
134-
setupOnce(): void {
135-
if (!isBrowser()) {
136-
return;
137-
}
138-
139-
this._setup();
140-
this._hasCalledSetupOnce = true;
141-
142-
// XXX: See method comments above
143-
setTimeout(() => this.start());
144-
}
145-
146-
/**
147-
* Initializes the plugin.
148-
*
149-
* Creates or loads a session, attaches listeners to varying events (DOM,
150-
* PerformanceObserver, Recording, Sentry SDK, etc)
151-
*/
152-
start(): void {
153-
if (!this._replay) {
154-
return;
155-
}
156-
157-
this._replay.start();
158-
}
159-
160-
/**
161-
* Currently, this needs to be manually called (e.g. for tests). Sentry SDK
162-
* does not support a teardown
163-
*/
164-
stop(): void {
165-
if (!this._replay) {
166-
return;
167-
}
168-
169-
this._replay.stop();
170-
}
171-
172-
private _setup(): void {
173-
// Client is not available in constructor, so we need to wait until setupOnce
174-
this._loadReplayOptionsFromClient();
175-
176-
this._replay = new ReplayContainer({
177-
options: this.options,
178-
recordingOptions: this.recordingOptions,
179-
});
180-
}
181-
182-
/** Parse Replay-related options from SDK options */
183-
private _loadReplayOptionsFromClient(): void {
184-
const client = getCurrentHub().getClient() as BrowserClient | undefined;
185-
const opt = client && (client.getOptions() as BrowserOptions | undefined);
186-
187-
if (opt && typeof opt.replaysSessionSampleRate === 'number') {
188-
this.options.sessionSampleRate = opt.replaysSessionSampleRate;
189-
}
190-
191-
if (opt && typeof opt.replaysOnErrorSampleRate === 'number') {
192-
this.options.errorSampleRate = opt.replaysOnErrorSampleRate;
193-
}
194-
}
195-
}
1+
export { Replay } from './integration';

packages/replay/src/integration.ts

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import type { BrowserClient, BrowserOptions } from '@sentry/browser';
2+
import { getCurrentHub } from '@sentry/core';
3+
import { Integration } from '@sentry/types';
4+
5+
import { DEFAULT_ERROR_SAMPLE_RATE, DEFAULT_SESSION_SAMPLE_RATE } from './constants';
6+
import { ReplayContainer } from './replay';
7+
import type { RecordingOptions, ReplayConfiguration, ReplayPluginOptions } from './types';
8+
import { captureInternalException } from './util/captureInternalException';
9+
import { isBrowser } from './util/isBrowser';
10+
11+
const MEDIA_SELECTORS = 'img,image,svg,path,rect,area,video,object,picture,embed,map,audio';
12+
13+
let _initialized = false;
14+
15+
export class Replay implements Integration {
16+
/**
17+
* @inheritDoc
18+
*/
19+
public static id: string = 'Replay';
20+
21+
/**
22+
* @inheritDoc
23+
*/
24+
public name: string = Replay.id;
25+
26+
/**
27+
* Options to pass to `rrweb.record()`
28+
*/
29+
readonly recordingOptions: RecordingOptions;
30+
31+
readonly options: ReplayPluginOptions;
32+
33+
/** In tests, this is only called the first time */
34+
protected _hasCalledSetupOnce: boolean = false;
35+
36+
private _replay?: ReplayContainer;
37+
38+
constructor({
39+
flushMinDelay = 5000,
40+
flushMaxDelay = 15000,
41+
initialFlushDelay = 5000,
42+
stickySession = true,
43+
useCompression = true,
44+
sessionSampleRate,
45+
errorSampleRate,
46+
maskAllText = true,
47+
maskAllInputs = true,
48+
blockAllMedia = true,
49+
blockClass = 'sentry-block',
50+
ignoreClass = 'sentry-ignore',
51+
maskTextClass = 'sentry-mask',
52+
blockSelector = '[data-sentry-block]',
53+
...recordingOptions
54+
}: ReplayConfiguration = {}) {
55+
this.recordingOptions = {
56+
maskAllInputs,
57+
blockClass,
58+
ignoreClass,
59+
maskTextClass,
60+
blockSelector,
61+
...recordingOptions,
62+
};
63+
64+
this.options = {
65+
flushMinDelay,
66+
flushMaxDelay,
67+
stickySession,
68+
initialFlushDelay,
69+
sessionSampleRate: DEFAULT_SESSION_SAMPLE_RATE,
70+
errorSampleRate: DEFAULT_ERROR_SAMPLE_RATE,
71+
useCompression,
72+
maskAllText,
73+
blockAllMedia,
74+
};
75+
76+
if (typeof sessionSampleRate === 'number') {
77+
// eslint-disable-next-line
78+
console.warn(
79+
`[Replay] You are passing \`sessionSampleRate\` to the Replay integration.
80+
This option is deprecated and will be removed soon.
81+
Instead, configure \`replaysSessionSampleRate\` directly in the SDK init options, e.g.:
82+
Sentry.init({ replaysSessionSampleRate: ${sessionSampleRate} })`,
83+
);
84+
85+
this.options.sessionSampleRate = sessionSampleRate;
86+
}
87+
88+
if (typeof errorSampleRate === 'number') {
89+
// eslint-disable-next-line
90+
console.warn(
91+
`[Replay] You are passing \`errorSampleRate\` to the Replay integration.
92+
This option is deprecated and will be removed soon.
93+
Instead, configure \`replaysOnErrorSampleRate\` directly in the SDK init options, e.g.:
94+
Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
95+
);
96+
97+
this.options.errorSampleRate = errorSampleRate;
98+
}
99+
100+
if (this.options.maskAllText) {
101+
// `maskAllText` is a more user friendly option to configure
102+
// `maskTextSelector`. This means that all nodes will have their text
103+
// content masked.
104+
this.recordingOptions.maskTextSelector = '*';
105+
}
106+
107+
if (this.options.blockAllMedia) {
108+
// `blockAllMedia` is a more user friendly option to configure blocking
109+
// embedded media elements
110+
this.recordingOptions.blockSelector = !this.recordingOptions.blockSelector
111+
? MEDIA_SELECTORS
112+
: `${this.recordingOptions.blockSelector},${MEDIA_SELECTORS}`;
113+
}
114+
115+
if (isBrowser() && _initialized) {
116+
const error = new Error('Multiple Sentry Session Replay instances are not supported');
117+
captureInternalException(error);
118+
throw error;
119+
}
120+
121+
_initialized = true;
122+
}
123+
124+
/**
125+
* We previously used to create a transaction in `setupOnce` and it would
126+
* potentially create a transaction before some native SDK integrations have run
127+
* and applied their own global event processor. An example is:
128+
* https://github.com/getsentry/sentry-javascript/blob/b47ceafbdac7f8b99093ce6023726ad4687edc48/packages/browser/src/integrations/useragent.ts
129+
*
130+
* So we call `replay.setup` in next event loop as a workaround to wait for other
131+
* global event processors to finish. This is no longer needed, but keeping it
132+
* here to avoid any future issues.
133+
*/
134+
setupOnce(): void {
135+
if (!isBrowser()) {
136+
return;
137+
}
138+
139+
this._setup();
140+
this._hasCalledSetupOnce = true;
141+
142+
// XXX: See method comments above
143+
setTimeout(() => this.start());
144+
}
145+
146+
/**
147+
* Initializes the plugin.
148+
*
149+
* Creates or loads a session, attaches listeners to varying events (DOM,
150+
* PerformanceObserver, Recording, Sentry SDK, etc)
151+
*/
152+
start(): void {
153+
if (!this._replay) {
154+
return;
155+
}
156+
157+
this._replay.start();
158+
}
159+
160+
/**
161+
* Currently, this needs to be manually called (e.g. for tests). Sentry SDK
162+
* does not support a teardown
163+
*/
164+
stop(): void {
165+
if (!this._replay) {
166+
return;
167+
}
168+
169+
this._replay.stop();
170+
}
171+
172+
private _setup(): void {
173+
// Client is not available in constructor, so we need to wait until setupOnce
174+
this._loadReplayOptionsFromClient();
175+
176+
this._replay = new ReplayContainer({
177+
options: this.options,
178+
recordingOptions: this.recordingOptions,
179+
});
180+
}
181+
182+
/** Parse Replay-related options from SDK options */
183+
private _loadReplayOptionsFromClient(): void {
184+
const client = getCurrentHub().getClient() as BrowserClient | undefined;
185+
const opt = client && (client.getOptions() as BrowserOptions | undefined);
186+
187+
if (opt && typeof opt.replaysSessionSampleRate === 'number') {
188+
this.options.sessionSampleRate = opt.replaysSessionSampleRate;
189+
}
190+
191+
if (opt && typeof opt.replaysOnErrorSampleRate === 'number') {
192+
this.options.errorSampleRate = opt.replaysOnErrorSampleRate;
193+
}
194+
}
195+
}

0 commit comments

Comments
 (0)