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

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 (300) hide show
  1. package/AGENTS.md +129 -63
  2. package/README.md +8 -29
  3. package/SKILL.md +33 -0
  4. package/dist/adobe-analytics.js +2 -0
  5. package/dist/ahrefs-analytics.js +2 -0
  6. package/dist/amplitude.js +2 -0
  7. package/dist/clearbit.js +2 -0
  8. package/dist/cloudflare-web-analytics.js +2 -0
  9. package/dist/cloudflare-zaraz.js +2 -0
  10. package/dist/crisp.js +2 -0
  11. package/dist/databuddy.js +2 -0
  12. package/dist/e2e-test-utils.js +2 -139
  13. package/dist/engine/compile.js +2 -89
  14. package/dist/engine/runtime.js +2 -448
  15. package/dist/events.js +2 -218
  16. package/dist/fathom-analytics.js +2 -0
  17. package/dist/front-chat.js +2 -0
  18. package/dist/google-tag-manager.js +2 -0
  19. package/dist/google-tag.js +2 -0
  20. package/dist/heap.js +2 -0
  21. package/dist/hightouch.js +2 -0
  22. package/dist/hotjar.js +2 -0
  23. package/dist/intercom.js +2 -0
  24. package/dist/klaviyo.js +2 -0
  25. package/dist/linkedin-insights.js +2 -0
  26. package/dist/logrocket.js +2 -0
  27. package/dist/matomo-analytics.js +2 -0
  28. package/dist/meta-pixel.js +2 -0
  29. package/dist/microsoft-clarity.js +2 -0
  30. package/dist/microsoft-uet.js +2 -0
  31. package/dist/mixpanel-analytics.js +2 -0
  32. package/dist/one-dollar-stats.js +2 -0
  33. package/dist/openai-pixel.js +2 -0
  34. package/dist/pinterest-tag.js +2 -0
  35. package/dist/pirsch.js +2 -0
  36. package/dist/plausible-analytics.js +2 -0
  37. package/dist/posthog.js +2 -0
  38. package/dist/promptwatch.js +2 -0
  39. package/dist/reddit-pixel.js +2 -0
  40. package/dist/registry.js +2 -422
  41. package/dist/resolve.js +2 -33
  42. package/dist/rudderstack.js +2 -0
  43. package/dist/rybbit-analytics.js +2 -0
  44. package/dist/segment.js +2 -0
  45. package/dist/snapchat-pixel.js +2 -0
  46. package/dist/tiktok-pixel.js +2 -0
  47. package/dist/types.js +2 -16
  48. package/dist/umami-analytics.js +2 -0
  49. package/dist/vendors/_shared/attributes.js +2 -14
  50. package/dist/vendors/_shared/google-consent.js +2 -27
  51. package/dist/vendors/_shared/install-builders.js +2 -21
  52. package/dist/vendors/_shared/required-id.js +2 -0
  53. package/dist/vendors/_shared/script-url.js +2 -28
  54. package/dist/vendors/ads-and-pixels/linkedin-insights.js +2 -48
  55. package/dist/vendors/ads-and-pixels/meta-pixel.js +2 -153
  56. package/dist/vendors/ads-and-pixels/microsoft-uet.js +2 -110
  57. package/dist/vendors/ads-and-pixels/openai-pixel.js +2 -88
  58. package/dist/vendors/ads-and-pixels/pinterest-tag.js +2 -123
  59. package/dist/vendors/ads-and-pixels/reddit-pixel.js +2 -107
  60. package/dist/vendors/ads-and-pixels/snapchat-pixel.js +2 -87
  61. package/dist/vendors/ads-and-pixels/tiktok-pixel.js +2 -89
  62. package/dist/vendors/ads-and-pixels/x-pixel.js +2 -48
  63. package/dist/vendors/analytics/adobe-analytics.js +2 -49
  64. package/dist/vendors/analytics/ahrefs-analytics.js +2 -27
  65. package/dist/vendors/analytics/amplitude.js +2 -134
  66. package/dist/vendors/analytics/clearbit.js +2 -28
  67. package/dist/vendors/analytics/cloudflare-web-analytics.js +2 -32
  68. package/dist/vendors/analytics/databuddy.js +2 -103
  69. package/dist/vendors/analytics/fathom-analytics.js +2 -35
  70. package/dist/vendors/analytics/google-tag.js +2 -78
  71. package/dist/vendors/analytics/heap.js +2 -134
  72. package/dist/vendors/analytics/hightouch.js +2 -109
  73. package/dist/vendors/analytics/hotjar.js +2 -44
  74. package/dist/vendors/analytics/logrocket.js +2 -58
  75. package/dist/vendors/analytics/matomo-analytics.js +2 -191
  76. package/dist/vendors/analytics/microsoft-clarity.js +2 -100
  77. package/dist/vendors/analytics/mixpanel-analytics.js +2 -93
  78. package/dist/vendors/analytics/one-dollar-stats.js +2 -30
  79. package/dist/vendors/analytics/pirsch.js +2 -67
  80. package/dist/vendors/analytics/plausible-analytics.js +2 -81
  81. package/dist/vendors/analytics/posthog.js +2 -200
  82. package/dist/vendors/analytics/promptwatch.js +2 -29
  83. package/dist/vendors/analytics/rudderstack.js +2 -183
  84. package/dist/vendors/analytics/rybbit-analytics.js +2 -63
  85. package/dist/vendors/analytics/segment.js +2 -65
  86. package/dist/vendors/analytics/umami-analytics.js +2 -39
  87. package/dist/vendors/analytics/vercel-analytics.js +2 -53
  88. package/dist/vendors/email-and-sms/klaviyo.js +2 -0
  89. package/dist/vendors/functional/crisp.js +2 -100
  90. package/dist/vendors/functional/front-chat.js +2 -64
  91. package/dist/vendors/functional/intercom.js +2 -45
  92. package/dist/vendors/tag-managers/cloudflare-zaraz.js +2 -98
  93. package/dist/vendors/tag-managers/google-tag-manager.js +2 -73
  94. package/dist/vercel-analytics.js +2 -0
  95. package/dist/x-pixel.js +2 -0
  96. package/dist-types/adobe-analytics.d.ts +2 -0
  97. package/dist-types/ahrefs-analytics.d.ts +2 -0
  98. package/dist-types/amplitude.d.ts +2 -0
  99. package/dist-types/clearbit.d.ts +2 -0
  100. package/dist-types/cloudflare-web-analytics.d.ts +2 -0
  101. package/dist-types/cloudflare-zaraz.d.ts +2 -0
  102. package/dist-types/crisp.d.ts +2 -0
  103. package/dist-types/databuddy.d.ts +2 -0
  104. package/dist-types/e2e-test-utils.d.ts +2 -0
  105. package/dist-types/engine/compile.d.ts +2 -3
  106. package/dist-types/engine/runtime.d.ts +2 -3
  107. package/dist-types/events.d.ts +2 -46
  108. package/dist-types/fathom-analytics.d.ts +2 -0
  109. package/dist-types/front-chat.d.ts +2 -0
  110. package/dist-types/google-tag-manager.d.ts +2 -0
  111. package/dist-types/google-tag.d.ts +2 -0
  112. package/dist-types/heap.d.ts +2 -0
  113. package/dist-types/hightouch.d.ts +2 -0
  114. package/dist-types/hotjar.d.ts +2 -0
  115. package/dist-types/intercom.d.ts +2 -0
  116. package/dist-types/klaviyo.d.ts +2 -0
  117. package/dist-types/linkedin-insights.d.ts +2 -0
  118. package/dist-types/logrocket.d.ts +2 -0
  119. package/dist-types/matomo-analytics.d.ts +2 -0
  120. package/dist-types/meta-pixel.d.ts +2 -0
  121. package/dist-types/microsoft-clarity.d.ts +2 -0
  122. package/dist-types/microsoft-uet.d.ts +2 -0
  123. package/dist-types/mixpanel-analytics.d.ts +2 -0
  124. package/dist-types/one-dollar-stats.d.ts +2 -0
  125. package/dist-types/openai-pixel.d.ts +2 -0
  126. package/dist-types/pinterest-tag.d.ts +2 -0
  127. package/dist-types/pirsch.d.ts +2 -0
  128. package/dist-types/plausible-analytics.d.ts +2 -0
  129. package/dist-types/posthog.d.ts +2 -0
  130. package/dist-types/promptwatch.d.ts +2 -0
  131. package/dist-types/reddit-pixel.d.ts +2 -0
  132. package/dist-types/registry.d.ts +2 -485
  133. package/dist-types/resolve.d.ts +2 -9
  134. package/dist-types/rudderstack.d.ts +2 -0
  135. package/dist-types/rybbit-analytics.d.ts +2 -0
  136. package/dist-types/segment.d.ts +2 -0
  137. package/dist-types/snapchat-pixel.d.ts +2 -0
  138. package/dist-types/tiktok-pixel.d.ts +2 -0
  139. package/dist-types/types.d.ts +2 -314
  140. package/dist-types/umami-analytics.d.ts +2 -0
  141. package/dist-types/vendors/_shared/attributes.d.ts +2 -35
  142. package/dist-types/vendors/_shared/google-consent.d.ts +2 -47
  143. package/dist-types/vendors/_shared/install-builders.d.ts +2 -30
  144. package/dist-types/vendors/_shared/required-id.d.ts +2 -0
  145. package/dist-types/vendors/_shared/script-url.d.ts +2 -75
  146. package/dist-types/vendors/ads-and-pixels/linkedin-insights.d.ts +2 -92
  147. package/dist-types/vendors/ads-and-pixels/meta-pixel.d.ts +2 -289
  148. package/dist-types/vendors/ads-and-pixels/microsoft-uet.d.ts +2 -105
  149. package/dist-types/vendors/ads-and-pixels/openai-pixel.d.ts +2 -211
  150. package/dist-types/vendors/ads-and-pixels/pinterest-tag.d.ts +2 -295
  151. package/dist-types/vendors/ads-and-pixels/reddit-pixel.d.ts +2 -210
  152. package/dist-types/vendors/ads-and-pixels/snapchat-pixel.d.ts +2 -171
  153. package/dist-types/vendors/ads-and-pixels/tiktok-pixel.d.ts +2 -106
  154. package/dist-types/vendors/ads-and-pixels/x-pixel.d.ts +2 -183
  155. package/dist-types/vendors/analytics/adobe-analytics.d.ts +2 -75
  156. package/dist-types/vendors/analytics/ahrefs-analytics.d.ts +2 -62
  157. package/dist-types/vendors/analytics/amplitude.d.ts +2 -234
  158. package/dist-types/vendors/analytics/clearbit.d.ts +2 -60
  159. package/dist-types/vendors/analytics/cloudflare-web-analytics.d.ts +2 -67
  160. package/dist-types/vendors/analytics/databuddy.d.ts +2 -147
  161. package/dist-types/vendors/analytics/fathom-analytics.d.ts +2 -90
  162. package/dist-types/vendors/analytics/google-tag.d.ts +2 -95
  163. package/dist-types/vendors/analytics/heap.d.ts +2 -316
  164. package/dist-types/vendors/analytics/hightouch.d.ts +2 -285
  165. package/dist-types/vendors/analytics/hotjar.d.ts +2 -73
  166. package/dist-types/vendors/analytics/logrocket.d.ts +2 -101
  167. package/dist-types/vendors/analytics/matomo-analytics.d.ts +2 -41
  168. package/dist-types/vendors/analytics/microsoft-clarity.d.ts +2 -97
  169. package/dist-types/vendors/analytics/mixpanel-analytics.d.ts +2 -113
  170. package/dist-types/vendors/analytics/one-dollar-stats.d.ts +2 -39
  171. package/dist-types/vendors/analytics/pirsch.d.ts +2 -96
  172. package/dist-types/vendors/analytics/plausible-analytics.d.ts +2 -122
  173. package/dist-types/vendors/analytics/posthog.d.ts +2 -175
  174. package/dist-types/vendors/analytics/promptwatch.d.ts +2 -36
  175. package/dist-types/vendors/analytics/rudderstack.d.ts +2 -330
  176. package/dist-types/vendors/analytics/rybbit-analytics.d.ts +2 -82
  177. package/dist-types/vendors/analytics/segment.d.ts +2 -164
  178. package/dist-types/vendors/analytics/umami-analytics.d.ts +2 -93
  179. package/dist-types/vendors/analytics/vercel-analytics.d.ts +2 -66
  180. package/dist-types/vendors/email-and-sms/klaviyo.d.ts +2 -0
  181. package/dist-types/vendors/functional/crisp.d.ts +2 -78
  182. package/dist-types/vendors/functional/front-chat.d.ts +2 -62
  183. package/dist-types/vendors/functional/intercom.d.ts +2 -135
  184. package/dist-types/vendors/tag-managers/cloudflare-zaraz.d.ts +2 -39
  185. package/dist-types/vendors/tag-managers/google-tag-manager.d.ts +2 -96
  186. package/dist-types/vercel-analytics.d.ts +2 -0
  187. package/dist-types/x-pixel.d.ts +2 -0
  188. package/docs/README.md +129 -63
  189. package/docs/assets/v3/bottom-bar.png +0 -0
  190. package/docs/assets/v3/brand-card.png +0 -0
  191. package/docs/assets/v3/brand-preferences.png +0 -0
  192. package/docs/assets/v3/choice-wall.png +0 -0
  193. package/docs/assets/v3/headless-bar-html.png +0 -0
  194. package/docs/assets/v3/headless-bar-mobile.png +0 -0
  195. package/docs/assets/v3/headless-bar.png +0 -0
  196. package/docs/assets/v3/slim-bar.png +0 -0
  197. package/docs/concepts/choose-your-setup.md +87 -0
  198. package/docs/concepts/consent-categories.md +84 -0
  199. package/docs/{guides → concepts}/consent-state.md +89 -105
  200. package/docs/{guides → concepts}/data-fetching.md +31 -27
  201. package/docs/concepts/how-consent-works.md +123 -0
  202. package/docs/concepts/policies.md +71 -0
  203. package/docs/customization/class-names.md +202 -0
  204. package/docs/customization/dark-mode.md +157 -0
  205. package/docs/customization/motion.md +119 -0
  206. package/docs/customization/overview.md +67 -34
  207. package/docs/customization/recipes.md +839 -49
  208. package/docs/customization/slots.md +216 -35
  209. package/docs/customization/stylesheets.md +147 -0
  210. package/docs/customization/tailwind.md +842 -0
  211. package/docs/customization/tokens.md +163 -96
  212. package/docs/customization/translations.md +60 -3
  213. package/docs/frameworks/astro/embeds.md +160 -0
  214. package/docs/frameworks/astro/network-blocker.md +86 -0
  215. package/docs/frameworks/astro/scripts.md +155 -0
  216. package/docs/frameworks/html/embeds.md +142 -0
  217. package/docs/frameworks/html/network-blocker.md +105 -0
  218. package/docs/frameworks/html/scripts.md +164 -0
  219. package/docs/frameworks/javascript/scripts.md +137 -0
  220. package/docs/frameworks/next/embeds.md +90 -0
  221. package/docs/frameworks/next/network-blocker.md +153 -0
  222. package/docs/frameworks/next/scripts.md +196 -0
  223. package/docs/frameworks/nuxt/embeds.md +81 -0
  224. package/docs/frameworks/nuxt/network-blocker.md +97 -0
  225. package/docs/frameworks/nuxt/scripts.md +89 -0
  226. package/docs/frameworks/react/embeds.md +89 -0
  227. package/docs/frameworks/react/network-blocker.md +140 -0
  228. package/docs/frameworks/react/scripts.md +115 -0
  229. package/docs/frameworks/svelte/embeds.md +96 -0
  230. package/docs/frameworks/svelte/network-blocker.md +141 -0
  231. package/docs/frameworks/svelte/scripts.md +144 -0
  232. package/docs/frameworks/sveltekit/embeds.md +103 -0
  233. package/docs/frameworks/sveltekit/network-blocker.md +159 -0
  234. package/docs/frameworks/sveltekit/scripts.md +172 -0
  235. package/docs/frameworks/tanstack-start/embeds.md +96 -0
  236. package/docs/frameworks/tanstack-start/network-blocker.md +145 -0
  237. package/docs/frameworks/tanstack-start/scripts.md +103 -0
  238. package/docs/frameworks/vue/embeds.md +84 -0
  239. package/docs/frameworks/vue/network-blocker.md +99 -0
  240. package/docs/frameworks/vue/scripts.md +93 -0
  241. package/docs/guides/banner-experiments.md +654 -0
  242. package/docs/guides/troubleshooting.md +120 -47
  243. package/docs/guides/verify-consent.md +81 -49
  244. package/docs/integrations/adobe-analytics.md +167 -159
  245. package/docs/integrations/ahrefs-analytics.md +152 -154
  246. package/docs/integrations/amplitude.md +162 -156
  247. package/docs/integrations/building-integrations.md +136 -37
  248. package/docs/integrations/clearbit.md +154 -154
  249. package/docs/integrations/cloudflare-web-analytics.md +156 -156
  250. package/docs/integrations/cloudflare-zaraz.md +209 -261
  251. package/docs/integrations/crisp.md +164 -158
  252. package/docs/integrations/databuddy.md +157 -173
  253. package/docs/integrations/fathom-analytics.md +158 -156
  254. package/docs/integrations/front-chat.md +167 -167
  255. package/docs/integrations/google-maps.md +118 -83
  256. package/docs/integrations/google-tag-manager.md +178 -163
  257. package/docs/integrations/google-tag.md +163 -160
  258. package/docs/integrations/heap.md +163 -155
  259. package/docs/integrations/hightouch.md +161 -157
  260. package/docs/integrations/hotjar.md +159 -155
  261. package/docs/integrations/intercom.md +183 -153
  262. package/docs/integrations/klaviyo.md +486 -0
  263. package/docs/integrations/linkedin-insights.md +174 -150
  264. package/docs/integrations/logrocket.md +160 -156
  265. package/docs/integrations/matomo-analytics.md +188 -178
  266. package/docs/integrations/meta-pixel.md +188 -150
  267. package/docs/integrations/microsoft-clarity.md +163 -155
  268. package/docs/integrations/microsoft-uet.md +148 -154
  269. package/docs/integrations/mixpanel-analytics.md +155 -160
  270. package/docs/integrations/one-dollar-stats.md +166 -165
  271. package/docs/integrations/openai-pixel.md +204 -301
  272. package/docs/integrations/overview.md +137 -80
  273. package/docs/integrations/pinterest-tag.md +191 -183
  274. package/docs/integrations/pirsch.md +169 -159
  275. package/docs/integrations/plausible-analytics.md +172 -158
  276. package/docs/integrations/posthog.md +313 -240
  277. package/docs/integrations/promptwatch.md +154 -154
  278. package/docs/integrations/reddit-pixel.md +185 -157
  279. package/docs/integrations/rudderstack.md +201 -186
  280. package/docs/integrations/rybbit-analytics.md +171 -160
  281. package/docs/integrations/segment.md +182 -154
  282. package/docs/integrations/snapchat-pixel.md +186 -156
  283. package/docs/integrations/tiktok-pixel.md +171 -150
  284. package/docs/integrations/umami-analytics.md +163 -158
  285. package/docs/integrations/vercel-analytics.md +166 -157
  286. package/docs/integrations/x-pixel.md +176 -150
  287. package/docs/integrations/youtube.md +121 -86
  288. package/docs/upgrade-v3.md +496 -467
  289. package/package.json +10 -257
  290. package/dist-types/__tests__/helpers.d.ts +0 -141
  291. package/docs/assets/v3/brand-bar.png +0 -0
  292. package/docs/assets/v3/mobile-card.png +0 -0
  293. package/docs/assets/v3/preferences.png +0 -0
  294. package/docs/frameworks/javascript/script-loader.md +0 -100
  295. package/docs/frameworks/next/script-loader.md +0 -216
  296. package/docs/frameworks/react/script-loader.md +0 -69
  297. package/docs/guides/deployment-modes.md +0 -75
  298. package/docs/guides/shared-consent-controls.md +0 -158
  299. package/docs/integrations/clear-on-revocation.md +0 -167
  300. package/docs/integrations/granular-consent.md +0 -210
