Skip to content

Commit 6de1dd8

Browse files
authored
Merge pull request #6270 from getsentry/replay-migration
Migrate the whole session replay code from https://github.com/getsentry/sentry-replay into the monorepo. Note: * Build, lint and tests work, but will need adjustments * It is excluded from releasing in craft * Some eslint rules etc. need to be fixed in later steps Ref: #6257
2 parents b52336b + a005fbf commit 6de1dd8

File tree

118 files changed

+16918
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+16918
-3
lines changed

.craft.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ targets:
2121
cacheControl: 'public, max-age=31536000'
2222
- name: github
2323
includeNames: /^sentry-.*$/
24+
excludeNames: /^sentry-replay-.*$/
2425
- name: npm
26+
excludeNames: /^sentry-replay-.*$/
2527
- name: registry
2628
sdks:
2729
'npm:@sentry/browser':

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"packages/opentelemetry-node",
5050
"packages/react",
5151
"packages/remix",
52+
"packages/replay",
5253
"packages/serverless",
5354
"packages/svelte",
5455
"packages/tracing",
@@ -63,6 +64,7 @@
6364
"@rollup/plugin-node-resolve": "^13.1.3",
6465
"@rollup/plugin-replace": "^3.0.1",
6566
"@rollup/plugin-sucrase": "^4.0.3",
67+
"@rollup/plugin-typescript": "^8.3.1",
6668
"@size-limit/preset-small-lib": "^4.5.5",
6769
"@strictsoftware/typedoc-plugin-monorepo": "^0.3.1",
6870
"@types/chai": "^4.1.3",

packages/e2e-tests/verdaccio-config/config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ packages:
104104
unpublish: $all
105105
# proxy: npmjs # Don't proxy for E2E tests!
106106

107+
'@sentry/replay':
108+
access: $all
109+
publish: $all
110+
unpublish: $all
111+
# proxy: npmjs # Don't proxy for E2E tests!
112+
107113
'@sentry/serverless':
108114
access: $all
109115
publish: $all

packages/replay/.craft.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
minVersion: '0.0.1'
2+
github:
3+
owner: getsentry
4+
repo: sentry-replay
5+
changelogPolicy: none
6+
preReleaseCommand: bash scripts/craft-pre-release.sh
7+
targets:
8+
- name: npm
9+
access: public
10+
- name: github
11+
tagPrefix: v
12+
access: public
13+
statusProvider:
14+
name: github
15+
artifactProvider:
16+
name: github

packages/replay/.eslintignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
build/
3+
demo/build/
4+
# TODO: Check if we can re-introduce linting in demo
5+
demo

