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
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
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.
This commit is contained in:
@@ -1070,6 +1070,17 @@ browser), so an open in-app session reflects it at once.
|
||||
it fills IndexedDB — and is surfaced on the **Scrabble — Users** dashboard. Lossy by
|
||||
design (a dropped beacon just retries its batch on the next flush); it is telemetry, never
|
||||
a game input.
|
||||
- **Unsupported-engine screens (client-reported):** the ES5 boot guard in `index.html` (§13)
|
||||
shows a full-screen "your device's OS or browser can't run the app" screen — instead of a white
|
||||
screen — when the engine lacks an unpolyfillable essential (`BigInt`, the 64-bit FlatBuffers
|
||||
decode; or `Proxy`, Svelte 5 runes) or an uncaught error aborts boot; the effective floor is
|
||||
Chrome 67. It then fires one fire-and-forget beacon (`POST /telemetry/unsupported` —
|
||||
unauthenticated, since the client never booted, but per-IP public-limited and body-capped),
|
||||
deduped in `localStorage` by app version + reason + Chromium so a user reopening the app is one
|
||||
report. The gateway folds it into `unsupported_engine_total` (`reason` =
|
||||
no_bigint/no_proxy/boot_error/other; `chromium` = the major version reduced to a bounded range so
|
||||
a spoofed beacon cannot inflate cardinality) and logs the full user agent (not a label). Surfaced
|
||||
on the **Scrabble — Users** dashboard beside app opens.
|
||||
- **Rate-limit observability:** every limiter rejection increments the gateway
|
||||
counter `gateway_rate_limited_total` (`class` = user/public/email/admin — aggregate
|
||||
only, honouring the no-per-user-label discipline above) and logs one **Debug** line;
|
||||
|
||||
Reference in New Issue
Block a user