Skip to content

[AAASM-3554] 🍪 (docs): Add GDPR cookie-consent gating to the Docusaurus site#178

Merged
Chisanan232 merged 5 commits into
masterfrom
v0.0.1/AAASM-3554/docusaurus_consent
Jun 22, 2026
Merged

[AAASM-3554] 🍪 (docs): Add GDPR cookie-consent gating to the Docusaurus site#178
Chisanan232 merged 5 commits into
masterfrom
v0.0.1/AAASM-3554/docusaurus_consent

Conversation

@Chisanan232

Copy link
Copy Markdown
Contributor

Target

  • Task summary:

    Make the node-sdk Docusaurus docs site GDPR-compliant by adding Google Consent Mode v2 gating around the existing GA4 analytics. Analytics is denied by default and only enabled after an explicit opt-in via a lightweight cookie-consent banner. This is a fast-follow to AAASM-3552 (which added GA4) and brings the node-sdk site in line with the python-sdk mkdocs site's opt-in behaviour.

  • Task tickets:

    • Task ID: Part of AAASM-3554 (node-sdk site — one of 4 doc sites).
    • Relative task IDs:
      • AAASM-3552 (added GA4 to this site; this PR gates it).
    • Relative PRs:
      • N/A.
  • Key point change (optional):

    • Consent default before first hit. We stop using preset-classic's gtag option and self-manage the gtag init via headTags. Docusaurus appends config.headTags after plugin-injected head tags, so a separate consent-default entry could not be guaranteed to precede the preset's gtag('config') hit. By owning the whole init we get a deterministic deny-before-hit order:
      1. consentDefaultScript — init dataLayer/gtag, gtag('consent','default', {...denied}) for analytics_storage / ad_storage / ad_user_data / ad_personalization, then restore a stored opt-in (localStorage['aa-analytics-consent'] === 'granted').
      2. gtag.js loader (async) + gtag('config', 'G-6FHQKGLDE4', {anonymize_ip:true}) — the first hit, now respecting the denied default.
    • Opt-in banner. A dependency-free vanilla-JS client module renders a fixed, non-modal, keyboard-accessible banner with Accept / Reject. Accept → gtag('consent','update',{analytics_storage:'granted'}) + persist granted; Reject → persist denied; both hide the banner. A stored choice suppresses the banner. Built with createElement + textContent (no innerHTML, CodeQL-clean).
    • No new dependency. Pure config + vanilla JS/CSS; pnpm-lock.yaml unchanged.
    • A small client module restores SPA route-change page_view tracking that was previously provided by the preset gtag plugin.

Effecting Scope

  • Action Types:
    • ✨ Adding new something
      • 🟢 No breaking change
    • 🍀 Improving something (performance, code quality, security, etc.)
  • Scopes:
    • 📚 Documentation
    • 📦 Project configurations
  • Additional description:
    Affects only the standalone website/ Docusaurus project. No SDK runtime, API, or CI behaviour changes. No new npm dependency.

