Skip to content

feat(analytics): restore Google Analytics and overseer frontend tracking - #331

Open
ety001 wants to merge 6 commits into
nextfrom
feat/ga-overseer-tracking
Open

feat(analytics): restore Google Analytics and overseer frontend tracking#331
ety001 wants to merge 6 commits into
nextfrom
feat/ga-overseer-tracking

Conversation

@ety001

@ety001 ety001 commented Aug 30, 2026

Copy link
Copy Markdown
Member

Summary

  • Inject gtag at runtime from GOOGLE_ANALYTICS_ID / SDC_GOOGLE_ANALYTICS_ID (legacy server-html.jsx / JsPlugins.js), including SPA pageviews and CSP allowlists for GTM/GA.
  • Restore overseer frontend tracking (route, user_action, user_login) with the same measurement/tags/fields as wallet-legacy, relayed via /api/analytics/overseer so browsers do not call api.steemit.com directly.

Test plan

  • Unset GOOGLE_ANALYTICS_ID — no gtag script, pages still load under CSP
  • Set GOOGLE_ANALYTICS_ID to a valid G- / UA- id — script loads, SPA navigations fire pageviews
  • Navigate wallet pages and confirm overseer route events (tag + is_login + user_index permlink)
  • Complete transfer / power up / power down / delegate / witness vote / change password — user_action payloads match legacy
  • Successful login emits user_login without delaying the response
  • Against an RPC without overseer, wallet actions still succeed (one warn, then silence)

ety001 added 4 commits August 30, 2026 22:50
Match wallet-legacy gtag injection and overseer.collect route/user_action/login events, relaying overseer through the server so browsers do not hit api.steemit.com.
…s CSRF

The csrf_token cookie was only issued by /api/auth/challenge (login flow),
so anonymous visitors' overseer route events were silently rejected 403.
The proxy now mints a rolling stateless HMAC token (Web Crypto, byte-identical
to the Node generator) on every document response; shared CSRF_SECRET keeps
it valid across instances and self-heals after secret rotation.
Match wallet-legacy server-html.jsx (and condenser PR #4010): plain script
tags rendered by the server layout so gtag loads at parse time with no
hydration dependency. Both scripts carry the per-request CSP nonce.
SPA pageviews stay in a small client component using next/navigation's
usePathname — next-intl's throws outside NextIntlClientProvider during SSR,
and the resulting error recovery silently swallowed the sibling scripts.
@ety001

ety001 commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

Post-review audit fixes (added to this PR)

Full audit of the original commits found and fixed four issues:

1. Anonymous tracking was silently dropped (CSRF cookie never issued)

The csrf_token cookie was only issued by /api/auth/challenge (login flow), so every anonymous visitor's overseer route event was rejected 403 and swallowed. The proxy middleware now mints a rolling stateless HMAC token on every document response (Web Crypto in the edge runtime, byte-identical to the Node-side generator — locked by a unit test). Shared CSRF_SECRET keeps it valid across instances and self-heals after secret rotation. Verified end-to-end against a production build: anonymous POST → {success:true}.

2. gtag injected via next/script afterInteractive (hydration-dependent)

Replaced with SSR-emitted script tags (parse-time load, legacy server-html.jsx parity — same conclusion as condenser PR #4010). Both scripts carry the per-request CSP nonce. SPA pageviews live in a small client component using next/navigation's usePathname: next-intl's throws outside NextIntlClientProvider during SSR, and the resulting error recovery silently swallowed the sibling server-rendered scripts.

3. Lint gate failure (no-sync-scripts in the GA test mock) — fixed earlier in d6f59f06

Known accepted items

  • collectOverseer still shares withFailover's RPC index rotation (only observable with multiple STEEM_RPC_URL entries and a dead overseer upstream; tracked for a follow-up before multi-RPC deploys).
  • No custom not-found page → the not_found route tag never fires (Next's default 404 renders outside the layout).
  • Whale tagging intentionally fixes a legacy bug (case mismatch made whale always false); dashboards will start seeing whale=true events.

Verified: tsc / eslint / 640 unit tests / next build / end-to-end on a production server (cookie + anonymous event + SSR gtag scripts with nonce).

Pages were already dynamic as a side effect of the CSP-nonce headers() call
in the layout; this makes the invariant explicit (condenser #4012 parity) so
a future refactor of the nonce mechanism can never silently turn routes
static and freeze or drop the runtime GOOGLE_ANALYTICS_ID.
collectOverseer reused withFailover, so a dead overseer upstream with
multiple STEEM_RPC_URL entries would retry every URL per event (a warn per
URL per event) and rotate the shared currentUrlIndex, moving real chain
traffic to a different RPC. Analytics now makes one attempt against the
current RPC and gives up; the warn-once flag already bounds logging.
@ety001

ety001 commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

Follow-up closed: collectOverseer no longer goes through withFailover (5f6c2487). It now makes exactly one attempt against the current RPC and gives up — a dead overseer upstream with multiple STEEM_RPC_URL entries can no longer rotate the shared failover index (moving real chain traffic) or log a warning per URL per event. Two new tests pin the contract (single attempt + no URL rotation; warn once per process).

With this, every actionable finding from the audit is fixed; remaining items are the accepted trade-offs listed above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant