@c15t/scripts 3.0.0-alpha.0 → 3.0.0-alpha.2

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.
Files changed (88) hide show
  1. package/AGENTS.md +7 -0
  2. package/README.md +4 -3
  3. package/dist/e2e-test-utils.js +5 -3
  4. package/dist/engine/runtime.js +14 -3
  5. package/dist/events.js +218 -0
  6. package/dist/registry.js +40 -0
  7. package/dist/vendors/ads-and-pixels/pinterest-tag.js +123 -0
  8. package/dist/vendors/analytics/google-tag.js +14 -2
  9. package/dist/vendors/analytics/microsoft-clarity.js +4 -1
  10. package/dist/vendors/analytics/one-dollar-stats.js +30 -0
  11. package/dist/vendors/analytics/segment.js +10 -1
  12. package/dist/vendors/functional/front-chat.js +64 -0
  13. package/dist/vendors/tag-managers/cloudflare-zaraz.js +98 -0
  14. package/dist/vendors/tag-managers/google-tag-manager.js +17 -3
  15. package/dist-types/__tests__/helpers.d.ts +2 -2
  16. package/dist-types/engine/compile.d.ts +1 -1
  17. package/dist-types/engine/runtime.d.ts +1 -1
  18. package/dist-types/events.d.ts +46 -0
  19. package/dist-types/registry.d.ts +36 -0
  20. package/dist-types/resolve.d.ts +1 -1
  21. package/dist-types/vendors/_shared/install-builders.d.ts +1 -1
  22. package/dist-types/vendors/ads-and-pixels/pinterest-tag.d.ts +295 -0
  23. package/dist-types/vendors/analytics/adobe-analytics.d.ts +1 -1
  24. package/dist-types/vendors/analytics/google-tag.d.ts +3 -1
  25. package/dist-types/vendors/analytics/matomo-analytics.d.ts +1 -1
  26. package/dist-types/vendors/analytics/one-dollar-stats.d.ts +39 -0
  27. package/dist-types/vendors/analytics/segment.d.ts +7 -1
  28. package/dist-types/vendors/functional/front-chat.d.ts +62 -0
  29. package/dist-types/vendors/tag-managers/cloudflare-zaraz.d.ts +39 -0
  30. package/dist-types/vendors/tag-managers/google-tag-manager.d.ts +3 -1
  31. package/docs/README.md +7 -0
  32. package/docs/customization/overview.md +4 -3
  33. package/docs/customization/recipes.md +4 -2
  34. package/docs/customization/tokens.md +66 -3
  35. package/docs/frameworks/javascript/script-loader.md +53 -0
  36. package/docs/frameworks/next/script-loader.md +60 -12
  37. package/docs/frameworks/react/script-loader.md +14 -0
  38. package/docs/guides/consent-state.md +327 -0
  39. package/docs/guides/deployment-modes.md +12 -0
  40. package/docs/guides/shared-consent-controls.md +158 -0
  41. package/docs/integrations/adobe-analytics.md +1 -1
  42. package/docs/integrations/ahrefs-analytics.md +1 -1
  43. package/docs/integrations/amplitude.md +1 -1
  44. package/docs/integrations/building-integrations.md +5 -0
  45. package/docs/integrations/clear-on-revocation.md +167 -0
  46. package/docs/integrations/clearbit.md +1 -1
  47. package/docs/integrations/cloudflare-web-analytics.md +1 -1
  48. package/docs/integrations/cloudflare-zaraz.md +399 -0
  49. package/docs/integrations/crisp.md +1 -1
  50. package/docs/integrations/databuddy.md +1 -1
  51. package/docs/integrations/fathom-analytics.md +1 -1
  52. package/docs/integrations/front-chat.md +322 -0
  53. package/docs/integrations/google-maps.md +21 -21
  54. package/docs/integrations/google-tag-manager.md +1 -1
  55. package/docs/integrations/google-tag.md +1 -1
  56. package/docs/integrations/granular-consent.md +210 -0
  57. package/docs/integrations/heap.md +1 -1
  58. package/docs/integrations/hightouch.md +1 -1
  59. package/docs/integrations/hotjar.md +1 -1
  60. package/docs/integrations/intercom.md +1 -1
  61. package/docs/integrations/linkedin-insights.md +1 -1
  62. package/docs/integrations/logrocket.md +1 -1
  63. package/docs/integrations/matomo-analytics.md +1 -1
  64. package/docs/integrations/meta-pixel.md +1 -1
  65. package/docs/integrations/microsoft-clarity.md +1 -1
  66. package/docs/integrations/microsoft-uet.md +1 -1
  67. package/docs/integrations/mixpanel-analytics.md +1 -1
  68. package/docs/integrations/one-dollar-stats.md +305 -0
  69. package/docs/integrations/openai-pixel.md +1 -1
  70. package/docs/integrations/overview.md +22 -18
  71. package/docs/integrations/pinterest-tag.md +321 -0
  72. package/docs/integrations/pirsch.md +1 -1
  73. package/docs/integrations/plausible-analytics.md +1 -1
  74. package/docs/integrations/posthog.md +1 -1
  75. package/docs/integrations/promptwatch.md +1 -1
  76. package/docs/integrations/reddit-pixel.md +1 -1
  77. package/docs/integrations/rudderstack.md +1 -1
  78. package/docs/integrations/rybbit-analytics.md +1 -1
  79. package/docs/integrations/segment.md +1 -1
  80. package/docs/integrations/snapchat-pixel.md +1 -1
  81. package/docs/integrations/tiktok-pixel.md +1 -1
  82. package/docs/integrations/umami-analytics.md +1 -1
  83. package/docs/integrations/vercel-analytics.md +1 -1
  84. package/docs/integrations/x-pixel.md +1 -1
  85. package/docs/integrations/youtube.md +27 -22
  86. package/docs/upgrade-v3.md +176 -1
  87. package/package.json +30 -2
  88. package/readme.json +0 -19
