mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-13 15:46:22 +00:00
feat: sentry improvements (#532)
* feat: process request record from sentry locally * feat: added analytics for logged in users
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { resetIdentity } from '@/lib/analytics/identify'
|
||||
import { signOut } from '@/lib/auth/auth-client'
|
||||
import { providersStorage } from '@/lib/llm-providers/storage'
|
||||
import { scheduledJobStorage } from '@/lib/schedules/scheduleStorage'
|
||||
@@ -26,6 +27,7 @@ export const LogoutPage: FC = () => {
|
||||
queryClient.clear()
|
||||
await localforage.clear()
|
||||
|
||||
resetIdentity()
|
||||
await signOut()
|
||||
navigate('/home', { replace: true })
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { sentry } from '../sentry/sentry'
|
||||
import { posthog } from './posthog'
|
||||
|
||||
/**
|
||||
* Identify the current user across all analytics and error tracking services.
|
||||
* Call this when the user logs in or when a stored session is restored.
|
||||
*/
|
||||
export function identify(user: { id: string; email?: string; name?: string }) {
|
||||
sentry.setUser({ id: user.id, email: user.email })
|
||||
posthog.identify(user.id, {
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear user identity across all services.
|
||||
* Call this when the user logs out.
|
||||
*/
|
||||
export function resetIdentity() {
|
||||
sentry.setUser(null)
|
||||
posthog.reset()
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { FC, PropsWithChildren } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { identify, resetIdentity } from '@/lib/analytics/identify'
|
||||
import { useSession } from './auth-client'
|
||||
import { useSessionInfo } from './sessionStorage'
|
||||
|
||||
@@ -14,6 +15,16 @@ export const AuthProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
session: data?.session,
|
||||
user: data?.user,
|
||||
})
|
||||
|
||||
if (data?.user?.id) {
|
||||
identify({
|
||||
id: data.user.id,
|
||||
email: data.user.email,
|
||||
name: data.user.name || undefined,
|
||||
})
|
||||
} else {
|
||||
resetIdentity()
|
||||
}
|
||||
}
|
||||
}, [data, isPending])
|
||||
|
||||
|
||||
77
packages/browseros-agent/apps/agent/lib/sentry/sanitize.ts
Normal file
77
packages/browseros-agent/apps/agent/lib/sentry/sanitize.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Sanitize Sentry event data by redacting values at keys that match known
|
||||
* sensitive patterns. Used in `beforeSend` to prevent credentials from
|
||||
* leaking into error reports.
|
||||
*/
|
||||
|
||||
const REDACTED = '[REDACTED]'
|
||||
|
||||
const SENSITIVE_KEY_PATTERNS = [
|
||||
'apikey',
|
||||
'api_key',
|
||||
'accesskeyid',
|
||||
'secretaccesskey',
|
||||
'sessiontoken',
|
||||
'authorization',
|
||||
'token',
|
||||
'password',
|
||||
'secret',
|
||||
'credential',
|
||||
]
|
||||
|
||||
function isSensitiveKey(key: string): boolean {
|
||||
const lower = key.toLowerCase()
|
||||
return SENSITIVE_KEY_PATTERNS.some((p) => lower.includes(p))
|
||||
}
|
||||
|
||||
function sanitize<T>(obj: T): T {
|
||||
if (obj === null || obj === undefined) return obj
|
||||
if (
|
||||
typeof obj === 'string' ||
|
||||
typeof obj === 'number' ||
|
||||
typeof obj === 'boolean'
|
||||
) {
|
||||
return obj
|
||||
}
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(sanitize) as T
|
||||
}
|
||||
if (typeof obj === 'object') {
|
||||
const result: Record<string, unknown> = {}
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
result[key] = isSensitiveKey(key) ? REDACTED : sanitize(value)
|
||||
}
|
||||
return result as T
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Sentry event type varies by SDK
|
||||
export function sanitizeEvent<E>(event: E): E {
|
||||
const e = event as Record<string, any>
|
||||
|
||||
if (Array.isArray(e.breadcrumbs)) {
|
||||
e.breadcrumbs = e.breadcrumbs.map((b: Record<string, unknown>) => ({
|
||||
...b,
|
||||
data: b.data ? sanitize(b.data) : b.data,
|
||||
}))
|
||||
}
|
||||
|
||||
if (e.contexts) {
|
||||
e.contexts = sanitize(e.contexts)
|
||||
}
|
||||
|
||||
if (e.extra) {
|
||||
e.extra = sanitize(e.extra)
|
||||
}
|
||||
|
||||
for (const value of e.exception?.values ?? []) {
|
||||
for (const frame of value.stacktrace?.frames ?? []) {
|
||||
if (frame.vars) {
|
||||
frame.vars = sanitize(frame.vars)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as Sentry from '@sentry/react'
|
||||
import { getBrowserOSAdapter } from '../browseros/adapter'
|
||||
import { env } from '../env'
|
||||
import { sanitizeEvent } from './sanitize'
|
||||
|
||||
/** Errors that are expected during normal operation and should not be reported */
|
||||
const SUPPRESSED_ERRORS = ['The browser is shutting down', 'No current window']
|
||||
@@ -35,7 +36,8 @@ if (env.VITE_PUBLIC_SENTRY_DSN) {
|
||||
...event.tags,
|
||||
extensionPage: getExtensionPage(),
|
||||
}
|
||||
return event
|
||||
|
||||
return sanitizeEvent(event)
|
||||
},
|
||||
|
||||
integrations: [
|
||||
|
||||
Reference in New Issue
Block a user