@c15t/scripts 2.2.0 → 3.0.0-alpha.1

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 (202) hide show
  1. package/AGENTS.md +77 -48
  2. package/README.md +4 -3
  3. package/dist/e2e-test-utils.js +60 -24
  4. package/dist/engine/compile.js +45 -45
  5. package/dist/engine/runtime.js +130 -119
  6. package/dist/registry.js +196 -176
  7. package/dist/resolve.js +12 -12
  8. package/dist/vendors/_shared/attributes.js +5 -5
  9. package/dist/vendors/_shared/google-consent.js +10 -10
  10. package/dist/vendors/_shared/install-builders.js +9 -9
  11. package/dist/vendors/_shared/script-url.js +12 -12
  12. package/dist/vendors/ads-and-pixels/linkedin-insights.js +16 -16
  13. package/dist/vendors/ads-and-pixels/meta-pixel.js +82 -82
  14. package/dist/vendors/ads-and-pixels/microsoft-uet.js +57 -57
  15. package/dist/vendors/ads-and-pixels/openai-pixel.js +88 -0
  16. package/dist/vendors/ads-and-pixels/reddit-pixel.js +39 -39
  17. package/dist/vendors/ads-and-pixels/snapchat-pixel.js +25 -25
  18. package/dist/vendors/ads-and-pixels/tiktok-pixel.js +31 -31
  19. package/dist/vendors/ads-and-pixels/x-pixel.js +17 -17
  20. package/dist/vendors/analytics/adobe-analytics.js +17 -17
  21. package/dist/vendors/analytics/ahrefs-analytics.js +8 -8
  22. package/dist/vendors/analytics/amplitude.js +39 -39
  23. package/dist/vendors/analytics/clearbit.js +11 -11
  24. package/dist/vendors/analytics/cloudflare-web-analytics.js +13 -13
  25. package/dist/vendors/analytics/databuddy.js +45 -45
  26. package/dist/vendors/analytics/fathom-analytics.js +15 -15
  27. package/dist/vendors/analytics/google-tag.js +23 -23
  28. package/dist/vendors/analytics/heap.js +37 -37
  29. package/dist/vendors/analytics/hightouch.js +30 -30
  30. package/dist/vendors/analytics/hotjar.js +14 -14
  31. package/dist/vendors/analytics/logrocket.js +24 -24
  32. package/dist/vendors/analytics/matomo-analytics.js +51 -51
  33. package/dist/vendors/analytics/microsoft-clarity.js +34 -31
  34. package/dist/vendors/analytics/mixpanel-analytics.js +31 -31
  35. package/dist/vendors/analytics/pirsch.js +27 -27
  36. package/dist/vendors/analytics/plausible-analytics.js +24 -24
  37. package/dist/vendors/analytics/posthog.js +84 -79
  38. package/dist/vendors/analytics/promptwatch.js +8 -8
  39. package/dist/vendors/analytics/rudderstack.js +50 -50
  40. package/dist/vendors/analytics/rybbit-analytics.js +30 -30
  41. package/dist/vendors/analytics/segment.js +16 -16
  42. package/dist/vendors/analytics/umami-analytics.js +16 -16
  43. package/dist/vendors/analytics/vercel-analytics.js +22 -22
  44. package/dist/vendors/functional/crisp.js +49 -51
  45. package/dist/vendors/functional/intercom.js +18 -18
  46. package/dist/vendors/tag-managers/cloudflare-zaraz.js +98 -0
  47. package/dist/vendors/tag-managers/google-tag-manager.js +20 -20
  48. package/dist-types/__tests__/helpers.d.ts +11 -11
  49. package/dist-types/engine/compile.d.ts +3 -3
  50. package/dist-types/engine/runtime.d.ts +3 -3
  51. package/dist-types/registry.d.ts +191 -173
  52. package/dist-types/resolve.d.ts +3 -3
  53. package/dist-types/types.d.ts +2 -2
  54. package/dist-types/vendors/_shared/attributes.d.ts +2 -2
  55. package/dist-types/vendors/_shared/google-consent.d.ts +2 -2
  56. package/dist-types/vendors/_shared/install-builders.d.ts +2 -2
  57. package/dist-types/vendors/_shared/script-url.d.ts +6 -6
  58. package/dist-types/vendors/ads-and-pixels/linkedin-insights.d.ts +14 -14
  59. package/dist-types/vendors/ads-and-pixels/meta-pixel.d.ts +27 -27
  60. package/dist-types/vendors/ads-and-pixels/microsoft-uet.d.ts +40 -40
  61. package/dist-types/vendors/ads-and-pixels/openai-pixel.d.ts +211 -0
  62. package/dist-types/vendors/ads-and-pixels/reddit-pixel.d.ts +28 -29
  63. package/dist-types/vendors/ads-and-pixels/snapchat-pixel.d.ts +23 -23
  64. package/dist-types/vendors/ads-and-pixels/tiktok-pixel.d.ts +22 -22
  65. package/dist-types/vendors/ads-and-pixels/x-pixel.d.ts +15 -15
  66. package/dist-types/vendors/analytics/adobe-analytics.d.ts +3 -3
  67. package/dist-types/vendors/analytics/ahrefs-analytics.d.ts +5 -5
  68. package/dist-types/vendors/analytics/amplitude.d.ts +24 -24
  69. package/dist-types/vendors/analytics/clearbit.d.ts +5 -5
  70. package/dist-types/vendors/analytics/cloudflare-web-analytics.d.ts +6 -6
  71. package/dist-types/vendors/analytics/databuddy.d.ts +34 -31
  72. package/dist-types/vendors/analytics/fathom-analytics.d.ts +8 -8
  73. package/dist-types/vendors/analytics/google-tag.d.ts +17 -17
  74. package/dist-types/vendors/analytics/heap.d.ts +17 -17
  75. package/dist-types/vendors/analytics/hightouch.d.ts +15 -15
  76. package/dist-types/vendors/analytics/hotjar.d.ts +9 -9
  77. package/dist-types/vendors/analytics/logrocket.d.ts +11 -11
  78. package/dist-types/vendors/analytics/matomo-analytics.d.ts +3 -3
  79. package/dist-types/vendors/analytics/microsoft-clarity.d.ts +12 -13
  80. package/dist-types/vendors/analytics/mixpanel-analytics.d.ts +20 -20
  81. package/dist-types/vendors/analytics/pirsch.d.ts +10 -10
  82. package/dist-types/vendors/analytics/plausible-analytics.d.ts +13 -13
  83. package/dist-types/vendors/analytics/posthog.d.ts +35 -32
  84. package/dist-types/vendors/analytics/promptwatch.d.ts +5 -5
  85. package/dist-types/vendors/analytics/rudderstack.d.ts +16 -16
  86. package/dist-types/vendors/analytics/rybbit-analytics.d.ts +15 -15
  87. package/dist-types/vendors/analytics/segment.d.ts +11 -11
  88. package/dist-types/vendors/analytics/umami-analytics.d.ts +9 -9
  89. package/dist-types/vendors/analytics/vercel-analytics.d.ts +13 -13
  90. package/dist-types/vendors/functional/crisp.d.ts +9 -9
  91. package/dist-types/vendors/functional/intercom.d.ts +12 -12
  92. package/dist-types/vendors/tag-managers/cloudflare-zaraz.d.ts +39 -0
  93. package/dist-types/vendors/tag-managers/google-tag-manager.d.ts +16 -16
  94. package/docs/README.md +77 -48
  95. package/docs/assets/v3/brand-bar.png +0 -0
  96. package/docs/assets/v3/brand-card.png +0 -0
  97. package/docs/assets/v3/choice-wall.png +0 -0
  98. package/docs/assets/v3/mobile-card.png +0 -0
  99. package/docs/assets/v3/preferences.png +0 -0
  100. package/docs/customization/overview.md +45 -0
  101. package/docs/customization/recipes.md +79 -0
  102. package/docs/customization/slots.md +55 -0
  103. package/docs/customization/tokens.md +76 -0
  104. package/docs/customization/translations.md +49 -0
  105. package/docs/frameworks/javascript/script-loader.md +81 -343
  106. package/docs/frameworks/next/script-loader.md +164 -455
  107. package/docs/frameworks/react/script-loader.md +41 -533
  108. package/docs/guides/consent-state.md +60 -0
  109. package/docs/guides/data-fetching.md +163 -0
  110. package/docs/guides/deployment-modes.md +75 -0
  111. package/docs/guides/troubleshooting.md +68 -0
  112. package/docs/guides/verify-consent.md +62 -0
  113. package/docs/integrations/adobe-analytics.md +239 -105
  114. package/docs/integrations/ahrefs-analytics.md +238 -104
  115. package/docs/integrations/amplitude.md +219 -157
  116. package/docs/integrations/building-integrations.md +36 -223
  117. package/docs/integrations/clear-on-revocation.md +167 -0
  118. package/docs/integrations/clearbit.md +247 -86
  119. package/docs/integrations/cloudflare-web-analytics.md +250 -84
  120. package/docs/integrations/cloudflare-zaraz.md +399 -0
  121. package/docs/integrations/crisp.md +251 -97
  122. package/docs/integrations/databuddy.md +259 -153
  123. package/docs/integrations/fathom-analytics.md +239 -96
  124. package/docs/integrations/google-maps.md +328 -207
  125. package/docs/integrations/google-tag-manager.md +248 -96
  126. package/docs/integrations/google-tag.md +261 -90
  127. package/docs/integrations/granular-consent.md +208 -0
  128. package/docs/integrations/heap.md +222 -149
  129. package/docs/integrations/hightouch.md +225 -131
  130. package/docs/integrations/hotjar.md +239 -90
  131. package/docs/integrations/intercom.md +239 -98
  132. package/docs/integrations/linkedin-insights.md +243 -113
  133. package/docs/integrations/logrocket.md +241 -123
  134. package/docs/integrations/matomo-analytics.md +256 -111
  135. package/docs/integrations/meta-pixel.md +197 -324
  136. package/docs/integrations/microsoft-clarity.md +233 -114
  137. package/docs/integrations/microsoft-uet.md +245 -110
  138. package/docs/integrations/mixpanel-analytics.md +252 -87
  139. package/docs/integrations/openai-pixel.md +441 -0
  140. package/docs/integrations/overview.md +96 -133
  141. package/docs/integrations/pirsch.md +249 -96
  142. package/docs/integrations/plausible-analytics.md +241 -100
  143. package/docs/integrations/posthog.md +353 -214
  144. package/docs/integrations/promptwatch.md +251 -81
  145. package/docs/integrations/reddit-pixel.md +226 -173
  146. package/docs/integrations/rudderstack.md +244 -187
  147. package/docs/integrations/rybbit-analytics.md +244 -91
  148. package/docs/integrations/segment.md +238 -92
  149. package/docs/integrations/snapchat-pixel.md +240 -110
  150. package/docs/integrations/tiktok-pixel.md +249 -81
  151. package/docs/integrations/umami-analytics.md +242 -95
  152. package/docs/integrations/vercel-analytics.md +242 -90
  153. package/docs/integrations/x-pixel.md +238 -104
  154. package/docs/integrations/youtube.md +359 -142
  155. package/docs/upgrade-v3.md +381 -0
  156. package/package.json +90 -78
  157. package/dist/e2e-test-utils.cjs +0 -166
  158. package/dist/engine/compile.cjs +0 -130
  159. package/dist/engine/runtime.cjs +0 -475
  160. package/dist/registry.cjs +0 -423
  161. package/dist/resolve.cjs +0 -71
  162. package/dist/types.cjs +0 -69
  163. package/dist/vendors/_shared/attributes.cjs +0 -55
  164. package/dist/vendors/_shared/google-consent.cjs +0 -69
  165. package/dist/vendors/_shared/install-builders.cjs +0 -59
  166. package/dist/vendors/_shared/script-url.cjs +0 -78
  167. package/dist/vendors/ads-and-pixels/linkedin-insights.cjs +0 -89
  168. package/dist/vendors/ads-and-pixels/meta-pixel.cjs +0 -206
  169. package/dist/vendors/ads-and-pixels/microsoft-uet.cjs +0 -151
  170. package/dist/vendors/ads-and-pixels/reddit-pixel.cjs +0 -151
  171. package/dist/vendors/ads-and-pixels/snapchat-pixel.cjs +0 -131
  172. package/dist/vendors/ads-and-pixels/tiktok-pixel.cjs +0 -130
  173. package/dist/vendors/ads-and-pixels/x-pixel.cjs +0 -92
  174. package/dist/vendors/analytics/adobe-analytics.cjs +0 -90
  175. package/dist/vendors/analytics/ahrefs-analytics.cjs +0 -68
  176. package/dist/vendors/analytics/amplitude.cjs +0 -193
  177. package/dist/vendors/analytics/clearbit.cjs +0 -69
  178. package/dist/vendors/analytics/cloudflare-web-analytics.cjs +0 -73
  179. package/dist/vendors/analytics/databuddy.cjs +0 -144
  180. package/dist/vendors/analytics/fathom-analytics.cjs +0 -76
  181. package/dist/vendors/analytics/google-tag.cjs +0 -107
  182. package/dist/vendors/analytics/heap.cjs +0 -181
  183. package/dist/vendors/analytics/hightouch.cjs +0 -153
  184. package/dist/vendors/analytics/hotjar.cjs +0 -85
  185. package/dist/vendors/analytics/logrocket.cjs +0 -99
  186. package/dist/vendors/analytics/matomo-analytics.cjs +0 -232
  187. package/dist/vendors/analytics/microsoft-clarity.cjs +0 -138
  188. package/dist/vendors/analytics/mixpanel-analytics.cjs +0 -134
  189. package/dist/vendors/analytics/pirsch.cjs +0 -108
  190. package/dist/vendors/analytics/plausible-analytics.cjs +0 -122
  191. package/dist/vendors/analytics/posthog.cjs +0 -236
  192. package/dist/vendors/analytics/promptwatch.cjs +0 -70
  193. package/dist/vendors/analytics/rudderstack.cjs +0 -227
  194. package/dist/vendors/analytics/rybbit-analytics.cjs +0 -104
  195. package/dist/vendors/analytics/segment.cjs +0 -97
  196. package/dist/vendors/analytics/umami-analytics.cjs +0 -80
  197. package/dist/vendors/analytics/vercel-analytics.cjs +0 -94
  198. package/dist/vendors/functional/crisp.cjs +0 -143
  199. package/dist/vendors/functional/intercom.cjs +0 -89
  200. package/dist/vendors/tag-managers/google-tag-manager.cjs +0 -100
  201. package/docs/shared/react/guides/script-loader.md +0 -311
  202. package/readme.json +0 -19