@@ -33,12 +33,12 @@ Render this component inside your existing consent boundary or provider.
33
33
  ```tsx title="src/consent-embed.tsx"
34
34
  'use client';
35
35
 
36
- import { Frame } from 'c15t/next';
36
+ import { ConsentGate } from 'c15t/next';
37
37
  import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
38
38
 
39
39
  export function ConsentEmbed() {
40
40
  return (
41
- <Frame category={embedCategory}>
41
+ <ConsentGate category={embedCategory}>
42
42
  <iframe
43
43
  src={embedURL}
44
44
  title={embedTitle}
@@ -46,12 +46,12 @@ export function ConsentEmbed() {
46
46
  allowFullScreen
47
47
  style={{ width: '100%', aspectRatio: embedAspectRatio, minHeight: 200, border: 0 }}
48
48
  />
49
- </Frame>
49
+ </ConsentGate>
50
50
  );
51
51
  }
52
52
  ```
53
53
 
54
- `Frame` keeps the iframe absent while permission is denied and removes it
54
+ `ConsentGate` keeps the iframe absent while permission is denied and removes it
55
55
  on revocation. Keep your existing consent styles and preferences dialog.
56
56
 
57
57
  **TanStack Start**
@@ -59,12 +59,12 @@ on revocation. Keep your existing consent styles and preferences dialog.
59
59
  Render this component inside your existing consent boundary or provider.
60
60
 
61
61
  ```tsx title="src/consent-embed.tsx"
62
- import { Frame } from 'c15t/tanstack-start';
62
+ import { ConsentGate } from 'c15t/tanstack-start';
63
63
  import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
64
64
 
65
65
  export function ConsentEmbed() {
66
66
  return (
67
- <Frame category={embedCategory}>
67
+ <ConsentGate category={embedCategory}>
68
68
  <iframe
69
69
  src={embedURL}
70
70
  title={embedTitle}
@@ -72,12 +72,12 @@ export function ConsentEmbed() {
72
72
  allowFullScreen
73
73
  style={{ width: '100%', aspectRatio: embedAspectRatio, minHeight: 200, border: 0 }}
74
74
  />
75
- </Frame>
75
+ </ConsentGate>
76
76
  );
77
77
  }
78
78
  ```
79
79
 
80
- `Frame` keeps the iframe absent while permission is denied and removes it
80
+ `ConsentGate` keeps the iframe absent while permission is denied and removes it
81
81
  on revocation. Keep your existing consent styles and preferences dialog.
