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 settingslink (useCookieSettingsLink) 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 recordsSec-GPC: 1on/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>
Banner analytics events
<CmpBanner variant="mb1|cc1|..." /> now emits lightweight analytics hooks ahead of your own callbacks:
- Accept buttons push
{ event: 'cmp_accept' }ontowindow.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 loaddata-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.stringsfrom remote config (title, description, button labels, and an optional policy link), and respects GPC.
API helpers:
window.DWConsent.setAll(true|false)— scripted acceptance for QAwindow.DWConsent.open()— opens the dialog (footer link)
Validation
- DevTools Network: no
gtag/jsorfbevents.jsbefore 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(orStrictwhen viable)
See apps/idp/src/main.ts and apps/idp/docs/backend-overview.md#security-headers-helmet for reference.
Theming & IDs (React)
ConsentBannerandConsentDialogexposeclassNameandstyleprops and stable selectors viadata-cmp="cmp-banner|cmp-dialog"and[data-cmp="panel"]for external theming.CookieSettingsLinkopens the dialog defined by the Provider’sdialogId(defaults tocmp-dialog). Prefer passingdialogIdviaConsentProviderrather 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).