Sentry plumbing, inert until a DSN exists

Server: errors-only @sentry/node behind SENTRY_DSN, capturing the
unhandled-protocol catch with its message type and room. Client:
errors-only @sentry/browser behind VITE_SENTRY_DSN baked at build.
Backup: Sentry Crons check-ins (in_progress/ok/error) when
/root/.wizwar-sentry-cron holds the check-in URL. No DSN, no behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
Eric Wagoner
2026-09-01 09:58:26 -04:00
co-authored by Claude Fable 5
parent 68f171215d
commit b06da1f1f1
6 changed files with 368 additions and 6 deletions
+1
View File
@@ -8,6 +8,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@sentry/node": "^10.73.0",
"@wizwar/engine": "*",
"ws": "^8.18.0"
},
+8
View File
@@ -30,6 +30,13 @@
// {type:"transferCode"|"transferClaimed"|"catchUp"|"games"|"stats"|"feedbackReceived"}
// {type:"error", message}
import * as Sentry from "@sentry/node";
// Errors only, no tracing: the box is small and the ledgers are the real
// telemetry. Without a DSN in the environment this is entirely inert.
if (process.env.SENTRY_DSN) {
Sentry.init({ dsn: process.env.SENTRY_DSN, environment: "production", tracesSampleRate: 0 });
}
import { createServer } from "node:http";
import { readFileSync, existsSync, realpathSync } from "node:fs";
import { extname, join, normalize, sep } from "node:path";
@@ -888,6 +895,7 @@ wss.on("connection", (socket) => {
// Expected failures travel as result.error values; anything thrown is a
// bug, and its message (paths, internals) is not for strangers' eyes.
console.error("unhandled protocol error:", e);
Sentry.captureException(e, { extra: { messageType: String(msg?.type ?? "?"), roomId: session.roomId } });
send(socket, { type: "error", message: "internal error" });
}
});
+1
View File
@@ -10,6 +10,7 @@
"typecheck": "svelte-check --tsconfig ./tsconfig.json"
},
"dependencies": {
"@sentry/browser": "^10.73.0",
"@wizwar/engine": "*"
},
"devDependencies": {
+7
View File
@@ -1,4 +1,11 @@
import { mount } from "svelte";
import * as Sentry from "@sentry/browser";
// Errors only — no tracing, no replay. Without a DSN baked at build time
// (VITE_SENTRY_DSN in .env.production) this is entirely inert.
if (import.meta.env.VITE_SENTRY_DSN) {
Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN, environment: "production" });
}
// /watch/<id> is the public share page — one turn, anyone may look. It
// loads its own component so the full app (and its live socket appetite)