feat(gateway): unsupported-engine telemetry beacon + Grafana counter #177

Merged
developer merged 1 commits from feature/unsupported-engine-telemetry into development 2026-07-04 21:16:23 +00:00
Owner

What

The follow-up to the old-Android-WebView support (#176): tell the owner how many real clients hit the unsupported-engine screen, and on which engines, on the Grafana Scrabble — Users dashboard beside the existing App opens count.

  • Client (index.html boot guard): on the hard gate (no BigInt/Proxy) or the reactive net (uncaught boot error), fire one fire-and-forget beacon POST /telemetry/unsupportednavigator.sendBeacon with a fetch fallback, deduped in localStorage by app version + reason + Chromium, so a user reopening the app is one report, not ten.
  • Gateway: a new unauthenticated POST /telemetry/unsupported handler (the client never booted → no session), per-IP public-limited + body-capped (mirrors the export-download route). Folds the beacon into the OTel counter unsupported_engine_total{reason, chromium} with reason allow-listed and the Chromium major reduced to a bounded range (normalizeUnsupported) so a spoofed beacon cannot inflate metric cardinality; the full user agent is logged, not labelled.
  • Caddy: /telemetry/* added to the @gateway matcher (else it falls to the landing catch-all — the lesson from /dict).
  • Grafana: two panels on Scrabble — Users (by reason, by Chromium major).
  • Docs: ARCHITECTURE §11.

Design (as agreed)

Dedup client-side; rate-limit + body-cap on the gateway (Caddy's stock build has no rate_limit module, so the limiter is Go-side; the global body cap already covers it); the counter lands in Prometheus → Grafana for volume, the UA in the gateway logs / Tempo for detail.

Verified

  • go build/vet/test ./gateway/... + gofmt clean. New tests: metric split, normalizeUnsupported (cardinality bounding), the handler route (204 / 405 / counter increment).
  • ui check / build / e2e (188) green — the beacon is inert on a capable engine.
  • Client beacon + dedup verified against a forced hard-gate (BigInt removed via addInitScript): exactly one POST /telemetry/unsupported, deduped on reopen, labels no_bigint / 148 / version correct.

Contour verification of the served /telemetry/unsupported route to follow after the deploy.

## What The follow-up to the old-Android-WebView support (#176): tell the owner **how many real clients hit the unsupported-engine screen, and on which engines**, on the Grafana **Scrabble — Users** dashboard beside the existing *App opens* count. - **Client** (`index.html` boot guard): on the hard gate (no `BigInt`/`Proxy`) or the reactive net (uncaught boot error), fire one fire-and-forget beacon `POST /telemetry/unsupported` — `navigator.sendBeacon` with a `fetch` fallback, **deduped in `localStorage`** by app version + reason + Chromium, so a user reopening the app is one report, not ten. - **Gateway**: a new **unauthenticated** `POST /telemetry/unsupported` handler (the client never booted → no session), **per-IP public-limited + body-capped** (mirrors the export-download route). Folds the beacon into the OTel counter `unsupported_engine_total{reason, chromium}` with **reason allow-listed** and the **Chromium major reduced to a bounded range** (`normalizeUnsupported`) so a spoofed beacon cannot inflate metric cardinality; the full user agent is **logged**, not labelled. - **Caddy**: `/telemetry/*` added to the `@gateway` matcher (else it falls to the landing catch-all — the lesson from `/dict`). - **Grafana**: two panels on Scrabble — Users (by reason, by Chromium major). - **Docs**: ARCHITECTURE §11. ## Design (as agreed) Dedup client-side; rate-limit + body-cap on the gateway (Caddy's stock build has no `rate_limit` module, so the limiter is Go-side; the global body cap already covers it); the counter lands in Prometheus → Grafana for volume, the UA in the gateway logs / Tempo for detail. ## Verified - `go build`/`vet`/`test ./gateway/...` + `gofmt` clean. New tests: metric split, `normalizeUnsupported` (cardinality bounding), the handler route (204 / 405 / counter increment). - ui `check` / `build` / `e2e` (188) green — the beacon is inert on a capable engine. - Client beacon + dedup verified against a **forced hard-gate** (BigInt removed via `addInitScript`): exactly one `POST /telemetry/unsupported`, **deduped on reopen**, labels `no_bigint` / `148` / version correct. Contour verification of the served `/telemetry/unsupported` route to follow after the deploy.
developer added 1 commit 2026-07-04 21:04:17 +00:00
feat(gateway): unsupported-engine telemetry beacon + Grafana counter
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m5s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m40s
2feb638329
The index.html boot guard now fires a fire-and-forget beacon (POST /telemetry/unsupported)
when it turns a client away on the unsupported-engine screen, so the owner can see — on the
Users dashboard beside "app opens" — how many real clients hit it and on which engines.

- Client: navigator.sendBeacon (fetch fallback) with a localStorage dedup keyed by app version
  + reason + Chromium, so a user reopening the app is one report, not ten.
- Gateway: a new unauthenticated POST /telemetry/unsupported handler (the client never booted,
  so it carries no session), per-IP public-limited and body-capped, mirroring the export-download
  route. It folds the beacon into the OTel counter unsupported_engine_total {reason =
  no_bigint/no_proxy/boot_error/other, chromium}, with reason allow-listed and the Chromium major
  reduced to a bounded range (normalizeUnsupported) so a spoofed beacon cannot inflate the metric
  cardinality; the full user agent is logged, not labelled.
- Caddy: /telemetry/* added to the @gateway matcher (else it falls to the landing catch-all).
- Grafana: two panels on the Scrabble — Users dashboard (by reason, by Chromium major).
- Docs: ARCHITECTURE.md §11.

Tests: recordUnsupportedEngine (metric split), normalizeUnsupported (cardinality bounding), and
the handler route end to end (204 / 405 / counter). go build+vet+test and gofmt clean; ui
check/build/e2e green; the client beacon + dedup verified against a forced hard-gate (BigInt
removed) — one POST, deduped on reopen, correct reason/chromium/version labels.
owner approved these changes 2026-07-04 21:15:05 +00:00
developer merged commit fcedadcb5b into development 2026-07-04 21:16:23 +00:00
developer deleted branch feature/unsupported-engine-telemetry 2026-07-04 21:16:23 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: developer/scrabble-game#177