82
82
 
83
83
  **React**
@@ -85,12 +85,12 @@ on revocation. Keep your existing consent styles and preferences dialog.
85
85
  Render this component inside your existing consent boundary or provider.
86
86
 
87
87
  ```tsx title="src/consent-embed.tsx"
88
- import { Frame } from 'c15t/react';
88
+ import { ConsentGate } from 'c15t/react';
89
89
  import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
90
90
 
91
91
  export function ConsentEmbed() {
92
92
  return (
93
- <Frame category={embedCategory}>
93
+ <ConsentGate category={embedCategory}>
94
94
  <iframe
95
95
  src={embedURL}
96
96
  title={embedTitle}
@@ -98,12 +98,12 @@ export function ConsentEmbed() {
98
98
  allowFullScreen
99
99
  style={{ width: '100%', aspectRatio: embedAspectRatio, minHeight: 200, border: 0 }}
100
100
  />
101
- </Frame>
101
+ </ConsentGate>
102
102
  );
103
103
  }
104
104
  ```
105
105
 
106
- `Frame` keeps the iframe absent while permission is denied and removes it
106
+ `ConsentGate` keeps the iframe absent while permission is denied and removes it
107
107
  on revocation. Keep your existing consent styles and preferences dialog.
108
108
 
109
109
  **Nuxt**
@@ -176,7 +176,7 @@ Add this component to pages using your consent-enabled base layout:
176
176
  <c15t-consent-embed style="display: block"></c15t-consent-embed>
177
177
 
178
178
  <script>
179
- import { getConsentClient } from '@c15t/astro/client';
179
+ import { getConsentClient } from 'c15t/astro/client';
180
180
  import { mountConsentEmbed } from '../consent-embed';
181
181
 