@@ -1,356 +1,94 @@
1
1
  ---
2
- title: Script Loader
3
- description: Gate third-party scripts behind consent — load Google Analytics,
4
- Meta Pixel, and other tracking scripts only when users grant permission.
2
+ title: JavaScript script loading
3
+ description: Attach a script loader to the consent kernel and dispose it with
4
+ the application.
5
5
  group: frameworks
6
6
  ---
7
- The script loader manages third-party JavaScript based on consent state. You declare scripts in your provider's `scripts` option, and c15t decides when each script should load, stay loaded, unload, or receive a consent update.
8
7
 
9
- Use it for analytics, pixels, tag managers, product analytics, and other vendor snippets that should not run until the right consent condition is satisfied. Prebuilt helpers live in [`@c15t/scripts`](/docs/integrations/overview); custom scripts can be declared directly when the vendor is specific to your app.
8
+ ## Attach the loader before initialization
10
9
 
11
- <PackageCommandTabs mode="install" command="@c15t/scripts" />
12
-
13
- > ℹ️ **Info:**
14
- > Start with the integrations overview before writing your own script. Built-in helpers encode vendor boot order, consent updates, and common defaults so you do not have to.
15
- >
16
- > 📝 **Note:**
17
- > If you need a vendor c15t does not ship yet, see the custom integration guide. It explains when a one-off Script is enough and when to build a reusable manifest-backed helper.
18
- >
19
- > ℹ️ **Info:**
20
- > The script loader handles JavaScript tags and callback lifecycles. For iframe-only embeds, use the iframe blocking pattern. For UI components such as maps or video players, combine consent state with a component-level placeholder or a dedicated renderable integration.
21
-
22
- ## Basic Usage
23
-
24
- Pass an array of `Script` objects to the runtime options:
10
+ Install `@c15t/scripts` alongside `c15t`. This browser example adds a
11
+ marketing integration to a hosted kernel:
25
12
 
