@c15t/react 3.0.0-alpha.1 → 3.0.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +97 -70
- package/README.md +21 -17
- package/SKILL.md +34 -0
- package/dist/aggregate-components.js +1 -1
- package/dist/chunk-warming.js +1 -1
- package/dist/component-hooks/use-headless-consent-ui.js +1 -1
- package/dist/component-hooks/use-headless-iab-consent-ui.js +1 -1
- package/dist/component-hooks/use-panel-trigger.js +1 -1
- package/dist/components/consent-gate/atoms.js +1 -0
- package/dist/components/consent-gate/consent-gate.js +2 -0
- package/dist/components/consent-gate/frame.js +2 -0
- package/dist/components/consent-gate/index.js +2 -0
- package/dist/components/consent-preferences-link/consent-preferences-link.js +1 -1
- package/dist/components/iab-panel/atoms/card.js +1 -1
- package/dist/components/iab-panel/atoms/feature-item.js +2 -0
- package/dist/components/iab-panel/atoms/header.js +1 -1
- package/dist/components/iab-panel/atoms/purpose-item.js +1 -1
- package/dist/components/iab-panel/atoms/root.js +1 -1
- package/dist/components/iab-panel/atoms/stack-item.js +1 -1
- package/dist/components/iab-panel/atoms/vendor-list.js +1 -1
- package/dist/components/iab-panel/hooks/use-gvl-data.js +1 -1
- package/dist/components/iab-panel/iab-panel.js +1 -1
- package/dist/components/iab-panel/index.js +1 -1
- package/dist/components/iab-prompt/atoms/overlay.js +1 -1
- package/dist/components/iab-prompt/atoms/root.js +1 -1
- package/dist/components/iab-prompt/iab-prompt.js +1 -1
- package/dist/components/panel/atoms/card.js +1 -1
- package/dist/components/panel/atoms/overlay.js +1 -1
- package/dist/components/panel/atoms/root.js +1 -1
- package/dist/components/panel/panel.js +1 -1
- package/dist/components/panel-trigger/atoms/button.js +1 -1
- package/dist/components/panel-trigger/atoms/icon.js +1 -1
- package/dist/components/panel-trigger/atoms/root.js +1 -1
- package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
- package/dist/components/panel-trigger/atoms/trigger-icons.js +1 -0
- package/dist/components/panel-trigger/panel-trigger-toolbar.js +1 -1
- package/dist/components/panel-trigger/panel-trigger.js +1 -1
- package/dist/components/panel-trigger/toolbar-loader.js +2 -0
- package/dist/components/preferences/atoms/accordion.js +1 -1
- package/dist/components/preferences/atoms/root.js +1 -1
- package/dist/components/preferences/atoms/vendor-list.js +1 -0
- package/dist/components/preferences/index.js +1 -1
- package/dist/components/prompt/atoms/early-tap-script.js +2 -0
- package/dist/components/prompt/atoms/overlay.js +1 -1
- package/dist/components/prompt/atoms/root.js +1 -1
- package/dist/components/prompt/components.js +1 -1
- package/dist/components/prompt/policy-actions.js +1 -1
- package/dist/components/prompt/prompt.js +1 -1
- package/dist/components/shared/dialog-sheets.js +1 -0
- package/dist/components/shared/first-paint-sheets.js +1 -0
- package/dist/components/shared/iab-dialog-sheets.js +1 -0
- package/dist/components/shared/iab-first-paint-sheets.js +1 -0
- package/dist/components/shared/libs/compose-refs.js +1 -0
- package/dist/components/shared/libs/slot.js +1 -1
- package/dist/components/shared/primitives/button.js +1 -1
- package/dist/components/shared/primitives/legal-links/index.js +1 -1
- package/dist/components/shared/streamed-surface.js +2 -0
- package/dist/components/shared/surface-styles.js +2 -0
- package/dist/components/shared/ui/dialog/dialog.js +1 -1
- package/dist/components/shared/ui/icon.js +1 -1
- package/dist/components/shared/ui/logo.js +1 -1
- package/dist/components/shared/ui/preference-item/preference-item.js +1 -1
- package/dist/consent-gate.js +2 -0
- package/dist/consent-theme.js +1 -0
- package/dist/context/consent-tracking-context.js +1 -1
- package/dist/context/index.js +1 -1
- package/dist/context.js +1 -1
- package/dist/devtools-launcher.js +2 -0
- package/dist/devtools.js +1 -1
- package/dist/draft-context.js +2 -0
- package/dist/draft.js +1 -1
- package/dist/external-iab-context.js +1 -1
- package/dist/gpp.js +2 -0
- package/dist/headless.js +1 -1
- package/dist/hooks/use-color-scheme.js +1 -1
- package/dist/hooks/use-committed-ref.js +2 -0
- package/dist/hooks/use-focus-trap.js +1 -1
- package/dist/hooks/use-headless-iab-consent-ui.js +1 -1
- package/dist/hooks/use-iab-policy-guard.js +1 -0
- package/dist/hooks/use-late-entry.js +2 -0
- package/dist/hooks.js +1 -1
- package/dist/iab/styles.tw3.css +5 -45
- package/dist/iab-context.js +1 -1
- package/dist/iab-dialog-deferred.js +2 -0
- package/dist/iab.js +1 -1
- package/dist/index-parts/consent-banner.js +1 -0
- package/dist/index-parts/consent-dialog-link.js +1 -0
- package/dist/index-parts/consent-dialog-trigger.js +1 -0
- package/dist/index-parts/consent-dialog.js +1 -0
- package/dist/index-parts/consent-gate.js +1 -0
- package/dist/index-parts/core.js +1 -0
- package/dist/index-parts/draft.js +1 -0
- package/dist/index-parts/frame.js +1 -0
- package/dist/index-parts/hooks.js +1 -0
- package/dist/index-parts/hosted.js +1 -0
- package/dist/index-parts/module-hooks.js +1 -0
- package/dist/index-parts/offline.js +1 -0
- package/dist/index-parts/presets.js +1 -0
- package/dist/index-parts/provider.js +1 -0
- package/dist/index-parts/theme.js +1 -0
- package/dist/index-parts/translations.js +1 -0
- package/dist/index.js +1 -1
- package/dist/kernel-selector.js +1 -0
- package/dist/modes.js +1 -0
- package/dist/module-hooks/iframe-blocker.js +1 -1
- package/dist/module-hooks/network-blocker.js +1 -1
- package/dist/module-hooks/network-hold.js +2 -0
- package/dist/module-hooks/persistence.js +1 -1
- package/dist/panel-link.js +1 -1
- package/dist/panel-trigger.js +1 -1
- package/dist/panel.js +1 -1
- package/dist/postcss-tailwind3.js +1 -0
- package/dist/preferences.js +1 -2
- package/dist/provider.js +1 -1
- package/dist/server/fetch-ssr-data.js +1 -1
- package/dist/server/headers.js +1 -1
- package/dist/server/normalize-url.js +1 -1
- package/dist/styles.tw3.css +33 -51
- package/dist/transports/hosted.js +1 -0
- package/dist/transports/manifest.js +1 -0
- package/dist/transports/offline.js +1 -1
- package/dist/utils/deferred-module.js +2 -0
- package/dist/utils/merge-slot-props.js +1 -1
- package/dist/utils/theme-utils.js +1 -1
- package/dist/version.js +1 -1
- package/dist-types/aggregate-components.d.ts +2 -2
- package/dist-types/chunk-warming.d.ts +42 -8
- package/dist-types/component-hooks/use-headless-consent-ui.d.ts +5 -5
- package/dist-types/component-hooks/use-headless-iab-consent-ui.d.ts +2 -2
- package/dist-types/components/consent-gate/atoms.d.ts +11 -0
- package/dist-types/components/consent-gate/consent-gate.d.ts +2 -0
- package/dist-types/components/consent-gate/frame.d.ts +19 -0
- package/dist-types/components/consent-gate/index.d.ts +25 -0
- package/dist-types/components/{frame → consent-gate}/types.d.ts +7 -7
- package/dist-types/components/consent-preferences-link/consent-preferences-link.d.ts +1 -1
- package/dist-types/components/consent-preferences-link/index.d.ts +1 -1
- package/dist-types/components/iab-panel/atoms/feature-item.d.ts +28 -0
- package/dist-types/components/iab-panel/atoms/purpose-item.d.ts +5 -2
- package/dist-types/components/iab-panel/atoms/stack-item.d.ts +7 -2
- package/dist-types/components/iab-panel/atoms/vendor-list.d.ts +1 -1
- package/dist-types/components/iab-panel/iab-panel.d.ts +3 -3
- package/dist-types/components/iab-panel/index.d.ts +29 -26
- package/dist-types/components/iab-panel/use-iab-translations.d.ts +4 -0
- package/dist-types/components/iab-prompt/iab-prompt.d.ts +3 -3
- package/dist-types/components/iab-prompt/index.d.ts +19 -19
- package/dist-types/components/panel/atoms/card.d.ts +5 -4
- package/dist-types/components/panel/atoms/overlay.d.ts +1 -1
- package/dist-types/components/panel/index.d.ts +8 -8
- package/dist-types/components/panel/panel.d.ts +3 -3
- package/dist-types/components/panel-link/index.d.ts +1 -1
- package/dist-types/components/panel-trigger/atoms/button.d.ts +1 -1
- package/dist-types/components/panel-trigger/atoms/icon.d.ts +1 -1
- package/dist-types/components/panel-trigger/atoms/root.d.ts +2 -2
- package/dist-types/components/panel-trigger/atoms/toolbar.d.ts +2 -2
- package/dist-types/components/panel-trigger/atoms/trigger-icons.d.ts +25 -0
- package/dist-types/components/panel-trigger/index.d.ts +4 -4
- package/dist-types/components/panel-trigger/panel-trigger-toolbar.d.ts +1 -1
- package/dist-types/components/panel-trigger/panel-trigger.d.ts +9 -17
- package/dist-types/components/panel-trigger/toolbar-loader.d.ts +34 -0
- package/dist-types/components/panel-trigger/types.d.ts +1 -1
- package/dist-types/components/preferences/atoms/accordion.d.ts +6 -4
- package/dist-types/components/preferences/atoms/button.d.ts +1 -1
- package/dist-types/components/preferences/atoms/footer.d.ts +1 -1
- package/dist-types/components/preferences/atoms/root.d.ts +1 -1
- package/dist-types/components/preferences/atoms/vendor-list.d.ts +23 -0
- package/dist-types/components/preferences/index.d.ts +14 -12
- package/dist-types/components/preferences/policy-actions.d.ts +2 -2
- package/dist-types/components/preferences/preferences.d.ts +1 -1
- package/dist-types/components/preferences/types.d.ts +1 -1
- package/dist-types/components/prompt/atoms/early-tap-script.d.ts +17 -0
- package/dist-types/components/prompt/atoms/root.d.ts +4 -0
- package/dist-types/components/prompt/components.d.ts +9 -3
- package/dist-types/components/prompt/index.d.ts +12 -12
- package/dist-types/components/prompt/policy-actions.d.ts +2 -2
- package/dist-types/components/prompt/prompt.d.ts +5 -1
- package/dist-types/components/shared/dialog-sheets.d.ts +9 -0
- package/dist-types/components/shared/first-paint-sheets.d.ts +8 -0
- package/dist-types/components/shared/iab-dialog-sheets.d.ts +3 -0
- package/dist-types/components/shared/iab-first-paint-sheets.d.ts +3 -0
- package/dist-types/components/shared/libs/compose-refs.d.ts +20 -0
- package/dist-types/components/shared/policy-actions.d.ts +2 -2
- package/dist-types/components/shared/primitives/box.d.ts +1 -1
- package/dist-types/components/shared/primitives/button.d.ts +2 -2
- package/dist-types/components/shared/primitives/button.types.d.ts +1 -1
- package/dist-types/components/shared/streamed-surface.d.ts +23 -0
- package/dist-types/components/shared/surface-styles.d.ts +60 -0
- package/dist-types/components/shared/ui/accordion/accordion.d.ts +2 -2
- package/dist-types/components/shared/ui/accordion/index.d.ts +2 -2
- package/dist-types/components/shared/ui/animated-collapse/index.d.ts +2 -2
- package/dist-types/components/shared/ui/branding.d.ts +1 -1
- package/dist-types/components/shared/ui/button/button.d.ts +1 -1
- package/dist-types/components/shared/ui/button/index.d.ts +2 -2
- package/dist-types/components/shared/ui/collapsible/index.d.ts +1 -1
- package/dist-types/components/shared/ui/dialog/index.d.ts +1 -1
- package/dist-types/components/shared/ui/icon.d.ts +2 -1
- package/dist-types/components/shared/ui/logo.d.ts +0 -10
- package/dist-types/components/shared/ui/preference-item/index.d.ts +1 -1
- package/dist-types/components/shared/ui/preference-item/preference-item.d.ts +13 -0
- package/dist-types/components/shared/ui/switch/index.d.ts +2 -2
- package/dist-types/components/shared/ui/switch/switch.d.ts +1 -1
- package/dist-types/components/shared/ui/tabs/index.d.ts +1 -1
- package/dist-types/consent-gate.d.ts +1 -0
- package/dist-types/consent-theme.d.ts +38 -0
- package/dist-types/context/consent-tracking-context.d.ts +10 -0
- package/dist-types/context/index.d.ts +3 -3
- package/dist-types/context/theme-context.d.ts +3 -3
- package/dist-types/context.d.ts +25 -1
- package/dist-types/devtools-launcher.d.ts +29 -0
- package/dist-types/devtools.d.ts +13 -2
- package/dist-types/draft-context.d.ts +31 -0
- package/dist-types/draft.d.ts +81 -9
- package/dist-types/gpp.d.ts +27 -0
- package/dist-types/headless.d.ts +6 -7
- package/dist-types/hooks/use-committed-ref.d.ts +24 -0
- package/dist-types/hooks/use-focus-trap.d.ts +3 -1
- package/dist-types/hooks/use-headless-iab-consent-ui.d.ts +1 -1
- package/dist-types/hooks/use-iab-manager.d.ts +5 -5
- package/dist-types/hooks/use-iab-policy-guard.d.ts +35 -0
- package/dist-types/hooks/use-late-entry.d.ts +14 -0
- package/dist-types/hooks/use-theme.d.ts +1 -1
- package/dist-types/hooks.d.ts +106 -11
- package/dist-types/iab-dialog-deferred.d.ts +36 -0
- package/dist-types/iab.d.ts +8 -7
- package/dist-types/index-parts/consent-banner.d.ts +7 -0
- package/dist-types/index-parts/consent-dialog-link.d.ts +7 -0
- package/dist-types/index-parts/consent-dialog-trigger.d.ts +7 -0
- package/dist-types/index-parts/consent-dialog.d.ts +7 -0
- package/dist-types/index-parts/consent-gate.d.ts +7 -0
- package/dist-types/index-parts/core.d.ts +7 -0
- package/dist-types/index-parts/draft.d.ts +7 -0
- package/dist-types/index-parts/frame.d.ts +7 -0
- package/dist-types/index-parts/hooks.d.ts +7 -0
- package/dist-types/index-parts/hosted.d.ts +7 -0
- package/dist-types/index-parts/module-hooks.d.ts +7 -0
- package/dist-types/index-parts/offline.d.ts +7 -0
- package/dist-types/index-parts/presets.d.ts +7 -0
- package/dist-types/index-parts/provider.d.ts +7 -0
- package/dist-types/index-parts/theme.d.ts +8 -0
- package/dist-types/index-parts/translations.d.ts +7 -0
- package/dist-types/index.d.ts +32 -28
- package/dist-types/kernel-selector.d.ts +68 -0
- package/dist-types/modes.d.ts +26 -0
- package/dist-types/module-hooks/iframe-blocker.d.ts +33 -1
- package/dist-types/module-hooks/network-blocker.d.ts +23 -0
- package/dist-types/module-hooks/network-hold.d.ts +47 -0
- package/dist-types/module-hooks.d.ts +4 -4
- package/dist-types/panel-link.d.ts +1 -1
- package/dist-types/panel-trigger.d.ts +1 -1
- package/dist-types/panel.d.ts +13 -1
- package/dist-types/postcss-tailwind3.d.ts +21 -0
- package/dist-types/preferences.d.ts +12 -1
- package/dist-types/primitives/accordion.d.ts +1 -1
- package/dist-types/primitives/button.d.ts +1 -1
- package/dist-types/primitives/collapsible.d.ts +1 -1
- package/dist-types/primitives/dialog.d.ts +1 -1
- package/dist-types/primitives/preference-item.d.ts +1 -1
- package/dist-types/primitives/switch.d.ts +1 -1
- package/dist-types/primitives/tabs.d.ts +1 -1
- package/dist-types/primitives.d.ts +7 -7
- package/dist-types/prompt.d.ts +1 -1
- package/dist-types/provider.d.ts +169 -28
- package/dist-types/server/fetch-ssr-data.d.ts +1 -1
- package/dist-types/server/headers.d.ts +14 -4
- package/dist-types/server/index.d.ts +5 -5
- package/dist-types/server/normalize-url.d.ts +14 -7
- package/dist-types/server/types.d.ts +12 -1
- package/dist-types/theme-provider.d.ts +2 -2
- package/dist-types/transports/hosted.d.ts +36 -0
- package/dist-types/transports/manifest.d.ts +48 -0
- package/dist-types/transports/offline.d.ts +7 -16
- package/dist-types/types/index.d.ts +3 -3
- package/dist-types/types/manager.d.ts +1 -1
- package/dist-types/types/theme/index.d.ts +1 -1
- package/dist-types/ui-config-context.d.ts +14 -2
- package/dist-types/utils/deferred-module.d.ts +13 -0
- package/dist-types/utils/index.d.ts +4 -4
- package/dist-types/utils/merge-slot-props.d.ts +17 -3
- package/dist-types/utils/merge-styles.d.ts +1 -1
- package/dist-types/utils/theme-utils.d.ts +18 -3
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +97 -70
- package/docs/assets/v3/bottom-bar.png +0 -0
- package/docs/assets/v3/brand-card.png +0 -0
- package/docs/assets/v3/brand-preferences.png +0 -0
- package/docs/assets/v3/choice-wall.png +0 -0
- package/docs/assets/v3/headless-bar-html.png +0 -0
- package/docs/assets/v3/headless-bar-mobile.png +0 -0
- package/docs/assets/v3/headless-bar.png +0 -0
- package/docs/assets/v3/slim-bar.png +0 -0
- package/docs/concepts/choose-your-setup.md +141 -0
- package/docs/concepts/consent-categories.md +84 -0
- package/docs/concepts/consent-state.md +421 -0
- package/docs/concepts/data-fetching.md +284 -0
- package/docs/concepts/how-consent-works.md +123 -0
- package/docs/concepts/modes.md +281 -0
- package/docs/{frameworks/react/concepts/policy-presets.md → concepts/policies.md} +8 -60
- package/docs/customization/class-names.md +202 -0
- package/docs/customization/dark-mode.md +157 -0
- package/docs/customization/motion.md +128 -0
- package/docs/customization/overview.md +68 -33
- package/docs/customization/recipes.md +838 -47
- package/docs/customization/slots.md +217 -35
- package/docs/customization/stylesheets.md +248 -0
- package/docs/customization/tailwind.md +863 -0
- package/docs/customization/tokens.md +167 -36
- package/docs/customization/translations.md +87 -3
- package/docs/frameworks/react/callbacks.md +118 -0
- package/docs/frameworks/react/clear-on-revocation.md +106 -0
- package/docs/frameworks/react/components/consent-banner.md +48 -27
- package/docs/frameworks/react/components/consent-dialog-link.md +13 -11
- package/docs/frameworks/react/components/consent-dialog-trigger.md +86 -76
- package/docs/frameworks/react/components/consent-dialog.md +111 -58
- package/docs/frameworks/react/components/{frame.md → consent-gate.md} +70 -49
- package/docs/frameworks/react/components/consent-provider.md +143 -0
- package/docs/frameworks/react/components/consent-widget.md +69 -24
- package/docs/frameworks/react/components/dev-tools.md +76 -85
- package/docs/frameworks/react/components.md +113 -0
- package/docs/frameworks/react/compose.md +354 -0
- package/docs/frameworks/react/content-security-policy.md +149 -0
- package/docs/frameworks/react/customize.md +258 -0
- package/docs/frameworks/react/embeds.md +89 -0
- package/docs/frameworks/react/gpp.md +163 -0
- package/docs/frameworks/react/headless.md +4 -4
- package/docs/frameworks/react/hooks.md +70 -0
- package/docs/frameworks/react/iab.md +278 -0
- package/docs/frameworks/react/network-blocker.md +140 -0
- package/docs/frameworks/react/quickstart.md +171 -107
- package/docs/frameworks/react/rendering.md +285 -0
- package/docs/frameworks/react/scripts.md +114 -0
- package/docs/frameworks/react/translations.md +155 -0
- package/docs/frameworks/react/troubleshooting.md +189 -61
- package/docs/frameworks/react/upgrade-v3.md +652 -0
- package/docs/frameworks/react/vendor-consent.md +188 -0
- package/docs/guides/banner-experiments.md +688 -0
- package/docs/guides/troubleshooting.md +192 -54
- package/docs/guides/verify-consent.md +96 -49
- package/docs/integrations/adobe-analytics.md +177 -183
- package/docs/integrations/ahrefs-analytics.md +162 -178
- package/docs/integrations/amplitude.md +172 -180
- package/docs/integrations/building-integrations.md +137 -33
- package/docs/integrations/clearbit.md +164 -178
- package/docs/integrations/cloudflare-web-analytics.md +166 -180
- package/docs/integrations/cloudflare-zaraz.md +333 -0
- package/docs/integrations/crisp.md +174 -182
- package/docs/integrations/databuddy.md +167 -197
- package/docs/integrations/fathom-analytics.md +168 -180
- package/docs/integrations/front-chat.md +308 -0
- package/docs/integrations/google-maps.md +142 -108
- package/docs/integrations/google-tag-manager.md +258 -185
- package/docs/integrations/google-tag.md +226 -182
- package/docs/integrations/heap.md +173 -179
- package/docs/integrations/hightouch.md +171 -181
- package/docs/integrations/hotjar.md +169 -179
- package/docs/integrations/intercom.md +193 -177
- package/docs/integrations/klaviyo.md +473 -0
- package/docs/integrations/linkedin-insights.md +184 -174
- package/docs/integrations/logrocket.md +170 -180
- package/docs/integrations/matomo-analytics.md +198 -202
- package/docs/integrations/meta-pixel.md +198 -174
- package/docs/integrations/microsoft-clarity.md +173 -179
- package/docs/integrations/microsoft-uet.md +158 -178
- package/docs/integrations/mixpanel-analytics.md +165 -184
- package/docs/integrations/one-dollar-stats.md +292 -0
- package/docs/integrations/openai-pixel.md +214 -325
- package/docs/integrations/overview.md +153 -83
- package/docs/integrations/pinterest-tag.md +315 -0
- package/docs/integrations/pirsch.md +179 -183
- package/docs/integrations/plausible-analytics.md +182 -182
- package/docs/integrations/posthog.md +323 -264
- package/docs/integrations/promptwatch.md +164 -178
- package/docs/integrations/reddit-pixel.md +195 -181
- package/docs/integrations/rudderstack.md +211 -210
- package/docs/integrations/rybbit-analytics.md +181 -184
- package/docs/integrations/segment.md +192 -178
- package/docs/integrations/sentry.md +402 -0
- package/docs/integrations/snapchat-pixel.md +196 -180
- package/docs/integrations/tiktok-pixel.md +181 -174
- package/docs/integrations/umami-analytics.md +173 -182
- package/docs/integrations/vercel-analytics.md +176 -181
- package/docs/integrations/vercel-speed-insights.md +306 -0
- package/docs/integrations/x-pixel.md +186 -174
- package/docs/integrations/youtube.md +150 -111
- package/package.json +48 -17
- package/dist/component-hooks/use-manager.js +0 -2
- package/dist/components/frame/atoms.js +0 -1
- package/dist/components/frame/frame.js +0 -2
- package/dist/components/frame/index.js +0 -2
- package/dist/frame.js +0 -2
- package/dist/ui-save.js +0 -1
- package/dist-types/component-hooks/use-manager.d.ts +0 -40
- package/dist-types/components/frame/atoms.d.ts +0 -11
- package/dist-types/components/frame/frame.d.ts +0 -2
- package/dist-types/components/frame/index.d.ts +0 -24
- package/dist-types/frame.d.ts +0 -1
- package/dist-types/ui-save.d.ts +0 -5
- package/docs/assets/v3/brand-bar.png +0 -0
- package/docs/assets/v3/mobile-card.png +0 -0
- package/docs/assets/v3/preferences.png +0 -0
- package/docs/frameworks/react/components/consent-manager-provider.md +0 -71
- package/docs/frameworks/react/concepts/consent-categories.md +0 -33
- package/docs/frameworks/react/hooks/use-consent-manager/overview.md +0 -58
- package/docs/frameworks/react/iab/overview.md +0 -53
- package/docs/frameworks/react/script-loader.md +0 -63
- package/docs/frameworks/react/styling/overview.md +0 -217
- package/docs/guides/consent-state.md +0 -60
- package/docs/guides/data-fetching.md +0 -163
- package/docs/guides/deployment-modes.md +0 -63
- package/docs/integrations/clear-on-revocation.md +0 -167
- package/docs/upgrade-v3.md +0 -334
- package/readme.json +0 -48
- package/src/iab/styles.css +0 -11
- package/src/styles.css +0 -10
- /package/dist/components/{frame → consent-gate}/types.js +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Choose your setup
|
|
3
|
+
description: Pick the c15t setup for your framework, rendering mode and hosting,
|
|
4
|
+
and decide who runs the consent backend.
|
|
5
|
+
group: concepts
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Find your app in this table
|
|
9
|
+
|
|
10
|
+
Most apps need one row. Follow its guide from start to finish; each guide links
|
|
11
|
+
to the alternatives it rules out.
|
|
12
|
+
|
|
13
|
+
| Your app | Setup | Guide |
|
|
14
|
+
| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
15
|
+
| Next.js App Router with a Next.js server | Server resolves consent from a build-time manifest and streams it to the page | [App Router](https://v3.c15t.com/docs/frameworks/next/quickstart) |
|
|
16
|
+
| Next.js App Router, page must arrive with its banner | Await consent inside `Suspense` | [Stream or await](https://v3.c15t.com/docs/frameworks/next/rendering#stream-or-await-consent-in-the-app-router) |
|
|
17
|
+
| Next.js with static, ISR, `'use cache'` or `ensureStatic = 'navigation'` pages | Browser resolves consent on those pages | [Static, ISR and cached pages](https://v3.c15t.com/docs/frameworks/next/rendering#render-static-isr-and-cached-pages) |
|
|
18
|
+
| Next.js Pages Router | Resolve consent in `getServerSideProps` | [Pages Router](https://v3.c15t.com/docs/frameworks/next/rendering#pick-your-rendering-path) |
|
|
19
|
+
| Next.js with `output: 'export'` | Browser resolves consent | [Static export](https://v3.c15t.com/docs/frameworks/next/static-export) |
|
|
20
|
+
| TanStack Start with a server | Root loader resolves consent on the server | [TanStack Start](https://v3.c15t.com/docs/frameworks/tanstack-start/quickstart) |
|
|
21
|
+
| TanStack Start on a serverless host, banner may follow the page | Root loader streams consent | [TanStack Start rendering](https://v3.c15t.com/docs/frameworks/tanstack-start/rendering#stream-the-page-while-consent-resolves) |
|
|
22
|
+
| TanStack Start in SPA mode or prerendered | Browser resolves consent | [TanStack Start rendering](https://v3.c15t.com/docs/frameworks/tanstack-start/rendering#spa-mode-prerendered-pages-and-static-hosts) |
|
|
23
|
+
| React with Vite, or another single-page app | Browser resolves consent | [React](../frameworks/react/quickstart.md) |
|
|
24
|
+
| React Router framework mode, Remix, or another server-rendered React app | Browser resolves consent after hydration | [React rendering](../frameworks/react/rendering.md#react-router-remix-and-other-server-rendered-apps) |
|
|
25
|
+
| Nuxt with a Nuxt server | Nuxt module resolves consent on the server from a build-time manifest | [Nuxt](https://v3.c15t.com/docs/frameworks/nuxt/quickstart) |
|
|
26
|
+
| Nuxt with prerendered or cached routes | Nuxt server for most pages, the browser on prerendered and cached routes | [Nuxt prerendered and cached routes](https://v3.c15t.com/docs/frameworks/nuxt/rendering#prerendered-and-cached-routes) |
|
|
27
|
+
| Nuxt with `nuxt generate` on static hosting | Nuxt module, browser resolves consent from the manifest or `/init` | [Nuxt static hosting](https://v3.c15t.com/docs/frameworks/nuxt/rendering#deploy-to-static-hosting) |
|
|
28
|
+
| Nuxt with `ssr: false` | Nuxt module, browser resolves consent from the manifest | [Nuxt single-page app](https://v3.c15t.com/docs/frameworks/nuxt/rendering#build-a-single-page-app-with-ssr-false) |
|
|
29
|
+
| Vue with Vite | Vue plugin, browser resolves consent | [Vue](https://v3.c15t.com/docs/frameworks/vue/quickstart) |
|
|
30
|
+
| Vue rendered on the server without Nuxt | Vue plugin; the banner mounts after hydration. Use Nuxt for the banner in server HTML. | [Vue rendering](https://v3.c15t.com/docs/frameworks/vue/rendering#server-rendering-without-nuxt) |
|
|
31
|
+
| Astro with static output | Browser resolves consent through the hosted backend | [Astro static output](https://v3.c15t.com/docs/frameworks/astro/rendering#static-output-with-hosted) |
|
|
32
|
+
| Astro with server output and an adapter | Middleware resolves consent from a build-time manifest | [Astro server output](https://v3.c15t.com/docs/frameworks/astro/rendering#server-output-with-manifest) |
|
|
33
|
+
| Svelte with Vite | Svelte provider, browser resolves consent | [Svelte](https://v3.c15t.com/docs/frameworks/svelte/quickstart) |
|
|
34
|
+
| SvelteKit with a server | Root layout load resolves consent on the server | [SvelteKit](https://v3.c15t.com/docs/frameworks/sveltekit/quickstart) |
|
|
35
|
+
| SvelteKit with `adapter-static`, prerendered pages or SPA mode | Browser resolves consent | [SvelteKit rendering](https://v3.c15t.com/docs/frameworks/sveltekit/rendering#build-a-static-site) |
|
|
36
|
+
| Plain HTML, a CMS, a page builder, or a static site generator such as Hugo or Eleventy | One script tag, stock banner included | [HTML](https://v3.c15t.com/docs/frameworks/html/quickstart) |
|
|
37
|
+
| JavaScript with a bundler and no UI framework | `@c15t/browser` module, stock banner included | [JavaScript](https://v3.c15t.com/docs/frameworks/javascript/quickstart) |
|
|
38
|
+
| Your own UI, or a framework without an adapter such as Solid | Headless runtime | [JavaScript headless](https://v3.c15t.com/docs/frameworks/javascript/headless) |
|
|
39
|
+
|
|
40
|
+
Solid has no c15t adapter. Use the [headless runtime](https://v3.c15t.com/docs/frameworks/javascript/headless#use-c15t-with-solid)
|
|
41
|
+
and subscribe to it from your components.
|
|
42
|
+
|
|
43
|
+
## Decide who runs the consent backend
|
|
44
|
+
|
|
45
|
+
The backend supplies policies and stores consent records. Every setup above
|
|
46
|
+
works with each option; only the URL changes.
|
|
47
|
+
|
|
48
|
+
| Option | Use it when |
|
|
49
|
+
| ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| [Inth](https://inth.com) | You want hosted policies and consent records. The guides use this. |
|
|
51
|
+
| [Self-hosted c15t backend](https://v3.c15t.com/docs/self-host/overview) | You need to run the service and its database yourself. |
|
|
52
|
+
| Offline mode | Local development and tests. Policies live in your code and choices stay in the browser, with no consent records. Not recommended for production environments. |
|
|
53
|
+
|
|
54
|
+
To use Inth, create a project, set its policy rules, add your site's origin to
|
|
55
|
+
its trusted origins, and copy the backend URL. The guides use the placeholder
|
|
56
|
+
`https://your-project.inth.app`; replace it with your project's backend URL,
|
|
57
|
+
including any path prefix.
|
|
58
|
+
|
|
59
|
+
## Server rendering or browser rendering
|
|
60
|
+
|
|
61
|
+
Where consent resolves decides what the visitor's first HTML contains.
|
|
62
|
+
|
|
63
|
+
| Rendering | What the visitor gets |
|
|
64
|
+
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
65
|
+
| The server resolves consent for each request | The server reads the visitor's cookie and location headers, so the browser starts with the answer. Gated scripts can run right after hydration, and the banner can be part of the HTML. |
|
|
66
|
+
| The browser resolves consent | The HTML is the same for everyone. After the page loads, the browser requests the policy, then shows the banner or runs gated scripts. |
|
|
67
|
+
|
|
68
|
+
A static or prerendered page cannot contain one visitor's consent, because the
|
|
69
|
+
same HTML goes to everyone. Static sites still use Inth; the browser talks to it
|
|
70
|
+
directly. A same-origin `/api/c15t` route only works where a server runs it.
|
|
71
|
+
|
|
72
|
+
Pages that are cached and shared between visitors, such as Next.js ISR or
|
|
73
|
+
`'use cache'` routes, need browser resolution for the same reason. Cache the
|
|
74
|
+
public policy manifest, never a response that contains a visitor's consent.
|
|
75
|
+
|
|
76
|
+
## How the server gets the policy
|
|
77
|
+
|
|
78
|
+
Use a build-time manifest as the default for supported production builds.
|
|
79
|
+
`manifest()` is the default mode in Next.js, TanStack Start, Nuxt, Astro and
|
|
80
|
+
SvelteKit, so their servers resolve requests from the deployment's snapshot.
|
|
81
|
+
React, Vue and Svelte single-page apps have no server, so their quickstarts
|
|
82
|
+
use `hosted()`: the backend reads each visitor's location from the `/init`
|
|
83
|
+
request. They can bundle the manifest for browser resolution too, but a
|
|
84
|
+
regional policy still needs `/init` on a first visit unless the page knows
|
|
85
|
+
the location. The framework quickstarts show the build
|
|
86
|
+
integration. [Consent modes](./modes.md) lists every mode, its
|
|
87
|
+
options and the backend URL variable each framework reads.
|
|
88
|
+
|
|
89
|
+
The build fetches your public policy once and bundles it, so the server never
|
|
90
|
+
fetches it at runtime.
|
|
91
|
+
|
|
92
|
+
The snapshot is fixed at build time:
|
|
93
|
+
|
|
94
|
+
* Rebuild after changing policies, translations or vendors. If your CI caches
|
|
95
|
+
build output, force a fresh build.
|
|
96
|
+
* Consent choices still go to the backend.
|
|
97
|
+
|
|
98
|
+
The build reads the backend URL from your public backend URL variable when
|
|
99
|
+
the config doesn't pass one: `NEXT_PUBLIC_C15T_BACKEND_URL` in Next.js,
|
|
100
|
+
`NUXT_PUBLIC_C15T_BACKEND_URL` in Nuxt, `PUBLIC_C15T_BACKEND_URL` in Astro,
|
|
101
|
+
Svelte and SvelteKit, and `VITE_C15T_BACKEND_URL` in TanStack Start and other
|
|
102
|
+
Vite apps. Each also reads the matching Inth variable, such as
|
|
103
|
+
`NEXT_PUBLIC_INTH_PROJECT_URL`, when the c15t one is unset. See
|
|
104
|
+
[set the backend URL](./modes.md#set-the-backend-url).
|
|
105
|
+
|
|
106
|
+
The fetch waits at most 10 seconds. When it fails, or no backend URL is set,
|
|
107
|
+
every framework does the same thing:
|
|
108
|
+
|
|
109
|
+
| Command | Default when the fetch fails |
|
|
110
|
+
| ------------------------------------------------------------------------- | -------------------------------------------------------- |
|
|
111
|
+
| Production build: `next build`, `vite build`, `nuxt build`, `astro build` | The build stops with an error. |
|
|
112
|
+
| Dev: `next dev`, `vite dev`, `nuxt dev`, `astro dev` | A warning, and the server fetches the policy at runtime. |
|
|
113
|
+
|
|
114
|
+
Set `onBuildError` to use one behaviour for both. `'fail'` stops dev too.
|
|
115
|
+
`'runtime'` lets a production build finish, and the server fetches the policy
|
|
116
|
+
at runtime. The `C15T_ON_BUILD_ERROR` environment variable overrides the
|
|
117
|
+
option, so you can deploy during a backend outage without a code change:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
C15T_ON_BUILD_ERROR=runtime npm run build
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Turborepo's strict environment mode hides undeclared variables from tasks, so
|
|
124
|
+
list `C15T_ON_BUILD_ERROR` in the build task's `passThroughEnv` there.
|
|
125
|
+
|
|
126
|
+
The build skips the fetch, without an error, when it can't use a snapshot,
|
|
127
|
+
for example when the backend URL is relative. With `onBuildError: 'fail'`, a
|
|
128
|
+
relative URL stops the build. [Consent modes](./modes.md#what-happens-when-the-download-fails)
|
|
129
|
+
lists every case.
|
|
130
|
+
|
|
131
|
+
For deployments that need policy updates without a rebuild, choose a runtime
|
|
132
|
+
path:
|
|
133
|
+
|
|
134
|
+
* **Cached manifest**, `manifest({ source: 'runtime' })`.
|
|
135
|
+
Your server downloads the project's public policy file, caches it, and
|
|
136
|
+
resolves each visitor locally. Consent saves still go to the backend.
|
|
137
|
+
* **`/init`**, `hosted()`. Each visit asks the backend to resolve the visitor.
|
|
138
|
+
Simpler, but every page load makes a request to the backend.
|
|
139
|
+
|
|
140
|
+
[Data fetching](./data-fetching.md) compares them in detail,
|
|
141
|
+
including proxies and custom transports.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Consent categories
|
|
3
|
+
description: Assign scripts, embeds and features to c15t consent categories, and
|
|
4
|
+
understand which categories the preference dialog shows.
|
|
5
|
+
group: concepts
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Choose categories by purpose
|
|
9
|
+
|
|
10
|
+
| Category | Purpose |
|
|
11
|
+
| --------------- | ---------------------------------------------- |
|
|
12
|
+
| `necessary` | Functionality required for the site to operate |
|
|
13
|
+
| `functionality` | Optional features such as support widgets |
|
|
14
|
+
| `measurement` | Analytics and usage measurement |
|
|
15
|
+
| `experience` | Optional personalization |
|
|
16
|
+
| `marketing` | Advertising and marketing |
|
|
17
|
+
|
|
18
|
+
`necessary` is always permitted. Assign categories based on what an integration
|
|
19
|
+
does in your application; renaming analytics to necessary does not change its
|
|
20
|
+
purpose.
|
|
21
|
+
|
|
22
|
+
c15t discovers categories from registered scripts, network rules, React `ConsentGate`
|
|
23
|
+
components, and iframes with `data-category` handled by the iframe blocker.
|
|
24
|
+
The browser client also discovers inert scripts tagged with `data-c15t-category`.
|
|
25
|
+
Compound script and network conditions contribute every category they reference.
|
|
26
|
+
|
|
27
|
+
Discovered categories are added to `consentCategories`, when supplied. The dialog
|
|
28
|
+
always includes Necessary and offers the optional categories in that combined
|
|
29
|
+
set that are also in the resolved policy scope. Choice completion uses the same
|
|
30
|
+
set.
|
|
31
|
+
|
|
32
|
+
If neither configuration nor integrations supply categories, the result depends
|
|
33
|
+
on the policy's `scopeMode`:
|
|
34
|
+
|
|
35
|
+
* Under a permissive policy, the dialog lists only Necessary. The banner still
|
|
36
|
+
appears when the policy prompts for a choice. Accept All, Reject All and Save
|
|
37
|
+
each record that the visitor saw it, send a consent receipt for Necessary
|
|
38
|
+
alone in hosted and manifest modes, and keep the banner dismissed after
|
|
39
|
+
reload. The acknowledgement expires with the policy's choice validity and
|
|
40
|
+
after a policy change, like a choice would. A stored choice that is still
|
|
41
|
+
valid under the current policy also counts as an acknowledgement.
|
|
42
|
+
* Under a strict policy, or an IAB TCF policy, the dialog uses the full policy
|
|
43
|
+
scope. TCF consent is given per purpose and recorded in the TC string.
|
|
44
|
+
|
|
45
|
+
A category declared after that acknowledgement, such as a newly registered
|
|
46
|
+
script or a discovered iframe, needs a choice, so the banner asks again.
|
|
47
|
+
|
|
48
|
+
Categories discovered later are added immediately and retained until the provider
|
|
49
|
+
or runtime is recreated. Adding a category can require a new choice; removing a
|
|
50
|
+
script or unmounting a frame does not remove its category or erase consent.
|
|
51
|
+
Discovery does not grant consent or change backend permission restrictions.
|
|
52
|
+
|
|
53
|
+
## Respect policy scope
|
|
54
|
+
|
|
55
|
+
A strict scope denies categories outside the rule. A permissive scope can allow
|
|
56
|
+
out-of-scope categories unless another restriction applies. When a rule selects
|
|
57
|
+
only some optional categories, set `scopeMode` explicitly. An omitted scope,
|
|
58
|
+
`['*']`, or a list containing only `necessary` expands to the default optional
|
|
59
|
+
categories; a necessary-only list is not a shortcut for disabling all tracking.
|
|
60
|
+
|
|
61
|
+
For example, a backend rule with `categories: ['necessary']` and scripts assigned
|
|
62
|
+
to `marketing` and `measurement` displays Necessary, Marketing, and Analytics,
|
|
63
|
+
even without `consentCategories`. Accept All records both optional choices and
|
|
64
|
+
keeps the banner dismissed after reload. An explicit list of
|
|
65
|
+
`['necessary', 'measurement']` with no other integrations displays Necessary and
|
|
66
|
+
Analytics.
|
|
67
|
+
|
|
68
|
+
Hidden optional categories do not need a choice to dismiss the banner. Their
|
|
69
|
+
permissions still follow the backend policy, so hidden opt-in categories remain
|
|
70
|
+
denied without a valid grant. React providers enable DOM iframe blocking and
|
|
71
|
+
discovery by default. The blocker loads when the first iframe with
|
|
72
|
+
`data-category` or `data-vendor` is on the page, so pages without one never
|
|
73
|
+
download it; until it runs, such an iframe that arrives with a `src` consent
|
|
74
|
+
does not allow is paused. Write gated iframes with `data-src`, not `src`. The
|
|
75
|
+
browser requests a `src` as soon as the iframe is in the document, before the
|
|
76
|
+
blocker can pause it, whether the iframe comes from the server HTML or a
|
|
77
|
+
client render. Set
|
|
78
|
+
`iframeBlocker: false` to disable it, or
|
|
79
|
+
`iframeBlocker: { disableAutomaticBlocking: true }` to scan manually with
|
|
80
|
+
`useIframeBlocker({ disableAutomaticBlocking: true }).processAllIframes()`.
|
|
81
|
+
|
|
82
|
+
Use effective permissions to gate work and explicit choices to inspect what the
|
|
83
|
+
visitor confirmed. Read [how consent works](./how-consent-works.md#a-permission-is-not-a-recorded-choice) for that
|
|
84
|
+
distinction and [policies](./policies.md) for v3 policy configuration.
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Consent state reference
|
|
3
|
+
description: How c15t saves choices, gates IAB vendors, hydrates server records
|
|
4
|
+
and keeps browser tabs and storage in step.
|
|
5
|
+
group: concepts
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
This reference covers the edge cases behind the model in
|
|
9
|
+
[how consent works](./how-consent-works.md). You need it when you
|
|
10
|
+
build custom consent UI, run c15t across subdomains, or debug a choice that
|
|
11
|
+
changes between tabs.
|
|
12
|
+
|
|
13
|
+
## Gate IAB vendors on the TC string
|
|
14
|
+
|
|
15
|
+
Under an IAB policy, a script, network rule or iframe that names a `vendorId`
|
|
16
|
+
or IAB purposes is an IAB target. It runs only while a confirmed TC string
|
|
17
|
+
grants what it declares: purpose and vendor consent for `iabPurposes`, purpose
|
|
18
|
+
and vendor legitimate interest for `iabLegIntPurposes`, and opt-ins for
|
|
19
|
+
`iabSpecialFeatures`. Every category it names must also be free of
|
|
20
|
+
restrictions, so GPC or strict scope blocks it.
|
|
21
|
+
|
|
22
|
+
A refused category is the one exception. It does not block an IAB target that
|
|
23
|
+
processes only on legitimate interest, after publisher restrictions, because
|
|
24
|
+
the TCF lets that processing run without consent. The visitor's control for it
|
|
25
|
+
is the objection, which the legitimate interest signals record. The category
|
|
26
|
+
itself stays refused: its `effectivePermissions` entry is `false`, and targets
|
|
27
|
+
that name only the category, or that also declare a consent purpose or special
|
|
28
|
+
feature, stay blocked.
|
|
29
|
+
|
|
30
|
+
## Categories under an IAB policy
|
|
31
|
+
|
|
32
|
+
Under an IAB policy the visitor chooses TCF purposes, and c15t derives each
|
|
33
|
+
category from them when the choice is saved. A category is granted when the
|
|
34
|
+
visitor consented to every purpose in it that a listed vendor processes on
|
|
35
|
+
consent:
|
|
36
|
+
|
|
37
|
+
| Category | TCF purposes |
|
|
38
|
+
| --------------- | ------------ |
|
|
39
|
+
| `marketing` | 2, 3, 4 |
|
|
40
|
+
| `measurement` | 7, 8, 9 |
|
|
41
|
+
| `experience` | 5, 6 |
|
|
42
|
+
| `functionality` | 10, 11 |
|
|
43
|
+
|
|
44
|
+
`necessary` is always granted. Purpose 1, device storage, decides no category.
|
|
45
|
+
|
|
46
|
+
Accept All consents only to the purposes and special features that the vendors
|
|
47
|
+
in your list declare, because the TCF Policies allow no consent signal for a
|
|
48
|
+
purpose the visitor was not shown. A category whose purposes no listed vendor
|
|
49
|
+
processes on consent therefore stays denied after Accept All, and so does every
|
|
50
|
+
script, iframe or network rule gated on it. With a vendor list whose vendors
|
|
51
|
+
declare only purposes 1 to 4 and 7, for example, `experience` and
|
|
52
|
+
`functionality` stay denied.
|
|
53
|
+
|
|
54
|
+
If your own scripts depend on such a category, do one of these:
|
|
55
|
+
|
|
56
|
+
* Declare the processing. Add a custom vendor for it, with the purposes it
|
|
57
|
+
uses, through the `customVendors` option of your IAB setup. The preference
|
|
58
|
+
centre then lists those purposes, and Accept All and the purpose switches
|
|
59
|
+
can grant the category.
|
|
60
|
+
* Gate the script on a category that your listed vendors' purposes cover, or on
|
|
61
|
+
`necessary` if it needs no consent.
|
|
62
|
+
|
|
63
|
+
Check `effectivePermissions` after Accept All on a fresh visit to see which
|
|
64
|
+
categories your vendor list leaves denied.
|
|
65
|
+
|
|
66
|
+
## When a choice is saved
|
|
67
|
+
|
|
68
|
+
A save records the choice in the browser first and sends it to the backend
|
|
69
|
+
afterwards. The stock banner, preference dialog and IAB surfaces in every
|
|
70
|
+
framework adapter, and the `acceptAll()`, `rejectAll()` and `save()` methods
|
|
71
|
+
of the browser and Astro clients, close without waiting for the backend. In
|
|
72
|
+
order:
|
|
73
|
+
|
|
74
|
+
1. In the click task, the explicit choice and effective permissions change,
|
|
75
|
+
`onChoiceRecorded` and `onPermissionsChanged` run, gated scripts, iframes
|
|
76
|
+
and network rules follow the new permissions, and the surface leaves the
|
|
77
|
+
active state. Its exit animation still plays.
|
|
78
|
+
2. In the next task, the choice is written to the cookie and localStorage.
|
|
79
|
+
3. After that write is queued, the request to the backend starts.
|
|
80
|
+
4. When the request settles, the kernel emits `command:save:completed`. The
|
|
81
|
+
promise returned by `kernel.commands.save()`, React's `performAction()` and
|
|
82
|
+
Svelte's `saveConsents()` resolves or rejects only then.
|
|
83
|
+
5. If the save turned off a category or vendor that was granted, the page
|
|
84
|
+
reloads in the next task, after `onBeforeConsentRevocationReload` runs.
|
|
85
|
+
Removing a script cannot stop code that already ran, so the reload starts a
|
|
86
|
+
page with only permitted code. With several saves in flight, it waits for
|
|
87
|
+
the last one. Set `reloadOnConsentRevoked: false` to handle revocation
|
|
88
|
+
yourself.
|
|
89
|
+
|
|
90
|
+
A failed request does not reopen the surface or roll the choice back. The
|
|
91
|
+
kernel emits `command:error`, which reaches the `onError` callback in adapters
|
|
92
|
+
that accept one, and queues the payload in localStorage. Where localStorage is
|
|
93
|
+
unavailable, the queue lives in memory until the page unloads. The queue is
|
|
94
|
+
replayed after the next successful initialization and when the browser comes
|
|
95
|
+
back online, up to 10 attempts over 7 days. A replay carries the original action
|
|
96
|
+
time and policy snapshot token, so the backend records when the visitor
|
|
97
|
+
decided, and a duplicate submission resolves to the same consent record. A
|
|
98
|
+
backend that signs policy snapshot tokens rejects a replay made after the
|
|
99
|
+
token expires, which is 30 minutes by default for the self-hosted backend. A
|
|
100
|
+
save still queued by then is recorded only in the browser.
|
|
101
|
+
|
|
102
|
+
A replay that fails and stays queued emits `save:replayed` with `ok: false` but
|
|
103
|
+
not `command:error`, since the save already reported its first failure. A save
|
|
104
|
+
that leaves the queue without the backend recording it, because it ran out of
|
|
105
|
+
attempts, waited more than 7 days or was refused, emits `command:error` once.
|
|
106
|
+
Development builds also log a `[c15t]` console warning for each of these.
|
|
107
|
+
|
|
108
|
+
IAB surfaces close in the click task too, but an IAB choice is recorded only
|
|
109
|
+
after its TC string is encoded, which can wait for the TCF library to load.
|
|
110
|
+
If that local step records nothing, for example because the vendor list
|
|
111
|
+
failed to load, the surface comes back so the visitor can try again.
|
|
112
|
+
|
|
113
|
+
Every adapter leaves the same surface after a save: the banner while the
|
|
114
|
+
policy still owes a choice or a notice, and nothing once no prompt is owed,
|
|
115
|
+
while the policy is still loading, or after it failed to resolve. A banner
|
|
116
|
+
the visitor reopened closes too. A save that records nothing new, such as an
|
|
117
|
+
unchanged selection, closes the surface when it resolves successfully. Opening
|
|
118
|
+
or closing a surface, or starting another save, before then leaves the surface
|
|
119
|
+
as the visitor set it.
|
|
120
|
+
|
|
121
|
+
Closing the dialog without saving, with Escape or the adapter's close call,
|
|
122
|
+
leaves the same surface a save would. A visitor who opens preferences from the
|
|
123
|
+
banner and backs out still owes a choice, so the banner comes back. Once a
|
|
124
|
+
choice is recorded, closing the dialog leaves nothing open.
|
|
125
|
+
|
|
126
|
+
## Preserve records during hydration
|
|
127
|
+
|
|
128
|
+
Server helpers return records with policy information and evaluation time.
|
|
129
|
+
Forward that configuration intact. Copying an allowed category into a receipt
|
|
130
|
+
would invent a grant and lose its original confirmation time.
|
|
131
|
+
|
|
132
|
+
Valid v2 records can be read without a startup rewrite. The next explicit action
|
|
133
|
+
writes the v3 format. The upgrade guides for
|
|
134
|
+
[Next.js](https://v3.c15t.com/docs/frameworks/next/upgrade-v3#keep-visitors-existing-choices),
|
|
135
|
+
[React](../frameworks/react/upgrade-v3.md#keep-visitors-existing-choices) and
|
|
136
|
+
[JavaScript](https://v3.c15t.com/docs/frameworks/javascript/upgrade-v3#keep-visitors-existing-choices)
|
|
137
|
+
cover how v3 treats v2 records.
|
|
138
|
+
|
|
139
|
+
## Keep open tabs in step
|
|
140
|
+
|
|
141
|
+
Tabs and windows on the same origin (scheme, host and port) share the consent
|
|
142
|
+
cookie and localStorage. When a visitor rejects in one tab, every other open tab
|
|
143
|
+
on that origin applies the rejection without a reload. Scripts and features
|
|
144
|
+
gated on `effectivePermissions` lose permission, subscribers are notified once
|
|
145
|
+
and `onPermissionsChanged` fires. Clearing records in one tab returns the others
|
|
146
|
+
to the active policy's defaults: an opt-in policy denies optional categories and
|
|
147
|
+
shows the prompt again.
|
|
148
|
+
|
|
149
|
+
Tabs on another subdomain that shares the consent cookie do not share
|
|
150
|
+
localStorage, so the browser sends them no `storage` event. They pick up the
|
|
151
|
+
change on their next focus or visibility change, or when you reconcile
|
|
152
|
+
yourself as described in
|
|
153
|
+
[Reconcile yourself or turn it off](#reconcile-yourself-or-turn-it-off).
|
|
154
|
+
|
|
155
|
+
The `storage` event only exists for localStorage. When localStorage is
|
|
156
|
+
unavailable (blocked by the browser, a sandboxed frame or a privacy mode) and
|
|
157
|
+
c15t stores in the cookie alone, another tab's change arrives only on the next
|
|
158
|
+
focus or visibility change, or when you call `runtime.reconcileStorage()` or
|
|
159
|
+
`persistence.reconcile()`. c15t does not poll the cookie or use a
|
|
160
|
+
`BroadcastChannel` for this.
|
|
161
|
+
|
|
162
|
+
Browser persistence reads stored records again at these moments:
|
|
163
|
+
|
|
164
|
+
| Moment | What happens |
|
|
165
|
+
| ------------------------------------------------------------------------ | --------------------------------- |
|
|
166
|
+
| Another tab or window on the same origin changes a c15t localStorage key | Reconciles on the `storage` event |
|
|
167
|
+
| The page becomes visible again | Reconciles on `visibilitychange` |
|
|
168
|
+
| The window regains focus | Reconciles on `focus` |
|
|
169
|
+
| The network reconnects | No storage read |
|
|
170
|
+
|
|
171
|
+
Several triggers in quick succession run one reconciliation in a later task.
|
|
172
|
+
Reconnecting does not read storage, because going online changes nothing in
|
|
173
|
+
browser storage. On reconnect the kernel retries saves the backend did not
|
|
174
|
+
accept and a failed initialization. Save retries do not write the cookie or
|
|
175
|
+
localStorage. A write that follows initialization obeys the rules in
|
|
176
|
+
[Ordering with pending writes](#ordering-with-pending-writes).
|
|
177
|
+
|
|
178
|
+
Every adapter that mounts browser persistence does this: the React, Next.js and
|
|
179
|
+
TanStack Start providers, Vue, Svelte, Astro, the script tag and
|
|
180
|
+
`createConsentRuntime`. With `persistence: false` nothing is stored or read.
|
|
181
|
+
|
|
182
|
+
### What a reconciliation applies
|
|
183
|
+
|
|
184
|
+
Stored records are merged into the ones in memory:
|
|
185
|
+
|
|
186
|
+
* Category decisions merge per category. Each category keeps the decision with
|
|
187
|
+
the newer confirmation time, so a tab that only changed marketing never
|
|
188
|
+
reverts another tab's newer measurement decision.
|
|
189
|
+
* The notice dismissal and the vendor record are single decisions. A stored one
|
|
190
|
+
at least as new as the one in memory replaces it; an older one is ignored.
|
|
191
|
+
* When two tabs record decisions in the same millisecond, the one stored first
|
|
192
|
+
wins in both tabs.
|
|
193
|
+
* Tabs keep one subject. A stored subject is considered only when the record
|
|
194
|
+
that carries it changed, never on focus alone. It replaces a subject the tab
|
|
195
|
+
generated on its first save or copied from storage, so a tab that opened
|
|
196
|
+
before another tab stored a subject joins it. A subject id the server
|
|
197
|
+
resolved (at init, from a prefetch or in a save response) is replaced only by
|
|
198
|
+
a strictly newer stored choice, and an identity set with `identify()` is
|
|
199
|
+
never replaced.
|
|
200
|
+
* Under an IAB policy, the TC string follows the reconciled choice. See
|
|
201
|
+
[How the TC string is reconciled](#how-the-tc-string-is-reconciled).
|
|
202
|
+
* A record removed from readable storage since this tab last saw it present is
|
|
203
|
+
cleared, and the active policy decides again. A record this tab never saw in
|
|
204
|
+
storage, such as a receipt merged from the server after `identify()` or a
|
|
205
|
+
choice seeded while storage was blocked, stays.
|
|
206
|
+
* Blocked storage, or bytes that do not decode, change nothing. A failed read
|
|
207
|
+
never grants a category.
|
|
208
|
+
|
|
209
|
+
Expiry is not decided here. The applied records are evaluated at the time of the
|
|
210
|
+
read, the same way as at startup.
|
|
211
|
+
|
|
212
|
+
### How the TC string is reconciled
|
|
213
|
+
|
|
214
|
+
Under an IAB policy, a tab adopts the TC string another tab stored, unless that
|
|
215
|
+
TC string conflicts with the reconciled choice. A conflicting TC string is
|
|
216
|
+
withdrawn, and `__tcfapi` reports no consent until the next save. Vendors never
|
|
217
|
+
receive a stale TC string: c15t withdraws it, or holds it back, before
|
|
218
|
+
`__tcfapi` publishes.
|
|
219
|
+
|
|
220
|
+
1. The `@c15t/iab` module loads the TC string the other tab stored, with its
|
|
221
|
+
purpose, vendor and special-feature selections, so `__tcfapi` and the
|
|
222
|
+
preference controls show the choice now in force. Selections the visitor
|
|
223
|
+
changed in this tab without saving are kept.
|
|
224
|
+
2. A missing or older stored TC string leaves the current one in place, unless
|
|
225
|
+
the current one conflicts with the reconciled choice. Then it is withdrawn.
|
|
226
|
+
3. A category denied after the TC string was saved conflicts if the TC string
|
|
227
|
+
grants any of its purposes. A partial selection saved through IAB, which
|
|
228
|
+
records its category as denied because not every purpose is granted, keeps
|
|
229
|
+
its TC string.
|
|
230
|
+
4. A TC string confirmed before the choice's newest decision no longer
|
|
231
|
+
describes it and is withdrawn, unless a newer receipt replaces it. That
|
|
232
|
+
receipt is adopted even when its TC string is identical, because TC strings
|
|
233
|
+
round their time to the day and custom-vendor selections live only in the
|
|
234
|
+
receipt. The receipt's expiry then applies.
|
|
235
|
+
5. The TC string and its receipt (`euconsent-v2`, `c15t-iab-authority-v1`)
|
|
236
|
+
belong to one origin. After a save on a sibling subdomain that shares the
|
|
237
|
+
consent cookie, this subdomain reports no consent through `__tcfapi` until
|
|
238
|
+
it saves again.
|
|
239
|
+
6. A tab reloads the TC string when another tab on the same origin stores a
|
|
240
|
+
new one. This covers a save in the same millisecond, or one that changed
|
|
241
|
+
only vendors.
|
|
242
|
+
7. When two tabs save in the same millisecond with different selections, the
|
|
243
|
+
more restrictive TC string wins in both, so a revoked vendor is never
|
|
244
|
+
advertised again. If each grants something the other denies, neither is
|
|
245
|
+
published until the next save, and the stored receipt is removed so a page
|
|
246
|
+
opened later does not restore it.
|
|
247
|
+
8. When another tab removes the receipt or clears localStorage, this tab
|
|
248
|
+
withdraws its TC string too, because a page opened now would find none. A
|
|
249
|
+
receipt this tab was still decoding is not installed.
|
|
250
|
+
9. The removal after a tie checks that the stored receipt is the one it read.
|
|
251
|
+
localStorage has no conditional removal, so a receipt another tab stored a
|
|
252
|
+
moment earlier can still be removed. Every tab then withdraws its TC string
|
|
253
|
+
until the next save, so the race only ever withholds consent.
|
|
254
|
+
|
|
255
|
+
### Clearing records across tabs
|
|
256
|
+
|
|
257
|
+
`clearRecords()` also drops every save queued for replay, with or without
|
|
258
|
+
browser persistence, so nothing the visitor decided before the clear reaches
|
|
259
|
+
the backend afterwards.
|
|
260
|
+
|
|
261
|
+
`clearRecords()` removes every record and then stores the time of the clear,
|
|
262
|
+
the clear epoch, under its own key: `c15t-epoch` in localStorage and a cookie
|
|
263
|
+
of the same name (`<storageKey>-epoch` with a custom `storageKey`). Clearing
|
|
264
|
+
never removes it. Every consent record written afterwards also records the
|
|
265
|
+
epoch it was written under.
|
|
266
|
+
|
|
267
|
+
A decision confirmed before the epoch was made before the clear, so it is void
|
|
268
|
+
wherever it turns up. A decision stamped in the very millisecond of the clear
|
|
269
|
+
counts only if it comes from a tab that had already seen that clear, so a tab's
|
|
270
|
+
own choice right after its own clear stands while another tab's decision in the
|
|
271
|
+
same millisecond does not. A stored record left with no decisions after the
|
|
272
|
+
clear is void too, subject included.
|
|
273
|
+
|
|
274
|
+
* A tab that reconciles only after another tab cleared and saved again drops
|
|
275
|
+
its pre-clear decisions instead of merging them back. Its subject from before
|
|
276
|
+
the clear is dropped too.
|
|
277
|
+
* A tab that missed the clear writes only decisions it made after it. Its queued
|
|
278
|
+
write of an earlier decision is discarded, so it cannot bring back a cleared
|
|
279
|
+
record.
|
|
280
|
+
* Browser hydration and server reads (`readStoredRecordsFromCookieHeader`, used
|
|
281
|
+
by the Next.js, TanStack Start, Nuxt, SvelteKit and Astro helpers) apply the
|
|
282
|
+
same rule, so a server render agrees with the browser.
|
|
283
|
+
|
|
284
|
+
Records from before any clear, including v2 and legacy records, read as epoch 0
|
|
285
|
+
and are unaffected. A corrupt epoch, or one that cannot be read, also reads as
|
|
286
|
+
0: it voids nothing, so a failed read never grants a category. A consent record
|
|
287
|
+
whose own epoch field is corrupt is kept, and only its epoch is ignored.
|
|
288
|
+
|
|
289
|
+
Each clear moves the epoch forward, even when the device clock went back, so a
|
|
290
|
+
later clear never lets earlier decisions back in. Two clears in the same
|
|
291
|
+
millisecond therefore leave the epoch a millisecond ahead, and a decision saved
|
|
292
|
+
in that millisecond is void. An epoch up to one hour ahead
|
|
293
|
+
of the clock is kept, as it is when the clock was set back after a clear. Until
|
|
294
|
+
the clock catches up, decisions saved in that window are void too. An epoch more
|
|
295
|
+
than an hour ahead is treated as corrupt and reads as 0, so a clear never writes
|
|
296
|
+
one: after the clock went back more than an hour, the new epoch is capped at an
|
|
297
|
+
hour ahead of the clock.
|
|
298
|
+
|
|
299
|
+
That cap is a known limit. A cleared record carries times from before the clock
|
|
300
|
+
went back, and a runtime that missed the clear can write those times back. The
|
|
301
|
+
capped epoch is lower than them, so once the clock has recovered, such a
|
|
302
|
+
decision counts again. Leaving the epoch uncapped does not help: every tab
|
|
303
|
+
whose clock is still behind reads it as corrupt, which voids nothing. Times
|
|
304
|
+
alone cannot order a clear against decisions stamped by a clock that went back
|
|
305
|
+
more than an hour.
|
|
306
|
+
|
|
307
|
+
This changes the stored format. After a clear, the consent cookie carries
|
|
308
|
+
`&e=<time>` (16 bytes) and the localStorage record an `epoch` field (22 bytes),
|
|
309
|
+
and the epoch cookie itself holds a 13-digit time. Visitors who never cleared
|
|
310
|
+
their records store exactly what they did before. An older c15t build rejects
|
|
311
|
+
both the cookie and the localStorage record once they carry the epoch, so a page
|
|
312
|
+
still running one treats the visitor as undecided. Under an opt-out policy that
|
|
313
|
+
page grants optional categories by default until a new choice is saved, and the
|
|
314
|
+
configured prompt may appear again. Deploy the new build to every page of the
|
|
315
|
+
site before visitors can clear their records.
|
|
316
|
+
|
|
317
|
+
### When the cookie and localStorage disagree
|
|
318
|
+
|
|
319
|
+
When both copies hold a decision for a category from the same millisecond and
|
|
320
|
+
the two conflict, the denial wins.
|
|
321
|
+
|
|
322
|
+
The subject and IAB metadata come from the cookie. A server response, such as
|
|
323
|
+
server-side consent restoration, and a sibling subdomain sharing the cookie
|
|
324
|
+
with `crossSubdomain` can both rewrite it without touching this origin's
|
|
325
|
+
localStorage, so the local copy can be the older one. The local copy's subject
|
|
326
|
+
is used only when this browser's last consent write reached localStorage but
|
|
327
|
+
not the cookie, for example because the cookie grew past the size limit, and
|
|
328
|
+
the cookie has not changed since. Such a write stores the cookie as it stood
|
|
329
|
+
under `<storageKey>-cookie-miss` in localStorage; the next write that reaches
|
|
330
|
+
the cookie, or a clear, removes it. When localStorage rejects a write that the
|
|
331
|
+
cookie takes, for example because storage is full, the older localStorage copy
|
|
332
|
+
is removed.
|
|
333
|
+
|
|
334
|
+
When a server render seeded the page from the consent cookie
|
|
335
|
+
(`skipHydration`), that seed stays authoritative. A denial that reached only
|
|
336
|
+
localStorage is still applied on top of it when the page mounts, since it can
|
|
337
|
+
only restrict, if it is newer than the seeded decision or from the same
|
|
338
|
+
millisecond as a seeded grant. A stored grant is not.
|
|
339
|
+
|
|
340
|
+
The notice dismissal and vendor denials are stored twice as well. A vendor
|
|
341
|
+
list in localStorage at least as new as the cookie's adds its denials but never
|
|
342
|
+
lifts one the cookie holds; a copy confirmed before the last clear is ignored, so its
|
|
343
|
+
denials never come back. The newer notice dismissal applies; it still only hides a
|
|
344
|
+
notice with the fingerprint it names. The consent record follows the rules
|
|
345
|
+
below.
|
|
346
|
+
|
|
347
|
+
The consent record is stored twice: as a cookie, which a server render reads,
|
|
348
|
+
and in localStorage. The cookie is authoritative. A well-formed cookie wins
|
|
349
|
+
even when it has expired, so a local copy can never bring back a grant the
|
|
350
|
+
cookie no longer carries.
|
|
351
|
+
|
|
352
|
+
A browser can still drop a cookie write, for example when the record grows past
|
|
353
|
+
the cookie size limit, while localStorage takes it. A denial in the local copy
|
|
354
|
+
that is newer than the cookie's decision for that category is therefore applied
|
|
355
|
+
on top of the cookie. A newer local grant is not, so a dropped cookie write only
|
|
356
|
+
ever leaves the visitor with less permission. A server render sees only the
|
|
357
|
+
cookie; after a dropped write that carried a denial, the browser is the stricter
|
|
358
|
+
of the two.
|
|
359
|
+
|
|
360
|
+
When the two copies were written under different clear epochs, each loses its
|
|
361
|
+
decisions from before the later epoch first, and the same rule applies to what
|
|
362
|
+
remains. A later epoch in the local copy never lets its grant replace a cookie
|
|
363
|
+
denial. The subject and IAB metadata come from the copy written under the later
|
|
364
|
+
epoch; a record written before the clear in force keeps its later decisions but
|
|
365
|
+
no subject.
|
|
366
|
+
|
|
367
|
+
A server render cannot know about a clear that never reached a cookie. If the
|
|
368
|
+
page could write localStorage but its cookie writes failed during
|
|
369
|
+
`clearRecords()` (cookies blocked for the page, or a cookie setter that
|
|
370
|
+
throws), the removal of the consent cookie failed too, and so did the epoch
|
|
371
|
+
cookie. The browser then applies the clear from localStorage, while a server
|
|
372
|
+
render still reads the old consent cookie until the next successful cookie
|
|
373
|
+
write. The same applies to a consent cookie set with a different `domain` than
|
|
374
|
+
the current `storageConfig` uses, which the clear cannot remove.
|
|
375
|
+
|
|
376
|
+
### Ordering with pending writes
|
|
377
|
+
|
|
378
|
+
A tab writes its own choices in a later task, not during the click. Before it
|
|
379
|
+
reads storage, it lands its own queued writes, so a reconciliation never undoes
|
|
380
|
+
the visitor's latest action in that tab. A queued write follows the same rules
|
|
381
|
+
as a read: it stores the per-category merge of its choice and the stored one,
|
|
382
|
+
and never replaces a newer notice or vendor record. It keeps the stored subject
|
|
383
|
+
unless this tab identified a different user.
|
|
384
|
+
When a slow save response returns a server subject id, the tab adds it only to
|
|
385
|
+
the record it wrote; it does not recreate records another tab cleared or
|
|
386
|
+
overwrite another tab's newer choice.
|
|
387
|
+
|
|
388
|
+
Two tabs that write at the same moment can both read storage before either
|
|
389
|
+
writes, and the later write can then drop the other tab's category decision.
|
|
390
|
+
The tab whose decision was dropped still holds it, and on its next
|
|
391
|
+
reconciliation it writes it back, merged with what storage holds. It does so
|
|
392
|
+
only for decisions it actually stored itself, never for one a clear voided or
|
|
393
|
+
another tab replaced with a newer one.
|
|
394
|
+
|
|
395
|
+
If another tab's change reaches this tab while one of its saves is pending, the
|
|
396
|
+
save depends on how far it got. A save not yet sent is dropped. A request
|
|
397
|
+
already sent still reaches the backend, which may record it; this tab ignores
|
|
398
|
+
the response, so it queues no retry and applies no subject id from it. A save
|
|
399
|
+
already queued for retry keeps its original decision time.
|
|
400
|
+
|
|
401
|
+
### Reconcile yourself or turn it off
|
|
402
|
+
|
|
403
|
+
Browsers send no event for a change made in the same document, or for a cookie
|
|
404
|
+
rewritten without a localStorage change, such as a `Set-Cookie` response header
|
|
405
|
+
or another subdomain sharing the cookie. The next focus or visibility change
|
|
406
|
+
picks it up. To apply it at once, call the method yourself:
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
// Runtime owners: returns true when any record changed.
|
|
410
|
+
runtime.reconcileStorage();
|
|
411
|
+
|
|
412
|
+
// Kernel owners with createPersistence from c15t/modules/persistence:
|
|
413
|
+
persistence.reconcile();
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
To keep storage but stop automatic reconciliation, pass `sync: false` in the
|
|
417
|
+
persistence options, for example `createConsentRuntime({ persistence: { sync:
|
|
418
|
+
false } })` or `createPersistence({ kernel, sync: false })`. The manual methods
|
|
419
|
+
still work.
|
|
420
|
+
|
|
421
|
+
`dispose()` removes the listeners and cancels a scheduled reconciliation.
|