Skip to main content

CMP (Cookie Consent) — Workspace Guide

Last updated: 2025-09-14 (Consolidated under docs/cmp)

This guide documents DigiWedge’s shared client‑side consent platform and how to integrate it in apps.

Packages

  • @digiwedge/cmp-consent-core — headless engine, storage, tag controller, GA4/Meta adapters
  • @digiwedge/cmp-consent-react — React Provider, Banner, Dialog, CookieSettingsLink, hooks
  • @digiwedge/cmp-consent-plugin — Nx generator to auto‑wire CMP into React apps

Compliance defaults

  • Non‑essential categories (analytics, marketing, functional) are denied until explicit opt‑in
  • Banner provides Accept / Reject / Manage; Dialog exposes granular toggles
  • Revocation: add Cookie settings link (use CookieSettingsLink) in your footer
  • GTM Consent Mode v2: initialize default‑denied and update on changes
  • Global Privacy Control (GPC): when navigator.globalPrivacyControl === true, non‑essential remain off by default (users can still opt‑in explicitly). The server records Sec-GPC: 1 on /v1/consent.

Quick integration (React)

import { createConsentCore, registerGA4, registerMetaPixel } from '@digiwedge/cmp-consent-core';
import {
ConsentProvider,
ConsentBanner,
ConsentDialog,
CookieSettingsLink,
} from '@digiwedge/cmp-consent-react';

const consent = createConsentCore({
version: 1,
store: 'localStorage',
geofencing: { eeaRequiresOptIn: true },
});
consent.tags.register({
id: 'ga4',
category: 'analytics',
loader: () => registerGA4({ measurementId: import.meta.env.VITE_GA_ID }),
enabled: false,
});
consent.tags.register({
id: 'fbp',
category: 'marketing',
loader: () => registerMetaPixel({ pixelId: import.meta.env.VITE_FB_PIXEL }),
enabled: false,
});

consent.subscribe((s) => {
window.gtag?.('consent', 'update', {
analytics_storage: s.categories.analytics ? 'granted' : 'denied',
ad_storage: s.categories.marketing ? 'granted' : 'denied',
ad_user_data: s.categories.marketing ? 'granted' : 'denied',
ad_personalization: s.categories.marketing ? 'granted' : 'denied',
});
});

// Optional localization strings from the Registry config (ui.strings)
const strings = {
title: 'Cookies & Privacy',
description: 'We use essential cookies. With your consent, …',
buttons: {
acceptAll: 'Accept all',
rejectAll: 'Reject all',
manage: 'Manage',
save: 'Save',
cancel: 'Cancel',
},
policy: { linkText: 'Cookie policy', href: 'https://example.com/cookies' },
};

<ConsentProvider core={consent} strings={strings}>
<App />
<ConsentBanner />
<ConsentDialog />
<CookieSettingsLink style={{ textDecoration: 'underline' }} />
</ConsentProvider>;

HTML shell — default denied:

<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('consent', 'default', {
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
analytics_storage: 'denied',
functionality_storage: 'denied',
personalization_storage: 'denied',
security_storage: 'granted',
});
</script>

<CmpBanner variant="mb1|cc1|..." /> now emits lightweight analytics hooks ahead of your own callbacks:

  • Accept buttons push { event: 'cmp_accept' } onto window.dataLayer.
  • Reject buttons (when present) push { event: 'cmp_reject' }.

The helper is resilient (no-ops if window or dataLayer is missing) and fires before onAccept/onReject. Consumers wanting richer payloads can listen for these events or wrap onAccept/onReject to send additional metadata.

Generator

pnpm nx build cmp-consent
pnpm nx g @digiwedge/cmp-consent-plugin:init --project=<react_app_name>

Drop-in (remote config + SRI)

Script tag (attributes):

<script
src="https://cdn.example.com/cmp/dw-cmp.min.js"
data-site-key="SITE_KEY_FROM_PORTAL"
data-config-url="https://cmp-registry.example.com"
integrity="sha256-..."
crossorigin="anonymous"
defer
></script>
  • data-site-key: identifies which Site config to load
  • data-config-url: base URL for the registry API (should include /api, e.g., https://cmp-registry.example.com/api; omit if hosting on same origin)
  • integrity: optional SRI (published alongside the bundle)
  • The drop-in applies ui.strings from remote config (title, description, button labels, and an optional policy link), and respects GPC.

API helpers:

  • window.DWConsent.setAll(true|false) — scripted acceptance for QA
  • window.DWConsent.open() — opens the dialog (footer link)

Validation

  • DevTools Network: no gtag/js or fbevents.js before consent; both load only after opt‑in
  • E2E guard example: apps/MCA/mca_frontend-e2e/src/e2e/cmp.spec.ts
  • A11y: the Banner/Dialog include roles/labels and a focus trap; we run axe-core checks in CI.

Server headers

Backends that render or serve the web app should:

  • Enable HSTS, CSP, Referrer‑Policy, X‑Content‑Type‑Options, frame restrictions
  • Ensure session/anti‑forgery cookies use Secure; HttpOnly; SameSite=Lax (or Strict when viable)

See apps/idp/src/main.ts and apps/idp/docs/backend-overview.md#security-headers-helmet for reference.

Theming & IDs (React)

  • ConsentBanner and ConsentDialog expose className and style props and stable selectors via data-cmp="cmp-banner|cmp-dialog" and [data-cmp="panel"] for external theming.
  • CookieSettingsLink opens the dialog defined by the Provider’s dialogId (defaults to cmp-dialog). Prefer passing dialogId via ConsentProvider rather than hard‑coding IDs.
  • SSR: React components access the DOM only in effects; the headless core’s cookie store/adapters no‑op on the server. Initialize the core in the browser and inject it into the Provider.
  • apply() on the core re-applies current categories to registered tag loaders without mutating state; call it after registering loaders when a non‑essential category starts as granted (opt‑out regions).