import * as Sentry from '@sentry/nextjs'; import type { ErrorReportingLevel, ErrorReportingService, ErrorReportingUser, } from './error-reporting-service'; import type { NotificationService } from '../notifications/notification-service'; type SentryLike = typeof Sentry; export type GlitchtipErrorReportingServiceOptions = { enabled: boolean; }; // GlitchTip speaks the Sentry protocol; @sentry/nextjs can send to GlitchTip via DSN. export class GlitchtipErrorReportingService implements ErrorReportingService { constructor( private readonly options: GlitchtipErrorReportingServiceOptions, private readonly notifications?: NotificationService, private readonly sentry: SentryLike = Sentry, ) {} async captureException(error: unknown, context?: Record) { if (!this.options.enabled) return undefined; const result = this.sentry.captureException(error, context as any) as any; // Send to Gotify if it's considered critical or if we just want all exceptions there // For now, let's send all exceptions to Gotify as requested "notify me via gotify about critical error messages" // We'll treat all captureException calls as potentially critical or at least noteworthy if (this.notifications) { const errorMessage = error instanceof Error ? error.message : String(error); const contextStr = context ? `\nContext: ${JSON.stringify(context, null, 2)}` : ''; await this.notifications.notify({ title: '🔥 Critical Error Captured', message: `Error: ${errorMessage}${contextStr}`, priority: 7, }); } return result; } captureMessage(message: string, level: ErrorReportingLevel = 'error') { if (!this.options.enabled) return undefined; return this.sentry.captureMessage(message, level as any) as any; } setUser(user: ErrorReportingUser | null) { if (!this.options.enabled) return; this.sentry.setUser(user as any); } setTag(key: string, value: string) { if (!this.options.enabled) return; this.sentry.setTag(key, value); } withScope(fn: () => T, context?: Record) { if (!this.options.enabled) return fn(); return this.sentry.withScope((scope) => { if (context) { for (const [key, value] of Object.entries(context)) { scope.setExtra(key, value); } } return fn(); }); } }