diff --git a/lobby_worker/index.html b/lobby_worker/index.html
index d3011a7..65aba5a 100644
--- a/lobby_worker/index.html
+++ b/lobby_worker/index.html
@@ -26,6 +26,8 @@
+
+
PONGO
diff --git a/lobby_worker/sentry-config.js b/lobby_worker/sentry-config.js
new file mode 100644
index 0000000..b617b5f
--- /dev/null
+++ b/lobby_worker/sentry-config.js
@@ -0,0 +1,6 @@
+window.TRE_STATIC_SENTRY_CONFIG = {
+ app: "pongo",
+ dsn: "",
+ environment: "production",
+ release: "",
+};
diff --git a/lobby_worker/sentry.js b/lobby_worker/sentry.js
new file mode 100644
index 0000000..1ce4305
--- /dev/null
+++ b/lobby_worker/sentry.js
@@ -0,0 +1,37 @@
+(() => {
+ const config = window.TRE_STATIC_SENTRY_CONFIG;
+ if (!config?.dsn) return;
+
+ const script = document.createElement("script");
+ script.src = "https://browser.sentry-cdn.com/10.57.0/bundle.min.js";
+ script.crossOrigin = "anonymous";
+ script.onload = () => {
+ if (!window.Sentry) return;
+ window.Sentry.init({
+ dsn: config.dsn,
+ environment: config.environment || "production",
+ release: config.release,
+ sendDefaultPii: false,
+ tracesSampleRate: config.environment === "production" ? 0.01 : 0,
+ replaysSessionSampleRate: 0,
+ replaysOnErrorSampleRate: 0,
+ beforeSend(event) {
+ if (event.request) {
+ delete event.request.cookies;
+ delete event.request.data;
+ if (event.request.headers) {
+ for (const key of Object.keys(event.request.headers)) {
+ const lowerKey = key.toLowerCase();
+ if (lowerKey.includes("authorization") || lowerKey.includes("cookie")) {
+ event.request.headers[key] = "[Filtered]";
+ }
+ }
+ }
+ }
+ event.tags = { ...event.tags, app: config.app || "pongo" };
+ return event;
+ },
+ });
+ };
+ document.head.appendChild(script);
+})();
diff --git a/package-lock.json b/package-lock.json
index 3d9952d..b2b7685 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,6 +7,9 @@
"": {
"name": "pongo",
"version": "0.1.0",
+ "dependencies": {
+ "@sentry/cloudflare": "^10.57.0"
+ },
"devDependencies": {
"@playwright/test": "^1.50.0",
"husky": "^9.1.7",
@@ -17,6 +20,15 @@
"node": ">=20.0.0"
}
},
+ "node_modules/@opentelemetry/api": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz",
+ "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/@playwright/test": {
"version": "1.60.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
@@ -33,6 +45,36 @@
"node": ">=18"
}
},
+ "node_modules/@sentry/cloudflare": {
+ "version": "10.57.0",
+ "resolved": "https://registry.npmjs.org/@sentry/cloudflare/-/cloudflare-10.57.0.tgz",
+ "integrity": "sha512-LDKk177la/uG92ILNozcwQR7+4/pizPu01Y7M7l9J7o1uwAi9WjElafh/HU2Jqw+vST1BKknw/tQB1pnsnkDlA==",
+ "license": "MIT",
+ "dependencies": {
+ "@opentelemetry/api": "^1.9.1",
+ "@sentry/core": "10.57.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@cloudflare/workers-types": "^4.x"
+ },
+ "peerDependenciesMeta": {
+ "@cloudflare/workers-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sentry/core": {
+ "version": "10.57.0",
+ "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.57.0.tgz",
+ "integrity": "sha512-kntItTA2kiT0YpL7encXaF6mkdZMB+y48lwj8w1wkfBpfJAC7sifdgrzLQZqmsqVNE3crg9VfufaAGA+78uFMg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/ansi-escapes": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz",
diff --git a/package.json b/package.json
index 5af0a0f..ec2a2e4 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"test:all": "prettier --check . && cargo fmt --check && cargo check --workspace && cargo clippy --workspace -- -D warnings && cargo test --workspace && npm run check:diagrams",
"format": "prettier --write .",
"build:client": "cd client_wasm && wasm-pack build --target web --out-dir ../worker/pkg/client_wasm",
- "build:server": "cd lobby_worker && wasm-pack build --target web --out-dir ../worker/pkg && cp index.html style.css *.js manifest.webmanifest ../worker/pkg/ && cp -r icons ../worker/pkg/",
+ "build:server": "node scripts/write-sentry-config.mjs && cd lobby_worker && wasm-pack build --target web --out-dir ../worker/pkg && cp index.html style.css *.js manifest.webmanifest ../worker/pkg/ && cp -r icons ../worker/pkg/",
"build": "npm run build:server && npm run build:client && node scripts/stamp-sw.mjs",
"dev": "npx wrangler dev --assets worker/pkg",
"deploy": "npx wrangler deploy",
@@ -32,5 +32,8 @@
"lint-staged": {
"*.rs": "rustfmt",
"**/*": "prettier --write --ignore-unknown"
+ },
+ "dependencies": {
+ "@sentry/cloudflare": "^10.57.0"
}
}
diff --git a/scripts/write-sentry-config.mjs b/scripts/write-sentry-config.mjs
new file mode 100644
index 0000000..f90d722
--- /dev/null
+++ b/scripts/write-sentry-config.mjs
@@ -0,0 +1,24 @@
+import { writeFileSync } from "node:fs";
+import { execSync } from "node:child_process";
+
+function fallbackRelease() {
+ try {
+ return execSync("git rev-parse --short HEAD", { encoding: "utf8" }).trim();
+ } catch {
+ return String(Date.now());
+ }
+}
+
+writeFileSync(
+ "lobby_worker/sentry-config.js",
+ `window.TRE_STATIC_SENTRY_CONFIG = ${JSON.stringify(
+ {
+ app: "pongo",
+ dsn: process.env.SENTRY_DSN || "",
+ environment: process.env.SENTRY_ENVIRONMENT || "production",
+ release: process.env.SENTRY_RELEASE || process.env.GITHUB_SHA || fallbackRelease(),
+ },
+ null,
+ 2
+ )};\n`
+);
diff --git a/worker/index.js b/worker/index.js
index 74d3473..2f0a64d 100644
--- a/worker/index.js
+++ b/worker/index.js
@@ -1,3 +1,4 @@
+import * as Sentry from "@sentry/cloudflare";
import init, { fetch as wasmFetch, MatchDO as WasmMatchDO } from "./pkg/lobby_worker.js";
import wasmUrl from "./pkg/lobby_worker_bg.wasm";
@@ -7,7 +8,34 @@ async function ensureInit() {
await initPromise;
}
-export default {
+const sentryOptions = (env) => {
+ if (!env.SENTRY_DSN) return undefined;
+ return {
+ dsn: env.SENTRY_DSN,
+ environment: env.SENTRY_ENVIRONMENT || "production",
+ release: env.SENTRY_RELEASE,
+ sendDefaultPii: false,
+ tracesSampleRate: env.SENTRY_ENVIRONMENT === "production" ? 0.01 : 0,
+ enableRpcTracePropagation: true,
+ beforeSend(event) {
+ if (event.request) {
+ delete event.request.cookies;
+ delete event.request.data;
+ if (event.request.headers) {
+ for (const key of Object.keys(event.request.headers)) {
+ const lowerKey = key.toLowerCase();
+ if (lowerKey.includes("authorization") || lowerKey.includes("cookie")) {
+ event.request.headers[key] = "[Filtered]";
+ }
+ }
+ }
+ }
+ return event;
+ },
+ };
+};
+
+const handler = {
async fetch(req, env, ctx) {
try {
await ensureInit();
@@ -23,6 +51,8 @@ export default {
},
};
+export default Sentry.withSentry(sentryOptions, handler);
+
// The generated WasmMatchDO constructor is synchronous and needs the wasm
// module ready. The DO runtime can construct it before the top-level init()
// resolves, so defer construction behind ensureInit().
@@ -47,4 +77,13 @@ class MatchDOWrapper {
}
}
-export { MatchDOWrapper as MatchDO };
+export const MatchDO = Sentry.instrumentDurableObjectWithSentry(
+ (env) =>
+ sentryOptions(env) || {
+ environment: env.SENTRY_ENVIRONMENT || "production",
+ sendDefaultPii: false,
+ tracesSampleRate: 0,
+ enableRpcTracePropagation: true,
+ },
+ MatchDOWrapper
+);
diff --git a/wrangler.toml b/wrangler.toml
index cb6017f..be78741 100644
--- a/wrangler.toml
+++ b/wrangler.toml
@@ -1,6 +1,11 @@
name = "pongo"
main = "worker/index.js"
compatibility_date = "2024-01-01"
+compatibility_flags = ["nodejs_als"]
+
+[vars]
+SENTRY_ENVIRONMENT = "production"
+SENTRY_PROJECT = "pongo"
[build]
# Mirror `npm run build` so a standalone `wrangler deploy` produces a complete worker/pkg.
@@ -10,6 +15,7 @@ command = "npm run build"
directory = "./worker/pkg"
binding = "ASSETS"
html_handling = "auto-trailing-slash"
+not_found_handling = "404-page"
[durable_objects]
bindings = [{ name = "MATCH", class_name = "MatchDO" }]