-
Notifications
You must be signed in to change notification settings - Fork 103
feat(instrumentation-replay): add experimental replay support #1417
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
Changes from 43 commits
373f4a9
4dd30ae
30a5e24
783c9c2
85db523
f3036e8
9c8dbd2
6aa7bc3
59bb0e1
15647fd
179dddd
255c605
fb49acf
cc9c05f
a278e23
3eedcc3
31d909c
50dd526
48186b9
4d31386
565472b
77c174c
a47544e
0d0a958
9c6e8ed
1e35537
f7c6951
ed02b0f
bd8ac63
9f66174
ac71a82
8f8111d
b4ffa60
8a7da7d
9fff3dc
d1ea363
9b369a8
86f27e9
1207c42
fdf74bc
c243fc1
040c95d
c7152c6
d6c4258
b41c40c
96cdd77
8996b75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| # @grafana/faro-instrumentation-replay | ||
|
|
||
| Faro instrumentation for session replay with rrweb. | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| npm install @grafana/faro-instrumentation-replay | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| ```typescript | ||
| import { ReplayInstrumentation } from '@grafana/faro-instrumentation-replay'; | ||
| import { getWebInstrumentations, initializeFaro } from '@grafana/faro-web-sdk'; | ||
|
|
||
| initializeFaro({ | ||
| url: 'https://your-faro-endpoint.com', | ||
| instrumentations: [ | ||
| ...getWebInstrumentations(), | ||
| new ReplayInstrumentation({ | ||
| maskInputOptions: { | ||
| password: true, | ||
| email: true, | ||
| }, | ||
| maskAllInputs: false, | ||
| recordCrossOriginIframes: false, | ||
| }), | ||
| ], | ||
| }); | ||
| ``` | ||
|
|
||
| ## Configuration Options | ||
|
|
||
| ### Privacy & Masking Options | ||
|
|
||
| - **`maskAllInputs`** (default: `false`): Whether to mask all input elements | ||
| - **`maskInputOptions`** (default: `{ password: true }`): Fine-grained control over which input types to mask. | ||
| Available options: | ||
| - `password` - Password inputs | ||
| - `text` - Text inputs | ||
| - `email` - Email inputs | ||
| - `tel` - Telephone inputs | ||
| - `number` - Number inputs | ||
| - `search` - Search inputs | ||
| - `url` - URL inputs | ||
| - `date`, `datetime-local`, `month`, `week`, `time` - Date/time inputs | ||
| - `color` - Color inputs | ||
| - `range` - Range inputs | ||
| - `textarea` - Textarea elements | ||
| - `select` - Select dropdowns | ||
| - **`maskSelector`**: Custom CSS selector to mask specific elements | ||
Blinkuu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - **`blockSelector`**: CSS selector to completely block elements from recording | ||
| - **`ignoreSelector`**: CSS selector to ignore specific elements | ||
|
|
||
| ### Recording Options | ||
|
|
||
| - **`recordCrossOriginIframes`** (default: `false`): Whether to record cross-origin iframes | ||
| - **`recordCanvas`** (default: `false`): Whether to record canvas elements | ||
| - **`collectFonts`** (default: `false`): Whether to collect font files | ||
| - **`inlineImages`** (default: `false`): Whether to inline images in the recording | ||
| - **`inlineStylesheet`** (default: `false`): Whether to inline stylesheets | ||
|
|
||
| ### Hooks | ||
|
|
||
| - **`beforeSend`**: Custom function to transform or filter events before they are sent. | ||
| Return the modified event or `null`/`undefined` to skip sending | ||
|
|
||
| ## Privacy and Security | ||
|
|
||
| This instrumentation records user interactions on your website. Make sure to: | ||
|
|
||
| 1. **Enable appropriate masking options** - By default, only password inputs are masked. | ||
| Configure `maskInputOptions` to mask additional sensitive fields | ||
| 2. **Use CSS selectors** - Use `maskSelector` to mask sensitive content, `blockSelector` to completely exclude elements | ||
Blinkuu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 3. **Implement filtering** - Use the `beforeSend` hook to filter or transform events before sending | ||
| 4. **Review your privacy policy** - Ensure you have proper user consent for session recording | ||
| 5. **Test your configuration** - Verify no sensitive information is captured in recordings | ||
|
|
||
| ### Example: Advanced Privacy Configuration | ||
|
|
||
| ```typescript | ||
| new ReplayInstrumentation({ | ||
| // Mask all text and email inputs, but allow number inputs | ||
| maskInputOptions: { | ||
| password: true, | ||
| text: true, | ||
| email: true, | ||
| tel: true, | ||
| textarea: true, | ||
| }, | ||
| // Mask elements with specific CSS classes | ||
| maskSelector: '.sensitive-data, .pii', | ||
Blinkuu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Block elements completely from recording | ||
| blockSelector: '.payment-form, .credit-card-info', | ||
| // Ignore certain elements (won't be recorded at all) | ||
| ignoreSelector: '.analytics-widget', | ||
| // Filter or transform events before sending | ||
| beforeSend: (event) => { | ||
| // Example: Skip events that might contain sensitive data | ||
| if (event.type === 3 && event.data?.source === 'CanvasMutation') { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Super NIT: why not export a constant for readability. E. g.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear at this point which events are really sensitive. This is just an example to show how one can use the |
||
| return null; // Skip this event | ||
| } | ||
| return event; // Send the event as-is | ||
| }, | ||
| }); | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| const { jestBaseConfig } = require('../../jest.config.base.js'); | ||
|
|
||
| module.exports = { | ||
| ...jestBaseConfig, | ||
| roots: ['experimental/instrumentation-replay/src'], | ||
| testEnvironment: 'jsdom', | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| { | ||
| "name": "@grafana/faro-instrumentation-replay", | ||
| "version": "2.0.2", | ||
| "description": "Faro instrumentation for session replay with rrweb", | ||
| "keywords": [ | ||
| "observability", | ||
| "apm", | ||
| "rum", | ||
| "dem", | ||
| "session-replay", | ||
| "rrweb", | ||
| "browser" | ||
| ], | ||
| "license": "Apache-2.0", | ||
| "author": "Grafana Labs", | ||
| "homepage": "https://github.com/grafana/faro-web-sdk", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/grafana/faro-web-sdk.git", | ||
| "directory": "experimental/instrumentation-replay" | ||
| }, | ||
| "main": "./dist/cjs/index.js", | ||
| "module": "./dist/esm/index.js", | ||
| "types": "./dist/types/index.d.ts", | ||
| "files": [ | ||
| "dist", | ||
| "README.md", | ||
| "LICENSE" | ||
| ], | ||
| "scripts": { | ||
| "start": "yarn watch", | ||
| "build": "run-s 'build:*'", | ||
| "build:compile": "run-p 'build:compile:*'", | ||
| "build:compile:cjs": "tsc --build tsconfig.cjs.json", | ||
| "build:compile:esm": "tsc --build tsconfig.esm.json", | ||
| "build:compile:bundle": "run-s 'build:compile:bundle:*'", | ||
| "build:compile:bundle:create": "rollup -c ./rollup.config.js", | ||
| "build:compile:bundle:remove-extras": "rimraf dist/bundle/dist", | ||
| "watch": "run-s watch:compile", | ||
| "watch:compile": "yarn build:compile:cjs -w", | ||
| "clean": "rimraf dist/ yarn-error.log", | ||
| "quality": "run-s 'quality:*'", | ||
| "quality:test": "jest", | ||
| "quality:format": "prettier --cache --cache-location=../../.cache/prettier/webSessionReplay --ignore-path ../../.prettierignore -w \"./**/*.{js,jsx,ts,tsx,css,scss,md,yaml,yml,json}\"", | ||
| "quality:lint": "run-s 'quality:lint:*'", | ||
| "quality:lint:eslint": "eslint --cache --cache-location ../../.cache/eslint/webSessionReplay \"./**/*.{js,jsx,ts,tsx}\"", | ||
| "quality:lint:prettier": "prettier --cache --cache-location=../../.cache/prettier/webSessionReplay --ignore-path ../../.prettierignore -c \"./**/*.{js,jsx,ts,tsx,css,scss,md,yaml,yml,json}\"", | ||
| "quality:lint:md": "markdownlint README.md", | ||
| "quality:circular-deps": "madge --circular ." | ||
| }, | ||
| "dependencies": { | ||
| "@grafana/faro-core": "^2.0.2", | ||
| "rrweb": "^2.0.0-alpha.18" | ||
Blinkuu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, | ||
| "publishConfig": { | ||
| "access": "public" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| const { getRollupConfigBase } = require('../../rollup.config.base.js'); | ||
|
|
||
| module.exports = getRollupConfigBase('instrumentationReplay'); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { type ReplayInstrumentationOptions } from './types'; | ||
|
|
||
| export const defaultReplayInstrumentationOptions: ReplayInstrumentationOptions = { | ||
| maskAllInputs: false, | ||
| maskInputOptions: { | ||
| password: true, | ||
| }, | ||
| maskTextSelector: undefined, | ||
| blockSelector: undefined, | ||
| ignoreSelector: undefined, | ||
| collectFonts: false, | ||
| inlineImages: false, | ||
| inlineStylesheet: false, | ||
| recordCanvas: false, | ||
| recordCrossOriginIframes: false, | ||
| beforeSend: undefined, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { ReplayInstrumentation } from './instrumentation'; | ||
| export type { ReplayInstrumentationOptions, MaskInputOptions } from './types'; |
Uh oh!
There was an error while loading. Please reload this page.