Chromium's crash reports reach the server, which logs each and files it in Sentry as a warning, and the page's own errors name the room and seat and leave extensions' failures out
A tab that crashes takes the page's error reporting down with it, so the page never says so. Every page now names a default Reporting API endpoint, /api/reports, where Chromium sends a crash report after the fact; the server logs the room, the reason when one is given, and the browser, and files a warning that rings no bell. Other report types are ignored, and an address may send ten an hour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9a1c98ca55
commit
956c908867
@@ -113,6 +113,46 @@ const roomsPerAddress = new SlidingLimit(12, 60 * 60 * 1000);
|
||||
/** Screenshots for the desk: a few per address per hour, like reports. */
|
||||
const imagesPerAddress = new SlidingLimit(6, 60 * 60 * 1000);
|
||||
const IMAGE_MAX_BYTES = 2_500_000;
|
||||
/** Crash reports from browsers: a crashing tab reloads and crashes again, so a few per address per hour say all there is. */
|
||||
const crashReportsPerAddress = new SlidingLimit(10, 60 * 60 * 1000);
|
||||
const CRASH_REPORT_MAX_BYTES = 64 * 1024;
|
||||
|
||||
/** Chromium sends the Reporting API's crash reports here, after the fact,
|
||||
* from the browser itself: a tab that crashes takes the page's own error
|
||||
* reporting down with it. Each crash is logged and filed in Sentry as a
|
||||
* warning, which rings no bell. Other report types are ignored. */
|
||||
function receiveBrowserReports(req: IncomingMessage, res: ServerResponse): void {
|
||||
const address = clientAddress(req.headers, req.socket.remoteAddress);
|
||||
let raw = "";
|
||||
let tooBig = false;
|
||||
req.setEncoding("utf8");
|
||||
req.on("data", (chunk: string) => {
|
||||
if (tooBig) return;
|
||||
raw += chunk;
|
||||
if (raw.length > CRASH_REPORT_MAX_BYTES) tooBig = true;
|
||||
});
|
||||
req.on("end", () => {
|
||||
res.writeHead(204).end();
|
||||
if (tooBig) return;
|
||||
let reports: unknown;
|
||||
try { reports = JSON.parse(raw); } catch { return; }
|
||||
if (!Array.isArray(reports)) return;
|
||||
for (const r of reports as { type?: unknown; url?: unknown; user_agent?: unknown; body?: Record<string, unknown> }[]) {
|
||||
if (r?.type !== "crash" || !crashReportsPerAddress.allow(address)) continue;
|
||||
const url = String(r.url ?? "").slice(0, 200);
|
||||
const room = url.match(/\/join\/([A-Za-z0-9]{4})/)?.[1]?.toUpperCase() ?? "none";
|
||||
const reason = typeof r.body?.reason === "string" ? r.body.reason.slice(0, 40) : "unknown";
|
||||
const userAgent = String(r.user_agent ?? "").slice(0, 300);
|
||||
console.warn(`browser tab crashed: room ${room}, reason ${reason}, ${userAgent}`);
|
||||
Sentry.captureMessage("A player's browser tab crashed", {
|
||||
level: "warning",
|
||||
fingerprint: ["browser-crash", reason],
|
||||
tags: { room, reason },
|
||||
extra: { url, userAgent, body: r.body ?? null },
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** A picture for a report, POSTed right after the report is filed: one
|
||||
* per report, within the hour, PNG/JPEG/WebP by its own first bytes, kept
|
||||
@@ -372,6 +412,7 @@ const httpServer = createServer((req, res) => {
|
||||
if (req.method === "POST") {
|
||||
const picture = (req.url ?? "").split("?")[0]!.match(/^\/api\/feedback-image\/([0-9a-f]{8})$/);
|
||||
if (picture) { receiveFeedbackImage(req, res, picture[1]!); return; }
|
||||
if ((req.url ?? "").split("?")[0] === "/api/reports") { receiveBrowserReports(req, res); return; }
|
||||
res.writeHead(404).end();
|
||||
return;
|
||||
}
|
||||
@@ -380,6 +421,8 @@ const httpServer = createServer((req, res) => {
|
||||
return;
|
||||
}
|
||||
res.setHeader("x-content-type-options", "nosniff");
|
||||
// Where Chromium sends a crash report about a tab that showed this page.
|
||||
res.setHeader("reporting-endpoints", `default="${requestBase(req)}/api/reports"`);
|
||||
if (!staticRoot) {
|
||||
res.writeHead(404).end("client not built");
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user