@vyro-x/sentry 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/dist/cjs/browser.d.ts +29 -0
- package/dist/cjs/browser.d.ts.map +1 -0
- package/dist/cjs/browser.js +57 -0
- package/dist/cjs/config.d.ts +8 -0
- package/dist/cjs/config.d.ts.map +1 -0
- package/dist/cjs/config.js +16 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +13 -0
- package/dist/cjs/nextjs.d.ts +29 -0
- package/dist/cjs/nextjs.d.ts.map +1 -0
- package/dist/cjs/nextjs.js +52 -0
- package/dist/cjs/node.d.ts +39 -0
- package/dist/cjs/node.d.ts.map +1 -0
- package/dist/cjs/node.js +84 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/scrub.d.ts +36 -0
- package/dist/cjs/scrub.d.ts.map +1 -0
- package/dist/cjs/scrub.js +70 -0
- package/dist/cjs/scrubbers.d.ts +27 -0
- package/dist/cjs/scrubbers.d.ts.map +1 -0
- package/dist/cjs/scrubbers.js +51 -0
- package/dist/esm/browser.d.ts +29 -0
- package/dist/esm/browser.d.ts.map +1 -0
- package/dist/esm/browser.js +31 -0
- package/dist/esm/config.d.ts +8 -0
- package/dist/esm/config.d.ts.map +1 -0
- package/dist/esm/config.js +12 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/nextjs.d.ts +29 -0
- package/dist/esm/nextjs.d.ts.map +1 -0
- package/dist/esm/nextjs.js +26 -0
- package/dist/esm/node.d.ts +39 -0
- package/dist/esm/node.d.ts.map +1 -0
- package/dist/esm/node.js +57 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/scrub.d.ts +36 -0
- package/dist/esm/scrub.d.ts.map +1 -0
- package/dist/esm/scrub.js +66 -0
- package/dist/esm/scrubbers.d.ts +27 -0
- package/dist/esm/scrubbers.d.ts.map +1 -0
- package/dist/esm/scrubbers.js +47 -0
- package/package.json +86 -0
- package/src/browser.ts +50 -0
- package/src/config.ts +11 -0
- package/src/index.ts +10 -0
- package/src/nextjs.ts +48 -0
- package/src/node.ts +81 -0
- package/src/scrub.test.ts +93 -0
- package/src/scrub.ts +85 -0
- package/src/scrubbers.ts +68 -0
package/src/nextjs.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as Sentry from '@sentry/nextjs'
|
|
2
|
+
import { DEFAULT_TRACES_SAMPLE_RATE, resolveEnvironment } from './config'
|
|
3
|
+
import type { SentryScrubOptions } from './scrub'
|
|
4
|
+
import { createSentryScrubbers } from './scrubbers'
|
|
5
|
+
|
|
6
|
+
/** Re-exported SDK, for the rare escape hatch. Prefer the helper below. */
|
|
7
|
+
export { Sentry }
|
|
8
|
+
export const captureException = Sentry.captureException
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Options for `initSentry`. Used by all three Next.js runtimes
|
|
12
|
+
* (`sentry.server.config.ts`, `sentry.client.config.ts`, `sentry.edge.config.ts`).
|
|
13
|
+
*/
|
|
14
|
+
export interface NextSentryInitOptions {
|
|
15
|
+
/** DSN; defaults to `process.env.NEXT_PUBLIC_SENTRY_DSN` then `process.env.SENTRY_DSN`. */
|
|
16
|
+
dsn?: string
|
|
17
|
+
/** Logical name shown as `server_name` (server/edge runtimes). */
|
|
18
|
+
serverName?: string
|
|
19
|
+
/** Performance tracing sample rate. Defaults to `0.2`. */
|
|
20
|
+
tracesSampleRate?: number
|
|
21
|
+
/** Overrides the resolved `environment`. */
|
|
22
|
+
environment?: string
|
|
23
|
+
/** Scrubber options (extra keys/patterns, placeholder, …). */
|
|
24
|
+
scrub?: SentryScrubOptions
|
|
25
|
+
/** Extra `@sentry/nextjs` init options, merged last so they can override defaults. */
|
|
26
|
+
init?: Parameters<typeof Sentry.init>[0]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialises `@sentry/nextjs` (server, client or edge) for the Vyro website with
|
|
31
|
+
* secret-scrubbing already wired in. Idempotent and a no-op when no DSN is configured.
|
|
32
|
+
*/
|
|
33
|
+
export function initSentry(options: NextSentryInitOptions = {}): void {
|
|
34
|
+
const dsn =
|
|
35
|
+
options.dsn ??
|
|
36
|
+
(typeof process !== 'undefined'
|
|
37
|
+
? (process.env.NEXT_PUBLIC_SENTRY_DSN ?? process.env.SENTRY_DSN)
|
|
38
|
+
: undefined)
|
|
39
|
+
if (!dsn) return
|
|
40
|
+
Sentry.init({
|
|
41
|
+
...createSentryScrubbers(options.scrub),
|
|
42
|
+
dsn,
|
|
43
|
+
serverName: options.serverName,
|
|
44
|
+
environment: options.environment ?? resolveEnvironment(),
|
|
45
|
+
tracesSampleRate: options.tracesSampleRate ?? DEFAULT_TRACES_SAMPLE_RATE,
|
|
46
|
+
...options.init,
|
|
47
|
+
})
|
|
48
|
+
}
|
package/src/node.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as Sentry from '@sentry/node'
|
|
2
|
+
import { DEFAULT_TRACES_SAMPLE_RATE, resolveEnvironment } from './config'
|
|
3
|
+
import type { SentryScrubOptions } from './scrub'
|
|
4
|
+
import { createSentryScrubbers } from './scrubbers'
|
|
5
|
+
|
|
6
|
+
/** Re-exported SDK, for the rare escape hatch. Prefer the helpers below. */
|
|
7
|
+
export { Sentry }
|
|
8
|
+
/**
|
|
9
|
+
* Express error handler (Sentry v8+). Call after your routes are registered so
|
|
10
|
+
* errors bubbling out of the app are reported. Replaces the removed
|
|
11
|
+
* `Sentry.Handlers.errorHandler()`.
|
|
12
|
+
*/
|
|
13
|
+
export const setupExpressErrorHandler = Sentry.setupExpressErrorHandler
|
|
14
|
+
|
|
15
|
+
let initialised = false
|
|
16
|
+
|
|
17
|
+
export interface NodeSentryInitOptions {
|
|
18
|
+
/** Logical service name — shown in Sentry as `server_name`. */
|
|
19
|
+
serverName: string
|
|
20
|
+
/** DSN; defaults to `process.env.SENTRY_DSN`. When absent, init is skipped. */
|
|
21
|
+
dsn?: string
|
|
22
|
+
/** Performance tracing sample rate. Defaults to `0.2`. */
|
|
23
|
+
tracesSampleRate?: number
|
|
24
|
+
/** Overrides the resolved `environment`. */
|
|
25
|
+
environment?: string
|
|
26
|
+
/** Scrubber options (extra keys/patterns, placeholder, …). */
|
|
27
|
+
scrub?: SentryScrubOptions
|
|
28
|
+
/** Extra `@sentry/node` init options, merged last so they can override defaults. */
|
|
29
|
+
init?: Parameters<typeof Sentry.init>[0]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initialises `@sentry/node` for a Vyro service with secret-scrubbing already wired
|
|
34
|
+
* in. Idempotent and a no-op when no DSN is configured.
|
|
35
|
+
*/
|
|
36
|
+
export function initSentry(options: NodeSentryInitOptions): void {
|
|
37
|
+
if (initialised) return
|
|
38
|
+
const dsn = options.dsn ?? process.env.SENTRY_DSN
|
|
39
|
+
if (!dsn) {
|
|
40
|
+
initialised = true
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
Sentry.init({
|
|
44
|
+
...createSentryScrubbers(options.scrub),
|
|
45
|
+
dsn,
|
|
46
|
+
serverName: options.serverName,
|
|
47
|
+
environment: options.environment ?? resolveEnvironment(),
|
|
48
|
+
tracesSampleRate: options.tracesSampleRate ?? DEFAULT_TRACES_SAMPLE_RATE,
|
|
49
|
+
...options.init,
|
|
50
|
+
})
|
|
51
|
+
initialised = true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface CaptureContext {
|
|
55
|
+
tags?: Record<string, string>
|
|
56
|
+
extra?: Record<string, unknown>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Reports an exception with optional tags/extra. No-op until `initSentry` has run
|
|
61
|
+
* with a DSN. Consolidates the helper previously copied into individual services.
|
|
62
|
+
*/
|
|
63
|
+
export function captureException(
|
|
64
|
+
error: unknown,
|
|
65
|
+
context?: CaptureContext,
|
|
66
|
+
): void {
|
|
67
|
+
if (!initialised) return
|
|
68
|
+
if (!context) {
|
|
69
|
+
Sentry.captureException(error)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
Sentry.withScope((scope) => {
|
|
73
|
+
if (context.tags) {
|
|
74
|
+
for (const [key, value] of Object.entries(context.tags)) {
|
|
75
|
+
scope.setTag(key, value)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (context.extra) scope.setExtras(context.extra)
|
|
79
|
+
Sentry.captureException(error)
|
|
80
|
+
})
|
|
81
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createSentryScrubbers, scrubSentrySensitiveData } from './index'
|
|
2
|
+
|
|
3
|
+
describe('scrubSentrySensitiveData', () => {
|
|
4
|
+
it('redacts authorization and api-key style headers, case-insensitively', () => {
|
|
5
|
+
const headers: Record<string, unknown> = {
|
|
6
|
+
Authorization: 'Bearer abc123',
|
|
7
|
+
'x-api-key': 'secret-key',
|
|
8
|
+
'X-Api-Key': 'other',
|
|
9
|
+
apikey: 'k',
|
|
10
|
+
api_key: 'k2',
|
|
11
|
+
'x-hasura-admin-secret': 'hasura',
|
|
12
|
+
'content-type': 'application/json',
|
|
13
|
+
}
|
|
14
|
+
scrubSentrySensitiveData(headers)
|
|
15
|
+
expect(headers.Authorization).toBe('[Filtered]')
|
|
16
|
+
expect(headers['x-api-key']).toBe('[Filtered]')
|
|
17
|
+
expect(headers['X-Api-Key']).toBe('[Filtered]')
|
|
18
|
+
expect(headers.apikey).toBe('[Filtered]')
|
|
19
|
+
expect(headers.api_key).toBe('[Filtered]')
|
|
20
|
+
expect(headers['x-hasura-admin-secret']).toBe('[Filtered]')
|
|
21
|
+
expect(headers['content-type']).toBe('application/json')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('walks nested objects and arrays', () => {
|
|
25
|
+
const value = {
|
|
26
|
+
list: [{ token: 't' }, { safe: 'ok' }],
|
|
27
|
+
nested: { deep: { secret: 's', keep: 1 } },
|
|
28
|
+
}
|
|
29
|
+
scrubSentrySensitiveData(value)
|
|
30
|
+
expect(value.list[0].token).toBe('[Filtered]')
|
|
31
|
+
expect((value.list[1] as { safe: string }).safe).toBe('ok')
|
|
32
|
+
expect(value.nested.deep.secret).toBe('[Filtered]')
|
|
33
|
+
expect(value.nested.deep.keep).toBe(1)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('ignores primitives and null without throwing', () => {
|
|
37
|
+
expect(() => scrubSentrySensitiveData(null)).not.toThrow()
|
|
38
|
+
expect(() => scrubSentrySensitiveData('string')).not.toThrow()
|
|
39
|
+
expect(() => scrubSentrySensitiveData(42)).not.toThrow()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('handles circular references', () => {
|
|
43
|
+
const a: Record<string, unknown> = { token: 't' }
|
|
44
|
+
a.self = a
|
|
45
|
+
expect(() => scrubSentrySensitiveData(a)).not.toThrow()
|
|
46
|
+
expect(a.token).toBe('[Filtered]')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('supports custom placeholder and extra keys', () => {
|
|
50
|
+
const value = { requestId: 'abc', keep: 'y' }
|
|
51
|
+
scrubSentrySensitiveData(value, {
|
|
52
|
+
placeholder: '***',
|
|
53
|
+
extraKeys: ['requestid'],
|
|
54
|
+
})
|
|
55
|
+
expect(value.requestId).toBe('***')
|
|
56
|
+
expect(value.keep).toBe('y')
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('createSentryScrubbers', () => {
|
|
61
|
+
it('scrubs request headers/cookies and returns the same event', () => {
|
|
62
|
+
const { beforeSend } = createSentryScrubbers()
|
|
63
|
+
const event = {
|
|
64
|
+
request: {
|
|
65
|
+
headers: { authorization: 'Bearer x', accept: '*/*' },
|
|
66
|
+
cookies: { session: 'abc', 'x-api-key': 'leak' },
|
|
67
|
+
},
|
|
68
|
+
extra: { apiKey: 'leak2', note: 'keep' },
|
|
69
|
+
}
|
|
70
|
+
const result = beforeSend(event)
|
|
71
|
+
expect(result).toBe(event)
|
|
72
|
+
expect(event.request.headers.authorization).toBe('[Filtered]')
|
|
73
|
+
expect(event.request.headers.accept).toBe('*/*')
|
|
74
|
+
expect(event.request.cookies['x-api-key']).toBe('[Filtered]')
|
|
75
|
+
expect(event.extra.apiKey).toBe('[Filtered]')
|
|
76
|
+
expect(event.extra.note).toBe('keep')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('scrubs breadcrumb data and returns the same breadcrumb', () => {
|
|
80
|
+
const { beforeBreadcrumb } = createSentryScrubbers()
|
|
81
|
+
const breadcrumb = {
|
|
82
|
+
category: 'fetch',
|
|
83
|
+
data: {
|
|
84
|
+
request_headers: { 'x-api-key': 'leak' },
|
|
85
|
+
status_code: 200,
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
const result = beforeBreadcrumb(breadcrumb)
|
|
89
|
+
expect(result).toBe(breadcrumb)
|
|
90
|
+
expect(breadcrumb.data.request_headers['x-api-key']).toBe('[Filtered]')
|
|
91
|
+
expect(breadcrumb.data.status_code).toBe(200)
|
|
92
|
+
})
|
|
93
|
+
})
|
package/src/scrub.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK-agnostic Sentry scrubbing helpers.
|
|
3
|
+
*
|
|
4
|
+
* This package intentionally does NOT depend on any `@sentry/*` package so it can
|
|
5
|
+
* be dropped into services and frontends running different Sentry SDK versions
|
|
6
|
+
* (`@sentry/node`, `@sentry/react`, `@sentry/nextjs`, …). The public `beforeSend` /
|
|
7
|
+
* `beforeBreadcrumb` helpers use a generic identity signature (`<T>(x: T) => T`) so
|
|
8
|
+
* they satisfy every SDK's `beforeSend` / `beforeBreadcrumb` option type while
|
|
9
|
+
* operating on the event purely at runtime.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Default set of key/header-name fragments that get redacted (case-insensitive).
|
|
14
|
+
* Matches `authorization`, `x-api-key` and every `api-key` / `apikey` / `api_key`
|
|
15
|
+
* variant, `x-hasura-admin-secret`, `access-token` / `refresh-token` / `token`,
|
|
16
|
+
* `secret`, `cookie`, `password` and `credential`.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_SENSITIVE_KEY_PATTERN =
|
|
19
|
+
/authorization|api[-_ ]?key|x-hasura-admin-secret|access[-_ ]?token|refresh[-_ ]?token|token|secret|cookie|password|credential/i
|
|
20
|
+
|
|
21
|
+
export interface SentryScrubOptions {
|
|
22
|
+
/** Extra regexes matched against key/header names (case-insensitive recommended). */
|
|
23
|
+
extraPatterns?: RegExp[]
|
|
24
|
+
/** Extra literal substrings matched against key/header names (case-insensitive). */
|
|
25
|
+
extraKeys?: string[]
|
|
26
|
+
/** Replace the whole default pattern instead of extending it. */
|
|
27
|
+
pattern?: RegExp
|
|
28
|
+
/** Value written in place of a redacted entry. Defaults to `"[Filtered]"`. */
|
|
29
|
+
placeholder?: string
|
|
30
|
+
/** Maximum object depth to walk. Defaults to `6`. */
|
|
31
|
+
maxDepth?: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const buildMatcher = (
|
|
35
|
+
options: SentryScrubOptions,
|
|
36
|
+
): ((key: string) => boolean) => {
|
|
37
|
+
const patterns: RegExp[] = [
|
|
38
|
+
options.pattern ?? DEFAULT_SENSITIVE_KEY_PATTERN,
|
|
39
|
+
...(options.extraPatterns ?? []),
|
|
40
|
+
]
|
|
41
|
+
const keys = (options.extraKeys ?? []).map((key) => key.toLowerCase())
|
|
42
|
+
return (key: string): boolean => {
|
|
43
|
+
if (patterns.some((pattern) => pattern.test(key))) return true
|
|
44
|
+
if (keys.length === 0) return false
|
|
45
|
+
const lower = key.toLowerCase()
|
|
46
|
+
return keys.some((needle) => lower.includes(needle))
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Recursively redacts sensitive keys of `value` **in place**. Safe on any input:
|
|
52
|
+
* primitives and `null`/`undefined` are ignored, arrays are walked, and circular
|
|
53
|
+
* references are handled. Sensitive values are replaced with the placeholder.
|
|
54
|
+
*/
|
|
55
|
+
export const scrubSentrySensitiveData = (
|
|
56
|
+
value: unknown,
|
|
57
|
+
options: SentryScrubOptions = {},
|
|
58
|
+
): void => {
|
|
59
|
+
const placeholder = options.placeholder ?? '[Filtered]'
|
|
60
|
+
const maxDepth = options.maxDepth ?? 6
|
|
61
|
+
const isSensitive = buildMatcher(options)
|
|
62
|
+
const seen = new WeakSet<object>()
|
|
63
|
+
|
|
64
|
+
const walk = (node: unknown, depth: number): void => {
|
|
65
|
+
if (!node || typeof node !== 'object' || depth > maxDepth) return
|
|
66
|
+
if (seen.has(node as object)) return
|
|
67
|
+
seen.add(node as object)
|
|
68
|
+
|
|
69
|
+
if (Array.isArray(node)) {
|
|
70
|
+
for (const item of node) walk(item, depth + 1)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const record = node as Record<string, unknown>
|
|
75
|
+
for (const key of Object.keys(record)) {
|
|
76
|
+
if (isSensitive(key)) {
|
|
77
|
+
record[key] = placeholder
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
walk(record[key], depth + 1)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
walk(value, 0)
|
|
85
|
+
}
|
package/src/scrubbers.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type SentryScrubOptions, scrubSentrySensitiveData } from './scrub'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A pair of scrubbing hooks. The signatures are generic identity functions so the
|
|
5
|
+
* object can be spread straight into any SDK's `Sentry.init({ ... })` regardless of
|
|
6
|
+
* its `Event` / `Breadcrumb` types.
|
|
7
|
+
*/
|
|
8
|
+
export interface SentryScrubbers {
|
|
9
|
+
beforeSend: <T>(event: T) => T
|
|
10
|
+
beforeBreadcrumb: <T>(breadcrumb: T) => T
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const scrubEvent = (event: unknown, options: SentryScrubOptions): void => {
|
|
14
|
+
if (!event || typeof event !== 'object') return
|
|
15
|
+
const record = event as Record<string, unknown>
|
|
16
|
+
|
|
17
|
+
const request = record.request as Record<string, unknown> | undefined
|
|
18
|
+
if (request) {
|
|
19
|
+
scrubSentrySensitiveData(request.headers, options)
|
|
20
|
+
scrubSentrySensitiveData(request.cookies, options)
|
|
21
|
+
scrubSentrySensitiveData(request.data, options)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
scrubSentrySensitiveData(record.contexts, options)
|
|
25
|
+
scrubSentrySensitiveData(record.extra, options)
|
|
26
|
+
scrubSentrySensitiveData(record.tags, options)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const scrubBreadcrumb = (
|
|
30
|
+
breadcrumb: unknown,
|
|
31
|
+
options: SentryScrubOptions,
|
|
32
|
+
): void => {
|
|
33
|
+
if (!breadcrumb || typeof breadcrumb !== 'object') return
|
|
34
|
+
scrubSentrySensitiveData(
|
|
35
|
+
(breadcrumb as Record<string, unknown>).data,
|
|
36
|
+
options,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Builds `beforeSend` / `beforeBreadcrumb` hooks that redact authorization headers,
|
|
42
|
+
* API keys, tokens and other secrets from outgoing Sentry events and breadcrumbs.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* import * as Sentry from "@sentry/node"
|
|
47
|
+
* import { createSentryScrubbers } from "@vyro-x/sentry"
|
|
48
|
+
*
|
|
49
|
+
* Sentry.init({
|
|
50
|
+
* ...createSentryScrubbers(),
|
|
51
|
+
* dsn: process.env.SENTRY_DSN,
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function createSentryScrubbers(
|
|
56
|
+
options: SentryScrubOptions = {},
|
|
57
|
+
): SentryScrubbers {
|
|
58
|
+
return {
|
|
59
|
+
beforeSend: <T>(event: T): T => {
|
|
60
|
+
scrubEvent(event, options)
|
|
61
|
+
return event
|
|
62
|
+
},
|
|
63
|
+
beforeBreadcrumb: <T>(breadcrumb: T): T => {
|
|
64
|
+
scrubBreadcrumb(breadcrumb, options)
|
|
65
|
+
return breadcrumb
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
}
|