packages/replay/.eslintrc.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// TODO: Remove this file after migration!
2+
// TODO: Remove Sentry ESLint config package from package.json
3+
// Note: All paths are relative to the directory in which eslint is being run, rather than the directory where this file
4+
// lives
5+
6+
// ESLint config docs: https://eslint.org/docs/user-guide/configuring/
7+
8+
module.exports = {
9+
root: true,
10+
env: {
11+
es6: true,
12+
},
13+
parserOptions: {
14+
ecmaVersion: 2018,
15+
},
16+
extends: ['@sentry-internal/sdk'],
17+
ignorePatterns: [
18+
'coverage/**',
19+
'build/**',
20+
'dist/**',
21+
'cjs/**',
22+
'esm/**',
23+
'examples/**',
24+
'test/manual/**',
25+
'types/**',
26+
// TODO: Remove these after migration
27+
'scripts/**',
28+
'config/**',
29+
'config/**',
30+
'__mocks__/**',
31+
],
32+
overrides: [
33+
{
34+
files: ['*.ts', '*.tsx', '*.d.ts'],
35+
parserOptions: {
36+
project: ['tsconfig.json'],
37+
},
38+
},
39+
// TODO: Extract this to package-specific config after mgiration
40+
{
41+
files: ['worker/**/*.ts'],
42+
parserOptions: {
43+
project: ['config/tsconfig.worker.json'],
44+
},
45+
},
46+
{
47+
files: ['*.ts', '*.tsx', '*.d.ts'],
48+
rules: {
49+
// TODO (high-prio): Go through console logs and figure out which ones should be replaced with the SDK logger
50+
'no-console': 'off',
51+
// TODO (high-pio): Re-enable this after migration
52+
'no-restricted-globals': 'off',
53+
// TODO (high-prio): Re-enable this after migration
54+
'@typescript-eslint/explicit-member-accessibility': 'off',
55+
// TODO (high-prio): Remove this exception from naming convention after migration
56+
'@typescript-eslint/naming-convention': [
57+
'error',
58+
{
59+
selector: 'memberLike',
60+
modifiers: ['private'],
61+
format: ['camelCase'],
62+
leadingUnderscore: 'allow',
63+
},
64+
{
65+
selector: 'memberLike',
66+
modifiers: ['protected'],
67+
format: ['camelCase'],
68+
leadingUnderscore: 'allow',
69+
},
70+
],
71+
// TODO (high-prio): Re-enable this after migration
72+
'@sentry-internal/sdk/no-async-await': 'off',
73+
// TODO (high-prio): Re-enable this after migration
74+
'@typescript-eslint/no-floating-promises': 'off',
75+
// TODO (medium-prio): Re-enable this after migration
76+
'jsdoc/require-jsdoc': 'off',
77+
// TODO: Do we even want to turn this on? Why not enable ++?
78+
'no-plusplus': 'off',
79+
},
80+
},
81+
{
82+
files: ['jest.setup.ts'],
83+
rules: {
84+
'no-console': 'off',
85+
},
86+
},
87+
{
88+
files: ['test/**/*.ts'],
89+
rules: {
90+
// TODO: decide if we want to keep our '@test' import paths
91+
'import/no-unresolved': 'off',
92+
// most of these errors come from `new Promise(process.nextTick)`
93+
'@typescript-eslint/unbound-method': 'off',
94+
// TODO: decide if we want to enable this again after the migration
95+
// We can take the freedom to be a bit more lenient with tests
96+
'@typescript-eslint/no-floating-promises': 'off',
97+
},
98+
},
99+
{
100+
files: ['src/worker/**/*.js'],
101+
parserOptions: {
102+
sourceType: 'module',
103+
},
104+
},
105+
// ----------------
106+
{
107+
files: ['*.tsx'],
108+
rules: {
109+
// Turn off jsdoc on tsx files until jsdoc is fixed for tsx files
110+
// See: https://github.com/getsentry/sentry-javascript/issues/3871
111+
'jsdoc/require-jsdoc': 'off',
112+
},
113+
},
114+
{
115+
files: ['scenarios/**', 'rollup/**'],
116+
parserOptions: {
117+
sourceType: 'module',
118+
},
119+
rules: {
120+
'no-console': 'off',
121+
},
122+
},
123+
],
124+
};

