fix: gate captureException on user consent and fix HEIC PII scrubbing

captureException now checks isRequestOptedIn before forwarding errors
to Sentry, closing a gap where server errors leaked to an external
service even when no user had consented. The PII scrubbing regex is
also fixed: he[ic]f? failed to match .heic due to word-boundary
behavior and is replaced with hei[cf]? which correctly covers .heic,
.heif, and .hei.

Adds 88 new analytics tests across unit, integration, and e2e layers
proving PostHog/Sentry are never invoked when analytics is disabled or
users have not consented, plus full 7-day reminder lifecycle coverage.
This commit is contained in:
SnapOtter
2026-04-29 23:47:19 +08:00
parent 53343a0836
commit fc8b549d78
17 changed files with 1446 additions and 5 deletions
+1 -1
View File
@@ -90,7 +90,7 @@ app.setErrorHandler((error: Error & { statusCode?: number }, request, reply) =>
{ err: error, url: request.url, method: request.method },
"Unhandled request error",
);
captureException(error);
captureException(error, request);
const isProduction = process.env.NODE_ENV === "production";
reply.status(statusCode).send({
error: statusCode >= 500 ? "Internal server error" : error.message,
+5 -3
View File
@@ -6,7 +6,7 @@ import { db, schema } from "../db/index.js";
import { getAuthUser } from "../plugins/auth.js";
const FILE_EXT_PATTERN =
/\.(jpe?g|png|pdf|webp|gif|tiff?|bmp|svg|he[ic]f?|avif|raw|cr2|nef|arw|dng|psd|tga|exr|hdr)\b/gi;
/\.(jpe?g|png|pdf|webp|gif|tiff?|bmp|svg|hei[cf]?|avif|raw|cr2|nef|arw|dng|psd|tga|exr|hdr)\b/gi;
const FILE_PATH_PATTERN = /\/(tmp\/workspace|data\/files|data\/ai)\//g;
let posthogClient: PostHog | null = null;
@@ -72,8 +72,10 @@ export async function initAnalytics(): Promise<void> {
}
}
export function captureException(error: unknown): void {
sentryModule?.captureException(error);
export function captureException(error: unknown, request?: FastifyRequest): void {
if (!sentryModule) return;
if (request && !isRequestOptedIn(request)) return;
sentryModule.captureException(error);
}
export async function shutdownAnalytics(): Promise<void> {