Description

  • website/src/analytics/consentInit.ts — consent-default + gtag-config head script sources (Measurement ID + localStorage key shared with the banner).
  • website/src/analytics/consentBanner.ts + consentBanner.css — vanilla-JS opt-in banner client module and its styles.
  • website/src/analytics/gtagRouteTracker.ts — SPA page_view tracker client module (replaces the dropped preset plugin's route tracking).
  • website/docusaurus.config.ts — remove the preset gtag option; add headTags (consent-default → preconnect → gtag.js loader → config) and clientModules (tracker + banner).

How to verify

  1. Build the standalone site:
    cd website
    pnpm install --ignore-workspace
    pnpm build
    The build succeeds. pnpm typecheck is also clean.
  2. Ordering (built HTML). In website/build/index.html <head>, confirm the gtag('consent','default', {...denied}) + opt-in-restore script appears before the gtag/js?id=G-6FHQKGLDE4 loader and the gtag('config', ...) hit. Verified positions: consent-default 3411 < loader 3844 < config 3911.
  3. Cookie / Network behaviour (dev tools). pnpm serve, open the site in a fresh profile:
    • On first load, DevTools → Application → Cookies shows no _ga* analytics cookies, and DevTools → Network shows the GA collect request carrying the denied consent state. The banner is visible.
    • Click Reject → banner hides, localStorage['aa-analytics-consent'] = 'denied', still no analytics cookie; reload keeps the banner hidden.
    • Click Accept (clear storage first) → gtag('consent','update',{analytics_storage:'granted'}) fires, localStorage['aa-analytics-consent'] = 'granted', _ga* cookies are now set; reload keeps the banner hidden and re-applies the granted consent before the first hit.

🤖 Generated with Claude Code

Chisanan232 and others added 5 commits June 22, 2026 22:39
Define the head <script> source that initialises dataLayer/gtag, sets the
Consent-Mode v2 default to denied for analytics + ads storage before any GA
hit, and restores a stored opt-in from localStorage. Also exports the gtag.js
config snippet that fires after the default is in place.

Part of AAASM-3554 (node-sdk site).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-contained, theme-variable-driven CSS for the fixed bottom opt-in banner
and its Accept/Reject buttons, with focus-visible outlines for keyboard use.

Part of AAASM-3554 (node-sdk site).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dependency-free vanilla-JS banner built with createElement + textContent (no
innerHTML, CodeQL-clean). Accept calls gtag('consent','update',
{analytics_storage:'granted'}) and persists 'granted'; Reject persists
'denied'; both hide the banner. A stored choice suppresses the banner.

Part of AAASM-3554 (node-sdk site).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restores route-change page_view tracking lost by dropping preset-classic's
gtag plugin: on client navigation it sends an updated page_path and a
page_view event. Consent Mode still gates whether GA stores anything.

Part of AAASM-3554 (node-sdk site).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace preset-classic's gtag option with self-managed headTags (consent
default-denied -> stored opt-in restore -> gtag.js loader -> config hit) so
deny-before-hit ordering is guaranteed, and register the banner + page-view
tracker via clientModules.

Part of AAASM-3554 (node-sdk site).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@Chisanan232

Copy link
Copy Markdown
Contributor Author

Claude Code — review result

CI green — all 16 checks pass (build, coverage, module-smoke, test 18/20/22/24, CodeQL, SonarCloud). No fails to ignore.

Scope vs AAASM-3554 (node-sdk Docusaurus site) — covered, with a justified design decision I reviewed and agree with. Docusaurus injects config.headTags after plugin head tags, so the preset-classic gtag plugin's config hit would fire before any headTags consent-default — too late. The PR therefore drops the preset gtag option (the old gtag: { trackingID, anonymizeIP } block is cleanly removed) and self-manages init via array-ordered headTags: consent-default-denied + opt-in restore → preconnects → async gtag.jsgtag('config', G-6FHQKGLDE4, {anonymize_ip}). To preserve the SPA route page_view tracking the preset plugin used to provide, a small gtagRouteTracker.ts clientModule is added alongside the consentBanner.ts opt-in banner (built with createElement/textContent, no innerHTML).

Verification (from the agent, confirmed against the diff): built build/index.html <head> byte order is consent-default (3411) < gtag.js loader (3844) < gtag('config') (3911) → deny-before-hit guaranteed. pnpm build succeeds, pnpm typecheck (strict) clean, no new dependency (pnpm-lock.yaml unchanged). Commit split is a clean 5 (init source / CSS / banner module / tracker module / config wiring), each bisectable.

Ready for approval + merge.

@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Chisanan232 Chisanan232 merged commit 75b43a8 into master Jun 22, 2026
17 checks passed
@Chisanan232 Chisanan232 deleted the v0.0.1/AAASM-3554/docusaurus_consent branch June 22, 2026 14:50
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