@c15t/react 3.1.0-canary.0 → 3.2.0-canary.0
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 +11 -4
- package/README.md +4 -4
- package/dist/component-hooks/use-consent-gate.js +2 -0
- package/dist/component-hooks/use-consent-labels.js +2 -0
- package/dist/component-hooks/use-consent-text-direction.js +2 -0
- package/dist/component-hooks/use-vendors-listed-under.js +2 -0
- package/dist/components/consent-gate/atoms.js +1 -1
- package/dist/components/consent-gate/consent-gate.js +1 -1
- package/dist/components/iab-prompt/atoms/root.js +1 -1
- package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
- package/dist/components/preferences/atoms/button.js +1 -1
- package/dist/components/preferences/atoms/vendor-list.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/prompt/surface-context.js +1 -1
- package/dist/components/prompt/use-banner-copy.js +1 -1
- package/dist/components/shared/streamed-surface.js +1 -1
- package/dist/event-dispatcher.js +2 -0
- package/dist/headless.js +1 -1
- package/dist/index-parts/event-dispatcher.js +1 -0
- package/dist/index-parts/offline.js +1 -1
- package/dist/index.js +1 -1
- package/dist/modes.js +1 -1
- package/dist/provider.js +1 -1
- package/dist/transports/offline.js +1 -1
- package/dist/version.js +1 -1
- package/dist-types/component-hooks/use-consent-gate.d.ts +102 -0
- package/dist-types/component-hooks/use-consent-labels.d.ts +32 -0
- package/dist-types/component-hooks/use-consent-text-direction.d.ts +22 -0
- package/dist-types/component-hooks/use-vendors-listed-under.d.ts +30 -0
- package/dist-types/components/prompt/atoms/root.d.ts +3 -3
- package/dist-types/components/prompt/prompt.d.ts +4 -3
- package/dist-types/components/prompt/surface-context.d.ts +1 -14
- package/dist-types/components/prompt/use-banner-copy.d.ts +8 -3
- package/dist-types/components/shared/streamed-surface.d.ts +47 -10
- package/dist-types/context.d.ts +3 -1
- package/dist-types/event-dispatcher.d.ts +24 -0
- package/dist-types/headless.d.ts +10 -1
- package/dist-types/hooks/use-iab-policy-guard.d.ts +17 -1
- package/dist-types/hooks/use-scroll-lock.d.ts +16 -2
- package/dist-types/index-parts/event-dispatcher.d.ts +7 -0
- package/dist-types/index-parts/offline.d.ts +1 -1
- package/dist-types/index.d.ts +2 -0
- package/dist-types/modes.d.ts +1 -1
- package/dist-types/module-hooks/script-loader.d.ts +9 -0
- package/dist-types/provider.d.ts +3 -3
- package/dist-types/transports/offline.d.ts +2 -2
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +11 -4
- package/docs/concepts/choose-your-setup.md +9 -9
- package/docs/concepts/consent-state.md +93 -9
- package/docs/concepts/data-fetching.md +30 -23
- package/docs/concepts/modes.md +85 -35
- package/docs/customization/recipes.md +4 -4
- package/docs/customization/stylesheets.md +3 -3
- package/docs/customization/tailwind.md +17 -34
- package/docs/customization/translations.md +18 -12
- package/docs/frameworks/react/clear-on-revocation.md +3 -2
- package/docs/frameworks/react/components/consent-banner.md +3 -3
- package/docs/frameworks/react/components/consent-provider.md +29 -29
- package/docs/frameworks/react/gpp.md +1 -1
- package/docs/frameworks/react/headless.md +146 -19
- package/docs/frameworks/react/iab.md +8 -1
- package/docs/frameworks/react/quickstart.md +1 -1
- package/docs/frameworks/react/rendering.md +1 -1
- package/docs/frameworks/react/scripts.md +44 -0
- package/docs/frameworks/react/translations.md +9 -9
- package/docs/frameworks/react/troubleshooting.md +1 -1
- package/docs/frameworks/react/upgrade-v3.md +39 -32
- package/docs/frameworks/react/vendor-consent.md +5 -0
- package/docs/guides/banner-experiments.md +3 -3
- package/docs/guides/troubleshooting.md +3 -2
- package/docs/integrations/adobe-analytics.md +2 -2
- package/docs/integrations/ahrefs-analytics.md +2 -2
- package/docs/integrations/amplitude.md +2 -2
- package/docs/integrations/clay.md +305 -0
- package/docs/integrations/clearbit.md +2 -2
- package/docs/integrations/cloudflare-web-analytics.md +2 -2
- package/docs/integrations/cloudflare-zaraz.md +2 -2
- package/docs/integrations/crisp.md +2 -2
- package/docs/integrations/databuddy.md +2 -2
- package/docs/integrations/fathom-analytics.md +2 -2
- package/docs/integrations/front-chat.md +2 -2
- package/docs/integrations/google-maps.md +2 -2
- package/docs/integrations/google-tag-manager.md +59 -2
- package/docs/integrations/google-tag.md +2 -2
- package/docs/integrations/heap.md +2 -2
- package/docs/integrations/hightouch.md +2 -2
- package/docs/integrations/hotjar.md +40 -26
- package/docs/integrations/intercom.md +2 -2
- package/docs/integrations/klaviyo.md +2 -2
- package/docs/integrations/linkedin-insights.md +2 -2
- package/docs/integrations/logrocket.md +2 -2
- package/docs/integrations/matomo-analytics.md +2 -2
- package/docs/integrations/meta-pixel.md +2 -2
- package/docs/integrations/microsoft-clarity.md +2 -2
- package/docs/integrations/microsoft-uet.md +2 -2
- package/docs/integrations/mixpanel-analytics.md +2 -2
- package/docs/integrations/one-dollar-stats.md +2 -2
- package/docs/integrations/openai-pixel.md +2 -2
- package/docs/integrations/other-ads-and-pixels.md +94 -0
- package/docs/integrations/other-analytics.md +108 -0
- package/docs/integrations/other-chat-and-support.md +112 -0
- package/docs/integrations/other-email-and-sms.md +156 -0
- package/docs/integrations/other-experience.md +222 -0
- package/docs/integrations/overview.md +102 -45
- package/docs/integrations/pinterest-tag.md +2 -2
- package/docs/integrations/pirsch.md +2 -2
- package/docs/integrations/plausible-analytics.md +2 -2
- package/docs/integrations/posthog.md +10 -2
- package/docs/integrations/promptwatch.md +2 -2
- package/docs/integrations/reddit-pixel.md +2 -2
- package/docs/integrations/rudderstack.md +2 -2
- package/docs/integrations/rybbit-analytics.md +2 -2
- package/docs/integrations/segment.md +2 -2
- package/docs/integrations/sentry.md +2 -2
- package/docs/integrations/shopify.md +323 -0
- package/docs/integrations/snapchat-pixel.md +2 -2
- package/docs/integrations/tiktok-pixel.md +2 -2
- package/docs/integrations/umami-analytics.md +2 -2
- package/docs/integrations/vercel-analytics.md +2 -2
- package/docs/integrations/vercel-speed-insights.md +7 -2
- package/docs/integrations/x-pixel.md +2 -2
- package/docs/integrations/youtube.md +2 -2
- package/package.json +12 -29
- package/dist/components/prompt/resolve-banner-primary-actions.js +0 -1
- package/dist-types/components/prompt/resolve-banner-primary-actions.d.ts +0 -14
|
@@ -63,6 +63,23 @@ If your own scripts depend on such a category, do one of these:
|
|
|
63
63
|
Check `effectivePermissions` after Accept All on a fresh visit to see which
|
|
64
64
|
categories your vendor list leaves denied.
|
|
65
65
|
|
|
66
|
+
## A subject ID from the first page load
|
|
67
|
+
|
|
68
|
+
Every visitor gets a subject ID (`sub_…`) before they decide anything, so
|
|
69
|
+
you can link activity to the visitor from the first page load and still
|
|
70
|
+
find it when they opt out later. It is in `snapshot.subject.subjectId` from
|
|
71
|
+
the first snapshot.
|
|
72
|
+
|
|
73
|
+
* A framework server sets the ID in the `c15t-sid` cookie on the first
|
|
74
|
+
response, where the adapter's server hook runs. Otherwise the browser
|
|
75
|
+
creates it when c15t starts and stores it in `c15t-sid` at once.
|
|
76
|
+
* The ID stays the same across reloads. The first save records the same ID
|
|
77
|
+
with the choice.
|
|
78
|
+
* `c15t-sid` is not a choice. A visitor with only an ID still sees the
|
|
79
|
+
banner.
|
|
80
|
+
* Clearing choices and revoking consent keep the ID. Only setting the subject
|
|
81
|
+
to `null` starts over with a new one.
|
|
82
|
+
|
|
66
83
|
## When a choice is saved
|
|
67
84
|
|
|
68
85
|
A save records the choice in the browser first and sends it to the backend
|
|
@@ -136,6 +153,18 @@ writes the v3 format. The upgrade guides for
|
|
|
136
153
|
[JavaScript](https://c15t.com/docs/frameworks/javascript/upgrade-v3#keep-visitors-existing-choices)
|
|
137
154
|
cover how v3 treats v2 records.
|
|
138
155
|
|
|
156
|
+
## Retry interrupted saves
|
|
157
|
+
|
|
158
|
+
Before sending a recorded choice to the backend, c15t puts it in a localStorage
|
|
159
|
+
queue. If the page closes while the request is in flight, the next initialization
|
|
160
|
+
retries it with the original decision time. A successful response removes the
|
|
161
|
+
queued choice. A permanent rejection, such as a stale policy, removes it too.
|
|
162
|
+
|
|
163
|
+
Queueing depends on the browser allowing storage. When storage is unavailable,
|
|
164
|
+
c15t still attempts the request, but cannot preserve an interrupted request for
|
|
165
|
+
the next page. A locally remembered choice alone does not prove that the backend
|
|
166
|
+
received it; check the saved consent record in Inth.
|
|
167
|
+
|
|
139
168
|
## Keep open tabs in step
|
|
140
169
|
|
|
141
170
|
Tabs and windows on the same origin (scheme, host and port) share the consent
|
|
@@ -192,10 +221,12 @@ Stored records are merged into the ones in memory:
|
|
|
192
221
|
wins in both tabs.
|
|
193
222
|
* Tabs keep one subject. A stored subject is considered only when the record
|
|
194
223
|
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
|
|
196
|
-
before another tab stored a subject joins it.
|
|
197
|
-
|
|
198
|
-
|
|
224
|
+
generated at startup or on its first save, or copied from storage, so a tab
|
|
225
|
+
that opened before another tab stored a subject joins it. When it replaces
|
|
226
|
+
an ID the tab generated at startup and never used, the kernel emits
|
|
227
|
+
`subject:resolved` with `previousSubjectId`. A subject id the server
|
|
228
|
+
resolved (at init, from a prefetch or in a save response) is replaced only
|
|
229
|
+
by a strictly newer stored choice, and an identity set with `identify()` is
|
|
199
230
|
never replaced.
|
|
200
231
|
* Under an IAB policy, the TC string follows the reconciled choice. See
|
|
201
232
|
[How the TC string is reconciled](#how-the-tc-string-is-reconciled).
|
|
@@ -269,11 +300,13 @@ wherever it turns up. A decision stamped in the very millisecond of the clear
|
|
|
269
300
|
counts only if it comes from a tab that had already seen that clear, so a tab's
|
|
270
301
|
own choice right after its own clear stands while another tab's decision in the
|
|
271
302
|
same millisecond does not. A stored record left with no decisions after the
|
|
272
|
-
clear is void too, subject included.
|
|
303
|
+
clear is void too, subject included. The subject ID itself survives the clear:
|
|
304
|
+
it lives in its own key, `c15t-sid`, which clearing never removes, so a
|
|
305
|
+
withdrawal stays linked to the same visitor.
|
|
273
306
|
|
|
274
307
|
* A tab that reconciles only after another tab cleared and saved again drops
|
|
275
|
-
its pre-clear decisions instead of merging them back.
|
|
276
|
-
the clear is dropped too.
|
|
308
|
+
its pre-clear decisions instead of merging them back. The external identity
|
|
309
|
+
linked to its subject before the clear is dropped too; the subject ID stays.
|
|
277
310
|
* A tab that missed the clear writes only decisions it made after it. Its queued
|
|
278
311
|
write of an earlier decision is discarded, so it cannot bring back a cleared
|
|
279
312
|
record.
|
|
@@ -336,13 +369,38 @@ When a server render seeded the page from the consent cookie
|
|
|
336
369
|
localStorage is still applied on top of it when the page mounts, since it can
|
|
337
370
|
only restrict, if it is newer than the seeded decision or from the same
|
|
338
371
|
millisecond as a seeded grant. A stored grant is not.
|
|
372
|
+
`persistence: { skipHydration: false }` reads storage back over the seed
|
|
373
|
+
instead, so an older stored decision replaces it. A server render then starts
|
|
374
|
+
no scripts from the HTML, since it can't tell what that read will return.
|
|
339
375
|
|
|
340
376
|
The notice dismissal and vendor denials are stored twice as well. A vendor
|
|
341
377
|
list in localStorage at least as new as the cookie's adds its denials but never
|
|
342
378
|
lifts one the cookie holds; a copy confirmed before the last clear is ignored, so its
|
|
343
379
|
denials never come back. The newer notice dismissal applies; it still only hides a
|
|
344
|
-
notice with the fingerprint it names.
|
|
345
|
-
|
|
380
|
+
notice with the fingerprint it names.
|
|
381
|
+
|
|
382
|
+
Every notice acknowledgement also stores the visitor's subject ID with it,
|
|
383
|
+
including a choice that acknowledges a notice. For a visitor whose only action
|
|
384
|
+
was acknowledging a prompt that offers only necessary categories, this record
|
|
385
|
+
is what keeps the ID. The acknowledgement still grants no optional category. The `<key>-notice` cookie keeps its v1 format, so older
|
|
386
|
+
runtimes still read it. A separate `<key>-notice-subject` cookie binds the
|
|
387
|
+
subject ID to that acknowledgement: readers use it only while its timestamp and
|
|
388
|
+
fingerprint match the notice, so removing or replacing the notice makes a stale
|
|
389
|
+
identity cookie ineffective. When the subject ID changes, through a save
|
|
390
|
+
response or `set.subjectId()`, c15t rewrites the acknowledgement with the new
|
|
391
|
+
ID, so a reload does not bring the old one back. For the same acknowledgement,
|
|
392
|
+
the cookie identity wins unless a failed cookie write left a newer local
|
|
393
|
+
identity or a local identity removal. That exception lasts only while both
|
|
394
|
+
notice cookies match the values observed after the failed write; a later server
|
|
395
|
+
or sibling-subdomain cookie update takes precedence. Category-based data cleanup
|
|
396
|
+
protects these records and `<key>-sid` even when a configured wildcard matches
|
|
397
|
+
their names.
|
|
398
|
+
|
|
399
|
+
Clearing the stored records removes the acknowledgement and its
|
|
400
|
+
`<key>-notice-subject` cookie. The subject ID stays in `<key>-sid`, as after any
|
|
401
|
+
other clear. External identity fields supplied through `identify()` go with the
|
|
402
|
+
record that carried them. Call `set.subjectId(null)` to give the visitor a new
|
|
403
|
+
ID. The consent record follows the rules below.
|
|
346
404
|
|
|
347
405
|
The consent record is stored twice: as a cookie, which a server render reads,
|
|
348
406
|
and in localStorage. The cookie is authoritative. A well-formed cookie wins
|
|
@@ -373,6 +431,32 @@ render still reads the old consent cookie until the next successful cookie
|
|
|
373
431
|
write. The same applies to a consent cookie set with a different `domain` than
|
|
374
432
|
the current `storageConfig` uses, which the clear cannot remove.
|
|
375
433
|
|
|
434
|
+
The subject ID follows the same split. When its cookie writes succeed, the
|
|
435
|
+
server render and the browser use the same ID. When the browser refuses the
|
|
436
|
+
rewrite of a record cookie after the subject ID changed (the consent cookie,
|
|
437
|
+
`c15t-vendors` or `c15t-notice-subject`), that cookie keeps the old ID while
|
|
438
|
+
localStorage holds the new one. A server render then reports the old ID, and
|
|
439
|
+
the browser starts with the new ID from its storage. This happens even when the
|
|
440
|
+
request's `c15t-sid` cookie already holds the new ID. It lasts until a later
|
|
441
|
+
write of that record reaches the cookie, which may never happen while the
|
|
442
|
+
browser keeps refusing it. The server never replaces a `c15t-sid` cookie the
|
|
443
|
+
request already has with the old ID. When this browser's storage holds the
|
|
444
|
+
newer ID, the browser does not store the old one again either.
|
|
445
|
+
|
|
446
|
+
The vendor record has a separate limit. A local vendor copy at least as new as
|
|
447
|
+
the cookie's keeps its subject ID, and a rekey to a new subject ID keeps the
|
|
448
|
+
original decision time. So with `crossSubdomain`, a visitor whose only record is
|
|
449
|
+
a vendor decision (no consent record) can hold a local copy and a shared cookie
|
|
450
|
+
copy with the same time but different subject IDs, for example after another
|
|
451
|
+
subdomain rekeyed it at any later moment. This origin then starts with the ID
|
|
452
|
+
from its local copy, on server-rendered loads as well as loads that hydrate. It
|
|
453
|
+
does not write that ID over the shared `c15t-sid` cookie when that cookie and
|
|
454
|
+
the record cookies agree on another ID. A later vendor save from this origin
|
|
455
|
+
does store its ID in the shared vendor cookie, where the other subdomains read
|
|
456
|
+
it. Once that cookie carries it, this origin can also write it to the shared
|
|
457
|
+
`c15t-sid` cookie on a later page load. No optional category is granted and no
|
|
458
|
+
denial is lifted.
|
|
459
|
+
|
|
376
460
|
### Ordering with pending writes
|
|
377
461
|
|
|
378
462
|
A tab writes its own choices in a later task, not during the click. Before it
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Data fetching
|
|
3
3
|
description: Choose the recommended build-time policy snapshot or runtime
|
|
4
|
-
manifest fetching, backend /init, browser resolution or
|
|
4
|
+
manifest fetching, backend /init, browser resolution or local rules, and
|
|
5
5
|
understand where consent choices are saved.
|
|
6
6
|
group: concepts
|
|
7
7
|
---
|
|
@@ -10,18 +10,18 @@ group: concepts
|
|
|
10
10
|
|
|
11
11
|
Where policy resolves and where choices are saved are separate from who runs
|
|
12
12
|
the backend. Inth and a [self-hosted backend](https://c15t.com/docs/self-host/quickstart) speak
|
|
13
|
-
the same protocol, so every path below works with either. Only `
|
|
13
|
+
the same protocol, so every path below works with either. Only `local()`
|
|
14
14
|
removes backend requests. For a recommendation by framework, start with
|
|
15
15
|
[choose your setup](./choose-your-setup.md).
|
|
16
16
|
|
|
17
|
-
| Fetching path | Where policy resolves | Where choices are submitted
|
|
18
|
-
| --------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
19
|
-
| Build-time manifest, recommended for server-rendered apps | Your server resolves a bundled policy snapshot with each visitor's inputs | Inth or your c15t backend
|
|
20
|
-
| Cached manifest on your server | Your application resolves public policy data with each request's location and signals | Inth or your c15t backend
|
|
21
|
-
| Regular backend `/init` | The consent backend | The same backend
|
|
22
|
-
| Manifest in the browser | The browser, using a supplied location, or `/init` when the policy needs one it doesn't have | Inth or your c15t backend
|
|
23
|
-
|
|
|
24
|
-
| Custom transport | Your implementation | Your implementation
|
|
17
|
+
| Fetching path | Where policy resolves | Where choices are submitted | Choose it when |
|
|
18
|
+
| --------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| Build-time manifest, recommended for server-rendered apps | Your server resolves a bundled policy snapshot with each visitor's inputs | Inth or your c15t backend | Your framework renders on the server, supports a build integration, and policy changes can ship with a rebuild |
|
|
20
|
+
| Cached manifest on your server | Your application resolves public policy data with each request's location and signals | Inth or your c15t backend | Policy changes must reach the app without rebuilding, or builds cannot access the backend |
|
|
21
|
+
| Regular backend `/init` | The consent backend | The same backend | You have no application server, as in a React, Vue or Svelte single-page app, want the fewest moving parts, or need backend-owned request resolution |
|
|
22
|
+
| Manifest in the browser | The browser, using a supplied location, or `/init` when the policy needs one it doesn't have | Inth or your c15t backend | The policy has no location rules or the page knows the visitor's location, and you accept about 3 KB more bundle and a rebuild for policy edits |
|
|
23
|
+
| Local | The browser or local runtime, using bundled rules | No backend submission. c15t keeps no server-side record of consent. | Local development and tests. Not recommended for production environments. |
|
|
24
|
+
| Custom transport | Your implementation | Your implementation | An existing service cannot use the c15t backend protocol |
|
|
25
25
|
|
|
26
26
|
## What is a consent manifest?
|
|
27
27
|
|
|
@@ -226,36 +226,43 @@ needing the upstream URL. A static export cannot serve a Next.js route or
|
|
|
226
226
|
rewrite at runtime; use the absolute Inth URL or a proxy provided by the static
|
|
227
227
|
host instead.
|
|
228
228
|
|
|
229
|
-
## When should I use
|
|
229
|
+
## When should I use local mode?
|
|
230
230
|
|
|
231
231
|
Not recommended for production environments. Use Inth or a self-hosted backend
|
|
232
232
|
for production policy and consent records.
|
|
233
233
|
|
|
234
|
-
`offline()` resolves bundled policy rules
|
|
235
|
-
saves locally. The runtime's
|
|
236
|
-
|
|
237
|
-
|
|
234
|
+
`local()`, called `offline()` before c15t 3.2, resolves bundled policy rules
|
|
235
|
+
without an init request and acknowledges saves locally. The runtime's
|
|
236
|
+
persistence module stores the choice in browser storage. c15t keeps no
|
|
237
|
+
server-side record of consent, so there is no audit history, no cross-device
|
|
238
|
+
record and no IP geolocation. [What a consent record proves](./modes.md#what-a-consent-record-proves)
|
|
239
|
+
explains what that leaves out.
|
|
238
240
|
|
|
239
|
-
Use your framework's `
|
|
241
|
+
Use your framework's `local()` so its translations and provider context are
|
|
240
242
|
included. In a React app it replaces the mode passed to `ConsentProvider`:
|
|
241
243
|
|
|
242
244
|
```ts title="src/consent-mode.ts"
|
|
243
|
-
import {
|
|
245
|
+
import { local } from 'c15t/react';
|
|
244
246
|
|
|
245
|
-
export const mode =
|
|
247
|
+
export const mode = local();
|
|
246
248
|
```
|
|
247
249
|
|
|
250
|
+
In development builds, local mode warns once per page that c15t keeps no
|
|
251
|
+
server-side record of consent. Pass `local({ acknowledgeNoRecords: true })` to
|
|
252
|
+
hide the warning once you have decided a browser-only record is enough; see
|
|
253
|
+
[the development warning](./modes.md#the-development-warning).
|
|
254
|
+
|
|
248
255
|
In Next.js, TanStack Start, Nuxt, Astro and SvelteKit, pass the data
|
|
249
|
-
`
|
|
256
|
+
`local()` from the framework package where the config takes `mode`; see
|
|
250
257
|
[consent modes](./modes.md#import-the-factories-from-your-framework-package).
|
|
251
258
|
|
|
252
|
-
With no `policyRules`, the
|
|
253
|
-
|
|
254
|
-
real resolution inputs;
|
|
259
|
+
With no `policyRules`, the local transport uses the recommended rule pack.
|
|
260
|
+
Supplying `policyRules` replaces that pack. Unknown country and region are
|
|
261
|
+
real resolution inputs; local mode does not discover a visitor's location.
|
|
255
262
|
Use [policy rules](./policies.md) to understand
|
|
256
263
|
matching and defaults, and test the missing-location case.
|
|
257
264
|
|
|
258
|
-
|
|
265
|
+
Local mode is an explicit architecture choice, not an automatic fallback for
|
|
259
266
|
a failed Inth request. If hosted initialization fails before a policy resolves,
|
|
260
267
|
optional permissions remain denied and the stock prompt stays hidden. Observe
|
|
261
268
|
initialization failures instead of silently changing policy sources.
|
package/docs/concepts/modes.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Consent modes
|
|
3
|
-
description: Reference for manifest(), hosted() and
|
|
3
|
+
description: Reference for manifest(), hosted() and local() in every c15t
|
|
4
4
|
framework package, where each one resolves the visitor's policy, what it adds
|
|
5
5
|
to first-load JavaScript, the build-time manifest fetch and its failure
|
|
6
6
|
policy, and the backend URL variable each framework reads.
|
|
@@ -11,7 +11,8 @@ group: concepts
|
|
|
11
11
|
|
|
12
12
|
A mode decides how c15t finds the policy that applies to a visitor. Every
|
|
13
13
|
framework package exports the same three factories, `manifest()`, `hosted()`
|
|
14
|
-
and `
|
|
14
|
+
and `local()`. `local()` was called `offline()` before c15t 3.2, and the old
|
|
15
|
+
name still works until c15t v4. Consent choices go to the backend in `manifest()` and
|
|
15
16
|
`hosted()`, whether that backend is [Inth](https://inth.com) or a
|
|
16
17
|
[self-hosted c15t backend](https://c15t.com/docs/self-host/quickstart).
|
|
17
18
|
|
|
@@ -19,7 +20,7 @@ and `offline()`. Consent choices go to the backend in `manifest()` and
|
|
|
19
20
|
| ------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
20
21
|
| `manifest()`, the default | Your server, from the policy the build downloaded. With `resolve: 'browser'`, or in a single-page app, the browser. | None to resolve the policy, unless the browser needs a location it doesn't have. Choices post to `/subjects`. | Server-rendered apps. Rebuild to ship policy changes, or set `source: 'runtime'`. |
|
|
21
22
|
| `hosted()` | The backend's `GET /init` | One `/init` per page that resolves consent. | Single-page apps and static hosting without a server, where the backend reads each visitor's location from the request. Policy edits apply without a rebuild. |
|
|
22
|
-
| `
|
|
23
|
+
| `local()` | The browser, from rules in your bundle | None. Choices stay in the browser, and c15t keeps no server-side record of consent. | Local development, tests and demos. Not recommended for production environments. |
|
|
23
24
|
|
|
24
25
|
`manifest()` is the default in Next.js, TanStack Start, Nuxt, Astro and
|
|
25
26
|
SvelteKit. React, Vue, Svelte and `@c15t/browser` have no default: pass a
|
|
@@ -38,18 +39,18 @@ custom transport and same-origin proxies.
|
|
|
38
39
|
There are two kinds of factory, and each framework package exports the kind it
|
|
39
40
|
needs under the same three names.
|
|
40
41
|
|
|
41
|
-
| Framework | Import from | Pass the mode to
|
|
42
|
-
| -------------- | --------------------- |
|
|
43
|
-
| Next.js | `c15t/next` | `mode` in `c15t.config.ts`
|
|
44
|
-
| TanStack Start | `c15t/tanstack-start` | `createConsentStateHandler({ mode })`
|
|
45
|
-
| Nuxt | `c15t/vue` | `c15t.mode` in `nuxt.config.ts`
|
|
46
|
-
| Astro | `c15t/astro` | `c15t({ mode })` in `astro.config.mjs`
|
|
47
|
-
| SvelteKit |
|
|
48
|
-
| React | `c15t/react` | `ConsentProvider`'s `options.mode`
|
|
49
|
-
| Vue | `c15t/vue/vue-plugin` | `app.use(c15tVue, { mode })`
|
|
50
|
-
| Svelte |
|
|
51
|
-
| JavaScript | `@c15t/browser` | `init({ mode })`
|
|
52
|
-
| Script tag | none | The bundle file: `c15t.js` is hosted, `c15t.
|
|
42
|
+
| Framework | Import from | Pass the mode to | Kind |
|
|
43
|
+
| -------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------- |
|
|
44
|
+
| Next.js | `c15t/next` | `mode` in `c15t.config.ts` | Data |
|
|
45
|
+
| TanStack Start | `c15t/tanstack-start` | `createConsentStateHandler({ mode })` | Data |
|
|
46
|
+
| Nuxt | `c15t/vue` | `c15t.mode` in `nuxt.config.ts` | Data |
|
|
47
|
+
| Astro | `c15t/astro` | `c15t({ mode })` in `astro.config.mjs` | Data |
|
|
48
|
+
| SvelteKit | `c15t/svelte/kit` | `c15tHandle({ mode })` in `src/hooks.server.ts` | Data |
|
|
49
|
+
| React | `c15t/react` | `ConsentProvider`'s `options.mode` | Transport |
|
|
50
|
+
| Vue | `c15t/vue/vue-plugin` | `app.use(c15tVue, { mode })` | Transport |
|
|
51
|
+
| Svelte | `c15t/svelte` | `<ConsentProvider mode={...}>` | Transport |
|
|
52
|
+
| JavaScript | `@c15t/browser` | `init({ mode })` | Transport |
|
|
53
|
+
| Script tag | none | The bundle file: `c15t.js` is hosted, `c15t.local.js` local. The headless and IAB scripts also take `data-mode="manifest"`. | Name |
|
|
53
54
|
|
|
54
55
|
**Data factories** return a plain object such as `{ type: 'manifest' }`.
|
|
55
56
|
Server-rendered frameworks read their config on the server and in the
|
|
@@ -86,7 +87,7 @@ holds your policy rules and copy, and no visitor data.
|
|
|
86
87
|
| `inputs` | none | `{ country, region }` when the page already knows the visitor's location, for example from an edge worker. Browser resolution only. |
|
|
87
88
|
|
|
88
89
|
The transport `manifest()` from `c15t/react`, `c15t/vue/vue-plugin`,
|
|
89
|
-
|
|
90
|
+
`c15t/svelte` and `@c15t/browser` always resolve in the browser, so they have
|
|
90
91
|
no `resolve` option. It also takes `backendURL`, `headers`, `credentials`,
|
|
91
92
|
`domain`, `fetch` and `initFallback`. With no options it reads the snapshot
|
|
92
93
|
and the backend URL your build integration downloaded. With `manifestURL`,
|
|
@@ -98,7 +99,7 @@ know the country or region, browser resolution asks `geoURL`, then the
|
|
|
98
99
|
backend's `/init`. Set `initFallback: false` to resolve for an unknown location
|
|
99
100
|
instead. A policy with location rules therefore saves no request in a
|
|
100
101
|
single-page app unless you pass `inputs` or `geoURL`. The `consentManifest()`
|
|
101
|
-
plugins from `c15t/build`, `c15t/vue/vite` and
|
|
102
|
+
plugins from `c15t/build`, `c15t/vue/vite` and `c15t/svelte/vite` warn
|
|
102
103
|
about this when a single-page app build bundles such a policy.
|
|
103
104
|
|
|
104
105
|
## hosted()
|
|
@@ -122,36 +123,85 @@ against and the backend can reject a save made against a stale policy.
|
|
|
122
123
|
`backendURL`. The framework exports fill it in from your config or your
|
|
123
124
|
build.
|
|
124
125
|
|
|
125
|
-
##
|
|
126
|
+
## local()
|
|
126
127
|
|
|
127
128
|
Not recommended for production environments.
|
|
128
129
|
|
|
129
|
-
`
|
|
130
|
-
storage. Nothing reaches a backend, so
|
|
131
|
-
cross-device history and no IP geolocation
|
|
130
|
+
`local()` resolves policy rules in the browser and stores choices in browser
|
|
131
|
+
storage. Nothing reaches a backend, so c15t keeps no server-side record of
|
|
132
|
+
consent. There is also no cross-device history, and with no IP geolocation
|
|
133
|
+
every visitor gets the policy for an unknown location unless you pass
|
|
134
|
+
`overrides`.
|
|
132
135
|
|
|
133
|
-
| Option
|
|
134
|
-
|
|
|
135
|
-
| `policyRules`
|
|
136
|
+
| Option | Default | Behavior |
|
|
137
|
+
| ---------------------- | ------------------------- | ------------------------------------------------------------------------------ |
|
|
138
|
+
| `policyRules` | the recommended rule pack | Rules to resolve. Passing rules replaces the pack entirely. |
|
|
139
|
+
| `acknowledgeNoRecords` | `false` | `true` hides the development warning described below. It changes nothing else. |
|
|
136
140
|
|
|
137
141
|
Unknown country and region are real inputs: test the missing-location case
|
|
138
142
|
against your rules. See [policy rules](./policies.md).
|
|
139
143
|
|
|
144
|
+
### The development warning
|
|
145
|
+
|
|
146
|
+
In a development build, local mode logs one `console.warn` per page load:
|
|
147
|
+
|
|
148
|
+
```txt
|
|
149
|
+
[c15t] Local mode keeps consent only in this browser. c15t keeps no
|
|
150
|
+
server-side record of consent. Local mode also has no geolocation, so
|
|
151
|
+
every visitor gets the same policy. Use it for development and tests. For
|
|
152
|
+
production, see https://c15t.com/docs/concepts/modes
|
|
153
|
+
Set acknowledgeNoRecords: true to hide this message.
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Production builds never log it, and it never blocks the banner or a save.
|
|
157
|
+
The script-tag files are production builds, so `c15t.local.js`, and
|
|
158
|
+
`c15t.headless.js` or `c15t.iab.js` without a backend URL, log it on
|
|
159
|
+
development hosts instead:
|
|
160
|
+
`localhost`, `127.0.0.1`, `[::1]`, `0.0.0.0` and names ending in
|
|
161
|
+
`.localhost`, `.local` or `.test`. A script tag hides it with
|
|
162
|
+
`data-acknowledge-no-records`.
|
|
163
|
+
|
|
164
|
+
Set `acknowledgeNoRecords: true` once you have decided a browser-only record is
|
|
165
|
+
enough, such as in tests, or on a site that keeps its own record through
|
|
166
|
+
[callbacks](https://c15t.com/docs/frameworks/javascript/callbacks) or a [`custom()` transport](./data-fetching.md#can-i-provide-my-own-transport):
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { local } from 'c15t/react';
|
|
170
|
+
|
|
171
|
+
const mode = local({ acknowledgeNoRecords: true });
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
c15t v4 removes the `offline()` name and makes `acknowledgeNoRecords: true`
|
|
175
|
+
required in `local()`'s type.
|
|
176
|
+
|
|
177
|
+
### What a consent record proves
|
|
178
|
+
|
|
179
|
+
A consent record is the backend's copy of a choice: which categories the
|
|
180
|
+
visitor granted or denied, when, and against which policy and copy. Under the
|
|
181
|
+
GDPR, the business that relies on consent must be able to show that the
|
|
182
|
+
visitor gave it (Article 7(1)). Browser storage alone cannot do that: the
|
|
183
|
+
visitor can clear it, and it never leaves their device.
|
|
184
|
+
|
|
185
|
+
`manifest()` and `hosted()` post each choice to the backend, which keeps the
|
|
186
|
+
record. To keep records without Inth, run a
|
|
187
|
+
[self-hosted backend](https://c15t.com/docs/self-host/overview), or send each choice to your
|
|
188
|
+
own API with `custom()`.
|
|
189
|
+
|
|
140
190
|
## What each mode adds to first-load JavaScript
|
|
141
191
|
|
|
142
192
|
In a server-rendered framework, the browser receives the resolved state with
|
|
143
193
|
the page. It only needs the code to save a choice, and loads the rest when it
|
|
144
194
|
runs:
|
|
145
195
|
|
|
146
|
-
| Mode | First-load JavaScript | Loaded with `import()` when it runs
|
|
147
|
-
| ---------------------------------- | ------------------------------------------------------ |
|
|
148
|
-
| `manifest()` | The code that saves choices and builds an init request | The hosted init path, if the browser has to resolve consent again
|
|
149
|
-
| `manifest({ resolve: 'browser' })` | The code that saves choices | The browser resolver, English copy and the fetch of `${routePrefix}/manifest` or `${backendURL}/manifest
|
|
150
|
-
| `hosted()` | The code that saves choices | The hosted init path
|
|
151
|
-
| `
|
|
196
|
+
| Mode | First-load JavaScript | Loaded with `import()` when it runs |
|
|
197
|
+
| ---------------------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
198
|
+
| `manifest()` | The code that saves choices and builds an init request | The hosted init path, if the browser has to resolve consent again |
|
|
199
|
+
| `manifest({ resolve: 'browser' })` | The code that saves choices | The browser resolver, English copy and the fetch of `${routePrefix}/manifest` or `${backendURL}/manifest`, unless the server state already holds the policy. Other languages load per language. |
|
|
200
|
+
| `hosted()` | The code that saves choices | The hosted init path |
|
|
201
|
+
| `local()` | Nothing mode-specific | The local transport, the rule pack and its copy |
|
|
152
202
|
|
|
153
203
|
With `manifest()` resolved on the server, the manifest snapshot, the resolver,
|
|
154
|
-
the
|
|
204
|
+
the local rule pack and other languages stay out of the browser bundle.
|
|
155
205
|
|
|
156
206
|
In a single-page app the bundle contains the mode you imported:
|
|
157
207
|
|
|
@@ -159,7 +209,7 @@ In a single-page app the bundle contains the mode you imported:
|
|
|
159
209
|
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
160
210
|
| `manifest()` | The snapshot, when the build downloaded one, and the code that saves choices. The resolver starts loading when `manifest()` runs, unless the policy needs a location the page does not know. English copy is bundled; other languages load the first time a visitor needs one. |
|
|
161
211
|
| `hosted()` | The hosted transport |
|
|
162
|
-
| `
|
|
212
|
+
| `local()` | The local transport, the recommended rule pack and its copy |
|
|
163
213
|
|
|
164
214
|
## Set the backend URL
|
|
165
215
|
|
|
@@ -204,7 +254,7 @@ build integration downloads from `${backendURL}/manifest`:
|
|
|
204
254
|
| TanStack Start | `consentManifest()` from `c15t/tanstack-start/build` | `@c15t/tanstack-start/build` |
|
|
205
255
|
| React and JavaScript | `consentManifest()` from `c15t/build` | `@c15t/core/build` |
|
|
206
256
|
| Vue | `consentManifest()` from `c15t/vue/vite` | `@c15t/vue/vite` |
|
|
207
|
-
| Svelte and SvelteKit | `consentManifest()` from
|
|
257
|
+
| Svelte and SvelteKit | `consentManifest()` from `c15t/svelte/vite` | `@c15t/svelte/vite` |
|
|
208
258
|
| Nuxt | the `c15t/vue` module | `@c15t/vue` |
|
|
209
259
|
| Astro | the `c15t()` integration | `@c15t/astro` |
|
|
210
260
|
|
|
@@ -266,13 +316,13 @@ snapshot:
|
|
|
266
316
|
* The backend URL is relative, such as `/api/c15t`. With an explicit
|
|
267
317
|
`onBuildError: 'fail'`, a relative URL stops the build instead.
|
|
268
318
|
* Next.js builds with `output: 'export'`, which has no server.
|
|
269
|
-
* The mode is `hosted()`, `
|
|
319
|
+
* The mode is `hosted()`, `local()`, `manifest({ snapshot })` or
|
|
270
320
|
`manifest({ source: 'runtime' })` in Next.js, Nuxt and Astro. Next.js reads
|
|
271
321
|
the mode from `c15t.config.ts`.
|
|
272
322
|
* A single-page app built with Vite (React, Vue, Svelte, JavaScript) does
|
|
273
323
|
not use `manifest()`. The mode is set in app code, so the plugin downloads
|
|
274
324
|
the manifest only once the bundle turns out to read it, after unused code
|
|
275
|
-
is dropped. A `hosted()` or `
|
|
325
|
+
is dropped. A `hosted()` or `local()` build never contacts the backend.
|
|
276
326
|
SvelteKit and TanStack Start choose the mode on the server at runtime, so
|
|
277
327
|
their builds always download it.
|
|
278
328
|
|
|
@@ -86,7 +86,7 @@ Render `CookieBanner` in place of `ConsentBanner`, inside
|
|
|
86
86
|
|
|
87
87
|
```svelte title="src/lib/cookie-banner.svelte"
|
|
88
88
|
<script lang="ts">
|
|
89
|
-
import { ConsentBanner } from '
|
|
89
|
+
import { ConsentBanner } from 'c15t/svelte';
|
|
90
90
|
</script>
|
|
91
91
|
|
|
92
92
|
<!-- The stock banner, drawn as a full-width bar along the bottom edge. -->
|
|
@@ -250,7 +250,7 @@ Svelte scopes component styles, so the classes live in a global
|
|
|
250
250
|
stylesheet.
|
|
251
251
|
|
|
252
252
|
```ts title="src/lib/consent-options.ts"
|
|
253
|
-
import type { ConsentManagerOptions } from '
|
|
253
|
+
import type { ConsentManagerOptions } from 'c15t/svelte';
|
|
254
254
|
|
|
255
255
|
import './slim-bar.css';
|
|
256
256
|
|
|
@@ -456,8 +456,8 @@ opens.
|
|
|
456
456
|
|
|
457
457
|
```svelte title="src/lib/cookie-bar.svelte"
|
|
458
458
|
<script lang="ts">
|
|
459
|
-
import { getHeadlessConsent } from '
|
|
460
|
-
import type { PresentationAction } from '
|
|
459
|
+
import { getHeadlessConsent } from 'c15t/svelte';
|
|
460
|
+
import type { PresentationAction } from 'c15t/svelte';
|
|
461
461
|
|
|
462
462
|
import './cookie-bar.css';
|
|
463
463
|
|
|
@@ -69,7 +69,7 @@ render. The elements stay after the surface unmounts. The Svelte dialog's
|
|
|
69
69
|
sheets, its rules and the primitives, load with the dialog's code.
|
|
70
70
|
|
|
71
71
|
On a server-rendered SvelteKit page, the banner leaves a
|
|
72
|
-
`<meta name="c15t-styles">` marker, and `c15tHandle` from
|
|
72
|
+
`<meta name="c15t-styles">` marker, and `c15tHandle` from `c15t/svelte/kit`
|
|
73
73
|
writes the sheets into the HTML `<head>` as `<style>` elements. The banner is
|
|
74
74
|
styled before hydration with no stylesheet request. Without `c15tHandle` in
|
|
75
75
|
`src/hooks.server.ts`, a server-rendered banner shows unstyled until
|
|
@@ -109,7 +109,7 @@ need control over the file:
|
|
|
109
109
|
| Next.js | `options.styles` in `c15t.config.ts` or on `ConsentRoot` | `c15t/next/styles.css`, and `c15t/next/iab/styles.css` for IAB TCF |
|
|
110
110
|
| TanStack Start | `options.styles` on `ConsentRoot` | `c15t/tanstack-start/styles.css`, and `c15t/tanstack-start/iab/styles.css` for IAB TCF |
|
|
111
111
|
| React | `options.styles` on `ConsentProvider` | `c15t/react/styles.css`, and `c15t/react/iab/styles.css` for IAB TCF |
|
|
112
|
-
| Svelte, SvelteKit | `options.styles` on `ConsentProvider` or `ConsentRoot` |
|
|
112
|
+
| Svelte, SvelteKit | `options.styles` on `ConsentProvider` or `ConsentRoot` | `c15t/svelte/styles.css`, and `c15t/svelte/iab/styles.css` for IAB TCF |
|
|
113
113
|
| Astro | `styles` in the `c15t()` integration options | See [load Astro's stylesheets yourself](#load-astros-stylesheets-yourself) |
|
|
114
114
|
|
|
115
115
|
```ts title="c15t.config.ts (Next.js)"
|
|
@@ -132,7 +132,7 @@ the first paint. Remove the import, or add `styles: false` if you need it.
|
|
|
132
132
|
| Next.js, TanStack Start, React | In the dialog sheet that `ConsentDialog` and `ConsentWidget` render. It ships with the dialog's lazily loaded code. With `styles: false`, in `styles.css`. No `@c15t/react` or `@c15t/ui` module imports CSS, so a Next.js app that uses only the Pages Router builds without `transpilePackages` |
|
|
133
133
|
| Nuxt, Vue | The dialog component imports its stylesheet, so the rules load with the dialog's code, not the banner's |
|
|
134
134
|
| Astro | In `@c15t/ui/styles/sheets/dialog.css`. The Svelte dialog, the default `ui`, also needs the primitives stylesheet. c15t links both when a visitor first points at, focuses or opens a control that opens the dialog, waits for them before it shows the dialog, and keeps them across `ClientRouter` navigation. On a Tailwind CSS 3 site, the dialog's rules are in `c15t/astro/styles.css`, and c15t links only the primitives stylesheet |
|
|
135
|
-
| Svelte, SvelteKit | In the dialog's sheets, which load with the dialog's code. With `styles: false`, in
|
|
135
|
+
| Svelte, SvelteKit | In the dialog's sheets, which load with the dialog's code. With `styles: false`, in `c15t/svelte/styles.css` |
|
|
136
136
|
| HTML, JavaScript | Part of the stylesheet in the shadow root, which holds only the rules the stock surfaces use |
|
|
137
137
|
|
|
138
138
|
`styles.css` still holds every stock surface's rules for apps that set
|