182
182
  class ConsentEmbed extends HTMLElement {
@@ -223,11 +223,11 @@ The provider from your quickstart supplies its consent state.
223
223
 
224
224
  ```svelte title="src/ConsentEmbed.svelte"
225
225
  <script lang="ts">
226
- import { Frame } from '@c15t/svelte';
226
+ import { ConsentGate } from '@c15t/svelte';
227
227
  import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
228
228
  </script>
229
229
 
230
- <Frame category={embedCategory}>
230
+ <ConsentGate category={embedCategory}>
231
231
  <iframe
232
232
  src={embedURL}
233
233
  title={embedTitle}
@@ -238,10 +238,10 @@ The provider from your quickstart supplies its consent state.
238
238
  style:min-height="200px"
239
239
  style:border="0"
240
240
  ></iframe>
241
- </Frame>
241
+ </ConsentGate>
242
242
  ```
243
243
 
244
- The Svelte `Frame` waits until the browser is mounted and the category is
244
+ The Svelte `ConsentGate` waits until the browser is mounted and the category is
245
245
  allowed. Its default placeholder opens preferences. Revocation removes the
246
246
  iframe.
247
247
 
@@ -252,11 +252,11 @@ Keep the SvelteKit root provider and its server prefetch unchanged.
252
252
 
253
253
  ```svelte title="src/lib/ConsentEmbed.svelte"
254
254
  <script lang="ts">
255
- import { Frame } from '@c15t/svelte';
255
+ import { ConsentGate } from '@c15t/svelte';
256
256
  import { embedCategory, embedURL, embedTitle, embedAspectRatio } from '../embed-config';
257
257
  </script>
258
258
 
259
- <Frame category={embedCategory}>
259
+ <ConsentGate category={embedCategory}>
260
260
  <iframe
261
261
  src={embedURL}
262
262
  title={embedTitle}
@@ -267,10 +267,10 @@ Keep the SvelteKit root provider and its server prefetch unchanged.
267
267
  style:min-height="200px"
268
268
  style:border="0"
269
269
  ></iframe>
270
- </Frame>
270
+ </ConsentGate>
271
271
  ```
272
272
 
273
- The Svelte `Frame` waits until the browser is mounted and the category is
273
+ The Svelte `ConsentGate` waits until the browser is mounted and the category is
274
274
  allowed. Its default placeholder opens preferences. Revocation removes the
275
275
  iframe.
276
276
 
@@ -380,7 +380,7 @@ Set playback options in the iframe URL. For example, append `start=36` to begin
380
380
  [YouTube player parameters](https://developers.google.com/youtube/player_parameters)
381
381
  and reserve at least a 200-by-200-pixel player area.
382
382
 
383
- React and Svelte `Frame` components provide a blocked-state placeholder and
383
+ React and Svelte `ConsentGate` components provide a blocked-state placeholder and
384
384
  consent action. Their `placeholder` APIs differ: React takes a node, Svelte
385
385
  takes a snippet. Keep a clear way to reopen preferences when customizing them.
386
386
  The Vue and browser examples show that action explicitly. `loading="lazy"` delays an
@@ -388,6 +388,11 @@ allowed iframe for performance; it does not implement consent gating.
388
388
 
389
389
  ## Consent behavior
390
390
 
391
+ Add `data-vendor="youtube"` next to `data-category` and declare a `youtube`
392
+ vendor in the runtime's `vendors` option or the backend manifest to let visitors
393
+ turn YouTube off while keeping the rest of the category on. See
394
+ [granular consent](./granular-consent.md).
395
+
391
396
  Each example keeps the iframe absent until the category is allowed. After
392
397
  permission, the iframe can load. Revoking permission removes the iframe and its
393
398
  embedded document. Requests already sent cannot be recalled.
@@ -5,6 +5,13 @@ description: Migrate policy configuration, consent records, callbacks, and
5
5
  group: reference
6
6
  ---
7
7
 
8
+ ## React version requirement
9
+
10
+ The v3 React and Next.js adapters require React and React DOM 18 or newer.
11
+ Upgrade both packages together before installing v3. Earlier alpha peer ranges
12
+ incorrectly allowed React 16 and 17 even though v3 already uses `useId` and
13
+ `useSyncExternalStore`, which those versions do not provide.
14
+
8
15
  ## Start with a policy rule
9
16
 
10
17
  ```ts
@@ -80,6 +87,115 @@ be true before a visitor acts. A notice dismissal updates `noticeDismissal` and
80
87
  does not record consent. Global Privacy Control updates privacy signals and
81
88
  configured opt-out directives without turning a browser signal into a choice.
82
89
 
90
+ ## Replace `useConsentManager()`
91
+
92
+ `useConsentManager()` is gone from `c15t/react`, `c15t/next`,
93
+ `c15t/tanstack-start` and their `/headless` entries. It subscribed to the whole
94
+ consent snapshot, so every component that called it re-rendered on every
95
+ change, including changes to categories it never read. Call one hook per field
96
+ instead; each re-renders only when its own value changes.
97
+
98
+ Before, in v2 and earlier v3 alphas:
99
+
100
+ ```tsx title="components/marketing-banner.tsx"
101
+ import { useConsentManager } from 'c15t/react';
102
+
103
+ export function MarketingBanner() {
104
+ const { activeUI, has, saveConsents } = useConsentManager();
105
+ if (activeUI !== 'banner' || has('marketing')) {
106
+ return null;
107
+ }
108
+ return <button type="button" onClick={() => void saveConsents('all')}>Accept</button>;
109
+ }
110
+ ```
111
+
112
+ After:
113
+
114
+ ```tsx title="components/marketing-banner.tsx"
115
+ import { useActiveUI, useConsent } from 'c15t/react';
116
+ import { useHeadlessConsentUI } from 'c15t/react/headless';
117
+
118
+ export function MarketingBanner() {
119
+ const activeUI = useActiveUI();
120
+ const marketing = useConsent('marketing');
121
+ const { performAction } = useHeadlessConsentUI();
122
+ if (activeUI !== 'banner' || marketing) {
123
+ return null;
124
+ }
125
+ return <button type="button" onClick={() => void performAction('accept')}>Accept</button>;
126
+ }
127
+ ```
128
+
129
+ Import hooks from the same entry you used for `useConsentManager()`:
130
+ `c15t/next` and `c15t/next/headless` in Next.js, `c15t/tanstack-start` and
131
+ `c15t/tanstack-start/headless` in TanStack Start.
132
+
133
+ | `useConsentManager()` field | v3 replacement |
134
+ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
135
+ | `activeUI` | `useActiveUI()`. It returns `null` before the kernel picks a surface; the old field reported `'none'`. |
136
+ | `setActiveUI(ui)` | `useSetActiveUI()` |
137
+ | `has(category)` | `useConsent(category)`, one call per category at the top of the component |
138
+ | `consents` | `useConsents()` |
139
+ | `effectivePermissions` | `useEffectivePermissions()` |
140
+ | `explicitChoice` | `useExplicitChoice()` |
141
+ | `promptRequirement` | `usePromptRequirement()` |
142
+ | `noticeDismissal` | `useNoticeDismissal()` |
143
+ | `privacySignals` | `usePrivacySignals()` |
144
+ | `optOutDirectives` | `useOptOutDirectives()` |
145
+ | `restrictions` | `useRestrictions()` |
146
+ | `resolution` | `usePolicyResolution()` |
147
+ | `policyRule` | `usePolicyRule()` |
148
+ | `policyCategories` | `usePolicyCategories()`. The old list started with `'necessary'`; this one does not. |
149
+ | `policyScopeMode` | `usePolicyScopeMode()` |
150
+ | `policyBanner` | `usePromptPresentation()` |
151
+ | `policyDialog` | `usePreferencesPresentation()` |
152
+ | `model` | `useModel()`. It returns `null` while no policy matches; the old field reported `'opt-in'`. |
153
+ | `branding` | `useBranding()`. It returns `null` when unset; the old field reported `'c15t'`. |
154
+ | `iab` | `useIABSnapshot()` |
155
+ | `vendors` | `useDeclaredVendors()` |
156
+ | `vendorChoice` | `useVendorChoice()` |
157
+ | `getDisplayedVendors(category)` | `useDeclaredVendors()`, filtered to vendors whose category condition names the category |
158
+ | `subscribeToConsentChanges(listener)` | `useSubscribeToConsentChanges()`, or the provider's `onPermissionsChanged` callback |
159
+ | `updateConsentCategories(categories)` | `useRegisterConsentCategories()` |
160
+ | `translationConfig` | `useTranslations()` for the active strings |
161
+ | `selectedConsents`, `selectedConsentTypes` | `useConsentDraft().values` |
162
+ | `setSelectedConsent(name, value)` | `useConsentDraft().set(name, value)` |
163
+ | `selectedVendors` | `useConsentDraft().vendors`, or `useVendorDraft().vendors` |
164
+ | `setSelectedVendor(id, granted)` | `useConsentDraft().setVendor(id, granted)` |
165
+ | `resetDraft()` | `useConsentDraft().reset()` |
166
+ | `draftIsStale` | `useConsentDraft().isStale` |
167
+ | `consentCategories`, `consentTypes`, `getDisplayedConsents()` | `useConsentDraft().displayedCategories`, with labels from `useTranslations().consentTypes` |
168
+ | `saveConsents('all')` | `useHeadlessConsentUI().performAction('accept')` |
169
+ | `saveConsents('necessary')` | `useHeadlessConsentUI().performAction('reject')` |
170
+ | `saveConsents('custom')` | `useHeadlessConsentUI().performAction('save')` |
171
+ | `manager` | Nothing. It was always `null` in v3. |
172
+
173
+ The old hook kept one private draft per component, shared by its selection and
174
+ its save. `useConsentDraft()` and `useHeadlessConsentUI()` read the nearest
175
+ `ConsentDraftProvider` instead. When one component stages choices and another
176
+ saves them, or one component calls both hooks, render them inside the same
177
+ `ConsentDraftProvider`; without one, each hook keeps its own draft and a custom
178
+ save commits nothing you staged. `ConsentWidget` already provides one.
179
+
180
+ `useHeadlessConsentUI()` saves the way the stock buttons do: it closes the
181
+ surface after a successful save and refuses a draft that the policy made stale.
182
+ `useSaveConsents()` calls the kernel directly and does neither.
183
+
184
+ The CLI rewrites the common destructuring forms for you:
185
+
186
+ ```bash
187
+ c15t codemods use-consent-manager-to-hooks --dry-run --json
188
+ ```
189
+
190
+ It replaces each field that has a one-hook equivalent, turns `has('marketing')`
191
+ calls with a literal category into `useConsent('marketing')`, and maps
192
+ `saveConsents('all' | 'necessary' | 'custom')` calls to
193
+ `useHeadlessConsentUI().saveCustomPreferences()`. Fields it cannot rewrite stay
194
+ on a `useConsentManager()` call under a `TODO(c15t v3)` comment that names the
195
+ replacement, so the build fails at each place that needs manual work. It also
196
+ marks components that now need a shared `ConsentDraftProvider`. Review the
197
+ result, then run it without `--dry-run`.
198
+
83
199
  ## Replace choice callbacks
84
200
 
85
201
  ```tsx
@@ -184,7 +300,7 @@ Notice acknowledgement uses `common.acknowledge`, falling back to
184
300
 
185
301
  ```astro
186
302
  ---
187
- import ConsentBanner from '@c15t/astro/components/consent-banner.astro';
303
+ import ConsentBanner from 'c15t/astro/components/consent-banner.astro';
188
304
  ---
189
305
 
190
306
  <ConsentBanner dismissButtonText="Got it" />
@@ -252,6 +368,43 @@ TanStack Start (`c15t/tanstack-start`, `c15t/tanstack-start/server`):
252
368
  `consentLoaderOptions`, `backendURL`, `initRoute` and `DEFAULT_INIT_ROUTE` are
253
369
  unchanged.
254
370
 
371
+ ## `Frame` is now `ConsentGate`
372
+
373
+ The component that mounts an embed only while its category is allowed is now
374
+ `ConsentGate` in React, Next.js, TanStack Start, Svelte and Vue. The old names
375
+ still work as deprecated aliases and will be removed in a later major release.
376
+
377
+ | v2 | v3 |
378
+ | -------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
379
+ | `Frame`, `Frame.Root`, `Frame.Title`, `Frame.Button` | `ConsentGate`, `ConsentGate.Root`, `ConsentGate.Title`, `ConsentGate.Button` |
380
+ | `FrameProps`, `FrameCompoundComponent` | `ConsentGateProps`, `ConsentGateCompoundComponent` |
381
+ | `c15t/react/frame`, `c15t/react/components/frame` | `c15t/react/consent-gate`, `c15t/react/components/consent-gate` |
382
+ | Vue `ConsentFrame`, `@c15t/vue/runtime/components/consent-frame.vue` | `ConsentGate`, `@c15t/vue/runtime/components/consent-gate.vue` |
383
+
384
+ Props and behavior are unchanged. The placeholder keeps its `frame.*`
385
+ translation keys, `--frame-*` CSS custom properties and
386
+ `data-testid="frame-placeholder"`, so custom copy, styles and tests continue
387
+ to apply.
388
+
389
+ ## Split dialog and widget entries are deferred
390
+
391
+ `c15t/react/consent-dialog` and `c15t/react/consent-widget` (and their
392
+ `@c15t/react/*` equivalents) now export the same deferred `ConsentDialog` and
393
+ `ConsentWidget` as `c15t/react`. The dialog's code loads when it first opens
394
+ instead of with every page. `<ConsentDialog />`, `<ConsentWidget />` and the
395
+ `ConsentDialog.<Part>` properties need no change.
396
+
397
+ The two entries no longer export the individual parts. Import those from the
398
+ component entries, which keep the old, eagerly bundled exports:
399
+
400
+ | Before | After |
401
+ | --------------------------------------------------------------- | -------------------------------------------------------------------------- |
402
+ | `import { Card, Header } from 'c15t/react/consent-dialog'` | `import { Card, Header } from 'c15t/react/components/consent-dialog'` |
403
+ | `import { Accordion, Switch } from 'c15t/react/consent-widget'` | `import { Accordion, Switch } from 'c15t/react/components/consent-widget'` |
404
+
405
+ To keep the dialog in the first load so its first open needs no download,
406
+ import `ConsentDialog` from `c15t/react/components/consent-dialog`.
407
+
255
408
  ## Next.js RSC banner removal
256
409
 
257
410
  The `@c15t/nextjs/rsc` entry (`c15t/next/rsc`) and its `RscConsentBanner`,
@@ -332,3 +485,25 @@ export function Banner() {
332
485
  );
333
486
  }
334
487
  ```
488
+
489
+ ## Session reports in manifest mode
490
+
491
+ A host that resolves init from a cached manifest never calls `/init`, so a v3
492
+ backend could not count the visitors it served. From this release every
493
+ server-side resolution sends `POST /sessions` to the backend after the fact,
494
+ server-to-server and detached from the response: the Next.js, TanStack Start,
495
+ SvelteKit, Nuxt and Astro init routes, and the Next.js, TanStack Start and
496
+ Astro render-time prefetches. The browser makes no request.
497
+
498
+ This is a new outbound data flow from your server to the consent backend. The
499
+ report carries the manifest revision, the matched policy, the jurisdiction,
500
+ country, region, language and GPC signal, plus the visitor's user agent and
501
+ address as request headers. It carries no cookies and no identifier. Nothing is
502
+ stored on the backend; the report reaches a `sessions.onReport` sink and the
503
+ request log.
504
+
505
+ Reporting is on by default and needs an absolute backend URL. Set
506
+ `reportSessions: false` on the adapter to send none. Each report is one
507
+ resolution, not one visitor: a page view can produce a `render` report and a
508
+ `route` report, and nothing in them links the two. A consumer counting sessions
509
+ groups reports by the forwarded address and user agent within a window.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c15t/scripts",
3
- "version": "3.0.0-alpha.0",
3
+ "version": "3.0.0-alpha.2",
4
4
  "description": "Consent-aware script integrations for Google Tag Manager, Google Consent Mode v2, GA4, Google Ads, Meta Pixel, and more.",
5
5
  "keywords": [
6
6
  "adobe-analytics",
@@ -26,6 +26,7 @@
26
26
  "logrocket",
27
27
  "meta-pixel",
28
28
  "microsoft-uet",
29
+ "pinterest-tag",
29
30
  "pirsch",
30
31
  "posthog",
31
32
  "privacy",
@@ -133,6 +134,11 @@
133
134
  "import": "./dist/vendors/analytics/mixpanel-analytics.js",
134
135
  "default": "./dist/vendors/analytics/mixpanel-analytics.js"
135
136
  },
137
+ "./one-dollar-stats": {
138
+ "types": "./dist-types/vendors/analytics/one-dollar-stats.d.ts",
139
+ "import": "./dist/vendors/analytics/one-dollar-stats.js",
140
+ "default": "./dist/vendors/analytics/one-dollar-stats.js"
141
+ },
136
142
  "./pirsch": {
137
143
  "types": "./dist-types/vendors/analytics/pirsch.d.ts",
138
144
  "import": "./dist/vendors/analytics/pirsch.js",
@@ -178,11 +184,21 @@
178
184
  "import": "./dist/vendors/functional/crisp.js",
179
185
  "default": "./dist/vendors/functional/crisp.js"
180
186
  },
187
+ "./front-chat": {
188
+ "types": "./dist-types/vendors/functional/front-chat.d.ts",
189
+ "import": "./dist/vendors/functional/front-chat.js",
190
+ "default": "./dist/vendors/functional/front-chat.js"
191
+ },
181
192
  "./intercom": {
182
193
  "types": "./dist-types/vendors/functional/intercom.d.ts",
183
194
  "import": "./dist/vendors/functional/intercom.js",
184
195
  "default": "./dist/vendors/functional/intercom.js"
185
196
  },
197
+ "./cloudflare-zaraz": {
198
+ "types": "./dist-types/vendors/tag-managers/cloudflare-zaraz.d.ts",
199
+ "import": "./dist/vendors/tag-managers/cloudflare-zaraz.js",
200
+ "default": "./dist/vendors/tag-managers/cloudflare-zaraz.js"
201
+ },
186
202
  "./google-tag-manager": {
187
203
  "types": "./dist-types/vendors/tag-managers/google-tag-manager.d.ts",
188
204
  "import": "./dist/vendors/tag-managers/google-tag-manager.js",
@@ -203,6 +219,11 @@
203
219
  "import": "./dist/vendors/ads-and-pixels/openai-pixel.js",
204
220
  "default": "./dist/vendors/ads-and-pixels/openai-pixel.js"
205
221
  },
222
+ "./pinterest-tag": {
223
+ "types": "./dist-types/vendors/ads-and-pixels/pinterest-tag.d.ts",
224
+ "import": "./dist/vendors/ads-and-pixels/pinterest-tag.js",
225
+ "default": "./dist/vendors/ads-and-pixels/pinterest-tag.js"
226
+ },
206
227
  "./reddit-pixel": {
207
228
  "types": "./dist-types/vendors/ads-and-pixels/reddit-pixel.d.ts",
208
229
  "import": "./dist/vendors/ads-and-pixels/reddit-pixel.js",
@@ -237,6 +258,11 @@
237
258
  "types": "./dist-types/*.d.ts",
238
259
  "import": "./dist/*.js",
239
260
  "default": "./dist/*.js"
261
+ },
262
+ "./events": {
263
+ "types": "./dist-types/events.d.ts",
264
+ "import": "./dist/events.js",
265
+ "default": "./dist/events.js"
240
266
  }
241
267
  },
242
268
  "publishConfig": {
@@ -254,8 +280,10 @@
254
280
  "test:live-vendors": "bun --tsconfig-override live-vendors/tsconfig.json live-vendors/runner.ts",
255
281
  "test:watch": "vitest"
256
282
  },
283
+ "dependencies": {
284
+ "@c15t/core": "3.0.0-alpha.3"
285
+ },
257
286
  "devDependencies": {
258
- "@c15t/core": "3.0.0-alpha.0",
259
287
  "@c15t/typescript-config": "0.0.1",
260
288
  "@c15t/vitest-config": "1.0.0",
261
289
  "playwright": "1.58.2",
package/readme.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "title": "@c15t/scripts: Prebuilt Consent Scripts",
3
- "description": "Consent-aware script integrations for Google Tag Manager, Google Consent Mode v2, GA4, Google Ads, Meta Pixel, analytics tools, pixels, tag managers, and widgets.",
4
- "features": [
5
- "Prebuilt script loaders for popular analytics, advertising, marketing, and functional tools",
6
- "Google Tag Manager support with Google Consent Mode v2 defaults and consent updates",
7
- "Google Analytics 4 and Google Ads support through gtag.js",
8
- "Consent-gated Meta Pixel and conversion pixel loading",
9
- "Easy integration with c15t's script loader",
10
- "Configuration options for each supported vendor",
11
- "Supported vendors include Google Tag Manager, Meta Pixel, OpenAI Pixel, Amplitude, Heap, PostHog, TikTok Pixel, LinkedIn Insights, Microsoft UET, X Pixel, Reddit Pixel, Snapchat Pixel, Intercom, Crisp, and more"
12
- ],
13
- "docsLink": "https://c15t.com/docs/integrations",
14
- "showCLIGeneration": false,
15
- "customSections": {
16
- "Integrations": "- **Google Tag Manager**: Loads with Google Consent Mode v2 defaults set to denied; GTM-managed tags fire only once matching consent is granted ([guide](https://c15t.com/docs/integrations/google-tag-manager))\n- **Google Analytics 4 + Google Ads (gtag.js)**: Consent Mode v2 defaults and consent updates when users make a choice ([guide](https://c15t.com/docs/integrations/google-tag))\n- **Conversion pixels**: Meta Pixel, OpenAI Pixel, TikTok Pixel, LinkedIn Insights, Microsoft UET (Microsoft Ads), X Pixel, Reddit Pixel, Snapchat Pixel\n- **Analytics**: PostHog, Amplitude, Heap, Segment, RudderStack, Hightouch, Mixpanel, Microsoft Clarity, Hotjar, Plausible, Fathom, Matomo, Umami, Vercel Analytics\n- **Chat widgets**: Intercom, Crisp",
17
- "Example": "```ts\nimport { googleTagManager } from '@c15t/scripts/google-tag-manager'\nimport { metaPixel } from '@c15t/scripts/meta-pixel'\n\nconst scripts = [\n googleTagManager({ id: 'GTM-XXXXXX' }),\n metaPixel({ pixelId: '000000000000000' }),\n]\n```"
18
- }
19
- }