26
13
  ```ts
27
- import { getOrCreateConsentRuntime } from 'c15t';
14
+ import { createConsentKernel, createHostedTransport } from 'c15t';
15
+ import { createPersistence } from 'c15t/modules/persistence';
16
+ import { createScriptLoader } from 'c15t/modules/script-loader';
28
17
  import { metaPixel } from '@c15t/scripts/meta-pixel';
29
18
 
30
- const { consentStore } = getOrCreateConsentRuntime({
31
- mode: 'hosted',
32
- backendURL: 'https://your-instance.c15t.dev',
33
- scripts: [
34
- metaPixel({ pixelId: '123456' }),
35
- {
36
- id: 'custom-analytics',
37
- src: 'https://cdn.example.com/analytics.js',
38
- category: 'measurement',
39
- },
40
- ],
19
+ const kernel = createConsentKernel({
20
+ transport: createHostedTransport({ backendURL: 'https://your-project.inth.app' }),
41
21
  });
42
- ```
43
-
44
- ## Mental Model
45
-
46
- Every script you register has the same lifecycle. c15t evaluates each script against the current consent state, then drives it through a small number of states:
47
-
48
- 1. **Pending** — registered but waiting for consent. Nothing is in the DOM yet.
49
- 2. **Loaded** — consent matched, c15t injected the script (or ran callbacks for callback-only scripts).
50
- 3. **Updated** — already loaded, consent state changed, `onConsentChange` ran so the SDK can react.
51
- 4. **Unloaded** — consent was revoked. c15t removed the script element unless you opted into persistence.
52
-
53
- Four lifecycle callbacks let you hook into transitions: `onBeforeLoad`, `onLoad`, `onConsentChange`, and `onError`. Two flags — [`alwaysLoad`](#always-load) and [`persistAfterConsentRevoked`](#persist-after-revocation) — change how c15t treats consent boundaries. Everything else (DOM placement, ad-block evasion, dynamic management) is a refinement on top of this core model.
54
-
55
- ## Choose the Right Approach
56
-
57
- Most projects mix more than one style. Pick the smallest one that keeps consent behavior obvious:
58
-
59
- |Style|Use when|
60
- |--|--|
61
- |**Built-in helper** from `@c15t/scripts`|c15t already ships the vendor. See the [integrations overview](/docs/integrations/overview).|
62
- |**Plain `Script`**|One-off app code with simple load and callback behavior.|
63
- |**Callback-only `Script`**|Another package already loaded the SDK; c15t only synchronizes consent.|
64
- |**Manifest-backed helper**|Reusable vendor integration with structured setup phases, queues, stubs, or a vendor consent API.|
65
- |**Iframe / renderable integration**|Vendor exposes an iframe or React component, not just a `<script>` tag.|
66
-
67
- ## Script Types
68
-
69
- ### Standard Scripts
70
-
71
- Standard scripts load an external JavaScript file via a `<script>` tag. This is the default for most analytics and pixel SDKs:
72
-
73
- ```tsx
74
- {
75
- id: 'analytics',
76
- src: 'https://cdn.example.com/analytics.js',
77
- category: 'measurement',
78
- }
79
- ```
80
-
81
- ### Inline Scripts
82
-
83
- Inline scripts execute JavaScript from `textContent` instead of loading a URL. Use these sparingly; a manifest-backed helper is usually better for reusable vendor code.
84
-
85
- ```tsx
86
- {
87
- id: 'gtag-config',
88
- textContent: `
89
- window.dataLayer = window.dataLayer || [];
90
- function gtag(){dataLayer.push(arguments);}
91
- gtag('js', new Date());
92
- gtag('config', 'G-XXXXXX');
93
- `,
94
- category: 'measurement',
95
- }
96
- ```
97
-
98
- ### Callback-Only Scripts
99
-
100
- Callback-only scripts do not inject a script tag. They run lifecycle callbacks when consent allows them to. Use this when another package has already loaded the SDK and c15t only needs to drive consent:
101
-
102
- ```tsx
103
- {
104
- id: 'posthog-consent',
105
- callbackOnly: true,
106
- category: 'measurement',
107
- onLoad: ({ hasConsent }) => {
108
- if (hasConsent) {
109
- posthog.opt_in_capturing();
110
- }
111
- },
112
- onConsentChange: ({ hasConsent }) => {
113
- if (hasConsent) {
114
- posthog.opt_in_capturing();
115
- } else {
116
- posthog.opt_out_capturing();
117
- }
118
- },
119
- }
120
- ```
121
-
122
- ### Manifest-Backed Helpers
123
-
124
- Built-in integrations in `@c15t/scripts` are manifest-backed. A manifest describes vendor setup as structured phases, then c15t compiles it into a `Script`. Manifests keep queue stubs, script URLs, consent signaling, and post-load work consistent across apps and they are safe to ship from a server.
125
-
126
- Use a manifest-backed helper when:
127
-
128
- * the integration should be reused across projects,
129
- * the vendor snippet has ordered setup steps,
130
- * the vendor exposes a consent API,
131
- * or you plan to contribute the integration back to c15t.
132
-
133
- Read the [custom integration guide](/docs/integrations/building-integrations) for the manifest contract, phases, and testing checklist.
134
-
135
- ### Iframe And Renderable Integrations
136
-
137
- Some vendors are not just script tags. YouTube embeds, maps, calendars, and checkout widgets often need a visible component, a placeholder, or an iframe.
138
-
139
- * For iframe-only embeds, gate the iframe `src` with the [iframe blocking](/docs/frameworks/react/iframe-blocking) pattern instead of loading a script just to hide an iframe.
140
- * For SDK-backed UI, use the script loader for the shared SDK and render the component only when consent and SDK readiness agree.
141
- * Use `YouTubeEmbed` for the iframe-only YouTube candidate and `GoogleMap` for the callback-based SDK candidate.
142
- * Use `useConsentScript()` when building custom wrappers. It registers scripts through the consent store, follows `loadedScripts`, and returns a promise-shaped readiness contract for callback-based SDKs.
143
-
144
- ## Lifecycle Callbacks
145
-
146
- Every script supports four callbacks. Each receives a `ScriptCallbackInfo` payload (id, element, hasConsent, consents):
147
-
148
- * `onBeforeLoad` — runs before the script tag is injected. Create globals, queues, or vendor stubs here.
149
- * `onLoad` — runs after the browser loads the script. Call vendor `init()` APIs here.
150
- * `onConsentChange` — runs for loaded scripts when consent changes. Forward the new consent state to the vendor SDK.
151
- * `onError` — runs when the script fails to load. Record diagnostics or render a fallback.
152
-
153
- ```tsx
154
- {
155
- id: 'analytics',
156
- src: 'https://analytics.example.com/v2.js',
157
- category: 'measurement',
158
- onBeforeLoad: ({ id }) => {
159
- window.analyticsQueue = window.analyticsQueue || [];
160
- },
161
- onLoad: () => {
162
- window.analytics.init('my-key');
163
- },
164
- onError: ({ error }) => {
165
- console.error('Failed to load analytics:', error);
166
- },
167
- onConsentChange: ({ hasConsent }) => {
168
- window.analytics.setConsent(hasConsent);
169
- },
170
- }
171
- ```
172
-
173
- ## Consent Conditions
174
-
175
- The `category` field accepts a `HasCondition`. It can be a single consent category or a logical expression:
176
-
177
- ```tsx
178
- // Simple: requires measurement consent
179
- { category: 'measurement' }
180
-
181
- // AND: requires both measurement and marketing
182
- { category: { and: ['measurement', 'marketing'] } }
183
-
184
- // OR: requires either measurement or marketing
185
- { category: { or: ['measurement', 'marketing'] } }
186
- ```
187
-
188
- Consent categories use the same names as the rest of c15t (`necessary`, `functionality`, `experience`, `measurement`, `marketing`).
189
-
190
- ## Persistence Options
191
-
192
- ### Always Load
193
-
194
- `alwaysLoad` loads the script regardless of whether its category is currently granted. Use it only when the vendor must be present early **and** has a reliable consent API of its own — Google Tag Manager with Consent Mode is the canonical example.
195
-
196
- ```tsx
197
- {
198
- id: 'google-tag-manager',
199
- src: 'https://www.googletagmanager.com/gtm.js?id=GTM-XXXX',
200
- category: 'measurement',
201
- alwaysLoad: true,
202
- }
203
- ```
204
-
205
- When `alwaysLoad` is on, `onConsentChange` becomes mandatory: it is how the loaded SDK learns about every transition.
206
-
207
- > ⚠️ **Warning:**
208
- > alwaysLoad shifts compliance responsibility to the vendor integration. Make sure the script receives denied-by-default consent signals before it can track.
209
-
210
- ### Persist After Revocation
211
-
212
- `persistAfterConsentRevoked` keeps a script in the page after consent is revoked instead of unloading it. Use it only when the vendor exposes a runtime consent toggle — otherwise unloading is safer because removing the element guarantees the SDK stops.
213
-
214
- ```tsx
215
- {
216
- id: 'error-tracking',
217
- src: 'https://errors.example.com/track.js',
218
- category: 'measurement',
219
- persistAfterConsentRevoked: true,
220
- onConsentChange: ({ hasConsent }) => {
221
- window.ErrorTracker.setConsent(hasConsent);
222
- },
223
- }
224
- ```
225
-
226
- As with `alwaysLoad`, `onConsentChange` is how the persisted SDK learns about consent updates.
227
-
228
- ### `alwaysLoad` vs `persistAfterConsentRevoked`
229
-
230
- These two flags answer different questions. Use this table to keep them straight:
231
-
232
- |Question|`alwaysLoad`|`persistAfterConsentRevoked`|
233
- |--|--|--|
234
- |Loads before consent is granted?|Yes|No (waits for consent like a normal script)|
235
- |Stays loaded after consent is revoked?|Yes|Yes|
236
- |Requires a vendor consent API?|Yes|Yes|
237
-
238
- ## DOM Placement
239
-
240
- Control where the script is injected and whether the element id is anonymized:
241
-
242
- ```tsx
243
- {
244
- id: 'widget',
245
- src: 'https://widget.example.com/embed.js',
246
- category: 'experience',
247
- target: 'body', // 'head' (default) or 'body'
248
- anonymizeId: true, // default: true, hides the c15t script id from ad blockers
249
- nonce: 'abc123', // optional CSP nonce
250
- }
251
- ```
252
-
253
- Set `anonymizeId: false` only when another script or test needs a stable DOM id. Pass `nonce` when your CSP requires it; c15t applies it directly to the generated `<script>` element.
254
-
255
- You usually do not need a per-script `nonce`. Setting `nonce` once on the provider covers every injected script (and the theme stylesheet); a per-script value overrides it for that script alone.
256
-
257
- ## Dynamic Management
258
-
259
- Framework packages expose script-manager methods so integrations can be added, removed, or inspected at runtime. Use this for tenant-specific tools, feature-flagged scripts, or vendors that are configured after sign-in:
260
-
261
- * `setScripts(scripts)` — registers script definitions and immediately evaluates them against consent.
262
- * `removeScript(id)` — removes a definition and unloads its element if needed.
263
- * `isScriptLoaded(id)` — returns whether c15t has loaded a script.
264
- * `getLoadedScriptIds()` — returns every currently loaded script id.
265
-
266
- Dynamic scripts should still use stable ids. If the same vendor is added repeatedly with different ids, c15t treats each call as a new script.
267
-
268
- ## Calling Vendor APIs From Your App
269
-
270
- The script loader controls **when the vendor SDK loads**. It does not intercept calls your application code makes to that SDK afterwards. Whether your event calls are safe before consent is granted depends on the script's persistence flags:
271
-
272
- |Vendor pattern|What c15t does|What your app code must do|
273
- |--|--|--|
274
- |Consent-gated load, unloaded on revoke (e.g. cookieless analytics)|Script not in DOM until consent granted; removed on revoke. Global is `undefined` outside that window.|**Guard every call.** Unguarded `window.vendor.track(...)` throws when the global is absent.|
275
- |Consent-gated load with `persistAfterConsentRevoked` (e.g. Meta Pixel)|Script not in DOM until consent granted; stays after revoke. c15t calls vendor's consent-revoke API on revocation.|Guard calls only for the pre-initial-consent window. Once loaded, the SDK handles its own suppression.|
276
- |`alwaysLoad: true` with a vendor consent API (e.g. GTM, gtag, Databuddy, PostHog)|Script in DOM on page start; c15t signals consent state through the vendor's API.|Calls are safe — the vendor SDK suppresses transmission when consent is denied.|
277
- |No app-facing API (e.g. Cloudflare Web Analytics)|Script in/out of DOM based on consent. Tracking is fully automatic.|Nothing to guard.|
278
-
279
- The safe pattern in React is to read consent state through `useConsentManager().has(category)` before calling the SDK:
280
-
281
- ```tsx
282
- import { useCallback } from 'react';
283
- import { useConsentManager } from '@c15t/react';
284
-
285
- function useTrackSignup() {
286
- const { has } = useConsentManager();
287
-
288
- return useCallback(() => {
289
- if (has('measurement')) {
290
- window.fathom?.trackEvent('signup');
291
- }
292
- }, [has]);
293
- }
294
-
295
- function SignupButton() {
296
- const trackSignup = useTrackSignup();
297
-
298
- return <button onClick={trackSignup}>Sign up</button>;
299
- }
300
- ```
301
-
302
- From non-React code, read the consent store directly:
303
-
304
- ```ts
305
- import { getOrCreateConsentRuntime } from 'c15t';
306
-
307
- const { consentStore } = getOrCreateConsentRuntime();
308
-
309
- if (consentStore.getState().has('measurement')) {
310
- window.fathom?.trackEvent('signup');
22
+ const persistence = createPersistence({ kernel });
23
+ const loader = createScriptLoader({ kernel, scripts: [metaPixel({ pixelId: '123456789012345' })] });
24
+ await kernel.commands.init();
25
+
26
+ function dispose() {
27
+ loader.dispose();
28
+ persistence.dispose();
29
+ kernel.dispose();
311
30
  }
312
31
  ```
313
32
 
314
- Each [integration page](/docs/integrations/overview) includes a vendor-specific **Tracking events in your app** block that names which pattern applies.
315
-
316
- ## Debugging Checklist
317
-
318
- When a script does not behave as expected:
319
-
320
- 1. Confirm the script's `category` matches the consent that has been granted.
321
- 2. Check whether the script is `alwaysLoad` or consent-gated.
322
- 3. Confirm `onBeforeLoad` creates any globals before the vendor code reads them.
323
- 4. Confirm `onConsentChange` updates persisted or always-loaded scripts when consent changes.
324
- 5. Check whether the browser or an ad blocker blocked the request.
325
- 6. Use c15t devtools to inspect script lifecycle events when available.
326
-
327
- ## Dynamic Script Management
328
-
329
- Add, remove, or check scripts at runtime via the store:
330
-
331
- ```ts
332
- const state = consentStore.getState();
333
-
334
- // Add scripts dynamically
335
- state.setScripts([
336
- { id: 'dynamic', src: 'https://cdn.example.com/widget.js', category: 'measurement' },
337
- ]);
338
-
339
- // Remove a script
340
- state.removeScript('dynamic');
341
-
342
- // Check if a script is loaded
343
- const loaded = state.isScriptLoaded('custom-analytics');
344
-
345
- // Get all loaded script IDs
346
- const allLoaded = state.getLoadedScriptIds();
347
- ```
348
-
349
- ## API Reference
350
-
351
- |Property|Value|
352
- |:--|:--|
353
- |Type Name|\`Script\`|
354
- |Source Path|\`./packages/core/src/libs/script-loader/types.ts\`|
355
-
356
- \*ExtractedTypeTable: Could not extract "Script" from "./packages/core/src/libs/script-loader/types.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
33
+ Replace the URL and pixel ID. Call `dispose()` when your application tears down.
34
+ The headless kernel does not render a banner; connect a policy-aware UI before
35
+ shipping this integration. A framework provider already manages these modules,
36
+ so do not attach a second loader to a provider-owned kernel.
37
+
38
+ ## Keep one owner per vendor
39
+
40
+ Use stable script IDs and remove the vendor's original snippet. Ordinary scripts
41
+ wait for effective permission. Helpers with `alwaysLoad` instead load and signal
42
+ permission through the vendor API. Read the individual integration guide before
43
+ assuming all helpers have the same network behavior.
44
+
45
+ The loader exposes `updateScripts`, `getLoadedScriptIds` and `dispose`.
46
+ Unloading an element cannot reverse requests or code that already ran. Test
47
+ revocation and vendor cleanup with [verification](../../guides/verify-consent.md).
48
+
49
+ ## Dispose integration resources
50
+
51
+ Ordinary scripts keep their mounted resource when `updateScripts` receives a
52
+ fresh object with the same ID and unchanged element configuration. New callback
53
+ functions alone do not reload the vendor or send a temporary denial. Changing
54
+ the source, inline code or element attributes starts a new loading lifecycle.
55
+ Consent conditions are reevaluated on every configuration update. Pending
56
+ load and error events use the latest registered callbacks and consent state.
57
+ Setting `persistAfterConsentRevoked` to `false` removes an owned retained element
58
+ when the script no longer has consent.
59
+
60
+ Custom script configurations can use `onDispose(info)` to release event
61
+ listeners or other resources. Adding this hook opts the configuration into an
62
+ object-owned lifecycle: replacing the object disposes its resources and starts
63
+ again, even with the same ID. Keep these objects stable across framework
64
+ rerenders. The loader also calls the hook when the configuration is removed or
65
+ the loader is disposed, including configurations that never loaded.
66
+ `info.element` contains the last loaded or retained element when available,
67
+ even when configuration removal has already detached it.
68
+
69
+ Duplicate references receive one cleanup per registration. Re-registering a
70
+ removed object starts a new lifecycle. Updates requested from lifecycle
71
+ callbacks run after the current pass; if several are requested, the latest
72
+ configuration wins. Disposal stops further reconciliation. A callback feedback
73
+ loop exceeding 100 consecutive passes disposes the loader and reports an
74
+ `error` debug event. Avoid callbacks that keep changing consent or replacing
75
+ their own configuration.
76
+
77
+ `onBeforeLoad` prepares a loading attempt. If a callback changes consent or
78
+ replaces configurations, the loader cancels that attempt before loading and
79
+ reevaluates the latest state. A still-eligible script can retry preparation
80
+ with a new element and updated consent. Make `onBeforeLoad` safe to repeat;
81
+ use `onLoad` for initialization that requires a completed load. This also
82
+ applies to callback-only scripts, whose `onLoad` is skipped when preparation
83
+ invalidates the current pass.
84
+
85
+ Consent revocation alone does not call `onDispose`. Use `onConsentChange` for
86
+ vendor opt-out commands. Cleanup errors are reported through the loader's debug
87
+ events and do not prevent other configurations from being cleaned up.
88
+
89
+ ## Clear stored tracking data
90
+
91
+ Script gating does not remove cookies or Web Storage entries that a script
92
+ already wrote. Configure [clear on revocation](../../integrations/clear-on-revocation.md)
93
+ on your runtime, or attach its module to your existing kernel, to remove
94
+ declared data when its category is denied.