@c15t/scripts 3.0.0-alpha.2 → 3.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +129 -63
- package/README.md +8 -29
- package/SKILL.md +33 -0
- package/dist/adobe-analytics.js +2 -0
- package/dist/ahrefs-analytics.js +2 -0
- package/dist/amplitude.js +2 -0
- package/dist/clearbit.js +2 -0
- package/dist/cloudflare-web-analytics.js +2 -0
- package/dist/cloudflare-zaraz.js +2 -0
- package/dist/crisp.js +2 -0
- package/dist/databuddy.js +2 -0
- package/dist/e2e-test-utils.js +2 -139
- package/dist/engine/compile.js +2 -89
- package/dist/engine/runtime.js +2 -448
- package/dist/events.js +2 -218
- package/dist/fathom-analytics.js +2 -0
- package/dist/front-chat.js +2 -0
- package/dist/google-tag-manager.js +2 -0
- package/dist/google-tag.js +2 -0
- package/dist/heap.js +2 -0
- package/dist/hightouch.js +2 -0
- package/dist/hotjar.js +2 -0
- package/dist/intercom.js +2 -0
- package/dist/klaviyo.js +2 -0
- package/dist/linkedin-insights.js +2 -0
- package/dist/logrocket.js +2 -0
- package/dist/matomo-analytics.js +2 -0
- package/dist/meta-pixel.js +2 -0
- package/dist/microsoft-clarity.js +2 -0
- package/dist/microsoft-uet.js +2 -0
- package/dist/mixpanel-analytics.js +2 -0
- package/dist/one-dollar-stats.js +2 -0
- package/dist/openai-pixel.js +2 -0
- package/dist/pinterest-tag.js +2 -0
- package/dist/pirsch.js +2 -0
- package/dist/plausible-analytics.js +2 -0
- package/dist/posthog.js +2 -0
- package/dist/promptwatch.js +2 -0
- package/dist/reddit-pixel.js +2 -0
- package/dist/registry.js +2 -422
- package/dist/resolve.js +2 -33
- package/dist/rudderstack.js +2 -0
- package/dist/rybbit-analytics.js +2 -0
- package/dist/segment.js +2 -0
- package/dist/snapchat-pixel.js +2 -0
- package/dist/tiktok-pixel.js +2 -0
- package/dist/types.js +2 -16
- package/dist/umami-analytics.js +2 -0
- package/dist/vendors/_shared/attributes.js +2 -14
- package/dist/vendors/_shared/google-consent.js +2 -27
- package/dist/vendors/_shared/install-builders.js +2 -21
- package/dist/vendors/_shared/required-id.js +2 -0
- package/dist/vendors/_shared/script-url.js +2 -28
- package/dist/vendors/ads-and-pixels/linkedin-insights.js +2 -48
- package/dist/vendors/ads-and-pixels/meta-pixel.js +2 -153
- package/dist/vendors/ads-and-pixels/microsoft-uet.js +2 -110
- package/dist/vendors/ads-and-pixels/openai-pixel.js +2 -88
- package/dist/vendors/ads-and-pixels/pinterest-tag.js +2 -123
- package/dist/vendors/ads-and-pixels/reddit-pixel.js +2 -107
- package/dist/vendors/ads-and-pixels/snapchat-pixel.js +2 -87
- package/dist/vendors/ads-and-pixels/tiktok-pixel.js +2 -89
- package/dist/vendors/ads-and-pixels/x-pixel.js +2 -48
- package/dist/vendors/analytics/adobe-analytics.js +2 -49
- package/dist/vendors/analytics/ahrefs-analytics.js +2 -27
- package/dist/vendors/analytics/amplitude.js +2 -134
- package/dist/vendors/analytics/clearbit.js +2 -28
- package/dist/vendors/analytics/cloudflare-web-analytics.js +2 -32
- package/dist/vendors/analytics/databuddy.js +2 -103
- package/dist/vendors/analytics/fathom-analytics.js +2 -35
- package/dist/vendors/analytics/google-tag.js +2 -78
- package/dist/vendors/analytics/heap.js +2 -134
- package/dist/vendors/analytics/hightouch.js +2 -109
- package/dist/vendors/analytics/hotjar.js +2 -44
- package/dist/vendors/analytics/logrocket.js +2 -58
- package/dist/vendors/analytics/matomo-analytics.js +2 -191
- package/dist/vendors/analytics/microsoft-clarity.js +2 -100
- package/dist/vendors/analytics/mixpanel-analytics.js +2 -93
- package/dist/vendors/analytics/one-dollar-stats.js +2 -30
- package/dist/vendors/analytics/pirsch.js +2 -67
- package/dist/vendors/analytics/plausible-analytics.js +2 -81
- package/dist/vendors/analytics/posthog.js +2 -200
- package/dist/vendors/analytics/promptwatch.js +2 -29
- package/dist/vendors/analytics/rudderstack.js +2 -183
- package/dist/vendors/analytics/rybbit-analytics.js +2 -63
- package/dist/vendors/analytics/segment.js +2 -65
- package/dist/vendors/analytics/umami-analytics.js +2 -39
- package/dist/vendors/analytics/vercel-analytics.js +2 -53
- package/dist/vendors/email-and-sms/klaviyo.js +2 -0
- package/dist/vendors/functional/crisp.js +2 -100
- package/dist/vendors/functional/front-chat.js +2 -64
- package/dist/vendors/functional/intercom.js +2 -45
- package/dist/vendors/tag-managers/cloudflare-zaraz.js +2 -98
- package/dist/vendors/tag-managers/google-tag-manager.js +2 -73
- package/dist/vercel-analytics.js +2 -0
- package/dist/x-pixel.js +2 -0
- package/dist-types/adobe-analytics.d.ts +2 -0
- package/dist-types/ahrefs-analytics.d.ts +2 -0
- package/dist-types/amplitude.d.ts +2 -0
- package/dist-types/clearbit.d.ts +2 -0
- package/dist-types/cloudflare-web-analytics.d.ts +2 -0
- package/dist-types/cloudflare-zaraz.d.ts +2 -0
- package/dist-types/crisp.d.ts +2 -0
- package/dist-types/databuddy.d.ts +2 -0
- package/dist-types/e2e-test-utils.d.ts +2 -0
- package/dist-types/engine/compile.d.ts +2 -3
- package/dist-types/engine/runtime.d.ts +2 -3
- package/dist-types/events.d.ts +2 -46
- package/dist-types/fathom-analytics.d.ts +2 -0
- package/dist-types/front-chat.d.ts +2 -0
- package/dist-types/google-tag-manager.d.ts +2 -0
- package/dist-types/google-tag.d.ts +2 -0
- package/dist-types/heap.d.ts +2 -0
- package/dist-types/hightouch.d.ts +2 -0
- package/dist-types/hotjar.d.ts +2 -0
- package/dist-types/intercom.d.ts +2 -0
- package/dist-types/klaviyo.d.ts +2 -0
- package/dist-types/linkedin-insights.d.ts +2 -0
- package/dist-types/logrocket.d.ts +2 -0
- package/dist-types/matomo-analytics.d.ts +2 -0
- package/dist-types/meta-pixel.d.ts +2 -0
- package/dist-types/microsoft-clarity.d.ts +2 -0
- package/dist-types/microsoft-uet.d.ts +2 -0
- package/dist-types/mixpanel-analytics.d.ts +2 -0
- package/dist-types/one-dollar-stats.d.ts +2 -0
- package/dist-types/openai-pixel.d.ts +2 -0
- package/dist-types/pinterest-tag.d.ts +2 -0
- package/dist-types/pirsch.d.ts +2 -0
- package/dist-types/plausible-analytics.d.ts +2 -0
- package/dist-types/posthog.d.ts +2 -0
- package/dist-types/promptwatch.d.ts +2 -0
- package/dist-types/reddit-pixel.d.ts +2 -0
- package/dist-types/registry.d.ts +2 -485
- package/dist-types/resolve.d.ts +2 -9
- package/dist-types/rudderstack.d.ts +2 -0
- package/dist-types/rybbit-analytics.d.ts +2 -0
- package/dist-types/segment.d.ts +2 -0
- package/dist-types/snapchat-pixel.d.ts +2 -0
- package/dist-types/tiktok-pixel.d.ts +2 -0
- package/dist-types/types.d.ts +2 -314
- package/dist-types/umami-analytics.d.ts +2 -0
- package/dist-types/vendors/_shared/attributes.d.ts +2 -35
- package/dist-types/vendors/_shared/google-consent.d.ts +2 -47
- package/dist-types/vendors/_shared/install-builders.d.ts +2 -30
- package/dist-types/vendors/_shared/required-id.d.ts +2 -0
- package/dist-types/vendors/_shared/script-url.d.ts +2 -75
- package/dist-types/vendors/ads-and-pixels/linkedin-insights.d.ts +2 -92
- package/dist-types/vendors/ads-and-pixels/meta-pixel.d.ts +2 -289
- package/dist-types/vendors/ads-and-pixels/microsoft-uet.d.ts +2 -105
- package/dist-types/vendors/ads-and-pixels/openai-pixel.d.ts +2 -211
- package/dist-types/vendors/ads-and-pixels/pinterest-tag.d.ts +2 -295
- package/dist-types/vendors/ads-and-pixels/reddit-pixel.d.ts +2 -210
- package/dist-types/vendors/ads-and-pixels/snapchat-pixel.d.ts +2 -171
- package/dist-types/vendors/ads-and-pixels/tiktok-pixel.d.ts +2 -106
- package/dist-types/vendors/ads-and-pixels/x-pixel.d.ts +2 -183
- package/dist-types/vendors/analytics/adobe-analytics.d.ts +2 -75
- package/dist-types/vendors/analytics/ahrefs-analytics.d.ts +2 -62
- package/dist-types/vendors/analytics/amplitude.d.ts +2 -234
- package/dist-types/vendors/analytics/clearbit.d.ts +2 -60
- package/dist-types/vendors/analytics/cloudflare-web-analytics.d.ts +2 -67
- package/dist-types/vendors/analytics/databuddy.d.ts +2 -147
- package/dist-types/vendors/analytics/fathom-analytics.d.ts +2 -90
- package/dist-types/vendors/analytics/google-tag.d.ts +2 -95
- package/dist-types/vendors/analytics/heap.d.ts +2 -316
- package/dist-types/vendors/analytics/hightouch.d.ts +2 -285
- package/dist-types/vendors/analytics/hotjar.d.ts +2 -73
- package/dist-types/vendors/analytics/logrocket.d.ts +2 -101
- package/dist-types/vendors/analytics/matomo-analytics.d.ts +2 -41
- package/dist-types/vendors/analytics/microsoft-clarity.d.ts +2 -97
- package/dist-types/vendors/analytics/mixpanel-analytics.d.ts +2 -113
- package/dist-types/vendors/analytics/one-dollar-stats.d.ts +2 -39
- package/dist-types/vendors/analytics/pirsch.d.ts +2 -96
- package/dist-types/vendors/analytics/plausible-analytics.d.ts +2 -122
- package/dist-types/vendors/analytics/posthog.d.ts +2 -175
- package/dist-types/vendors/analytics/promptwatch.d.ts +2 -36
- package/dist-types/vendors/analytics/rudderstack.d.ts +2 -330
- package/dist-types/vendors/analytics/rybbit-analytics.d.ts +2 -82
- package/dist-types/vendors/analytics/segment.d.ts +2 -164
- package/dist-types/vendors/analytics/umami-analytics.d.ts +2 -93
- package/dist-types/vendors/analytics/vercel-analytics.d.ts +2 -66
- package/dist-types/vendors/email-and-sms/klaviyo.d.ts +2 -0
- package/dist-types/vendors/functional/crisp.d.ts +2 -78
- package/dist-types/vendors/functional/front-chat.d.ts +2 -62
- package/dist-types/vendors/functional/intercom.d.ts +2 -135
- package/dist-types/vendors/tag-managers/cloudflare-zaraz.d.ts +2 -39
- package/dist-types/vendors/tag-managers/google-tag-manager.d.ts +2 -96
- package/dist-types/vercel-analytics.d.ts +2 -0
- package/dist-types/x-pixel.d.ts +2 -0
- package/docs/README.md +129 -63
- package/docs/assets/v3/bottom-bar.png +0 -0
- package/docs/assets/v3/brand-card.png +0 -0
- package/docs/assets/v3/brand-preferences.png +0 -0
- package/docs/assets/v3/choice-wall.png +0 -0
- package/docs/assets/v3/headless-bar-html.png +0 -0
- package/docs/assets/v3/headless-bar-mobile.png +0 -0
- package/docs/assets/v3/headless-bar.png +0 -0
- package/docs/assets/v3/slim-bar.png +0 -0
- package/docs/concepts/choose-your-setup.md +87 -0
- package/docs/concepts/consent-categories.md +84 -0
- package/docs/{guides → concepts}/consent-state.md +71 -101
- package/docs/{guides → concepts}/data-fetching.md +31 -27
- package/docs/concepts/how-consent-works.md +123 -0
- package/docs/concepts/policies.md +71 -0
- package/docs/customization/class-names.md +202 -0
- package/docs/customization/dark-mode.md +157 -0
- package/docs/customization/motion.md +119 -0
- package/docs/customization/overview.md +67 -34
- package/docs/customization/recipes.md +839 -49
- package/docs/customization/slots.md +216 -35
- package/docs/customization/stylesheets.md +147 -0
- package/docs/customization/tailwind.md +842 -0
- package/docs/customization/tokens.md +163 -96
- package/docs/customization/translations.md +61 -3
- package/docs/frameworks/astro/embeds.md +160 -0
- package/docs/frameworks/astro/network-blocker.md +86 -0
- package/docs/frameworks/astro/scripts.md +146 -0
- package/docs/frameworks/html/embeds.md +142 -0
- package/docs/frameworks/html/network-blocker.md +105 -0
- package/docs/frameworks/html/scripts.md +164 -0
- package/docs/frameworks/javascript/scripts.md +119 -0
- package/docs/frameworks/next/embeds.md +90 -0
- package/docs/frameworks/next/network-blocker.md +153 -0
- package/docs/frameworks/next/scripts.md +196 -0
- package/docs/frameworks/nuxt/embeds.md +81 -0
- package/docs/frameworks/nuxt/network-blocker.md +97 -0
- package/docs/frameworks/nuxt/scripts.md +89 -0
- package/docs/frameworks/react/embeds.md +89 -0
- package/docs/frameworks/react/network-blocker.md +140 -0
- package/docs/frameworks/react/scripts.md +115 -0
- package/docs/frameworks/svelte/embeds.md +96 -0
- package/docs/frameworks/svelte/network-blocker.md +141 -0
- package/docs/frameworks/svelte/scripts.md +137 -0
- package/docs/frameworks/sveltekit/embeds.md +103 -0
- package/docs/frameworks/sveltekit/network-blocker.md +149 -0
- package/docs/frameworks/sveltekit/scripts.md +141 -0
- package/docs/frameworks/tanstack-start/embeds.md +96 -0
- package/docs/frameworks/tanstack-start/network-blocker.md +145 -0
- package/docs/frameworks/tanstack-start/scripts.md +103 -0
- package/docs/frameworks/vue/embeds.md +84 -0
- package/docs/frameworks/vue/network-blocker.md +99 -0
- package/docs/frameworks/vue/scripts.md +93 -0
- package/docs/guides/banner-experiments.md +654 -0
- package/docs/guides/troubleshooting.md +120 -47
- package/docs/guides/verify-consent.md +81 -49
- package/docs/integrations/adobe-analytics.md +167 -159
- package/docs/integrations/ahrefs-analytics.md +152 -154
- package/docs/integrations/amplitude.md +162 -156
- package/docs/integrations/building-integrations.md +136 -37
- package/docs/integrations/clearbit.md +154 -154
- package/docs/integrations/cloudflare-web-analytics.md +156 -156
- package/docs/integrations/cloudflare-zaraz.md +209 -261
- package/docs/integrations/crisp.md +164 -158
- package/docs/integrations/databuddy.md +157 -173
- package/docs/integrations/fathom-analytics.md +158 -156
- package/docs/integrations/front-chat.md +167 -167
- package/docs/integrations/google-maps.md +118 -83
- package/docs/integrations/google-tag-manager.md +178 -163
- package/docs/integrations/google-tag.md +163 -160
- package/docs/integrations/heap.md +163 -155
- package/docs/integrations/hightouch.md +161 -157
- package/docs/integrations/hotjar.md +159 -155
- package/docs/integrations/intercom.md +183 -153
- package/docs/integrations/klaviyo.md +486 -0
- package/docs/integrations/linkedin-insights.md +174 -150
- package/docs/integrations/logrocket.md +160 -156
- package/docs/integrations/matomo-analytics.md +188 -178
- package/docs/integrations/meta-pixel.md +188 -150
- package/docs/integrations/microsoft-clarity.md +163 -155
- package/docs/integrations/microsoft-uet.md +148 -154
- package/docs/integrations/mixpanel-analytics.md +155 -160
- package/docs/integrations/one-dollar-stats.md +166 -165
- package/docs/integrations/openai-pixel.md +204 -301
- package/docs/integrations/overview.md +137 -80
- package/docs/integrations/pinterest-tag.md +191 -183
- package/docs/integrations/pirsch.md +169 -159
- package/docs/integrations/plausible-analytics.md +172 -158
- package/docs/integrations/posthog.md +226 -241
- package/docs/integrations/promptwatch.md +154 -154
- package/docs/integrations/reddit-pixel.md +185 -157
- package/docs/integrations/rudderstack.md +201 -186
- package/docs/integrations/rybbit-analytics.md +171 -160
- package/docs/integrations/segment.md +182 -154
- package/docs/integrations/snapchat-pixel.md +186 -156
- package/docs/integrations/tiktok-pixel.md +171 -150
- package/docs/integrations/umami-analytics.md +163 -158
- package/docs/integrations/vercel-analytics.md +166 -157
- package/docs/integrations/x-pixel.md +176 -150
- package/docs/integrations/youtube.md +121 -86
- package/docs/upgrade-v3.md +475 -467
- package/package.json +10 -257
- package/dist-types/__tests__/helpers.d.ts +0 -141
- package/docs/assets/v3/brand-bar.png +0 -0
- package/docs/assets/v3/mobile-card.png +0 -0
- package/docs/assets/v3/preferences.png +0 -0
- package/docs/frameworks/javascript/script-loader.md +0 -100
- package/docs/frameworks/next/script-loader.md +0 -216
- package/docs/frameworks/react/script-loader.md +0 -69
- package/docs/guides/deployment-modes.md +0 -75
- package/docs/guides/shared-consent-controls.md +0 -158
- package/docs/integrations/clear-on-revocation.md +0 -167
- package/docs/integrations/granular-consent.md +0 -210
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Scripts
|
|
3
|
+
description: Load vendor scripts by consent category in a Nuxt app with the c15t
|
|
4
|
+
Nuxt module, and what happens when a visitor withdraws consent.
|
|
5
|
+
group: frameworks
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Register vendor scripts
|
|
9
|
+
|
|
10
|
+
A banner does not stop a script you load with a `<script>` tag, `useHead` or a
|
|
11
|
+
vendor's Nuxt module. Remove those loaders and register the vendor with c15t
|
|
12
|
+
instead, so c15t loads it only while its category is allowed.
|
|
13
|
+
|
|
14
|
+
Create the script configuration with the helpers from `@c15t/integrations`:
|
|
15
|
+
|
|
16
|
+
```ts title="app/consent-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
|
+
Register it under the `c15t` key in `app/app.config.ts`:
|
|
32
|
+
|
|
33
|
+
```ts title="app/app.config.ts"
|
|
34
|
+
import { scripts } from './consent-scripts';
|
|
35
|
+
|
|
36
|
+
export default defineAppConfig({ c15t: { scripts } });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The module starts one script loader in the browser after hydration, once it
|
|
40
|
+
has applied the visitor's stored choice and privacy signals. Do not also call
|
|
41
|
+
`createScriptLoader` yourself, or each script loads twice.
|
|
42
|
+
|
|
43
|
+
`scripts` must go in `app.config.ts`. Module options in `nuxt.config.ts`
|
|
44
|
+
reach the browser as JSON, which drops the functions inside each script.
|
|
45
|
+
|
|
46
|
+
Every vendor guide under [integrations](../../integrations/overview.md) gives the
|
|
47
|
+
helper and options for that vendor.
|
|
48
|
+
|
|
49
|
+
## Embeds and other requests
|
|
50
|
+
|
|
51
|
+
Scripts cover vendor code c15t loads for you. For the rest:
|
|
52
|
+
|
|
53
|
+
* [Embeds](./embeds.md) gates iframes with `ConsentGate` or
|
|
54
|
+
the iframe blocker.
|
|
55
|
+
* [Network blocker](./network-blocker.md) holds `fetch` and
|
|
56
|
+
XHR calls that match a rule until their category is allowed.
|
|
57
|
+
|
|
58
|
+
## When a visitor withdraws consent
|
|
59
|
+
|
|
60
|
+
Removing a script tag cannot stop code that already ran. When a save turns off
|
|
61
|
+
a category or vendor that was allowed, c15t reloads the page so the new
|
|
62
|
+
document starts with only permitted code. Set `reloadOnConsentRevoked: false`
|
|
63
|
+
to handle revocation yourself, or use the
|
|
64
|
+
[`onBeforeConsentRevocationReload` callback](https://c15t.com/docs/frameworks/nuxt/callbacks#before-a-revocation-reload)
|
|
65
|
+
to run code before the reload.
|
|
66
|
+
|
|
67
|
+
`clearOnRevocation` deletes first-party cookies and storage keys that belong to
|
|
68
|
+
a category when it is withdrawn. It is plain data, so it can go in
|
|
69
|
+
`nuxt.config.ts`. See [clear on revocation](https://c15t.com/docs/frameworks/nuxt/clear-on-revocation).
|
|
70
|
+
|
|
71
|
+
## Let visitors turn off one vendor
|
|
72
|
+
|
|
73
|
+
A visitor can allow marketing and still switch off one vendor in it. Declare
|
|
74
|
+
the vendors in the `vendors` option; helpers from `@c15t/integrations` already carry
|
|
75
|
+
their vendor slug. See [vendor consent](https://c15t.com/docs/frameworks/nuxt/vendor-consent).
|
|
76
|
+
|
|
77
|
+
## Content Security Policy
|
|
78
|
+
|
|
79
|
+
Allow each vendor's script host in your `script-src` directive. The module's
|
|
80
|
+
`nonce` option is fixed when the app builds, so prefer the host allowlist.
|
|
81
|
+
See [Content Security Policy](https://c15t.com/docs/frameworks/nuxt/content-security-policy).
|
|
82
|
+
|
|
83
|
+
## Verify gating
|
|
84
|
+
|
|
85
|
+
In a private window, open the Network tab and load a page under a policy that
|
|
86
|
+
asks for consent. Requests to your vendors are absent.
|
|
87
|
+
Allow one category and save, and only that category's vendors load. Withdraw
|
|
88
|
+
it, and the page reloads without loading the vendor again.
|
|
89
|
+
[Verify consent](../../guides/verify-consent.md) has the full checklist.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Embeds
|
|
3
|
+
description: Keep YouTube videos, maps and other iframes out of a React page
|
|
4
|
+
until their consent category is allowed, with ConsentGate or the iframe
|
|
5
|
+
blocker in ConsentProvider.
|
|
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 markup 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` anywhere inside `ConsentProvider`:
|
|
22
|
+
|
|
23
|
+
```tsx title="src/video-embed.tsx"
|
|
24
|
+
import { ConsentDialogLink, ConsentGate } from 'c15t/react';
|
|
25
|
+
|
|
26
|
+
export const VideoEmbed = () => (
|
|
27
|
+
<ConsentGate
|
|
28
|
+
category="measurement"
|
|
29
|
+
placeholder={
|
|
30
|
+
<div className="placeholder">
|
|
31
|
+
<p>Allow measurement to load this YouTube video.</p>
|
|
32
|
+
<ConsentDialogLink>Choose video permissions</ConsentDialogLink>
|
|
33
|
+
</div>
|
|
34
|
+
}
|
|
35
|
+
>
|
|
36
|
+
<iframe
|
|
37
|
+
title="YouTube video"
|
|
38
|
+
sandbox="allow-scripts allow-same-origin allow-presentation"
|
|
39
|
+
src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y?playsinline=1"
|
|
40
|
+
allow="encrypted-media; picture-in-picture"
|
|
41
|
+
allowFullScreen
|
|
42
|
+
/>
|
|
43
|
+
</ConsentGate>
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The iframe is absent from the page until the visitor allows measurement. The
|
|
48
|
+
`placeholder` shows a message and a
|
|
49
|
+
[`ConsentDialogLink`](https://c15t.com/docs/frameworks/react/components/consent-dialog-link)
|
|
50
|
+
until then. When the visitor withdraws measurement, React removes the iframe.
|
|
51
|
+
|
|
52
|
+
Pick the category that matches what the embed does. The example uses
|
|
53
|
+
measurement for YouTube because its player measures views. A map or chat
|
|
54
|
+
widget usually belongs under functionality or experience.
|
|
55
|
+
[ConsentGate](https://c15t.com/docs/frameworks/react/components/consent-gate) documents the
|
|
56
|
+
built-in placeholder and every prop.
|
|
57
|
+
|
|
58
|
+
## Gate an iframe with the iframe blocker
|
|
59
|
+
|
|
60
|
+
`ConsentProvider` runs the iframe blocker by default. Give an iframe
|
|
61
|
+
`data-src` instead of `src`, and a `data-category`:
|
|
62
|
+
|
|
63
|
+
```html title="Markup from your CMS"
|
|
64
|
+
<iframe
|
|
65
|
+
data-src="https://www.google.com/maps/embed?pb=YOUR_EMBED_ID"
|
|
66
|
+
data-category="functionality"
|
|
67
|
+
title="Store map"
|
|
68
|
+
></iframe>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
c15t sets `src` from `data-src` when the category is allowed and removes `src`
|
|
72
|
+
when the category is withdrawn. It watches the page, so iframes added later
|
|
73
|
+
are handled too. Add `data-vendor` with a vendor ID to also block the iframe
|
|
74
|
+
while the visitor has turned that vendor off.
|
|
75
|
+
|
|
76
|
+
To turn the blocker off, set `iframeBlocker: false` in the provider `options`.
|
|
77
|
+
|
|
78
|
+
## Verify the embeds
|
|
79
|
+
|
|
80
|
+
Open the production build in a private window with DevTools open, under a
|
|
81
|
+
policy that asks for consent.
|
|
82
|
+
|
|
83
|
+
1. No iframe has a `src` pointing at `youtube-nocookie.com` or
|
|
84
|
+
`google.com/maps`, and the Network panel shows no request to them.
|
|
85
|
+
2. Allow the embed's category and save. The iframe loads.
|
|
86
|
+
3. Withdraw the category and save. The page reloads without the embed.
|
|
87
|
+
|
|
88
|
+
The [YouTube](../../integrations/youtube.md) and
|
|
89
|
+
[Google Maps](../../integrations/google-maps.md) guides cover sizing and titles.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Network blocker
|
|
3
|
+
description: Hold fetch and XMLHttpRequest calls in a React app until their
|
|
4
|
+
consent category is allowed, with networkBlocker rules in the ConsentProvider
|
|
5
|
+
options.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Add rules
|
|
10
|
+
|
|
11
|
+
Some SDKs are already on the page and send beacons or API calls of their own.
|
|
12
|
+
Add `networkBlocker` rules to the provider options to stop `fetch` and
|
|
13
|
+
`XMLHttpRequest` calls to those domains until the category is allowed. Load
|
|
14
|
+
vendor SDKs through [`scripts`](./scripts.md) first, so they
|
|
15
|
+
do not run at all before consent.
|
|
16
|
+
|
|
17
|
+
This partial example adds the option next to the existing `mode` and
|
|
18
|
+
`scripts`:
|
|
19
|
+
|
|
20
|
+
```tsx title="src/consent.tsx"
|
|
21
|
+
const networkBlocker = {
|
|
22
|
+
rules: [
|
|
23
|
+
{
|
|
24
|
+
id: 'google-analytics',
|
|
25
|
+
domain: 'google-analytics.com',
|
|
26
|
+
category: 'measurement',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'meta-pixel',
|
|
30
|
+
domain: 'facebook.com',
|
|
31
|
+
pathIncludes: '/tr',
|
|
32
|
+
category: 'marketing',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
<ConsentProvider options={{ mode, scripts, networkBlocker }}>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Match requests with rules
|
|
41
|
+
|
|
42
|
+
Each rule names a `domain` and the consent `category` a request needs. The
|
|
43
|
+
domain also matches its subdomains: `google-analytics.com` covers
|
|
44
|
+
`www.google-analytics.com`. `pathIncludes` narrows the rule to paths that
|
|
45
|
+
contain a substring, and `methods` narrows it to HTTP methods. A request is
|
|
46
|
+
blocked when a matching rule's condition is not met by the visitor's
|
|
47
|
+
effective permissions.
|
|
48
|
+
|
|
49
|
+
`category` takes the same conditions as scripts:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
{ category: 'measurement' }
|
|
53
|
+
{ category: { and: ['measurement', 'marketing'] } }
|
|
54
|
+
{ category: { or: ['measurement', 'marketing'] } }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Add `vendor` to also block the request while the visitor has turned that
|
|
58
|
+
vendor off. IAB TCF rules use `vendorId` and the `iab*` purpose fields
|
|
59
|
+
instead.
|
|
60
|
+
|
|
61
|
+
| Option | Default | Purpose |
|
|
62
|
+
| -------------------- | -------- | ------------------------------------------------------------ |
|
|
63
|
+
| `rules` | required | Rules described above |
|
|
64
|
+
| `enabled` | `true` | Set `false` to keep the rules but stop blocking |
|
|
65
|
+
| `logBlockedRequests` | `true` | Log each blocked request with `console.warn` |
|
|
66
|
+
| `onRequestBlocked` | none | Called with `{ method, url, rule }` for each blocked request |
|
|
67
|
+
|
|
68
|
+
## What a blocked request looks like
|
|
69
|
+
|
|
70
|
+
The blocker wraps `window.fetch` and `XMLHttpRequest`. A blocked `fetch`
|
|
71
|
+
resolves to a `451` response with the status text
|
|
72
|
+
`Request blocked by consent`, and nothing is sent. A blocked XHR is aborted
|
|
73
|
+
and fires an `error` event. Requests that match no rule are not delayed.
|
|
74
|
+
|
|
75
|
+
## When blocking starts
|
|
76
|
+
|
|
77
|
+
The provider holds matching requests from its first render in the browser,
|
|
78
|
+
before any of its children render or run effects. That covers requests from
|
|
79
|
+
child components, including their mount effects, and from effects in
|
|
80
|
+
components rendered next to the provider. The blocker module itself loads
|
|
81
|
+
after mount and decides each held request. Apps without `networkBlocker` do
|
|
82
|
+
not download it.
|
|
83
|
+
|
|
84
|
+
The standalone `useNetworkBlocker` hook works the same way from the first
|
|
85
|
+
render of the component that calls it. That render patches `fetch` and
|
|
86
|
+
`XMLHttpRequest`. If React throws the render away and never commits it, the
|
|
87
|
+
hold ends after 10 seconds. Nothing checked consent for the requests it held,
|
|
88
|
+
so they fail the way the blocker fails a blocked request: a 451 response for
|
|
89
|
+
`fetch`, a failed XHR. The same happens when the component unmounts before
|
|
90
|
+
the blocker loads.
|
|
91
|
+
|
|
92
|
+
While consent is unknown, a matching request that would be blocked waits
|
|
93
|
+
instead of failing. Consent is unknown until the policy has loaded, which is
|
|
94
|
+
also when a returning visitor's stored choice takes effect. The request is
|
|
95
|
+
then sent if the choice allows it and blocked otherwise. If the policy fails
|
|
96
|
+
to load, optional categories stay denied and waiting requests are blocked.
|
|
97
|
+
If the policy request never finishes, they keep waiting and are never sent.
|
|
98
|
+
|
|
99
|
+
A synchronous XHR cannot wait. Before the blocker module has loaded, a
|
|
100
|
+
matching one throws a `NetworkError` from `send()`. After that, one that
|
|
101
|
+
consent does not allow yet is blocked.
|
|
102
|
+
|
|
103
|
+
## What the network blocker cannot stop
|
|
104
|
+
|
|
105
|
+
The blocker only sees requests made after the provider starts rendering in
|
|
106
|
+
the browser, through `fetch` or `XMLHttpRequest`. It cannot stop:
|
|
107
|
+
|
|
108
|
+
* Code that runs before the provider renders: inline scripts in the HTML,
|
|
109
|
+
third-party tags in `<head>`, scripts loaded before hydration (such as
|
|
110
|
+
`next/script` with `beforeInteractive`), and client modules that evaluate
|
|
111
|
+
earlier. Webpack builds evaluate a route's client component modules when
|
|
112
|
+
its chunk loads, so their top-level code runs first. Turbopack evaluates a
|
|
113
|
+
client component module when its first element renders, which inside the
|
|
114
|
+
provider is after blocking starts.
|
|
115
|
+
* Code that saved its own reference to `fetch` or `XMLHttpRequest` before the
|
|
116
|
+
provider rendered.
|
|
117
|
+
* `navigator.sendBeacon`, `WebSocket`, `EventSource`, and requests made by
|
|
118
|
+
`<img>`, `<script>` or `<iframe>` elements, web workers and service
|
|
119
|
+
workers.
|
|
120
|
+
* With the standalone `useNetworkBlocker` hook instead of the provider
|
|
121
|
+
option, requests sent before the component that calls it renders.
|
|
122
|
+
Blocking starts in that component's first render, not the provider's.
|
|
123
|
+
|
|
124
|
+
Keep tracking calls out of that window:
|
|
125
|
+
|
|
126
|
+
* Send them from an effect or an event handler, never at module top level.
|
|
127
|
+
* Load vendor SDKs through `scripts` instead of a `<script>` tag or
|
|
128
|
+
`next/script`, so they wait for consent before they run at all.
|
|
129
|
+
* Check `useConsent('measurement')` (or the category you need) before you
|
|
130
|
+
call a vendor from your own code, and treat the blocker as a backstop.
|
|
131
|
+
|
|
132
|
+
## Verify the blocked requests
|
|
133
|
+
|
|
134
|
+
Open DevTools Network in a private window, under a policy that asks for
|
|
135
|
+
consent.
|
|
136
|
+
|
|
137
|
+
1. Before a choice, requests matching a rule are absent, and the console logs
|
|
138
|
+
each blocked request.
|
|
139
|
+
2. Allow the rule's category and save. Matching requests go out.
|
|
140
|
+
3. Reject, reload, and check that they stay absent.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Scripts
|
|
3
|
+
description: Load vendor scripts by consent category in a React app with
|
|
4
|
+
ConsentProvider, 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 `ConsentProvider` through `options.scripts`. The
|
|
12
|
+
provider loads each script when its category is allowed and removes it when
|
|
13
|
+
the category is denied. The [quickstart](https://c15t.com/docs/frameworks/react/quickstart)
|
|
14
|
+
registers PostHog and X Pixel this way:
|
|
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
|
+
}),
|
|
26
|
+
xPixel({ pixelId: 'your-pixel-id' }),
|
|
27
|
+
];
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The provider in `src/consent.tsx` receives them:
|
|
31
|
+
|
|
32
|
+
```tsx title="src/consent.tsx"
|
|
33
|
+
import {
|
|
34
|
+
ConsentBanner,
|
|
35
|
+
ConsentDialog,
|
|
36
|
+
ConsentDialogLink,
|
|
37
|
+
ConsentProvider,
|
|
38
|
+
hosted,
|
|
39
|
+
} from 'c15t/react';
|
|
40
|
+
import type { ReactNode } from 'react';
|
|
41
|
+
|
|
42
|
+
import { scripts } from './scripts';
|
|
43
|
+
|
|
44
|
+
import 'c15t/react/styles.css';
|
|
45
|
+
|
|
46
|
+
const mode = hosted({ url: 'https://your-project.inth.app' });
|
|
47
|
+
|
|
48
|
+
export const Consent = ({ children }: { children: ReactNode }) => (
|
|
49
|
+
<ConsentProvider options={{ mode, scripts }}>
|
|
50
|
+
{children}
|
|
51
|
+
<ConsentBanner />
|
|
52
|
+
<ConsentDialog />
|
|
53
|
+
<footer>
|
|
54
|
+
<ConsentDialogLink>Privacy settings</ConsentDialogLink>
|
|
55
|
+
</footer>
|
|
56
|
+
</ConsentProvider>
|
|
57
|
+
);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Each helper from `@c15t/integrations` sets its own category and a stable `id`.
|
|
61
|
+
Remove every other loader for the same vendor, such as a `<script>` tag in
|
|
62
|
+
`index.html` or an SDK you initialize at module level, so the vendor loads
|
|
63
|
+
once and only through c15t. [Integrations](../../integrations/overview.md) lists
|
|
64
|
+
every helper, and [building integrations](../../integrations/building-integrations.md)
|
|
65
|
+
covers a vendor without one.
|
|
66
|
+
|
|
67
|
+
## What happens when consent changes
|
|
68
|
+
|
|
69
|
+
* **Allowed.** The script loads, or its SDK is started, the first time its
|
|
70
|
+
category is allowed.
|
|
71
|
+
* **Denied later.** The provider removes the script element. Code that already
|
|
72
|
+
ran keeps running, so after the visitor turns off a category they had
|
|
73
|
+
allowed, the provider reloads the page once the save finishes. Set
|
|
74
|
+
`reloadOnConsentRevoked: false` only if every gated vendor stops itself.
|
|
75
|
+
* **`alwaysLoad` helpers.** Some integrations, such as Google Consent Mode,
|
|
76
|
+
load before consent and pass the visitor's choice to the vendor. Read the
|
|
77
|
+
vendor's guide; a category on a script does not always mean zero requests.
|
|
78
|
+
|
|
79
|
+
## Embeds and other requests
|
|
80
|
+
|
|
81
|
+
Scripts cover vendor code c15t loads for you. For the rest:
|
|
82
|
+
|
|
83
|
+
* [Embeds](./embeds.md) keeps iframes out of the page with
|
|
84
|
+
`ConsentGate` or the iframe blocker.
|
|
85
|
+
* [Network blocker](./network-blocker.md) holds `fetch` and
|
|
86
|
+
XHR calls that match a rule until their category is allowed.
|
|
87
|
+
|
|
88
|
+
## Clear stored data after revocation
|
|
89
|
+
|
|
90
|
+
Removing a script does not delete the cookies or storage entries it wrote. Add
|
|
91
|
+
`clearOnRevocation` to the provider options to delete the entries you list for
|
|
92
|
+
each category when it is denied. The option is read once, when the provider
|
|
93
|
+
mounts. [Clear on revocation](https://c15t.com/docs/frameworks/react/clear-on-revocation) covers
|
|
94
|
+
configuration and browser limits.
|
|
95
|
+
|
|
96
|
+
## Let visitors turn off one vendor
|
|
97
|
+
|
|
98
|
+
A visitor can allow marketing and still switch off one vendor in it. Pass
|
|
99
|
+
`vendors` to the provider options. Helpers from `@c15t/integrations` already carry
|
|
100
|
+
their vendor slug. See [vendor consent](https://c15t.com/docs/frameworks/react/vendor-consent).
|
|
101
|
+
|
|
102
|
+
To send your own events only to allowed integrations, see
|
|
103
|
+
[send events only to allowed integrations](../../integrations/overview.md#send-events-only-to-allowed-integrations).
|
|
104
|
+
|
|
105
|
+
## Check the scripts
|
|
106
|
+
|
|
107
|
+
Open DevTools Network in a private window, filter by each vendor's domain and
|
|
108
|
+
reload.
|
|
109
|
+
|
|
110
|
+
1. Before a choice under an opt-in policy, no vendor script loads.
|
|
111
|
+
2. Allow one category. Only that category's vendors load.
|
|
112
|
+
3. Turn the category off. The page reloads and the vendor stays absent.
|
|
113
|
+
4. Reload again. The rejection holds.
|
|
114
|
+
|
|
115
|
+
[Verify consent](../../guides/verify-consent.md) has the full checklist.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Embeds
|
|
3
|
+
description: Keep YouTube videos, maps and other iframes out of a Svelte page
|
|
4
|
+
until their consent category is allowed, with ConsentGate or the iframe
|
|
5
|
+
blocker.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Pick a way to gate the embed
|
|
10
|
+
|
|
11
|
+
A YouTube video, a map or a social post in an iframe contacts its vendor as
|
|
12
|
+
soon as it loads. c15t offers two ways to keep it from loading before
|
|
13
|
+
consent:
|
|
14
|
+
|
|
15
|
+
| Your markup | Use | Placeholder |
|
|
16
|
+
| -------------------------------------------------------- | ------------------------------------------------------ | ----------------------------- |
|
|
17
|
+
| A Svelte component you write | `ConsentGate` around the iframe | Built in, or your own snippet |
|
|
18
|
+
| HTML you do not control, such as CMS or Markdown content | The iframe blocker with `data-category` and `data-src` | None; the iframe stays empty |
|
|
19
|
+
|
|
20
|
+
Both use the visitor's effective permission for one category, and both
|
|
21
|
+
remove the embed again when the visitor withdraws that category.
|
|
22
|
+
|
|
23
|
+
## Wrap the iframe in ConsentGate
|
|
24
|
+
|
|
25
|
+
```svelte title="src/YouTubeEmbed.svelte"
|
|
26
|
+
<script lang="ts">
|
|
27
|
+
import { ConsentDialogLink, ConsentGate } from '@c15t/svelte';
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<!-- The iframe mounts only while measurement is allowed. -->
|
|
31
|
+
<ConsentGate category="measurement">
|
|
32
|
+
{#snippet placeholder()}<div class="placeholder">
|
|
33
|
+
<p>Allow measurement to load this YouTube video.</p>
|
|
34
|
+
<ConsentDialogLink>Choose video permissions</ConsentDialogLink>
|
|
35
|
+
</div>{/snippet}
|
|
36
|
+
<iframe
|
|
37
|
+
title="YouTube video"
|
|
38
|
+
src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y?playsinline=1"
|
|
39
|
+
allow="encrypted-media; picture-in-picture"
|
|
40
|
+
allowfullscreen
|
|
41
|
+
></iframe>
|
|
42
|
+
</ConsentGate>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The iframe is absent from the DOM until measurement is allowed, so the
|
|
46
|
+
browser never requests it. The placeholder snippet replaces the built-in one
|
|
47
|
+
and includes a `ConsentDialogLink`, so a visitor can allow the category from
|
|
48
|
+
the embed's own spot. Keep `ConsentDialog` mounted for that link.
|
|
49
|
+
[ConsentGate](https://c15t.com/docs/frameworks/svelte/components/consent-gate) lists its props.
|
|
50
|
+
|
|
51
|
+
Pick the category the vendor's embed needs. YouTube and maps usually fit
|
|
52
|
+
`measurement` or `marketing`; check what each vendor sets.
|
|
53
|
+
|
|
54
|
+
## Gate iframes you do not render
|
|
55
|
+
|
|
56
|
+
The provider's iframe blocker is on by default. It watches the page for
|
|
57
|
+
iframes with a `data-category` attribute:
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<iframe
|
|
61
|
+
title="Store locations"
|
|
62
|
+
data-category="functionality"
|
|
63
|
+
data-src="https://www.google.com/maps/embed?pb=..."
|
|
64
|
+
></iframe>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
* While the category is denied, the iframe has no `src`, so it loads nothing.
|
|
68
|
+
* When the category is allowed, the blocker copies `data-src` to `src`.
|
|
69
|
+
Only `http` and `https` URLs are used.
|
|
70
|
+
* When the category is withdrawn, the blocker removes `src` again.
|
|
71
|
+
|
|
72
|
+
Iframes without `data-category` or `data-vendor` are never touched. Put
|
|
73
|
+
`data-src` in the HTML instead of `src`; an iframe that arrives with `src`
|
|
74
|
+
already starts loading before the blocker sees it. Add `data-vendor` with a
|
|
75
|
+
vendor slug to also keep the iframe empty while the visitor has that vendor
|
|
76
|
+
turned off.
|
|
77
|
+
|
|
78
|
+
The blocker has no placeholder. Style the empty iframe, or place a note and a
|
|
79
|
+
`ConsentDialogLink` next to it.
|
|
80
|
+
|
|
81
|
+
Pass `iframeBlocker={false}` on the provider to turn the blocker off.
|
|
82
|
+
|
|
83
|
+
## Vendor guides
|
|
84
|
+
|
|
85
|
+
The [YouTube](../../integrations/youtube.md) and
|
|
86
|
+
[Google Maps](../../integrations/google-maps.md) guides have ready embed
|
|
87
|
+
configurations. [Integrations](../../integrations/overview.md) lists the rest.
|
|
88
|
+
|
|
89
|
+
## Verify the embeds
|
|
90
|
+
|
|
91
|
+
Clear site data and reload with DevTools open:
|
|
92
|
+
|
|
93
|
+
1. The Network panel has no request to the embed's host, and the Elements
|
|
94
|
+
panel shows the placeholder or an iframe without `src`.
|
|
95
|
+
2. Allow the category in preferences. The embed loads without a page reload.
|
|
96
|
+
3. Withdraw the category. The embed disappears, or its iframe loses `src`.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Network blocker
|
|
3
|
+
description: Hold fetch and XMLHttpRequest calls to tracking domains in a Svelte
|
|
4
|
+
app until their consent category is allowed, with the provider's
|
|
5
|
+
networkBlocker option.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## When to use the network blocker
|
|
10
|
+
|
|
11
|
+
The network blocker stops `fetch` and `XMLHttpRequest` calls to domains you
|
|
12
|
+
list until their consent category is allowed. Use it as a backstop for
|
|
13
|
+
tracking calls from code that is already on the page, such as your own
|
|
14
|
+
analytics wrapper or an SDK you import. It is off until you configure it.
|
|
15
|
+
|
|
16
|
+
Load vendor SDKs through the provider's `scripts` prop first; see
|
|
17
|
+
[scripts](./scripts.md). A script that never loads sends nothing, which is
|
|
18
|
+
stronger than blocking its requests one by one.
|
|
19
|
+
|
|
20
|
+
## Configure the rules
|
|
21
|
+
|
|
22
|
+
Write the configuration in its own module:
|
|
23
|
+
|
|
24
|
+
```ts title="src/lib/network-blocker.ts"
|
|
25
|
+
import type { UseNetworkBlockerOptions } from '@c15t/svelte';
|
|
26
|
+
|
|
27
|
+
// Pass as `networkBlocker={networkBlocker}` on ConsentManagerProvider.
|
|
28
|
+
export const networkBlocker: UseNetworkBlockerOptions = {
|
|
29
|
+
onRequestBlocked: ({ method, url }) => {
|
|
30
|
+
console.info('Blocked until consent', method, url);
|
|
31
|
+
},
|
|
32
|
+
rules: [
|
|
33
|
+
{
|
|
34
|
+
category: 'measurement',
|
|
35
|
+
domain: 'google-analytics.com',
|
|
36
|
+
id: 'google-analytics',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
category: 'marketing',
|
|
40
|
+
domain: 'connect.facebook.net',
|
|
41
|
+
id: 'meta-pixel',
|
|
42
|
+
pathIncludes: '/signals',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Pass it to the provider:
|
|
49
|
+
|
|
50
|
+
```svelte
|
|
51
|
+
<ConsentManagerProvider {mode} {scripts} {networkBlocker}>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The provider reads `networkBlocker` once, when it is created. Remount the
|
|
55
|
+
provider to change the rules.
|
|
56
|
+
|
|
57
|
+
## Match requests with rules
|
|
58
|
+
|
|
59
|
+
Each rule names a `domain` and the consent `category` a request needs. The
|
|
60
|
+
domain also matches its subdomains: `google-analytics.com` covers
|
|
61
|
+
`www.google-analytics.com`. `pathIncludes` narrows the rule to paths that
|
|
62
|
+
contain a substring, and `methods` narrows it to HTTP methods. A request is
|
|
63
|
+
blocked when a matching rule's condition is not met by the visitor's
|
|
64
|
+
effective permissions.
|
|
65
|
+
|
|
66
|
+
`category` takes the same conditions as scripts:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
{ category: 'measurement' }
|
|
70
|
+
{ category: { and: ['measurement', 'marketing'] } }
|
|
71
|
+
{ category: { or: ['measurement', 'marketing'] } }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Add `vendor` to also block the request while the visitor has turned that
|
|
75
|
+
vendor off. IAB TCF rules use `vendorId` and the `iab*` purpose fields
|
|
76
|
+
instead.
|
|
77
|
+
|
|
78
|
+
| Option | Default | Purpose |
|
|
79
|
+
| -------------------- | -------- | ------------------------------------------------------------ |
|
|
80
|
+
| `rules` | required | Rules described above |
|
|
81
|
+
| `enabled` | `true` | Set `false` to keep the rules but stop blocking |
|
|
82
|
+
| `logBlockedRequests` | `true` | Log each blocked request with `console.warn` |
|
|
83
|
+
| `onRequestBlocked` | none | Called with `{ method, url, rule }` for each blocked request |
|
|
84
|
+
|
|
85
|
+
## What a blocked request looks like
|
|
86
|
+
|
|
87
|
+
A blocked `fetch` resolves to a response with status `451` and the status
|
|
88
|
+
text `Request blocked by consent`, and nothing is sent. A blocked
|
|
89
|
+
`XMLHttpRequest` is aborted and fires an `error` event. Requests that match no
|
|
90
|
+
rule are not delayed. With `logBlockedRequests` on, the default, each blocked
|
|
91
|
+
request is logged with `console.warn`; `onRequestBlocked` receives
|
|
92
|
+
`{ method, url, rule }`.
|
|
93
|
+
|
|
94
|
+
## When blocking starts
|
|
95
|
+
|
|
96
|
+
The provider starts holding matching requests when it is created in the
|
|
97
|
+
browser, before its children run their own code. The blocker module loads
|
|
98
|
+
when the provider mounts and then decides each held request:
|
|
99
|
+
|
|
100
|
+
* While the policy is still loading, a matching request waits instead of
|
|
101
|
+
failing. When the policy arrives, with the visitor's stored choice applied,
|
|
102
|
+
the request is sent if its category is allowed and blocked otherwise.
|
|
103
|
+
* If the policy fails to load, optional categories stay denied and the
|
|
104
|
+
waiting requests are blocked.
|
|
105
|
+
* A synchronous XHR cannot wait. Before the blocker module loads, a matching
|
|
106
|
+
one throws a `NetworkError` from `send()`.
|
|
107
|
+
|
|
108
|
+
When the visitor allows a category later, new requests to its domains go
|
|
109
|
+
through. Requests blocked earlier are not replayed.
|
|
110
|
+
|
|
111
|
+
## What it cannot stop
|
|
112
|
+
|
|
113
|
+
The blocker sees only `fetch` and `XMLHttpRequest` calls made after the
|
|
114
|
+
provider is created in the browser. It cannot stop:
|
|
115
|
+
|
|
116
|
+
* Scripts that run before the provider, such as tags in `index.html` or
|
|
117
|
+
`app.html` and code at the top level of modules that load first.
|
|
118
|
+
* Code that kept its own reference to `fetch` or `XMLHttpRequest` from before
|
|
119
|
+
the provider was created.
|
|
120
|
+
* `navigator.sendBeacon`, `WebSocket`, `EventSource`, and requests made by
|
|
121
|
+
`<img>`, `<script>` and `<iframe>` elements, web workers and service
|
|
122
|
+
workers.
|
|
123
|
+
* Requests your server makes, such as from a SvelteKit `load` or endpoint.
|
|
124
|
+
|
|
125
|
+
Keep tracking calls out of that window. Send them from event handlers or
|
|
126
|
+
effects, not at a module's top level, and check
|
|
127
|
+
`getConsentManager().has('measurement')` before you call a vendor from your
|
|
128
|
+
own code.
|
|
129
|
+
|
|
130
|
+
## Verify the blocker
|
|
131
|
+
|
|
132
|
+
Open DevTools, clear site data for your origin and reload:
|
|
133
|
+
|
|
134
|
+
1. Before you choose, requests matching a rule do not reach the network, and
|
|
135
|
+
the console logs each blocked request.
|
|
136
|
+
2. Allow the rule's category. New matching requests appear in the Network
|
|
137
|
+
panel.
|
|
138
|
+
3. Reject, reload and confirm they stay blocked.
|
|
139
|
+
|
|
140
|
+
`fetch('https://www.google-analytics.com/g/collect')` in the console returns a
|
|
141
|
+
response with status `451` while measurement is denied.
|