WS-7 PR-3 commit 1. Frontend mirror of the backend SDK install (commits bdb89a2..adab3be), wired against the existing apps/app SPA. - pnpm add @sentry/vue@10.52.0 (pinned). - src/observability/sentry.ts: initSentry() — empty DSN no-op (RFC §3.3), errors-only (tracesSampleRate=0, profilesSampleRate=0; RFC §2 amend.B), sendDefaultPii=false, Console integration off, beforeSend wired to the scrubber, initial scope tag app=app for GlitchTip filtering. - src/observability/scrubber.ts: TypeScript port of backend SentryEventScrubber. RFC §3.7 frontend block — body / header / query scrubbing, form_values wholesale replacement, cookies wholesale, defensive strip of contexts.storage and user.cookies, max-depth guard. - src/observability/contextBinding.ts: Vue Router beforeEach guard that binds RFC §3.6 auth-scope tags per navigation. Three zones via route.meta.public + route.path matching: - portal token zone (meta.public + meta.context=portal) → actor_scope= portal, no user_id (RFC §3.6 explicit) - /platform/* with super_admin → actor_scope=platform, no org tag - default authenticated → actor_scope=organisation when an active organisation is selected (useOrganisationStore.activeOrganisationId), otherwise actor_scope=user - unauthenticated public pages → actor_scope=anonymous Reads useAuthStore (user, appRoles, isSuperAdmin) and useOrganisationStore (activeOrganisationId) — corrected vs. RFC's speculative auth-store API. - src/observability/index.ts: barrel. - src/main.ts: initSentry runs before registerPlugins so Sentry's Vue errorHandler hooks before any plugin or component initialises; installContextBinding runs after registerPlugins so pinia is up. - env.d.ts: VITE_SENTRY_DSN_FRONTEND + VITE_SENTRY_RELEASE typed. - .env.example: new file (didn't exist before) documenting all SPA env vars including the new Sentry pair. - vite.config.ts: build.sourcemap=true (RFC §3.5 — generated, uploaded to GlitchTip by deploy.sh, then stripped before nginx serves dist/). Typecheck: green. Build: green, *.map files emitted alongside *.js chunks as expected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
63 lines
2.0 KiB
TypeScript
63 lines
2.0 KiB
TypeScript
import * as Sentry from '@sentry/vue'
|
|
import type { App } from 'vue'
|
|
import type { Router } from 'vue-router'
|
|
import { scrubEvent } from './scrubber'
|
|
|
|
export interface SentryInitOptions {
|
|
app: App
|
|
router: Router
|
|
dsn: string
|
|
release: string
|
|
environment: string
|
|
}
|
|
|
|
/**
|
|
* Initialises @sentry/vue for the SPA per RFC-WS-7 §3.2-§3.9.
|
|
*
|
|
* - Empty DSN → no-op (RFC §3.3, mirrors backend).
|
|
* - Errors-only — tracesSampleRate / profilesSampleRate hard-pinned to 0
|
|
* (RFC §2 amendment B).
|
|
* - sendDefaultPii=false (RFC §3.7 / §3.8); user identity is bound
|
|
* explicitly by {@link installContextBinding} as a ULID-only object.
|
|
* - app=app initial scope tag (RFC §3.6) so GlitchTip can filter
|
|
* frontend vs backend events.
|
|
* - PII scrubbing via {@link scrubEvent} as the beforeSend hook (RFC §3.7
|
|
* frontend block).
|
|
*/
|
|
export function initSentry(options: SentryInitOptions): void {
|
|
if (options.dsn === '')
|
|
return
|
|
|
|
Sentry.init({
|
|
app: options.app,
|
|
dsn: options.dsn,
|
|
release: options.release === '' ? undefined : options.release,
|
|
environment: options.environment,
|
|
|
|
// RFC §2 amendment B — errors-only.
|
|
tracesSampleRate: 0,
|
|
profilesSampleRate: 0,
|
|
|
|
// RFC §3.7 / §3.8: never let Sentry's auto-context capture IP, locals
|
|
// from stack frames, or the User session-cookie payload.
|
|
sendDefaultPii: false,
|
|
|
|
// RFC §3.7 frontend point 5: console-logging integration off in prod
|
|
// (info / debug breadcrumbs may include user data through formatted
|
|
// arguments). Keep the BrowserApiErrors/Vue/global integrations.
|
|
integrations: defaults => defaults.filter(i => i.name !== 'Console'),
|
|
|
|
// RFC §3.7 frontend block — scrubber applied on every event.
|
|
beforeSend: scrubEvent,
|
|
|
|
// RFC §3.6 — route-scope baseline tag. AuthScopeContextListener-style
|
|
// binding of actor_scope / user_id / actor_type happens per route
|
|
// navigation in {@link installContextBinding}.
|
|
initialScope: {
|
|
tags: {
|
|
app: 'app',
|
|
},
|
|
},
|
|
})
|
|
}
|