mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 08:15:41 +00:00
A review found that an open page kept sending to a DSN after consent was taken back, that a state file or config file could name where telemetry goes, and that `BULWARK_TELEMETRY=off` did nothing on the desktop build. A due heartbeat now waits a random 30 s to 10 min, a refusal from the collector isn't retried, crash files go at sign-out and account switch, and server builds parse stacks as they did. Existing assertions changed in the desktop telemetry tests: the catch-up wait is now a range, the endpoint comes from the shell, and the platform is `desktop` against the collector's patched branch.
61 lines
2.5 KiB
TypeScript
61 lines
2.5 KiB
TypeScript
import { configManager } from '@/lib/admin/config-manager';
|
|
import { parseDsn } from './envelope';
|
|
import { metricsEnabled } from '@/lib/metrics';
|
|
import { effectiveErrorsConsent } from '@/lib/telemetry/state';
|
|
import { IS_DESKTOP_BUILD } from '@/lib/desktop/flag';
|
|
import type { ClientSettings } from './client';
|
|
|
|
/**
|
|
* The error-reporting settings as the server resolves them: admin config,
|
|
* then ERROR_REPORTING_DSN(_FILE) / ERROR_REPORTING_SAMPLE_RATE /
|
|
* ERROR_REPORTING_ENVIRONMENT, then off. Bulwark Lite reads the same three
|
|
* from policy.json (lib/error-reporting/lite.ts).
|
|
*/
|
|
|
|
export interface ErrorReportingSettings {
|
|
/** A valid DSN, or '' when none is set or the value doesn't parse. */
|
|
dsn: string;
|
|
sampleRate: number;
|
|
environment: string;
|
|
}
|
|
|
|
export function parseSampleRate(value: unknown): number {
|
|
const n = typeof value === 'number' ? value : typeof value === 'string' && value.trim() !== '' ? Number(value) : NaN;
|
|
if (!Number.isFinite(n)) return 1;
|
|
return Math.min(1, Math.max(0, n));
|
|
}
|
|
|
|
export function parseEnvironment(value: unknown): string {
|
|
const text = typeof value === 'string' ? value.trim() : '';
|
|
return /^[\w.-]{1,64}$/.test(text) ? text : 'production';
|
|
}
|
|
|
|
export function errorReportingSettings(): ErrorReportingSettings {
|
|
const raw = configManager.get<string>('errorReportingDsn', '');
|
|
return {
|
|
// A desktop user has no admin, so a config file can't name somewhere to send errors.
|
|
dsn: !IS_DESKTOP_BUILD && parseDsn(raw) ? raw.trim() : '',
|
|
sampleRate: parseSampleRate(configManager.get<string>('errorReportingSampleRate', '1')),
|
|
environment: parseEnvironment(configManager.get<string>('errorReportingEnvironment', 'production')),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Whether browsers should post their errors to /api/errors: when there is a
|
|
* DSN to report to, a metrics endpoint that counts them, or consent to send
|
|
* error counts with the telemetry heartbeat.
|
|
*/
|
|
export async function shouldCollectErrors(): Promise<boolean> {
|
|
await configManager.ensureLoaded();
|
|
// The desktop user's own yes, and nothing else.
|
|
if (IS_DESKTOP_BUILD) return (await effectiveErrorsConsent()).consent !== 'off';
|
|
if (errorReportingSettings().dsn || metricsEnabled()) return true;
|
|
return (await effectiveErrorsConsent()).consent !== 'off';
|
|
}
|
|
|
|
/** What /api/config hands the browser. */
|
|
export async function browserErrorSettings(): Promise<ClientSettings> {
|
|
const settings = errorReportingSettings();
|
|
return { ...settings, collect: await shouldCollectErrors() };
|
|
}
|