packages/replay/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
/*.tgz
3+
.eslintcache
4+
dist
5+
build

packages/replay/.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# TODO after we migrated, we should revisit this file and check if we can use the monorepo's master
2+
# .npmigore file or if we should add custom entries here.
3+
# For the time being, this can stay empty.

packages/replay/LICENSE

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Copyright (c) 2022 Sentry (https://sentry.io) and individual contributors. All rights reserved.
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
5+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
6+
persons to whom the Software is furnished to do so, subject to the following conditions:
7+
8+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
9+
Software.
10+
11+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
12+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
13+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
14+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

packages/replay/README.md

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# sentry-replay
2+
3+
Note: Session Replay is currently in beta.
4+
5+
## Pre-requisites
6+
7+
For the sentry-replay integration to work, you must have the [Sentry browser SDK package](https://www.npmjs.com/package/@sentry/browser), or an equivalent framework SDK (e.g. [@sentry/react](https://www.npmjs.com/package/@sentry/react)) installed. The minimum version required for the SDK is `7.x`.
8+
9+
10+
`@sentry/replay` requires Node 12+, and browsers newer than IE11.
11+
12+
## Installation
13+
14+
with npm:
15+
16+
```shell
17+
npm install --save @sentry/browser @sentry/replay
18+
```
19+
20+
with yarn:
21+
22+
```shell
23+
yarn add @sentry/browser @sentry/replay
24+
```
25+
26+
## Setup
27+
28+
To set up the integration, add the following to your Sentry initialization. Several options are supported and passable via the integration constructor.
29+
See the [configuration section](#configuration) below for more details.
30+
31+
```javascript
32+
import * as Sentry from '@sentry/browser';
33+
import { Replay } from '@sentry/replay';
34+
35+
Sentry.init({
36+
dsn: '__DSN__',
37+
integrations: [
38+
new Replay({
39+
// This sets the sample rate to be 10%. You may want this to be 100% while
40+
// in development and sample at a lower rate in production
41+
sessionSampleRate: 0.1,
42+
43+
// If the entire session is not sampled, use the below sample rate to sample
44+
// sessions when an error occurs.
45+
errorSampleRate: 1.0,
46+
47+
// Mask all text content with asterisks (*). Passes text
48+
// content through to `maskTextFn` before sending to server.
49+
//
50+
// Defaults to true, uncomment to change
51+
// maskAllText: true,
52+
53+
// Block all media elements (img, svg, video, object,
54+
// picture, embed, map, audio)
55+
//
56+
// Defaults to true, uncomment to change
57+
// blockAllMedia: true,
58+
})
59+
],
60+
// ...
61+
});
62+
```
63+
64+
### Identifying Users
65+
66+
If you have only followed the above instructions to setup session replays, you will only see IP addresses in Sentry's UI. In order to associate a user identity to a session replay, use [`setUser`](https://docs.sentry.io/platforms/javascript/enriching-events/identify-user/).
67+
68+
```javascript
69+
import * as Sentry from "@sentry/browser";
70+
Sentry.setUser({ email: "[email protected]" });
71+
```
72+
73+
### Start and Stop Recording
74+
75+
Replay recording only starts automatically when it is included in the `integrations` key when calling `Sentry.init`. Otherwise you can initialize the plugin and manually call the `start()` method on the integration instance. To stop recording you can call the `stop()`.
76+
77+
```javascript
78+
const replay = new Replay(); // This will *NOT* begin recording replays
79+
80+
replay.start(); // Start recording
81+
82+
replay.stop(); // Stop recording
83+
```
84+
85+
## Sessions
86+
87+
A session starts when the Session Replay SDK is first loaded and initialized. The session will continue until 5 minutes passes without any user interactions[^1] with the application *OR* until a maximum of 30 minutes have elapsed. Closing the browser tab will end the session immediately according to the rules for [SessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage).
88+
89+
[^1]: An 'interaction' refers to either a mouse click or a browser navigation event.
90+
91+
### Replay Captures Only on Errors
92+
93+
Alternatively, rather than recording an entire session, you can capture a replay only when an error occurs. In this case, the integration will buffer up to one minute worth of events prior to the error being thrown. It will continue to record the session following the rules above regarding session life and activity. Read the [sampling](#Sampling) section for configuration options.
94+
95+
## Sampling
96+
97+
Sampling allows you to control how much of your website's traffic will result in a Session Replay. There are two sample rates you can adjust to get the replays more relevant to your interests:
98+
99+
- `sessionSampleRate` - The sample rate for replays that begin recording immediately and last the entirety of the user's session.
100+
- `errorSampleRate` - The sample rate for replays that are recorded when an error happens. This type of replay will record up to a minute of events prior to the error and continue recording until the session ends.
101+
102+
Sampling occurs when the session is first started. `sessionSampleRate` is evaluated first. If it is sampled, then the replay recording begins. Otherwise, `errorSampleRate` is evaluated and if it is sampled, the integration will begin buffering the replay and will only upload a replay to Sentry when an error occurs. The remainder of the replay will behave similarly to a whole-session replay.
103+
104+
105+
## Configuration
106+
107+
### General Configuration
108+
109+
| key | type | default | description |
110+
| ------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
111+
| sessionSampleRate | number | `0.1` | The sample rate for all sessions, which will capture the entirety from when a user begins a session until the session ends. (1.0 will collect all replays, 0 will collect no replays) |
112+
| errorSampleRate | number | `1.0` | If a session isn't already being recorded via `sessionSampleRate`, based on `errorSampleRate` the SDK will send the captured replay when an error occurs. (1.0 capturing all sessions with an error, and 0 capturing none). |
113+
| stickySession | boolean | `true` | Keep track of the user across page loads. Note a single user using multiple tabs will result in multiple sessions. Closing a tab will result in the session being closed as well. |
114+
115+
### Privacy Configuration
116+
117+
| key | type | default | description |
118+
| ---------------- | ------------------------ | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
119+
| maskAllText | boolean | `true` | Mask _all_ text content. Will pass text content through `maskTextFn` before sending to server. |
120+
| blockAllMedia | boolean | `true` | Block _all_ media elements (`img, svg, video, object, picture, embed, map, audio`)
121+
| maskTextFn | (text: string) => string | `(text) => '*'.repeat(text.length)` | Function to customize how text content is masked before sending to server. By default, masks text with `*`. |
122+
| maskAllInputs | boolean | `true` | Mask values of `<input>` elements. Passes input values through `maskInputFn` before sending to server. |
123+
| maskInputOptions | Record<string, boolean> | `{ password: true }` | Customize which inputs `type` to mask. <br /> Available `<input>` types: `color, date, datetime-local, email, month, number, range, search, tel, text, time, url, week, textarea, select, password`. |
124+
| maskInputFn | (text: string) => string | `(text) => '*'.repeat(text.length)` | Function to customize how form input values are masked before sending to server. By default, masks values with `*`. |
125+
| blockClass | string \| RegExp | `'sentry-block'` | Redact all elements that match the class name. See [privacy](#blocking) section for an example. |
126+
| blockSelector | string | `'[data-sentry-block]'` | Redact all elements that match the DOM selector. See [privacy](#blocking) section for an example. |
127+
| ignoreClass | string \| RegExp | `'sentry-ignore'` | Ignores all events on the matching input field. See [privacy](#ignoring) section for an example. |
128+
| maskTextClass | string \| RegExp | `'sentry-mask'` | Mask all elements that match the class name. See [privacy](#masking) section for an example. |
129+
130+
### Optimization Configuration
131+
132+
| key | type | default | description |
133+
| ---------------- | ----------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
134+
| collectFonts | boolean | `false` | Should collect fonts used on the website |
135+
| inlineImages | boolean | `false` | Should inline `<image>` content |
136+
| inlineStylesheet | boolean | `true` | Should inline stylesheets used in the recording |
137+
| recordCanvas | boolean | `false` | Should record `<canvas>` elements |
138+
| slimDOMOptions | Record<string, boolean> | `{}` | Remove unnecessary parts of the DOM <br /> Available keys: `script, comment, headFavicon, headWhitespace, headMetaDescKeywords, headMetaSocial, headMetaRobots, headMetaHttpEquiv, headMetaAuthorship, headMetaVerification` |
139+
140+
## Privacy
141+
There are several ways to deal with PII. By default, the integration will mask all text content with `*` and block all media elements (`img, svg, video, object, picture, embed, map, audio`). This can be disabled by setting `maskAllText` to `false`. It is also possible to add the following CSS classes to specific DOM elements to prevent recording its contents: `sentry-block`, `sentry-ignore`, and `sentry-mask`. The following sections will show examples of how content is handled by the differing methods.
142+
143+
### Masking
144+
Masking replaces the text content with something else. The default masking behavior is to replace each character with a `*`. In this example the relevant html code is: `<table class="sentry-mask">...</table>`.
145+
![Masking example](https://user-images.githubusercontent.com/79684/193118192-dee1d3d8-5813-47e8-b532-f9ee1c8714b3.png)
146+
147+
### Blocking
148+
Blocking replaces the element with a placeholder that has the same dimensions. The recording will show an empty space where the content was. In this example the relevant html code is: `<table data-sentry-block>...</table>`.
149+
![Blocking example](https://user-images.githubusercontent.com/79684/193118084-51a589fc-2160-476a-a8dc-b681eddb136c.png)
150+
151+
### Ignoring
152+
Ignoring only applies to form inputs. Events will be ignored on the input element so that the replay does not show what occurs inside of the input. In the below example, notice how the results in the table below the input changes, but no text is visible in the input.
153+
154+
https://user-images.githubusercontent.com/79684/192815134-a6451c3f-d3cb-455f-a699-7c3fe04d0a2e.mov
155+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const captureEvent = jest.fn();
2+
const getCurrentHub = jest.fn(() => ({
3+
captureEvent,
4+
getClient: jest.fn(() => ({
5+
getDsn: jest.fn(),
6+
})),
7+
}));
8+
9+
const addGlobalEventProcessor = jest.fn();
10+
const configureScope = jest.fn();
11+
12+
export { getCurrentHub, addGlobalEventProcessor, configureScope };

0 commit comments

Comments
 (0)