@@ -0,0 +1,172 @@
1
+ ---
2
+ title: Scripts
3
+ description: Load vendor scripts, iframes and network requests in a SvelteKit
4
+ app only after the visitor allows their consent category, and stop them when
5
+ consent is withdrawn.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Register scripts in the root layout
10
+
11
+ Pass the array from `src/lib/example-scripts.ts` to `ConsentManagerProvider`
12
+ as the `scripts` prop in `src/routes/+layout.svelte`. The
13
+ [quickstart](https://c15t.com/docs/frameworks/sveltekit/quickstart) sets up both files:
14
+
15
+ ```ts title="src/lib/example-scripts.ts"
16
+ import { posthog } from '@c15t/integrations/posthog';
17
+ import { xPixel } from '@c15t/integrations/x-pixel';
18
+
19
+ export const scripts = [
20
+ posthog({
21
+ id: 'phc_your_project_key',
22
+ initOptions: { cookieless_mode: 'never' },
23
+ loadMode: 'after-consent',
24
+ region: 'eu',
25
+ }),
26
+ xPixel({ pixelId: 'your-pixel-id' }),
27
+ ];
28
+ ```
29
+
30
+ Create the scripts in the layout component, not in `+layout.server.ts`. Script
31
+ configurations hold functions, which a server load cannot send to the
32
+ browser. Scripts load only in the browser, after hydration, so they never
33
+ appear in server HTML.
34
+
35
+ ## Preload the script loader
36
+
37
+ The provider loads the script loader only on pages whose `scripts` array is
38
+ not empty or that have [network blocker](./network-blocker.md) rules. The loader
39
+ and the blocker share one separate chunk. Pages with neither never download
40
+ it. On a page with either, the browser would otherwise request the chunk after
41
+ the app's JavaScript has run, and a returning visitor's consented scripts and
42
+ held requests would wait for that extra request.
43
+
44
+ Add the `c15tPreload()` Vite plugin so `c15tHandle` can link the chunk from
45
+ the page's `<head>` with `<link rel="modulepreload">`:
46
+
47
+ ```ts title="vite.config.ts (partial)"
48
+ import { c15tPreload } from '@c15t/svelte/vite';
49
+
50
+ export default defineConfig({ plugins: [sveltekit(), c15tPreload()] });
51
+ ```
52
+
53
+ SvelteKit builds the server before the client, so the server cannot know the
54
+ chunk's file name. After the client build, the plugin writes the chunk URL
55
+ into the server output, before SvelteKit prerenders pages and before the
56
+ adapter copies the build. Then `c15tHandle` adds one link to every page whose
57
+ provider has scripts or blocker rules, prerendered pages included. The link
58
+ carries the provider's `nonce`, or else the nonce SvelteKit put on its own
59
+ scripts. It asks for low priority (`fetchpriority="low"`): the
60
+ runtime needs the chunk only after hydration, so the browser fetches your
61
+ app's own chunks first. It does nothing in `vite dev`.
62
+
63
+ Without the plugin, or without `c15tHandle` in `hooks.server.ts`, scripts
64
+ still load, one request later.
65
+
66
+ ## How registered scripts load
67
+
68
+ The provider's `scripts` prop takes an array of script configurations. Each
69
+ has a category. The loader adds a script to the page when its category becomes
70
+ allowed and removes it when the category is withdrawn. Nothing optional loads
71
+ while the policy is still resolving, or when it fails.
72
+
73
+ Helpers in `@c15t/integrations`, such as `posthog()` from `@c15t/integrations/posthog`,
74
+ return a configuration with the right category and the vendor's own consent
75
+ calls. [Integrations](../../integrations/overview.md) lists every helper. For an
76
+ SDK without a helper, write a configuration with an `id`, `category` and `src`
77
+ as shown in [building integrations](../../integrations/building-integrations.md).
78
+
79
+ Remove the vendor's original `<script>` tag, `app.html` snippet or SDK import
80
+ before you register it. A banner does not block code that loads some other
81
+ way, and a vendor loaded twice sends events twice.
82
+
83
+ The `scripts` array is read when the provider is created. Build it once, at
84
+ the top level of the component, not inside an effect.
85
+
86
+ ## Script options
87
+
88
+ Helpers set these for you. For a script without a helper, write the object
89
+ yourself:
90
+
91
+ | Option | Default | Behavior |
92
+ | ------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
93
+ | `id` | required | A unique name. The loader uses it to add and remove the script once. |
94
+ | `category` | required | The category or condition, such as `'measurement'` or `{ and: ['measurement', 'marketing'] }`, that must be allowed. |
95
+ | `src` or `textContent` | none | The script's URL, or inline code. |
96
+ | `callbackOnly` | `false` | Adds no `<script>` element and only runs the callbacks. Use it to switch an SDK you load yourself on and off. |
97
+ | `alwaysLoad` | `false` | Loads at once, whatever the consent state. Only for scripts that apply consent themselves, such as a tag manager with Consent Mode. |
98
+ | `persistAfterConsentRevoked` | `false` | Keeps the element after withdrawal instead of removing it. |
99
+ | `target` | `'head'` | Where the element goes: `'head'` or `'body'`. |
100
+ | `async`, `defer`, `fetchPriority`, `attributes`, `nonce` | none | Set on the `<script>` element. |
101
+ | `anonymizeId` | `true` | Gives the element a random `id`, so ad blockers do not match it by name. |
102
+ | `vendor` | none | Also waits for this vendor to be allowed, for vendor-level consent outside IAB. |
103
+ | `onBeforeLoad`, `onLoad`, `onError`, `onConsentChange`, `onDispose` | none | Lifecycle callbacks. See [callbacks](https://c15t.com/docs/frameworks/sveltekit/callbacks#script-callbacks). |
104
+
105
+ ## Gate embeds
106
+
107
+ Iframes are not scripts. Wrap them in `ConsentGate`, which keeps the iframe
108
+ out of the DOM until its category is allowed:
109
+
110
+ ```svelte title="src/YouTubeEmbed.svelte"
111
+ <script lang="ts">
112
+ import { ConsentDialogLink, ConsentGate } from '@c15t/svelte';
113
+ </script>
114
+
115
+ <!-- The iframe mounts only while measurement is allowed. -->
116
+ <ConsentGate category="measurement">
117
+ {#snippet placeholder()}<div class="placeholder">
118
+ <p>Allow measurement to load this YouTube video.</p>
119
+ <ConsentDialogLink>Choose video permissions</ConsentDialogLink>
120
+ </div>{/snippet}
121
+ <iframe
122
+ title="YouTube video"
123
+ src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y?playsinline=1"
124
+ allow="encrypted-media; picture-in-picture"
125
+ allowfullscreen
126
+ ></iframe>
127
+ </ConsentGate>
128
+ ```
129
+
130
+ For iframes from a CMS or Markdown, which you cannot wrap, use the iframe
131
+ blocker's `data-category` and `data-src` attributes. [Embeds](./embeds.md) covers
132
+ both.
133
+
134
+ ## Block requests from code already on the page
135
+
136
+ The provider's `networkBlocker` option holds `fetch` and `XMLHttpRequest`
137
+ calls to the domains you list until their category is allowed. It is a
138
+ backstop for code you cannot move into `scripts`. [Network blocker](./network-blocker.md)
139
+ covers the rules and what it cannot stop.
140
+
141
+ ## What happens when consent is withdrawn
142
+
143
+ Removing a script tag cannot stop code that already ran. So when a save
144
+ withdraws a category that was granted, the provider reloads the page after the
145
+ save request, and the new page starts with only the permitted code. Set
146
+ `reloadOnConsentRevoked: false` on the provider if you handle withdrawal
147
+ yourself, for example through a vendor's own opt-out call in
148
+ `onConsentChange`.
149
+
150
+ `ConsentGate` content unmounts without a reload. To delete first-party cookies
151
+ a vendor set, configure `clearOnRevocation`; see
152
+ [clearing data on revocation](https://c15t.com/docs/frameworks/sveltekit/clear-on-revocation).
153
+
154
+ ## Let visitors turn off one vendor
155
+
156
+ A visitor can allow marketing and still switch off one vendor in it. Pass
157
+ `vendors` to the provider; helpers from `@c15t/integrations` already carry their
158
+ vendor slug. See [vendor consent](https://c15t.com/docs/frameworks/sveltekit/vendor-consent).
159
+
160
+ ## Verify vendor loading
161
+
162
+ Open DevTools, clear site data for your origin and reload:
163
+
164
+ 1. With the banner showing, the Network panel has no requests to your vendors,
165
+ and gated iframes are absent from the Elements panel.
166
+ 2. Allow one category in preferences. Only that category's vendors load, and
167
+ its iframes appear.
168
+ 3. Reject, reload, and confirm the vendor requests stay absent.
169
+ 4. Withdraw a category you allowed. The page reloads and its vendors no longer
170
+ load.
171
+
172
+ [Verify consent](../../guides/verify-consent.md) covers automated checks.
@@ -0,0 +1,96 @@
1
+ ---
2
+ title: Embeds
3
+ description: Keep YouTube videos, maps and other iframes out of a TanStack Start
4
+ page until their consent category is allowed, with ConsentGate or the iframe
5
+ blocker in ConsentRoot.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Pick a method
10
+
11
+ | Method | Use it when |
12
+ | ------------------ | -------------------------------------------------------------------------------------------------------- |
13
+ | `ConsentGate` | You render the iframe in a React component and want a placeholder until the visitor allows its category. |
14
+ | The iframe blocker | The iframe comes from HTML you do not render with React, such as CMS or Markdown content. |
15
+
16
+ Both keep the iframe's `src` out of the page until the category is allowed, so
17
+ the embed's host receives no request before consent.
18
+
19
+ ## Gate an embed with ConsentGate
20
+
21
+ Wrap the iframe in `ConsentGate` in any route under the root route that
22
+ mounts `ConsentRoot`:
23
+
24
+ ```tsx title="src/components/video-embed.tsx"
25
+ import { ConsentDialogLink, ConsentGate } from 'c15t/tanstack-start';
26
+
27
+ export const VideoEmbed = () => (
28
+ <ConsentGate
29
+ category="measurement"
30
+ placeholder={
31
+ <div>
32
+ <p>
33
+ Allow measurement to load this YouTube video. No video request is sent
34
+ before permission.
35
+ </p>
36
+ <ConsentDialogLink>Open privacy settings</ConsentDialogLink>
37
+ </div>
38
+ }
39
+ >
40
+ <iframe
41
+ src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y"
42
+ title="YouTube video"
43
+ sandbox="allow-scripts allow-same-origin allow-presentation"
44
+ allowFullScreen
45
+ />
46
+ </ConsentGate>
47
+ );
48
+ ```
49
+
50
+ The server HTML contains the placeholder, never the iframe, unless the root
51
+ loader resolved measurement as allowed. The placeholder shows a message and a
52
+ [`ConsentDialogLink`](https://c15t.com/docs/frameworks/tanstack-start/components/consent-dialog-link)
53
+ until then. When the visitor withdraws measurement, React removes the iframe.
54
+
55
+ Pick the category that matches what the embed does. The example uses
56
+ measurement for YouTube because its player measures views. A map or chat
57
+ widget usually belongs under functionality or experience.
58
+ [ConsentGate](https://c15t.com/docs/frameworks/tanstack-start/components/consent-gate)
59
+ documents the built-in placeholder and every prop.
60
+
61
+ ## Gate an iframe with the iframe blocker
62
+
63
+ `ConsentRoot` runs the iframe blocker in the browser by default. Give an
64
+ iframe `data-src` instead of `src`, and a `data-category`:
65
+
66
+ ```html title="Markup from your CMS"
67
+ <iframe
68
+ data-src="https://www.google.com/maps/embed?pb=YOUR_EMBED_ID"
69
+ data-category="functionality"
70
+ title="Store map"
71
+ ></iframe>
72
+ ```
73
+
74
+ The server HTML has no `src`, so nothing loads before hydration. After
75
+ hydration, c15t sets `src` from `data-src` when the category is allowed and
76
+ removes `src` when the category is withdrawn. It watches the page, so iframes
77
+ added by client-side navigation are handled too. Add `data-vendor` with a
78
+ vendor ID to also block the iframe while the visitor has turned that vendor
79
+ off.
80
+
81
+ To turn the blocker off, set `iframeBlocker: false` in `ConsentRoot`'s
82
+ `options`.
83
+
84
+ ## Verify the embeds
85
+
86
+ Open the production build in a private window with DevTools open, under a
87
+ policy that asks for consent.
88
+
89
+ 1. View the page source. No iframe has a `src` pointing at
90
+ `youtube-nocookie.com` or `google.com/maps`, and the Network panel shows no
91
+ request to them.
92
+ 2. Allow the embed's category and save. The iframe loads.
93
+ 3. Withdraw the category and save. The page reloads without the embed.
94
+
95
+ The [YouTube](../../integrations/youtube.md) and
96
+ [Google Maps](../../integrations/google-maps.md) guides cover sizing and titles.
@@ -0,0 +1,145 @@
1
+ ---
2
+ title: Network blocker
3
+ description: Hold fetch and XMLHttpRequest calls in a TanStack Start app until
4
+ their consent category is allowed, with networkBlocker rules on ConsentRoot.
5
+ group: frameworks
6
+ ---
7
+
8
+ ## Add rules
9
+
10
+ Pass `networkBlocker` rules to `ConsentRoot` to stop `fetch` and
11
+ `XMLHttpRequest` calls to a domain until its category is allowed. Use it as a
12
+ backstop for beacons an SDK sends itself. Load vendor SDKs through
13
+ [`scripts`](./scripts.md) first, so they do not run
14
+ at all before consent.
15
+
16
+ ```tsx title="src/routes/__root.tsx"
17
+ const networkBlocker = {
18
+ rules: [
19
+ {
20
+ id: 'google-analytics',
21
+ domain: 'google-analytics.com',
22
+ category: 'measurement',
23
+ },
24
+ {
25
+ id: 'meta-pixel',
26
+ domain: 'facebook.com',
27
+ pathIncludes: '/tr',
28
+ category: 'marketing',
29
+ },
30
+ ],
31
+ };
32
+
33
+ <ConsentRoot
34
+ state={consent}
35
+ backendURL={backendURL}
36
+ initRoute={false}
37
+ scripts={scripts}
38
+ networkBlocker={networkBlocker}
39
+ >
40
+ ```
41
+
42
+ The blocker runs in the browser only. It does not see requests your server
43
+ functions or server routes make.
44
+
45
+ ## Match requests with rules
46
+
47
+ Each rule names a `domain` and the consent `category` a request needs. The
48
+ domain also matches its subdomains: `google-analytics.com` covers
49
+ `www.google-analytics.com`. `pathIncludes` narrows the rule to paths that
50
+ contain a substring, and `methods` narrows it to HTTP methods. A request is
51
+ blocked when a matching rule's condition is not met by the visitor's
52
+ effective permissions.
53
+
54
+ `category` takes the same conditions as scripts:
55
+
56
+ ```ts
57
+ { category: 'measurement' }
58
+ { category: { and: ['measurement', 'marketing'] } }
59
+ { category: { or: ['measurement', 'marketing'] } }
60
+ ```
61
+
62
+ Add `vendor` to also block the request while the visitor has turned that
63
+ vendor off. IAB TCF rules use `vendorId` and the `iab*` purpose fields
64
+ instead.
65
+
66
+ | Option | Default | Purpose |
67
+ | -------------------- | -------- | ------------------------------------------------------------ |
68
+ | `rules` | required | Rules described above |
69
+ | `enabled` | `true` | Set `false` to keep the rules but stop blocking |
70
+ | `logBlockedRequests` | `true` | Log each blocked request with `console.warn` |
71
+ | `onRequestBlocked` | none | Called with `{ method, url, rule }` for each blocked request |
72
+
73
+ ## What a blocked request looks like
74
+
75
+ The blocker wraps `window.fetch` and `XMLHttpRequest`. A blocked `fetch`
76
+ resolves to a `451` response with the status text
77
+ `Request blocked by consent`, and nothing is sent. A blocked XHR is aborted
78
+ and fires an `error` event. Requests that match no rule are not delayed.
79
+
80
+ ## When blocking starts
81
+
82
+ The provider holds matching requests from its first render in the browser,
83
+ before any of its children render or run effects. That covers requests from
84
+ child components, including their mount effects, and from effects in
85
+ components rendered next to the provider. The blocker module itself loads
86
+ after mount and decides each held request. Apps without `networkBlocker` do
87
+ not download it.
88
+
89
+ The standalone `useNetworkBlocker` hook works the same way from the first
90
+ render of the component that calls it. That render patches `fetch` and
91
+ `XMLHttpRequest`. If React throws the render away and never commits it, the
92
+ hold ends after 10 seconds. Nothing checked consent for the requests it held,
93
+ so they fail the way the blocker fails a blocked request: a 451 response for
94
+ `fetch`, a failed XHR. The same happens when the component unmounts before
95
+ the blocker loads.
96
+
97
+ While consent is unknown, a matching request that would be blocked waits
98
+ instead of failing. Consent is unknown until the policy has loaded, which is
99
+ also when a returning visitor's stored choice takes effect. The request is
100
+ then sent if the choice allows it and blocked otherwise. If the policy fails
101
+ to load, optional categories stay denied and waiting requests are blocked.
102
+ If the policy request never finishes, they keep waiting and are never sent.
103
+
104
+ A synchronous XHR cannot wait. Before the blocker module has loaded, a
105
+ matching one throws a `NetworkError` from `send()`. After that, one that
106
+ consent does not allow yet is blocked.
107
+
108
+ ## What the network blocker cannot stop
109
+
110
+ The blocker only sees requests made after the provider starts rendering in
111
+ the browser, through `fetch` or `XMLHttpRequest`. It cannot stop:
112
+
113
+ * Code that runs before the provider renders: inline scripts in the HTML,
114
+ third-party tags in `<head>`, scripts loaded before hydration (such as
115
+ `next/script` with `beforeInteractive`), and client modules that evaluate
116
+ earlier. Webpack builds evaluate a route's client component modules when
117
+ its chunk loads, so their top-level code runs first. Turbopack evaluates a
118
+ client component module when its first element renders, which inside the
119
+ provider is after blocking starts.
120
+ * Code that saved its own reference to `fetch` or `XMLHttpRequest` before the
121
+ provider rendered.
122
+ * `navigator.sendBeacon`, `WebSocket`, `EventSource`, and requests made by
123
+ `<img>`, `<script>` or `<iframe>` elements, web workers and service
124
+ workers.
125
+ * With the standalone `useNetworkBlocker` hook instead of the provider
126
+ option, requests sent before the component that calls it renders.
127
+ Blocking starts in that component's first render, not the provider's.
128
+
129
+ Keep tracking calls out of that window:
130
+
131
+ * Send them from an effect or an event handler, never at module top level.
132
+ * Load vendor SDKs through `scripts` instead of a `<script>` tag or
133
+ `next/script`, so they wait for consent before they run at all.
134
+ * Check `useConsent('measurement')` (or the category you need) before you
135
+ call a vendor from your own code, and treat the blocker as a backstop.
136
+
137
+ ## Verify the blocked requests
138
+
139
+ Open DevTools Network in a private window, under a policy that asks for
140
+ consent.
141
+
142
+ 1. Before a choice, requests matching a rule are absent, and the console logs
143
+ each blocked request.
144
+ 2. Allow the rule's category and save. Matching requests go out.
145
+ 3. Reject, reload, and check that they stay absent.
@@ -0,0 +1,103 @@
1
+ ---
2
+ title: Scripts
3
+ description: Load vendor scripts by consent category in a TanStack Start app
4
+ with ConsentRoot, and clear stored data or reload the page when a visitor
5
+ withdraws consent.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Register vendor scripts
10
+
11
+ Pass vendor loaders to `ConsentRoot` through its `scripts` prop. `ConsentRoot`
12
+ loads each script in the browser when its category is allowed and removes it
13
+ when the category is denied. The [quickstart](https://c15t.com/docs/frameworks/tanstack-start/quickstart)
14
+ registers PostHog and X Pixel in `src/scripts.ts`:
15
+
16
+ ```ts title="src/scripts.ts"
17
+ import { posthog } from '@c15t/integrations/posthog';
18
+ import { xPixel } from '@c15t/integrations/x-pixel';
19
+
20
+ export const scripts = [
21
+ posthog({
22
+ id: 'phc_your_project_key',
23
+ initOptions: { cookieless_mode: 'never' },
24
+ loadMode: 'after-consent',
25
+ region: 'eu',
26
+ }),
27
+ xPixel({ pixelId: 'your-pixel-id' }),
28
+ ];
29
+ ```
30
+
31
+ The root route imports that module and passes it on:
32
+
33
+ ```tsx title="src/routes/__root.tsx"
34
+ import { scripts } from '../scripts';
35
+
36
+ <ConsentRoot
37
+ state={consent}
38
+ backendURL={backendURL}
39
+ initRoute={false}
40
+ scripts={scripts}
41
+ >
42
+ ```
43
+
44
+ Import vendor helpers in route modules, not in a server function. A server
45
+ function's return value must be serializable, and vendor loaders contain
46
+ functions. Scripts only load in the browser, so importing them in the root
47
+ route adds nothing to the server HTML.
48
+
49
+ Each helper from `@c15t/integrations` sets its own category and a stable `id`.
50
+ Remove every other loader for the same vendor, such as a `scripts` entry in a
51
+ route's `head()` or an SDK you initialize at module level, so the vendor loads
52
+ once and only through c15t. [Integrations](../../integrations/overview.md) lists
53
+ every helper, and [building integrations](../../integrations/building-integrations.md)
54
+ covers a vendor without one.
55
+
56
+ ## What happens when consent changes
57
+
58
+ * **Allowed.** The script loads, or its SDK starts, the first time its category
59
+ is allowed. With an awaited root loader, a returning visitor's allowed
60
+ scripts start right after hydration.
61
+ * **Denied later.** The script element is removed. Code that already ran keeps
62
+ running, so after the visitor turns off a category they had allowed, the page
63
+ reloads once the save finishes. Set `reloadOnConsentRevoked: false` in
64
+ `ConsentRoot`'s `options` only if every gated vendor stops itself.
65
+ * **`alwaysLoad` helpers.** Some integrations, such as Google Consent Mode,
66
+ load before consent and pass the visitor's choice to the vendor. Read the
67
+ vendor's guide; a category on a script does not always mean zero requests.
68
+
69
+ ## Embeds and other requests
70
+
71
+ Scripts cover vendor code c15t loads for you. For the rest:
72
+
73
+ * [Embeds](./embeds.md) keeps iframes out of the
74
+ page with `ConsentGate` or the iframe blocker.
75
+ * [Network blocker](./network-blocker.md) holds
76
+ `fetch` and XHR calls that match a rule until their category is allowed.
77
+
78
+ ## Clear stored data after revocation
79
+
80
+ Removing a script does not delete the cookies or storage entries it wrote. Pass
81
+ `clearOnRevocation` to `ConsentRoot` to delete the entries you list for each
82
+ category when it is denied. `ConsentRoot` reads it once, when it mounts.
83
+ [Clear on revocation](https://c15t.com/docs/frameworks/tanstack-start/clear-on-revocation) covers
84
+ configuration and browser limits.
85
+
86
+ ## Let visitors turn off one vendor
87
+
88
+ A visitor can allow marketing and still switch off one vendor in it. Pass
89
+ `vendors` to `ConsentRoot`. Helpers from `@c15t/integrations` already carry
90
+ their vendor slug. See [vendor consent](https://c15t.com/docs/frameworks/tanstack-start/vendor-consent).
91
+
92
+ ## Check the scripts
93
+
94
+ Open DevTools Network in a private window, filter by each vendor's domain and
95
+ reload.
96
+
97
+ 1. Before a choice under an opt-in policy, no vendor script loads. The page
98
+ source has no vendor `<script>` tags.
99
+ 2. Allow one category. Only that category's vendors load.
100
+ 3. Turn the category off. The page reloads and the vendor stays absent.
101
+ 4. Reload again. The rejection holds, and the server HTML shows no banner.
102
+
103
+ [Verify consent](../../guides/verify-consent.md) has the full checklist.
@@ -0,0 +1,84 @@
1
+ ---
2
+ title: Embeds
3
+ description: Keep YouTube videos, maps and other iframes out of a Vue page until
4
+ their consent category is allowed, with ConsentGate or the iframe blocker.
5
+ group: frameworks
6
+ ---
7
+
8
+ ## Pick a method
9
+
10
+ | Method | Use it when |
11
+ | ------------------ | ------------------------------------------------------------------------------------------------------------- |
12
+ | `ConsentGate` | You render the iframe in a Vue component and want a placeholder with a way to open preferences. |
13
+ | The iframe blocker | The iframe comes from markup you do not render with a component, such as CMS content, or you want no wrapper. |
14
+
15
+ Both remove the iframe's `src` until the category is allowed, so the embed's
16
+ host receives no request before consent.
17
+
18
+ ## Gate an embed with ConsentGate
19
+
20
+ Wrap the iframe in [`ConsentGate`](https://c15t.com/docs/frameworks/vue/components/consent-gate)
21
+ from `c15t/vue/runtime/components/consent-gate.vue`:
22
+
23
+ ```vue title="src/VideoEmbed.vue"
24
+ <script setup lang="ts">
25
+ import ConsentGate from 'c15t/vue/runtime/components/consent-gate.vue';
26
+ import ConsentPreferencesLink from 'c15t/vue/runtime/components/consent-preferences-link.vue';
27
+ </script>
28
+
29
+ <template>
30
+ <ConsentGate category="measurement">
31
+ <iframe
32
+ src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y"
33
+ title="YouTube video"
34
+ loading="lazy"
35
+ allowfullscreen
36
+ />
37
+ <template #placeholder>
38
+ <p>Allow measurement to load this YouTube video.</p>
39
+ <ConsentPreferencesLink>Choose video permissions</ConsentPreferencesLink>
40
+ </template>
41
+ </ConsentGate>
42
+ </template>
43
+ ```
44
+
45
+ The iframe does not exist in the page until the visitor allows measurement.
46
+ The `placeholder` slot shows a message and a
47
+ [`ConsentPreferencesLink`](https://c15t.com/docs/frameworks/vue/components/consent-preferences-link)
48
+ until then. When the visitor withdraws measurement, Vue removes the iframe.
49
+
50
+ Pick the category that matches what the embed does. The example uses
51
+ measurement for YouTube because its player measures views. A map or chat
52
+ widget usually belongs under functionality or experience.
53
+
54
+ ## Gate an iframe with the iframe blocker
55
+
56
+ The Vue plugin runs the iframe blocker by default. Give an iframe `data-src`
57
+ instead of `src`, and a `data-category`:
58
+
59
+ ```vue title="src/MapEmbed.vue"
60
+ <template>
61
+ <iframe
62
+ data-src="https://www.google.com/maps/embed?pb=YOUR_EMBED_ID"
63
+ data-category="functionality"
64
+ title="Store map"
65
+ />
66
+ </template>
67
+ ```
68
+
69
+ c15t sets `src` from `data-src` when the category is allowed and removes it
70
+ when the category is withdrawn. It watches the page, so iframes added later,
71
+ for example by a route change, are handled too. The iframe element stays in
72
+ the page while blocked, empty. Add `data-vendor` with a vendor ID to also
73
+ block the iframe while the visitor has turned that vendor off.
74
+
75
+ Pass `iframeBlocker: false` to the plugin to turn the blocker off.
76
+
77
+ ## Verify
78
+
79
+ Open the page in a private window with the Network tab open, under a policy
80
+ that asks for consent. No request goes to `youtube-nocookie.com` or
81
+ `google.com/maps`. Allow the category and save: the iframe loads. Withdraw it
82
+ and save: the page reloads without the embed. The
83
+ [YouTube](../../integrations/youtube.md) and
84
+ [Google Maps](../../integrations/google-maps.md) guides cover sizing and titles.