mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-01 16:12:02 +00:00
Admins had logs and nothing else: client errors only reached the browser console. An admin can now set a Sentry-protocol address (DSN) for Bugsink, GlitchTip or Sentry under System > Monitoring, or with `ERROR_REPORTING_DSN`, `ERROR_REPORTING_DSN_FILE`, `ERROR_REPORTING_SAMPLE_RATE` and `ERROR_REPORTING_ENVIRONMENT`. Bulwark Lite reads the same from `errorReporting` in policy.json. Everything stays off while no address is set. - browsers report errors from the error boundaries, window.onerror, unhandled rejections and failed JMAP requests (method and error type only), with route and JMAP method breadcrumbs and the chunks' debug IDs - the server reports what Next hands to onRequestError and what routes log with logger.error - every report is scrubbed first: quoted text, addresses, URLs, file names, tokens and the rest after the first line are removed, and the server scrubs what browsers post to /api/errors again - reports are deduplicated and rate-limited per session and process - errors are counted per day for "errors today" on the admin page - startup messages and audit entries go through the logger, so `LOG_FORMAT=json` covers them
23 lines
1.2 KiB
TypeScript
23 lines
1.2 KiB
TypeScript
import type { ClientSettings } from './client';
|
|
import { parseDsn } from './envelope';
|
|
|
|
/**
|
|
* Bulwark Lite has no server: error reporting is set in policy.json and
|
|
* browsers send straight to the DSN. There are no per-day counts.
|
|
*
|
|
* "errorReporting": { "dsn": "https://<key>@host/<project>", "sampleRate": 1, "environment": "production" }
|
|
*
|
|
* The DSN's host must also be in the CSP's connect-src of the Lite host.
|
|
* https://bulwarkmail.org/docs/lite/configuration
|
|
*/
|
|
export function liteErrorReporting(policy: unknown): ClientSettings | null {
|
|
const raw = policy && typeof policy === 'object' ? (policy as Record<string, unknown>).errorReporting : undefined;
|
|
if (!raw || typeof raw !== 'object') return null;
|
|
const input = raw as Record<string, unknown>;
|
|
const dsn = typeof input.dsn === 'string' && parseDsn(input.dsn) ? input.dsn.trim() : '';
|
|
if (!dsn) return null;
|
|
const rate = typeof input.sampleRate === 'number' && Number.isFinite(input.sampleRate) ? Math.min(1, Math.max(0, input.sampleRate)) : 1;
|
|
const environment = typeof input.environment === 'string' && /^[\w.-]{1,64}$/.test(input.environment) ? input.environment : 'production';
|
|
return { dsn, sampleRate: rate, environment, collect: false };
|
|
}
|