@c15t/react 3.0.0-alpha.4 → 3.0.0-alpha.6
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 +8 -5
- 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/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/index.js +1 -1
- package/dist/components/panel/index.js +1 -1
- package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
- package/dist/components/panel-trigger/atoms/trigger-icons.js +1 -1
- package/dist/components/panel-trigger/panel-trigger.js +1 -1
- package/dist/components/preferences/atoms/accordion.js +1 -1
- package/dist/components/preferences/index.js +1 -1
- package/dist/components/shared/primitives/button.js +1 -1
- package/dist/components/shared/ui/accordion/accordion.js +1 -1
- package/dist/components/shared/ui/collapsible/collapsible.js +1 -1
- package/dist/components/shared/ui/preference-item/preference-item.js +1 -1
- package/dist/components/shared/ui/switch/switch.js +1 -1
- package/dist/components/shared/ui/tabs/tabs.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/hooks/use-color-scheme.js +1 -1
- package/dist/hooks/use-headless-iab-consent-ui.js +1 -1
- package/dist/hooks.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/hooks.js +1 -0
- package/dist/index-parts/module-hooks.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 -1
- package/dist/module-hooks/iframe-blocker.js +1 -1
- package/dist/provider.js +1 -1
- package/dist/styles.tw3.css +27 -3
- package/dist/version.js +1 -1
- package/dist-types/component-hooks/use-headless-consent-ui.d.ts +4 -4
- package/dist-types/components/iab-panel/index.d.ts +0 -1
- package/dist-types/components/panel/index.d.ts +0 -1
- package/dist-types/components/panel-trigger/atoms/trigger-icons.d.ts +5 -0
- package/dist-types/components/panel-trigger/panel-trigger.d.ts +0 -8
- package/dist-types/components/preferences/index.d.ts +0 -1
- package/dist-types/components/shared/ui/accordion/accordion.d.ts +0 -1
- package/dist-types/components/shared/ui/collapsible/collapsible.d.ts +0 -1
- package/dist-types/components/shared/ui/preference-item/preference-item.d.ts +0 -1
- package/dist-types/components/shared/ui/switch/switch.d.ts +0 -1
- package/dist-types/components/shared/ui/tabs/tabs.d.ts +0 -1
- package/dist-types/devtools-launcher.d.ts +29 -0
- package/dist-types/draft-context.d.ts +31 -0
- package/dist-types/draft.d.ts +13 -36
- package/dist-types/gpp.d.ts +27 -0
- package/dist-types/hooks/use-iab-manager.d.ts +4 -4
- package/dist-types/hooks.d.ts +30 -5
- 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 +8 -0
- package/dist-types/index-parts/draft.d.ts +7 -0
- package/dist-types/index-parts/hooks.d.ts +7 -0
- package/dist-types/index-parts/module-hooks.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 +15 -16
- package/dist-types/kernel-selector.d.ts +0 -8
- package/dist-types/module-hooks/iframe-blocker.d.ts +12 -7
- package/dist-types/provider.d.ts +47 -13
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +8 -5
- package/docs/concepts/choose-your-setup.md +22 -5
- package/docs/concepts/consent-state.md +60 -5
- package/docs/concepts/data-fetching.md +34 -11
- package/docs/customization/recipes.md +4 -5
- package/docs/customization/slots.md +3 -2
- package/docs/customization/stylesheets.md +29 -39
- package/docs/customization/tailwind.md +5 -5
- package/docs/customization/tokens.md +1 -1
- package/docs/customization/translations.md +33 -7
- package/docs/frameworks/react/components/consent-dialog-trigger.md +5 -0
- package/docs/frameworks/react/components/consent-dialog.md +2 -1
- package/docs/frameworks/react/components/consent-provider.md +6 -2
- package/docs/frameworks/react/components/dev-tools.md +24 -1
- package/docs/frameworks/react/gpp.md +163 -0
- package/docs/frameworks/react/headless.md +4 -4
- package/docs/frameworks/react/hooks.md +10 -5
- package/docs/frameworks/react/iab.md +24 -18
- package/docs/frameworks/react/quickstart.md +77 -16
- package/docs/frameworks/react/rendering.md +82 -11
- package/docs/frameworks/react/scripts.md +8 -3
- package/docs/frameworks/react/upgrade-v3.md +596 -0
- package/docs/frameworks/react/vendor-consent.md +6 -0
- package/docs/guides/banner-experiments.md +26 -5
- package/docs/guides/troubleshooting.md +3 -2
- package/docs/integrations/overview.md +28 -27
- package/docs/integrations/posthog.md +99 -11
- package/docs/integrations/sentry.md +416 -0
- package/docs/upgrade-v3.md +222 -314
- package/package.json +13 -8
- package/dist/ui-save.js +0 -1
- package/dist-types/ui-save.d.ts +0 -25
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: IAB GPP
|
|
3
|
+
description: Add the IAB Global Privacy Platform API (__gpp) to a React app with
|
|
4
|
+
ConsentGPP from @c15t/react/gpp, so ad tech can read US state opt-outs and the
|
|
5
|
+
TCF EU consent string.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What GPP does
|
|
10
|
+
|
|
11
|
+
`ConsentGPP` from `@c15t/react/gpp` installs `window.__gpp`, the GPP 1.1 CMP
|
|
12
|
+
API, and keeps its GPP string in step with the visitor's choices. Prebid.js,
|
|
13
|
+
Google Ad Manager and other ad tech read US privacy signals from it.
|
|
14
|
+
|
|
15
|
+
## Turn on GPP
|
|
16
|
+
|
|
17
|
+
Render `ConsentGPP` inside `ConsentProvider`:
|
|
18
|
+
|
|
19
|
+
```tsx title="src/consent.tsx"
|
|
20
|
+
import { ConsentGPP } from '@c15t/react/gpp';
|
|
21
|
+
|
|
22
|
+
<ConsentProvider options={{ mode, scripts }}>
|
|
23
|
+
<ConsentGPP />
|
|
24
|
+
{children}
|
|
25
|
+
</ConsentProvider>;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`ConsentGPP` renders nothing. It installs `__gpp` after it mounts and
|
|
29
|
+
removes it when it unmounts. It lives in its own entry, like
|
|
30
|
+
`@c15t/react/iab`, so apps that do not import it bundle none of the GPP
|
|
31
|
+
code. `@c15t/react` already depends on `@c15t/iab`; with the `c15t`
|
|
32
|
+
package, import from `c15t/react/gpp`.
|
|
33
|
+
|
|
34
|
+
The props are the GPP options listed below, for example
|
|
35
|
+
`<ConsentGPP usFallback="none" />`. It works the same with a runtime you
|
|
36
|
+
pass to `ConsentProvider` as `runtime`.
|
|
37
|
+
|
|
38
|
+
The GPP string follows the policy and location your backend resolves for the
|
|
39
|
+
visitor, so a US visitor's state comes from the backend. With `offline()`
|
|
40
|
+
mode, pass the state yourself, for example
|
|
41
|
+
`overrides: { country: 'US', region: 'CA' }`.
|
|
42
|
+
|
|
43
|
+
To add the TCF EU section for visitors under an `iab` policy, set up
|
|
44
|
+
[IAB TCF](./iab.md) as well.
|
|
45
|
+
|
|
46
|
+
## How the policy decides the section
|
|
47
|
+
|
|
48
|
+
The policy rule c15t matched for the visitor decides whether a section
|
|
49
|
+
applies. The visitor's location only picks which US section carries it.
|
|
50
|
+
|
|
51
|
+
| Matched rule and visitor | Section in the GPP string |
|
|
52
|
+
| ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
53
|
+
| `iab` rule, with IAB TCF set up | `tcfeuv2` (ID 2): the TC String the CMP confirmed, unchanged |
|
|
54
|
+
| `iab` rule, any visitor, with `tcf: false` | None; `applicableSections` is `[-1]` |
|
|
55
|
+
| Any other rule with the `preferences` or `opt-out` right, US visitor in a state with a section | That state's section, such as `usca` (ID 8) |
|
|
56
|
+
| The same rule, US visitor whose region is unknown or whose state has no section | `usnat` (ID 7), MSPA US National version 2; none with `usFallback: 'none'` |
|
|
57
|
+
| The same rule, `usApproach: 'national'` | `usnat` for every US visitor |
|
|
58
|
+
| A rule without those rights, such as `none` | None |
|
|
59
|
+
| A visitor outside the US under any rule other than `iab` | None |
|
|
60
|
+
|
|
61
|
+
Every `opt-in` and `opt-out` rule has the `preferences` right, and every
|
|
62
|
+
`opt-out` rule also has `opt-out`. A `none` rule has them only if you add
|
|
63
|
+
them. A rule that gives the visitor no way to opt out produces no US
|
|
64
|
+
section, even when the visitor sends a GPC signal.
|
|
65
|
+
|
|
66
|
+
State sections exist for California, Colorado, Connecticut, Delaware, Florida,
|
|
67
|
+
Iowa, Minnesota, Montana, Nebraska, New Hampshire, New Jersey, Oregon,
|
|
68
|
+
Tennessee, Texas, Utah and Virginia. Indiana, Kentucky, Maryland and Rhode
|
|
69
|
+
Island are not encoded yet: their published specifications start the section
|
|
70
|
+
with a header that the IAB reference implementation does not encode. Visitors
|
|
71
|
+
there get the fallback, `usnat` by default. So does a US visitor whose region
|
|
72
|
+
the geo headers did not supply.
|
|
73
|
+
|
|
74
|
+
The MSPA US National specification describes `usnat` for MSPA signatories
|
|
75
|
+
that chose the national approach. Without `mspaMode`, c15t reports the
|
|
76
|
+
fallback as a transaction the MSPA does not cover (`MspaCoveredTransaction:
|
|
77
|
+
2`), which still carries the opt-outs to vendors such as Prebid.js. If you
|
|
78
|
+
reserve `usnat` for the national approach, set `usFallback: 'none'`; those
|
|
79
|
+
visitors then get no section until their state resolves.
|
|
80
|
+
|
|
81
|
+
## What the US sections report
|
|
82
|
+
|
|
83
|
+
* `SaleOptOut`, `SharingOptOut` and `TargetedAdvertisingOptOut`: `1` (opted
|
|
84
|
+
out) when the `marketing` category is not permitted, otherwise `2`. A
|
|
85
|
+
refusal, a GPC signal the rule maps to `marketing` through
|
|
86
|
+
`privacySignals.gpc`, and an opt-in rule without a grant all count.
|
|
87
|
+
* The opt-out notices: `1` (given), since the rule offers an opt-out.
|
|
88
|
+
* The sharing or processing notice: `1` when the rule has the `disclosure`
|
|
89
|
+
right, otherwise `2`.
|
|
90
|
+
* `Gpc`: the browser's Global Privacy Control signal, in sections that have a
|
|
91
|
+
GPC subsection. It is reported even when the rule does not map GPC to a
|
|
92
|
+
category, so give US rules a `privacySignals.gpc` mapping if the opt-outs
|
|
93
|
+
should follow it too.
|
|
94
|
+
* Sensitive data and known-child consents: `0` (not applicable). c15t has no
|
|
95
|
+
category for them, so the string states that you do not process sensitive
|
|
96
|
+
data or knowingly process children's data. If you do, GPP from c15t does
|
|
97
|
+
not describe your processing; collect and signal that consent separately.
|
|
98
|
+
* `MspaCoveredTransaction`: `2` (not covered) unless you set `mspaMode`.
|
|
99
|
+
|
|
100
|
+
The notice fields are your attestation, made through the rule's rights:
|
|
101
|
+
c15t cannot see whether your privacy notice is on the page. Show the notices
|
|
102
|
+
the rule promises, including a persistent opt-out control.
|
|
103
|
+
|
|
104
|
+
## GPP options
|
|
105
|
+
|
|
106
|
+
Every field is optional. Pass them in the `gpp` option, where `gpp: true`
|
|
107
|
+
uses the defaults, as props on `ConsentGPP` in React, or to `mountGPP()` in
|
|
108
|
+
`@c15t/browser`.
|
|
109
|
+
|
|
110
|
+
| Option | Default | Effect |
|
|
111
|
+
| ------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
112
|
+
| `cmpId` | the IAB CMP ID c15t holds, else `1` | CMP ID reported by `ping`. The GPP specification has string creators without a registered ID, including MSPA US National creators, use `1`. A TCF EU section needs the registered ID its TC String names. |
|
|
113
|
+
| `usApproach` | `'state'` | `'state'` uses the visitor's state section. `'national'` reports `usnat` for every US visitor; the MSPA reserves it for signatories that chose the national approach. |
|
|
114
|
+
| `usFallback` | `'usnat'` | Under the state approach, the section for a US visitor whose state is unknown or has no section: `'usnat'` or `'none'`. |
|
|
115
|
+
| `mspaMode` | unset | `'opt-out-option'` or `'service-provider'`. Set it only if you signed the IAB Multi-State Privacy Agreement; the transaction is then reported as covered. In service provider mode the opt-outs are reported as not applicable. |
|
|
116
|
+
| `optOutCategories` | `['marketing']` | Categories whose refusal reports an opt-out of sale, sharing and targeted advertising. |
|
|
117
|
+
| `tcf` | `true` | Include the `tcfeuv2` section under an `iab` policy. It needs IAB TCF set up as well. |
|
|
118
|
+
|
|
119
|
+
## Check the signal
|
|
120
|
+
|
|
121
|
+
Open the browser console on a page with GPP on and call:
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
__gpp('ping', (data) => console.log(data));
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
For a California visitor who has not opted out, the result includes
|
|
128
|
+
`applicableSections: [8]`, `signalStatus: 'ready'` and
|
|
129
|
+
`parsedSections.usca[0].SaleOptOut === 2`. After the visitor refuses
|
|
130
|
+
`marketing`, `SaleOptOut` is `1`. `__gpp('getField', cb, 'usca.SaleOptOut')`
|
|
131
|
+
returns the same value. Scripts in iframes reach the API through the
|
|
132
|
+
`__gppLocator` frame and `postMessage`.
|
|
133
|
+
|
|
134
|
+
Under an `iab` rule, `parsedSections.tcfeuv2` uses the field names of the
|
|
135
|
+
IAB Europe TCF section, such as `PurposeConsent`, `VendorConsent` and
|
|
136
|
+
`PubRestrictions`. `getSection` and `getField` answer `null` for `tcfeuv2`,
|
|
137
|
+
as the IAB reference implementation does: TCF vendors read consent from
|
|
138
|
+
events, not on demand.
|
|
139
|
+
|
|
140
|
+
`signalStatus` stays `'not ready'` while the policy is pending, while the
|
|
141
|
+
banner or dialog is open, while an `iab` rule has no confirmed TC String,
|
|
142
|
+
and while a new TC String is decoded. Listeners added with
|
|
143
|
+
`addEventListener` get `signalStatus: 'not ready'`, then `cmpDisplayStatus`
|
|
144
|
+
and `sectionChange` events, then `signalStatus: 'ready'`.
|
|
145
|
+
|
|
146
|
+
## Ad tags that load before c15t
|
|
147
|
+
|
|
148
|
+
c15t installs `__gpp` in the browser once consent starts, after it loads the
|
|
149
|
+
GPP code. Vendor scripts that call `__gpp` earlier need a stub. c15t takes over
|
|
150
|
+
the calls and listeners a stub queued, whether the stub keeps listeners on
|
|
151
|
+
`__gpp.events`, as the GPP specification's sample does, or returns them from
|
|
152
|
+
`__gpp('events')`, as `@iabgpp/stub` does. Call `initializeGPPStub()` from
|
|
153
|
+
`@c15t/iab/gpp` in your first script, or use the IAB's own stub snippet.
|
|
154
|
+
When another stub already answers iframes, c15t leaves that to it, so each
|
|
155
|
+
iframe call gets one reply.
|
|
156
|
+
|
|
157
|
+
If a CMP that has already loaded owns `__gpp`, c15t leaves it in place.
|
|
158
|
+
The `gpp` option reports the conflict to the `onError` callback,
|
|
159
|
+
`ConsentGPP` logs it, and `mountGPP()` and `createGPP()` throw. Run one GPP
|
|
160
|
+
CMP per page.
|
|
161
|
+
|
|
162
|
+
GPP signals what the visitor chose; it does not block scripts. Gate vendor
|
|
163
|
+
scripts with c15t's script loading as well.
|
|
@@ -34,13 +34,13 @@ import { useHeadlessConsentUI, useTranslations } from 'c15t/react/headless';
|
|
|
34
34
|
|
|
35
35
|
const ACTION_LABELS = {
|
|
36
36
|
accept: 'acceptAll',
|
|
37
|
-
reject: 'rejectAll',
|
|
38
37
|
customize: 'customize',
|
|
39
38
|
dismiss: 'acknowledge',
|
|
39
|
+
reject: 'rejectAll',
|
|
40
40
|
save: 'save',
|
|
41
41
|
} as const;
|
|
42
42
|
|
|
43
|
-
export
|
|
43
|
+
export const Banner = () => {
|
|
44
44
|
const { banner, performAction, openDialog } = useHeadlessConsentUI();
|
|
45
45
|
const { common, rights } = useTranslations();
|
|
46
46
|
|
|
@@ -49,7 +49,7 @@ export function Banner() {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
return (
|
|
52
|
-
<section
|
|
52
|
+
<section aria-label="Privacy">
|
|
53
53
|
{banner.preferenceControls.map((right) => (
|
|
54
54
|
<button key={right} type="button" onClick={openDialog}>
|
|
55
55
|
{right === 'opt-out' ? rights?.optOut : rights?.preferences}
|
|
@@ -71,7 +71,7 @@ export function Banner() {
|
|
|
71
71
|
))}
|
|
72
72
|
</section>
|
|
73
73
|
);
|
|
74
|
-
}
|
|
74
|
+
};
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
`banner.actionGroups` is the resolved layout: reject and accept share a group
|
|
@@ -14,10 +14,10 @@ from components rendered inside that provider and import them from
|
|
|
14
14
|
```tsx title="src/optional-feature.tsx"
|
|
15
15
|
import { useConsent } from 'c15t/react';
|
|
16
16
|
|
|
17
|
-
export
|
|
17
|
+
export const OptionalFeature = () => {
|
|
18
18
|
const allowed = useConsent('marketing');
|
|
19
19
|
return allowed ? <div>Optional marketing content</div> : null;
|
|
20
|
-
}
|
|
20
|
+
};
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Call hooks inside the consent provider. `useConsent` reads effective permission,
|
|
@@ -42,14 +42,19 @@ Render this component inside your existing `ConsentProvider`.
|
|
|
42
42
|
```tsx title="src/reject-button.tsx"
|
|
43
43
|
import { useSaveConsents } from 'c15t/react';
|
|
44
44
|
|
|
45
|
-
export
|
|
45
|
+
export const RejectButton = () => {
|
|
46
46
|
const save = useSaveConsents();
|
|
47
47
|
return (
|
|
48
|
-
<button
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
onClick={() => {
|
|
51
|
+
void save('none');
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
49
54
|
Reject optional
|
|
50
55
|
</button>
|
|
51
56
|
);
|
|
52
|
-
}
|
|
57
|
+
};
|
|
53
58
|
```
|
|
54
59
|
|
|
55
60
|
This is an isolated action example, not a complete policy-aware banner. Use the
|
|
@@ -33,14 +33,12 @@ import {
|
|
|
33
33
|
IABConsentDialog,
|
|
34
34
|
} from 'c15t/react/iab';
|
|
35
35
|
|
|
36
|
-
export
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
);
|
|
43
|
-
}
|
|
36
|
+
export const IABConsent = ({ cmpId }: { cmpId: number }) => (
|
|
37
|
+
<IABProvider cmpId={cmpId}>
|
|
38
|
+
<IABConsentBanner />
|
|
39
|
+
<IABConsentDialog />
|
|
40
|
+
</IABProvider>
|
|
41
|
+
);
|
|
44
42
|
```
|
|
45
43
|
|
|
46
44
|
Supply the CMP ID from your deployment's IAB configuration. Keep a persistent
|
|
@@ -85,14 +83,12 @@ const publisherRestrictions = [
|
|
|
85
83
|
{ purposeId: 7, restrictionType: 2 as const, vendorIds: [755] },
|
|
86
84
|
];
|
|
87
85
|
|
|
88
|
-
export
|
|
89
|
-
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
);
|
|
95
|
-
}
|
|
86
|
+
export const IABConsent = ({ cmpId }: { cmpId: number }) => (
|
|
87
|
+
<IABProvider cmpId={cmpId} publisherRestrictions={publisherRestrictions}>
|
|
88
|
+
<IABConsentBanner />
|
|
89
|
+
<IABConsentDialog />
|
|
90
|
+
</IABProvider>
|
|
91
|
+
);
|
|
96
92
|
```
|
|
97
93
|
|
|
98
94
|
Each restriction type has a requirement that depends on the vendor list:
|
|
@@ -154,8 +150,10 @@ because turning one off would change nothing the vendors rely on; its
|
|
|
154
150
|
objection control is the opt-out. Such a purpose does not decide its c15t
|
|
155
151
|
category: the category follows the purposes in it that the visitor can
|
|
156
152
|
consent to. Legitimate interest never grants a category on its own. If none of
|
|
157
|
-
a category's purposes has a consent basis,
|
|
158
|
-
|
|
153
|
+
a category's purposes has a consent basis, nothing can consent to them, Accept
|
|
154
|
+
All included, so the category stays denied. See
|
|
155
|
+
[categories under an IAB policy](../../concepts/consent-state.md#categories-under-an-iab-policy).
|
|
156
|
+
Stack switches
|
|
159
157
|
cover only the purposes some vendor processes on consent. Display-model rows
|
|
160
158
|
expose this as `hasConsentBasis`. Custom preference UIs can call
|
|
161
159
|
`applyPublisherRestrictionsToGVL` from `@c15t/iab/headless`, or pass
|
|
@@ -209,6 +207,14 @@ the special purposes only. `featureRows` holds the features, with
|
|
|
209
207
|
`featuresStandardText` is the standard text, or `null` when you should use the
|
|
210
208
|
translation.
|
|
211
209
|
|
|
210
|
+
Each vendor's privacy policy and legitimate interest links come from its
|
|
211
|
+
`urls[]` entry in the Global Vendor List. The stock preference centres pick
|
|
212
|
+
the entry for the language the banner shows, then English. A custom UI gets
|
|
213
|
+
the same links from `resolveIABVendorUrls(vendor, language)` in
|
|
214
|
+
`@c15t/iab/headless`, or from `policyUrl` and `legitimateInterestUrl` on the
|
|
215
|
+
vendors `processGVLForDialog` returns when you pass it `language`. Vendor lists
|
|
216
|
+
older than GVL v3 have a single `policyUrl` field, which c15t still reads.
|
|
217
|
+
|
|
212
218
|
## Consent scope
|
|
213
219
|
|
|
214
220
|
c15t stores TCF choices per site and browser, which the TCF calls
|
|
@@ -8,8 +8,9 @@ group: frameworks
|
|
|
8
8
|
## Before you start
|
|
9
9
|
|
|
10
10
|
This guide sets up a client-rendered React app built with Vite. The browser
|
|
11
|
-
loads the page,
|
|
12
|
-
the
|
|
11
|
+
loads the page, resolves the visitor's policy, then shows the banner or runs
|
|
12
|
+
the scripts the visitor allowed. The build-time step bundles the public
|
|
13
|
+
policy; regional policies may still need a backend location request.
|
|
13
14
|
|
|
14
15
|
* **Next.js or TanStack Start?** Use that framework's guide from the
|
|
15
16
|
[framework list](https://c15t.com/docs/frameworks). Their adapters resolve consent on the
|
|
@@ -26,6 +27,10 @@ the backend URL.
|
|
|
26
27
|
[Choose your setup](../../concepts/choose-your-setup.md#decide-who-runs-the-consent-backend)
|
|
27
28
|
covers self-hosting and offline mode.
|
|
28
29
|
|
|
30
|
+
Build-time manifests are the recommended production setup. Complete the
|
|
31
|
+
[build-time manifest step](#bundle-the-manifest-during-builds) before
|
|
32
|
+
deploying, and rebuild after changing policies, translations or vendors.
|
|
33
|
+
|
|
29
34
|
## Install c15t
|
|
30
35
|
|
|
31
36
|
| Package manager | Command |
|
|
@@ -40,14 +45,12 @@ vendor loaders used below.
|
|
|
40
45
|
|
|
41
46
|
## Register your vendor scripts
|
|
42
47
|
|
|
43
|
-
Create `src/scripts.ts`. PostHog waits for measurement permission
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
once.
|
|
48
|
+
Create `src/scripts.ts`. PostHog waits for measurement permission, so your
|
|
49
|
+
policy must include that category. Remove any other PostHog loader from your
|
|
50
|
+
app so PostHog loads once.
|
|
47
51
|
|
|
48
52
|
```ts title="src/scripts.ts"
|
|
49
53
|
import { posthog } from '@c15t/integrations/posthog';
|
|
50
|
-
import { xPixel } from '@c15t/integrations/x-pixel';
|
|
51
54
|
|
|
52
55
|
export const scripts = [
|
|
53
56
|
posthog({
|
|
@@ -55,13 +58,11 @@ export const scripts = [
|
|
|
55
58
|
initOptions: { cookieless_mode: 'never' },
|
|
56
59
|
loadMode: 'after-consent',
|
|
57
60
|
}),
|
|
58
|
-
xPixel({ pixelId: 'your-pixel-id' }),
|
|
59
61
|
];
|
|
60
62
|
```
|
|
61
63
|
|
|
62
|
-
Replace `phc_your_project_key` with your PostHog project key
|
|
63
|
-
|
|
64
|
-
`region: 'us'` for a US project. Remove the loader for a vendor you don't use.
|
|
64
|
+
Replace `phc_your_project_key` with your PostHog project key. PostHog uses its
|
|
65
|
+
EU host; add `region: 'us'` for a US project.
|
|
65
66
|
|
|
66
67
|
`loadMode: 'after-consent'` keeps the PostHog SDK off the page until the
|
|
67
68
|
visitor allows measurement. `cookieless_mode: 'never'` turns off PostHog's
|
|
@@ -130,6 +131,66 @@ router, render the router inside `Consent` so navigation does not remount the
|
|
|
130
131
|
provider. The stylesheet import in `src/consent.tsx` styles the banner and
|
|
131
132
|
dialog; import it once.
|
|
132
133
|
|
|
134
|
+
## Bundle the manifest during builds
|
|
135
|
+
|
|
136
|
+
The build fetches your public policy once and bundles it, so the server never
|
|
137
|
+
fetches it at runtime.
|
|
138
|
+
|
|
139
|
+
Add `consentManifest` to your existing Vite plugin list:
|
|
140
|
+
|
|
141
|
+
```ts title="vite.config.ts (plugins option)"
|
|
142
|
+
import { consentManifest } from 'c15t/build';
|
|
143
|
+
|
|
144
|
+
export default {
|
|
145
|
+
plugins: [
|
|
146
|
+
// Keep your existing framework plugins here.
|
|
147
|
+
consentManifest({ backendURL: 'https://your-project.inth.app' }),
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Vite writes `src/c15t-manifest.ts` before compiling, including at development
|
|
153
|
+
startup. Keep the generated file out of Git. Install `@c15t/browser@alpha` for
|
|
154
|
+
its browser manifest transport. In the quickstart's `src/consent.tsx`, replace
|
|
155
|
+
the `hosted()` mode with this factory:
|
|
156
|
+
|
|
157
|
+
```tsx title="src/consent.tsx"
|
|
158
|
+
import { manifest } from '@c15t/browser/headless';
|
|
159
|
+
|
|
160
|
+
import { consentManifest } from './c15t-manifest';
|
|
161
|
+
|
|
162
|
+
const mode = manifest({
|
|
163
|
+
backendURL: 'https://your-project.inth.app',
|
|
164
|
+
manifest: consentManifest,
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Keep passing `mode` to `ConsentProvider`. The browser resolves the snapshot
|
|
169
|
+
without `/manifest` when it has the geography the policy needs, or the policy
|
|
170
|
+
does not depend on geography. Supply known country and region through `inputs`
|
|
171
|
+
or overrides to keep regional resolution on the snapshot.
|
|
172
|
+
|
|
173
|
+
When the required country or region is missing, the transport calls backend
|
|
174
|
+
`/init` and uses its resolved policy and translations. That response can use
|
|
175
|
+
newer backend policy than the build snapshot. This is a full consent resolution,
|
|
176
|
+
so these visitors still wait for a backend round trip.
|
|
177
|
+
|
|
178
|
+
Policy and resolver code are part of the browser bundle.
|
|
179
|
+
|
|
180
|
+
The snapshot is fixed at build time:
|
|
181
|
+
|
|
182
|
+
* Rebuild after changing policies, translations or vendors. If your CI caches
|
|
183
|
+
build output, force a fresh build.
|
|
184
|
+
* The build must reach the backend. A failed fetch fails the build.
|
|
185
|
+
* Consent choices still go to the backend.
|
|
186
|
+
|
|
187
|
+
Run `vite build` or `vite dev` before a standalone `tsc`, `vue-tsc` or
|
|
188
|
+
`svelte-check`; the generated file doesn't exist on a fresh checkout.
|
|
189
|
+
|
|
190
|
+
For policy updates without a rebuild, keep the original `hosted()` factory
|
|
191
|
+
and omit the build plugin. That setup resolves policy through the backend
|
|
192
|
+
`/init` on each page load.
|
|
193
|
+
|
|
133
194
|
## Gate your own features
|
|
134
195
|
|
|
135
196
|
Read a category's permission with `useConsent` inside the provider:
|
|
@@ -155,16 +216,16 @@ then open the site in a private window with DevTools open on the Network tab.
|
|
|
155
216
|
Test under a policy that asks for a choice, such as an EU opt-in policy.
|
|
156
217
|
|
|
157
218
|
1. **Before a choice.** The banner shows. There are no requests to
|
|
158
|
-
`posthog.com
|
|
219
|
+
`posthog.com`.
|
|
159
220
|
2. **Reject All.** The banner closes and the vendor requests stay absent.
|
|
160
221
|
3. **Reload.** The banner stays closed and the vendor requests stay absent.
|
|
161
222
|
4. **Privacy settings.** The footer link opens the dialog with **Analytics** (the
|
|
162
|
-
`measurement` category) and **Marketing** off. Turn on **Analytics** and
|
|
163
|
-
|
|
223
|
+
`measurement` category) and **Marketing** off. Turn on **Analytics** and
|
|
224
|
+
save. PostHog's `array.js` loads.
|
|
164
225
|
5. **Turn Analytics off again.** The page reloads and PostHog does not load.
|
|
165
226
|
|
|
166
|
-
If the banner never appears, check the
|
|
167
|
-
`/init` on your backend URL. [Troubleshooting](./troubleshooting.md)
|
|
227
|
+
If the banner never appears, check the generated policy and any location
|
|
228
|
+
request to `/init` on your backend URL. [Troubleshooting](./troubleshooting.md)
|
|
168
229
|
covers the common causes. [Verify consent](../../guides/verify-consent.md) has the
|
|
169
230
|
full release checklist.
|
|
170
231
|
|
|
@@ -23,14 +23,79 @@ read the visitor's cookie and location on the server, so the banner can be in
|
|
|
23
23
|
the first HTML. [Choose your setup](../../concepts/choose-your-setup.md#server-rendering-or-browser-rendering)
|
|
24
24
|
explains the difference in general terms.
|
|
25
25
|
|
|
26
|
+
## Bundle the manifest during Vite builds
|
|
27
|
+
|
|
28
|
+
The build fetches your public policy once and bundles it, so the server never
|
|
29
|
+
fetches it at runtime.
|
|
30
|
+
|
|
31
|
+
Add `consentManifest` to your existing Vite plugin list:
|
|
32
|
+
|
|
33
|
+
```ts title="vite.config.ts (plugins option)"
|
|
34
|
+
import { consentManifest } from 'c15t/build';
|
|
35
|
+
|
|
36
|
+
export default {
|
|
37
|
+
plugins: [
|
|
38
|
+
// Keep your existing framework plugins here.
|
|
39
|
+
consentManifest({ backendURL: 'https://your-project.inth.app' }),
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Vite writes `src/c15t-manifest.ts` before compiling, including at development
|
|
45
|
+
startup. Keep the generated file out of Git. Install `@c15t/browser@alpha` for
|
|
46
|
+
its browser manifest transport. In the quickstart's `src/consent.tsx`, replace
|
|
47
|
+
the `hosted()` mode with this factory:
|
|
48
|
+
|
|
49
|
+
```tsx title="src/consent.tsx"
|
|
50
|
+
import { manifest } from '@c15t/browser/headless';
|
|
51
|
+
|
|
52
|
+
import { consentManifest } from './c15t-manifest';
|
|
53
|
+
|
|
54
|
+
const mode = manifest({
|
|
55
|
+
backendURL: 'https://your-project.inth.app',
|
|
56
|
+
manifest: consentManifest,
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Keep passing `mode` to `ConsentProvider`. The browser resolves the snapshot
|
|
61
|
+
without `/manifest` when it has the geography the policy needs, or the policy
|
|
62
|
+
does not depend on geography. Supply known country and region through `inputs`
|
|
63
|
+
or overrides to keep regional resolution on the snapshot.
|
|
64
|
+
|
|
65
|
+
When the required country or region is missing, the transport calls backend
|
|
66
|
+
`/init` and uses its resolved policy and translations. That response can use
|
|
67
|
+
newer backend policy than the build snapshot. This is a full consent resolution,
|
|
68
|
+
so these visitors still wait for a backend round trip.
|
|
69
|
+
|
|
70
|
+
Policy and resolver code are part of the browser bundle.
|
|
71
|
+
|
|
72
|
+
The snapshot is fixed at build time:
|
|
73
|
+
|
|
74
|
+
* Rebuild after changing policies, translations or vendors. If your CI caches
|
|
75
|
+
build output, force a fresh build.
|
|
76
|
+
* The build must reach the backend. A failed fetch fails the build.
|
|
77
|
+
* Consent choices still go to the backend.
|
|
78
|
+
|
|
79
|
+
Run `vite build` or `vite dev` before a standalone `tsc`, `vue-tsc` or
|
|
80
|
+
`svelte-check`; the generated file doesn't exist on a fresh checkout.
|
|
81
|
+
|
|
82
|
+
For policy updates without a rebuild, keep the original `hosted()` factory
|
|
83
|
+
and omit the build plugin. That setup resolves policy through the backend
|
|
84
|
+
`/init` on each page load.
|
|
85
|
+
|
|
26
86
|
## Client-rendered apps
|
|
27
87
|
|
|
28
|
-
The [quickstart](./quickstart.md)
|
|
29
|
-
browser
|
|
88
|
+
The recommended [quickstart](./quickstart.md) bundles the
|
|
89
|
+
manifest and resolves it in the browser. See [build-time manifests](#bundle-the-manifest-during-vite-builds).
|
|
90
|
+
|
|
91
|
+
For policy updates without rebuilding, keep `hosted({ url })`. That runtime
|
|
92
|
+
path works as follows:
|
|
30
93
|
|
|
31
94
|
1. The page loads with no consent state. Every optional category is denied.
|
|
32
|
-
2. `ConsentProvider` requests `GET /init` from your backend URL
|
|
33
|
-
picks the visitor's policy
|
|
95
|
+
2. `ConsentProvider` requests `GET /init` from your backend URL while it first
|
|
96
|
+
renders, before the page paints. The backend picks the visitor's policy
|
|
97
|
+
from their location. With an `experiment`, the request waits until the
|
|
98
|
+
provider mounts, because it carries the visitor's arm.
|
|
34
99
|
3. The provider applies the policy and any stored choice. It shows the banner
|
|
35
100
|
if the policy asks for one, and loads the scripts the visitor allowed.
|
|
36
101
|
|
|
@@ -47,8 +112,11 @@ around the outlet. In React Router framework mode that is `app/root.tsx`.
|
|
|
47
112
|
browser takes over after hydration:
|
|
48
113
|
|
|
49
114
|
* The server HTML contains your page but no banner and no consent state.
|
|
50
|
-
* After hydration the browser
|
|
51
|
-
policy asks for one.
|
|
115
|
+
* After hydration the browser resolves the bundled manifest, then shows the
|
|
116
|
+
banner if the policy asks for one. When required country or region is
|
|
117
|
+
missing, it calls `/init` and uses the backend's current policy and
|
|
118
|
+
translations, which can differ from the build snapshot. With `hosted()`,
|
|
119
|
+
it always calls `/init`.
|
|
52
120
|
* The server render and the first browser render agree, so hydration does not
|
|
53
121
|
warn or change a stored choice.
|
|
54
122
|
|
|
@@ -96,8 +164,9 @@ browser path.
|
|
|
96
164
|
## Static and prerendered pages
|
|
97
165
|
|
|
98
166
|
A prerendered page is the same HTML for every visitor, so it cannot contain one
|
|
99
|
-
visitor's consent. Use the quickstart
|
|
100
|
-
backend URL directly, so the static host
|
|
167
|
+
visitor's consent. Use the quickstart's build-time snapshot. Consent saves and
|
|
168
|
+
any `/init` requests go to the backend URL directly, so the static host
|
|
169
|
+
needs no server route. Add the site's
|
|
101
170
|
origin to the trusted origins in your Inth project.
|
|
102
171
|
|
|
103
172
|
Do not cache a response that contains a visitor's consent in a CDN or a shared
|
|
@@ -122,7 +191,9 @@ custom transports.
|
|
|
122
191
|
1. Open the page in a private window and view the page source. In a
|
|
123
192
|
server-rendered app the HTML contains your content and no element with
|
|
124
193
|
`data-testid="consent-banner-root"`.
|
|
125
|
-
2. In DevTools Network,
|
|
126
|
-
|
|
127
|
-
|
|
194
|
+
2. In DevTools Network, confirm there is no `/manifest` request with a
|
|
195
|
+
build-time snapshot. A regional policy may still need an `/init` request
|
|
196
|
+
to resolve consent when geography is missing. With `hosted()`, one `/init`
|
|
197
|
+
request runs unless you supply `initialData`.
|
|
198
|
+
3. The banner appears once the policy resolves. Vendor requests stay absent
|
|
128
199
|
until you allow their category.
|
|
@@ -11,11 +11,10 @@ group: frameworks
|
|
|
11
11
|
Pass vendor loaders to `ConsentProvider` through `options.scripts`. The
|
|
12
12
|
provider loads each script when its category is allowed and removes it when
|
|
13
13
|
the category is denied. The [quickstart](./quickstart.md)
|
|
14
|
-
registers PostHog
|
|
14
|
+
registers PostHog this way:
|
|
15
15
|
|
|
16
16
|
```ts title="src/scripts.ts"
|
|
17
17
|
import { posthog } from '@c15t/integrations/posthog';
|
|
18
|
-
import { xPixel } from '@c15t/integrations/x-pixel';
|
|
19
18
|
|
|
20
19
|
export const scripts = [
|
|
21
20
|
posthog({
|
|
@@ -23,7 +22,6 @@ export const scripts = [
|
|
|
23
22
|
initOptions: { cookieless_mode: 'never' },
|
|
24
23
|
loadMode: 'after-consent',
|
|
25
24
|
}),
|
|
26
|
-
xPixel({ pixelId: 'your-pixel-id' }),
|
|
27
25
|
];
|
|
28
26
|
```
|
|
29
27
|
|
|
@@ -76,6 +74,13 @@ covers a vendor without one.
|
|
|
76
74
|
load before consent and pass the visitor's choice to the vendor. Read the
|
|
77
75
|
vendor's guide; a category on a script does not always mean zero requests.
|
|
78
76
|
|
|
77
|
+
## When the script loader downloads
|
|
78
|
+
|
|
79
|
+
The script loader is a separate chunk, downloaded only when `scripts` is not
|
|
80
|
+
empty. `ConsentProvider` loads it when it mounts. The Next.js and TanStack
|
|
81
|
+
Start `ConsentRoot` start the download earlier, during their first render,
|
|
82
|
+
when the visitor's consent already lets a script run.
|
|
83
|
+
|
|
79
84
|
## Embeds and other requests
|
|
80
85
|
|
|
81
86
|
Scripts cover vendor code c15t loads for you. For the rest:
|