|
1 | | -// Lightweight Sentry integration (CDN-based, no-op if DSN unset) |
2 | | -// - Loads Sentry browser bundle at runtime if a DSN is provided |
3 | | -// - Exposes helpers to set tags, add breadcrumbs, and send a final session event |
4 | | - |
5 | | -let sentryInited = false |
6 | | -let sentryInitPromise = null |
7 | | - |
8 | | -function loadScript(src) { |
9 | | - return new Promise((resolve, reject) => { |
10 | | - const script = document.createElement('script') |
11 | | - script.src = src |
12 | | - script.crossOrigin = 'anonymous' |
13 | | - script.async = true |
14 | | - script.onload = () => resolve() |
15 | | - script.onerror = () => reject(new Error(`Failed to load ${src}`)) |
16 | | - document.head.appendChild(script) |
17 | | - }) |
18 | | -} |
19 | | - |
20 | | -// Best-effort CDN load for Sentry + Replay. If already present, skips. |
21 | | -async function ensureSentryBundle() { |
22 | | - if (window.Sentry) return |
23 | | - // Use a stable recent v7 bundle that includes tracing + replay |
24 | | - // If you want to pin a newer version later, update this URL. |
25 | | - const url = 'https://browser.sentry-cdn.com/7.114.0/bundle.tracing.replay.min.js' |
26 | | - await loadScript(url) |
27 | | -} |
| 1 | +import * as Sentry from '@sentry/react' |
28 | 2 |
|
29 | 3 | const SENTRY_DSN = 'https://[email protected]/4510604761825280' |
30 | 4 |
|
31 | | -/** Initialize Sentry */ |
32 | | -export async function initSentry(options = {}) { |
33 | | - if (sentryInited || sentryInitPromise) return sentryInitPromise |
34 | | - |
35 | | - sentryInitPromise = (async () => { |
36 | | - const dsn = SENTRY_DSN |
37 | | - try { |
38 | | - await ensureSentryBundle() |
39 | | - if (!window.Sentry) return false |
40 | | - |
41 | | - const release = import.meta.env.VITE_PUBLIC_GIT_SHA || 'dev' |
42 | | - const environment = import.meta.env.MODE || 'development' |
43 | | - |
44 | | - // Default to replay only on error to keep costs down; can be tuned. |
45 | | - window.Sentry.init({ |
46 | | - dsn, |
47 | | - release, |
48 | | - environment, |
49 | | - // Breadcrumbs already capture console by default; keep it. |
50 | | - // Limit traces by default; you can increase later if desired. |
51 | | - tracesSampleRate: options.tracesSampleRate ?? 0.0, |
52 | | - replaysSessionSampleRate: options.replaysSessionSampleRate ?? 0.0, |
53 | | - replaysOnErrorSampleRate: options.replaysOnErrorSampleRate ?? 1.0, |
54 | | - maxBreadcrumbs: options.maxBreadcrumbs ?? 100, |
55 | | - integrations: [new window.Sentry.Replay({ |
56 | | - maskAllText: true, |
57 | | - blockAllMedia: true, |
58 | | - })], |
59 | | - }) |
60 | | - |
61 | | - sentryInited = true |
62 | | - return true |
63 | | - } catch (e) { |
64 | | - // Swallow init errors to avoid breaking the app |
65 | | - // eslint-disable-next-line no-console |
66 | | - console.warn('[Sentry] init failed:', e) |
67 | | - sentryInited = false |
68 | | - return false |
69 | | - } |
70 | | - })() |
71 | | - |
72 | | - return sentryInitPromise |
73 | | -} |
74 | | - |
75 | | -// Safe no-op wrappers if Sentry isn’t available |
76 | | -function withSentry(cb) { |
77 | | - if (!sentryInited || !window.Sentry) return |
78 | | - try { cb(window.Sentry) } catch { /* no-op */ } |
| 5 | +export function initSentry() { |
| 6 | + Sentry.init({ |
| 7 | + dsn: SENTRY_DSN, |
| 8 | + sendDefaultPii: true, |
| 9 | + }) |
79 | 10 | } |
80 | 11 |
|
81 | 12 | export function setTags(tags = {}) { |
82 | | - withSentry((S) => S.setTags(tags)) |
| 13 | + Sentry.setTags(tags) |
83 | 14 | } |
84 | 15 |
|
85 | 16 | export function setTag(key, value) { |
86 | | - withSentry((S) => S.setTag(key, value)) |
| 17 | + Sentry.setTag(key, value) |
87 | 18 | } |
88 | 19 |
|
89 | 20 | export function setContext(key, context) { |
90 | | - withSentry((S) => S.setContext(key, context)) |
| 21 | + Sentry.setContext(key, context) |
91 | 22 | } |
92 | 23 |
|
93 | 24 | export function addBreadcrumb({ category, message, level = 'info', data }) { |
94 | | - withSentry((S) => S.addBreadcrumb({ category, message, level, data })) |
| 25 | + Sentry.addBreadcrumb({ category, message, level, data }) |
95 | 26 | } |
96 | 27 |
|
97 | | -/** |
98 | | - * Send a compact session summary event with pass/fail and optional console tail. |
99 | | - * To keep payloads small, logs are placed in `extra.console_tail` and truncated. |
100 | | - */ |
101 | 28 | export function captureSessionSummary({ |
102 | 29 | sessionId, |
103 | | - result, // 'pass' | 'fail' | 'abort' |
| 30 | + result, |
104 | 31 | errorCode, |
105 | 32 | step, |
106 | 33 | meta = {}, |
107 | | - consoleTail = [], // Array<{ time, level, message }> |
| 34 | + consoleTail = [], |
108 | 35 | }) { |
109 | | - withSentry((S) => { |
110 | | - const level = result === 'pass' ? 'info' : 'error' |
111 | | - |
112 | | - // Attach tags for easier filtering |
113 | | - S.setTags({ |
114 | | - session_id: sessionId, |
115 | | - result, |
116 | | - error_code: typeof errorCode === 'number' ? String(errorCode) : (errorCode || 'none'), |
117 | | - last_step: typeof step === 'number' ? String(step) : (step || 'unknown'), |
118 | | - }) |
| 36 | + const level = result === 'pass' ? 'info' : 'error' |
119 | 37 |
|
120 | | - // Try to attach full logs if the SDK supports attachments (v7+ feature; guarded) |
121 | | - let usedAttachment = false |
122 | | - try { |
123 | | - const hasAddAttachment = !!S.addAttachment |
124 | | - if (hasAddAttachment && consoleTail && consoleTail.length) { |
125 | | - const blob = new Blob([JSON.stringify(consoleTail)], { type: 'application/json' }) |
126 | | - // Some SDKs attach to current scope and include on next captured event |
127 | | - S.addAttachment({ filename: 'console_tail.json', data: blob, contentType: 'application/json' }) |
128 | | - usedAttachment = true |
129 | | - } |
130 | | - } catch { /* ignore */ } |
| 38 | + Sentry.setTags({ |
| 39 | + session_id: sessionId, |
| 40 | + result, |
| 41 | + error_code: typeof errorCode === 'number' ? String(errorCode) : (errorCode || 'none'), |
| 42 | + last_step: typeof step === 'number' ? String(step) : (step || 'unknown'), |
| 43 | + }) |
131 | 44 |
|
132 | | - // Always include a compact tail in extras as a fallback |
133 | | - const safeTail = (consoleTail || []).slice(-200) |
134 | | - S.captureMessage('flash_session', { |
135 | | - level, |
136 | | - tags: undefined, // tags set via setTags above |
137 | | - extra: { |
138 | | - usedAttachment, |
139 | | - meta, |
140 | | - console_tail: safeTail, |
141 | | - }, |
142 | | - }) |
| 45 | + Sentry.captureMessage('flash_session', { |
| 46 | + level, |
| 47 | + extra: { |
| 48 | + meta, |
| 49 | + console_tail: consoleTail.slice(-200), |
| 50 | + }, |
143 | 51 | }) |
144 | 52 | } |
145 | | - |
0 commit comments