@c15t/scripts 2.2.0 → 3.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +74 -48
- package/README.md +3 -3
- package/dist/e2e-test-utils.js +60 -24
- package/dist/engine/compile.js +45 -45
- package/dist/engine/runtime.js +119 -119
- package/dist/registry.js +186 -176
- package/dist/resolve.js +12 -12
- package/dist/vendors/_shared/attributes.js +5 -5
- package/dist/vendors/_shared/google-consent.js +10 -10
- package/dist/vendors/_shared/install-builders.js +9 -9
- package/dist/vendors/_shared/script-url.js +12 -12
- package/dist/vendors/ads-and-pixels/linkedin-insights.js +16 -16
- package/dist/vendors/ads-and-pixels/meta-pixel.js +82 -82
- package/dist/vendors/ads-and-pixels/microsoft-uet.js +57 -57
- package/dist/vendors/ads-and-pixels/openai-pixel.js +88 -0
- package/dist/vendors/ads-and-pixels/reddit-pixel.js +39 -39
- package/dist/vendors/ads-and-pixels/snapchat-pixel.js +25 -25
- package/dist/vendors/ads-and-pixels/tiktok-pixel.js +31 -31
- package/dist/vendors/ads-and-pixels/x-pixel.js +17 -17
- package/dist/vendors/analytics/adobe-analytics.js +17 -17
- package/dist/vendors/analytics/ahrefs-analytics.js +8 -8
- package/dist/vendors/analytics/amplitude.js +39 -39
- package/dist/vendors/analytics/clearbit.js +11 -11
- package/dist/vendors/analytics/cloudflare-web-analytics.js +13 -13
- package/dist/vendors/analytics/databuddy.js +45 -45
- package/dist/vendors/analytics/fathom-analytics.js +15 -15
- package/dist/vendors/analytics/google-tag.js +23 -23
- package/dist/vendors/analytics/heap.js +37 -37
- package/dist/vendors/analytics/hightouch.js +30 -30
- package/dist/vendors/analytics/hotjar.js +14 -14
- package/dist/vendors/analytics/logrocket.js +24 -24
- package/dist/vendors/analytics/matomo-analytics.js +51 -51
- package/dist/vendors/analytics/microsoft-clarity.js +31 -31
- package/dist/vendors/analytics/mixpanel-analytics.js +31 -31
- package/dist/vendors/analytics/pirsch.js +27 -27
- package/dist/vendors/analytics/plausible-analytics.js +24 -24
- package/dist/vendors/analytics/posthog.js +84 -79
- package/dist/vendors/analytics/promptwatch.js +8 -8
- package/dist/vendors/analytics/rudderstack.js +50 -50
- package/dist/vendors/analytics/rybbit-analytics.js +30 -30
- package/dist/vendors/analytics/segment.js +16 -16
- package/dist/vendors/analytics/umami-analytics.js +16 -16
- package/dist/vendors/analytics/vercel-analytics.js +22 -22
- package/dist/vendors/functional/crisp.js +49 -51
- package/dist/vendors/functional/intercom.js +18 -18
- package/dist/vendors/tag-managers/google-tag-manager.js +20 -20
- package/dist-types/__tests__/helpers.d.ts +10 -10
- package/dist-types/engine/compile.d.ts +2 -2
- package/dist-types/engine/runtime.d.ts +3 -3
- package/dist-types/registry.d.ts +182 -173
- package/dist-types/resolve.d.ts +2 -2
- package/dist-types/types.d.ts +2 -2
- package/dist-types/vendors/_shared/attributes.d.ts +2 -2
- package/dist-types/vendors/_shared/google-consent.d.ts +2 -2
- package/dist-types/vendors/_shared/install-builders.d.ts +1 -1
- package/dist-types/vendors/_shared/script-url.d.ts +6 -6
- package/dist-types/vendors/ads-and-pixels/linkedin-insights.d.ts +14 -14
- package/dist-types/vendors/ads-and-pixels/meta-pixel.d.ts +27 -27
- package/dist-types/vendors/ads-and-pixels/microsoft-uet.d.ts +40 -40
- package/dist-types/vendors/ads-and-pixels/openai-pixel.d.ts +211 -0
- package/dist-types/vendors/ads-and-pixels/reddit-pixel.d.ts +28 -29
- package/dist-types/vendors/ads-and-pixels/snapchat-pixel.d.ts +23 -23
- package/dist-types/vendors/ads-and-pixels/tiktok-pixel.d.ts +22 -22
- package/dist-types/vendors/ads-and-pixels/x-pixel.d.ts +15 -15
- package/dist-types/vendors/analytics/adobe-analytics.d.ts +3 -3
- package/dist-types/vendors/analytics/ahrefs-analytics.d.ts +5 -5
- package/dist-types/vendors/analytics/amplitude.d.ts +24 -24
- package/dist-types/vendors/analytics/clearbit.d.ts +5 -5
- package/dist-types/vendors/analytics/cloudflare-web-analytics.d.ts +6 -6
- package/dist-types/vendors/analytics/databuddy.d.ts +34 -31
- package/dist-types/vendors/analytics/fathom-analytics.d.ts +8 -8
- package/dist-types/vendors/analytics/google-tag.d.ts +17 -17
- package/dist-types/vendors/analytics/heap.d.ts +17 -17
- package/dist-types/vendors/analytics/hightouch.d.ts +15 -15
- package/dist-types/vendors/analytics/hotjar.d.ts +9 -9
- package/dist-types/vendors/analytics/logrocket.d.ts +11 -11
- package/dist-types/vendors/analytics/matomo-analytics.d.ts +3 -3
- package/dist-types/vendors/analytics/microsoft-clarity.d.ts +12 -13
- package/dist-types/vendors/analytics/mixpanel-analytics.d.ts +20 -20
- package/dist-types/vendors/analytics/pirsch.d.ts +10 -10
- package/dist-types/vendors/analytics/plausible-analytics.d.ts +13 -13
- package/dist-types/vendors/analytics/posthog.d.ts +35 -32
- package/dist-types/vendors/analytics/promptwatch.d.ts +5 -5
- package/dist-types/vendors/analytics/rudderstack.d.ts +16 -16
- package/dist-types/vendors/analytics/rybbit-analytics.d.ts +15 -15
- package/dist-types/vendors/analytics/segment.d.ts +11 -11
- package/dist-types/vendors/analytics/umami-analytics.d.ts +9 -9
- package/dist-types/vendors/analytics/vercel-analytics.d.ts +13 -13
- package/dist-types/vendors/functional/crisp.d.ts +9 -9
- package/dist-types/vendors/functional/intercom.d.ts +12 -12
- package/dist-types/vendors/tag-managers/google-tag-manager.d.ts +16 -16
- package/docs/README.md +74 -48
- package/docs/assets/v3/brand-bar.png +0 -0
- package/docs/assets/v3/brand-card.png +0 -0
- package/docs/assets/v3/choice-wall.png +0 -0
- package/docs/assets/v3/mobile-card.png +0 -0
- package/docs/assets/v3/preferences.png +0 -0
- package/docs/customization/overview.md +45 -0
- package/docs/customization/recipes.md +79 -0
- package/docs/customization/slots.md +55 -0
- package/docs/customization/tokens.md +76 -0
- package/docs/customization/translations.md +49 -0
- package/docs/frameworks/javascript/script-loader.md +30 -339
- package/docs/frameworks/next/script-loader.md +134 -467
- package/docs/frameworks/react/script-loader.md +35 -535
- package/docs/guides/consent-state.md +60 -0
- package/docs/guides/data-fetching.md +163 -0
- package/docs/guides/deployment-modes.md +63 -0
- package/docs/guides/troubleshooting.md +68 -0
- package/docs/guides/verify-consent.md +62 -0
- package/docs/integrations/adobe-analytics.md +239 -105
- package/docs/integrations/ahrefs-analytics.md +238 -104
- package/docs/integrations/amplitude.md +219 -157
- package/docs/integrations/building-integrations.md +32 -224
- package/docs/integrations/clearbit.md +247 -86
- package/docs/integrations/cloudflare-web-analytics.md +250 -84
- package/docs/integrations/crisp.md +251 -97
- package/docs/integrations/databuddy.md +259 -153
- package/docs/integrations/fathom-analytics.md +239 -96
- package/docs/integrations/google-maps.md +328 -207
- package/docs/integrations/google-tag-manager.md +248 -96
- package/docs/integrations/google-tag.md +261 -90
- package/docs/integrations/heap.md +222 -149
- package/docs/integrations/hightouch.md +225 -131
- package/docs/integrations/hotjar.md +239 -90
- package/docs/integrations/intercom.md +239 -98
- package/docs/integrations/linkedin-insights.md +243 -113
- package/docs/integrations/logrocket.md +241 -123
- package/docs/integrations/matomo-analytics.md +256 -111
- package/docs/integrations/meta-pixel.md +197 -324
- package/docs/integrations/microsoft-clarity.md +233 -114
- package/docs/integrations/microsoft-uet.md +245 -110
- package/docs/integrations/mixpanel-analytics.md +252 -87
- package/docs/integrations/openai-pixel.md +441 -0
- package/docs/integrations/overview.md +95 -133
- package/docs/integrations/pirsch.md +249 -96
- package/docs/integrations/plausible-analytics.md +241 -100
- package/docs/integrations/posthog.md +353 -214
- package/docs/integrations/promptwatch.md +251 -81
- package/docs/integrations/reddit-pixel.md +226 -173
- package/docs/integrations/rudderstack.md +244 -187
- package/docs/integrations/rybbit-analytics.md +244 -91
- package/docs/integrations/segment.md +238 -92
- package/docs/integrations/snapchat-pixel.md +240 -110
- package/docs/integrations/tiktok-pixel.md +249 -81
- package/docs/integrations/umami-analytics.md +242 -95
- package/docs/integrations/vercel-analytics.md +242 -90
- package/docs/integrations/x-pixel.md +238 -104
- package/docs/integrations/youtube.md +354 -142
- package/docs/upgrade-v3.md +334 -0
- package/package.json +85 -78
- package/readme.json +2 -2
- package/dist/e2e-test-utils.cjs +0 -166
- package/dist/engine/compile.cjs +0 -130
- package/dist/engine/runtime.cjs +0 -475
- package/dist/registry.cjs +0 -423
- package/dist/resolve.cjs +0 -71
- package/dist/types.cjs +0 -69
- package/dist/vendors/_shared/attributes.cjs +0 -55
- package/dist/vendors/_shared/google-consent.cjs +0 -69
- package/dist/vendors/_shared/install-builders.cjs +0 -59
- package/dist/vendors/_shared/script-url.cjs +0 -78
- package/dist/vendors/ads-and-pixels/linkedin-insights.cjs +0 -89
- package/dist/vendors/ads-and-pixels/meta-pixel.cjs +0 -206
- package/dist/vendors/ads-and-pixels/microsoft-uet.cjs +0 -151
- package/dist/vendors/ads-and-pixels/reddit-pixel.cjs +0 -151
- package/dist/vendors/ads-and-pixels/snapchat-pixel.cjs +0 -131
- package/dist/vendors/ads-and-pixels/tiktok-pixel.cjs +0 -130
- package/dist/vendors/ads-and-pixels/x-pixel.cjs +0 -92
- package/dist/vendors/analytics/adobe-analytics.cjs +0 -90
- package/dist/vendors/analytics/ahrefs-analytics.cjs +0 -68
- package/dist/vendors/analytics/amplitude.cjs +0 -193
- package/dist/vendors/analytics/clearbit.cjs +0 -69
- package/dist/vendors/analytics/cloudflare-web-analytics.cjs +0 -73
- package/dist/vendors/analytics/databuddy.cjs +0 -144
- package/dist/vendors/analytics/fathom-analytics.cjs +0 -76
- package/dist/vendors/analytics/google-tag.cjs +0 -107
- package/dist/vendors/analytics/heap.cjs +0 -181
- package/dist/vendors/analytics/hightouch.cjs +0 -153
- package/dist/vendors/analytics/hotjar.cjs +0 -85
- package/dist/vendors/analytics/logrocket.cjs +0 -99
- package/dist/vendors/analytics/matomo-analytics.cjs +0 -232
- package/dist/vendors/analytics/microsoft-clarity.cjs +0 -138
- package/dist/vendors/analytics/mixpanel-analytics.cjs +0 -134
- package/dist/vendors/analytics/pirsch.cjs +0 -108
- package/dist/vendors/analytics/plausible-analytics.cjs +0 -122
- package/dist/vendors/analytics/posthog.cjs +0 -236
- package/dist/vendors/analytics/promptwatch.cjs +0 -70
- package/dist/vendors/analytics/rudderstack.cjs +0 -227
- package/dist/vendors/analytics/rybbit-analytics.cjs +0 -104
- package/dist/vendors/analytics/segment.cjs +0 -97
- package/dist/vendors/analytics/umami-analytics.cjs +0 -80
- package/dist/vendors/analytics/vercel-analytics.cjs +0 -94
- package/dist/vendors/functional/crisp.cjs +0 -143
- package/dist/vendors/functional/intercom.cjs +0 -89
- package/dist/vendors/tag-managers/google-tag-manager.cjs +0 -100
- package/docs/shared/react/guides/script-loader.md +0 -311
|
@@ -1,263 +1,384 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Google Maps
|
|
3
|
-
description:
|
|
4
|
-
JavaScript API loader and independently managed map instances.
|
|
5
|
-
icon: google-maps
|
|
3
|
+
description: Prevent a map iframe from mounting before the required permission.
|
|
6
4
|
group: integrations
|
|
7
5
|
---
|
|
8
|
-
`GoogleMap` is a renderable integration for React and Next.js. It keeps the
|
|
9
|
-
Google Maps JavaScript API off the page until the configured consent category is
|
|
10
|
-
allowed, then loads the SDK once and creates a map for each mounted component.
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
script lifecycle and the visible map container.
|
|
7
|
+
## Configure the map
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
Copy the map's iframe embed URL. Choose the category matching its use in your
|
|
10
|
+
application; this example uses functionality permission.
|
|
17
11
|
|
|
18
|
-
|
|
12
|
+
```ts title="src/embed-config.ts"
|
|
13
|
+
export const embedCategory = 'functionality' as const;
|
|
14
|
+
export const embedURL = 'https://www.google.com/maps/embed?pb=YOUR_EMBED_PARAMETERS';
|
|
15
|
+
export const embedTitle = 'Map showing our office location';
|
|
16
|
+
export const embedAspectRatio = '4 / 3';
|
|
17
|
+
```
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
[Next.js quickstart](/docs/frameworks/next/quickstart) first.
|
|
19
|
+
Replace the example URL with the complete embed URL for your map. Keep the
|
|
20
|
+
address and a directions link outside the consent-gated embed.
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
## Add the embed to your framework
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
Create `src/embed-config.ts` using the configuration on this page, then select
|
|
25
|
+
your framework. Keep the existing [consent setup](https://c15t.com/docs/frameworks), its policy
|
|
26
|
+
and preferences UI. These examples do not create a second consent provider.
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
**Next.js**
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
if (!apiKey) {
|
|
33
|
-
return <p>Google Maps is not configured.</p>;
|
|
34
|
-
}
|
|
30
|
+
Render this component inside your existing consent boundary or provider.
|
|
35
31
|
|
|
32
|
+
```tsx title="src/consent-embed.tsx"
|
|
33
|
+
'use client';
|
|
34
|
+
|
|
35
|
+
import { Frame } from 'c15t/next';
|
|
36
|
+
import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
|
|
37
|
+
|
|
38
|
+
export function ConsentEmbed() {
|
|
36
39
|
return (
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
<Frame category={embedCategory}>
|
|
41
|
+
<iframe
|
|
42
|
+
src={embedURL}
|
|
43
|
+
title={embedTitle}
|
|
44
|
+
loading="lazy"
|
|
45
|
+
allowFullScreen
|
|
46
|
+
style={{ width: '100%', aspectRatio: embedAspectRatio, minHeight: 200, border: 0 }}
|
|
47
|
+
/>
|
|
48
|
+
</Frame>
|
|
45
49
|
);
|
|
46
50
|
}
|
|
47
51
|
```
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
```tsx
|
|
52
|
-
'use client';
|
|
53
|
+
`Frame` keeps the iframe absent while permission is denied and removes it
|
|
54
|
+
on revocation. Keep your existing consent styles and preferences dialog.
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
**TanStack Start**
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
Render this component inside your existing consent boundary or provider.
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
60
|
+
```tsx title="src/consent-embed.tsx"
|
|
61
|
+
import { Frame } from 'c15t/tanstack-start';
|
|
62
|
+
import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
|
|
62
63
|
|
|
64
|
+
export function ConsentEmbed() {
|
|
63
65
|
return (
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
<Frame category={embedCategory}>
|
|
67
|
+
<iframe
|
|
68
|
+
src={embedURL}
|
|
69
|
+
title={embedTitle}
|
|
70
|
+
loading="lazy"
|
|
71
|
+
allowFullScreen
|
|
72
|
+
style={{ width: '100%', aspectRatio: embedAspectRatio, minHeight: 200, border: 0 }}
|
|
73
|
+
/>
|
|
74
|
+
</Frame>
|
|
72
75
|
);
|
|
73
76
|
}
|
|
74
77
|
```
|
|
75
78
|
|
|
76
|
-
`
|
|
77
|
-
|
|
78
|
-
to your users.
|
|
79
|
-
|
|
80
|
-
## How c15t loads it
|
|
81
|
-
|
|
82
|
-
* **Before consent:** no Maps JavaScript API script is requested and no map
|
|
83
|
-
instance is created.
|
|
84
|
-
* **After consent:** c15t registers one shared SDK loader and waits for Google's
|
|
85
|
-
callback before constructing the map.
|
|
86
|
-
* **Multiple maps:** components with the same `scriptId` reuse the SDK while
|
|
87
|
-
keeping separate map instances.
|
|
88
|
-
* **On revocation or unmount:** the component clears the map instance and its
|
|
89
|
-
listeners. The page-level SDK registration is retained after its first load
|
|
90
|
-
because Google supports one loader per page.
|
|
91
|
-
* **Existing SDK:** if another part of the application already loaded a
|
|
92
|
-
compatible Google Maps global, the component adopts it instead of adding a
|
|
93
|
-
duplicate script.
|
|
94
|
-
* **Visible states:** the blocked, loading, and error states are accessible and
|
|
95
|
-
use the active c15t translations by default.
|
|
96
|
-
|
|
97
|
-
Keep one `scriptId` and one loader configuration across the page. Override the
|
|
98
|
-
default `scriptId` only to coordinate with a known c15t script registration, not
|
|
99
|
-
to load a second Maps SDK configuration. If two maps register conflicting
|
|
100
|
-
loader options, c15t reports the conflict and tells you to align the key,
|
|
101
|
-
language, region, libraries, and other loader options. A different `scriptId`
|
|
102
|
-
does not make a second Google loader safe.
|
|
103
|
-
|
|
104
|
-
## Configure the map
|
|
105
|
-
|
|
106
|
-
Pass `center`, `zoom`, `mapId`, and `options` as you would when constructing a
|
|
107
|
-
Google map:
|
|
108
|
-
|
|
109
|
-
```tsx
|
|
110
|
-
<GoogleMap
|
|
111
|
-
apiKey={apiKey}
|
|
112
|
-
center={{ lat: 51.5072, lng: -0.1276 }}
|
|
113
|
-
consentCategory="measurement"
|
|
114
|
-
mapId="YOUR_MAP_ID"
|
|
115
|
-
options={{
|
|
116
|
-
disableDefaultUI: true,
|
|
117
|
-
gestureHandling: 'cooperative',
|
|
118
|
-
}}
|
|
119
|
-
zoom={11}
|
|
120
|
-
/>
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
Changes to `center`, `zoom`, and updateable `options` are applied to the existing
|
|
124
|
-
map. Changing `mapId` recreates it because Google treats that value as
|
|
125
|
-
construction-time configuration.
|
|
126
|
-
|
|
127
|
-
The component has a default height of `320px`. Override `style.height` or apply
|
|
128
|
-
a class with an explicit height when your layout needs another size.
|
|
129
|
-
|
|
130
|
-
### Loader options
|
|
131
|
-
|
|
132
|
-
The direct Maps JavaScript API loader options are available as component props:
|
|
79
|
+
`Frame` keeps the iframe absent while permission is denied and removes it
|
|
80
|
+
on revocation. Keep your existing consent styles and preferences dialog.
|
|
133
81
|
|
|
134
|
-
|
|
135
|
-
* `language`
|
|
136
|
-
* `region`
|
|
137
|
-
* `version`
|
|
138
|
-
* `authReferrerPolicy`
|
|
139
|
-
* `mapIds`
|
|
140
|
-
* `channel`
|
|
141
|
-
* `solutionChannel`
|
|
142
|
-
* `nonce`
|
|
82
|
+
**React**
|
|
143
83
|
|
|
144
|
-
|
|
145
|
-
component instance.
|
|
84
|
+
Render this component inside your existing consent boundary or provider.
|
|
146
85
|
|
|
147
|
-
|
|
86
|
+
```tsx title="src/consent-embed.tsx"
|
|
87
|
+
import { Frame } from 'c15t/react';
|
|
88
|
+
import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
|
|
148
89
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
90
|
+
export function ConsentEmbed() {
|
|
91
|
+
return (
|
|
92
|
+
<Frame category={embedCategory}>
|
|
93
|
+
<iframe
|
|
94
|
+
src={embedURL}
|
|
95
|
+
title={embedTitle}
|
|
96
|
+
loading="lazy"
|
|
97
|
+
allowFullScreen
|
|
98
|
+
style={{ width: '100%', aspectRatio: embedAspectRatio, minHeight: 200, border: 0 }}
|
|
99
|
+
/>
|
|
100
|
+
</Frame>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
153
104
|
|
|
154
|
-
|
|
155
|
-
|
|
105
|
+
`Frame` keeps the iframe absent while permission is denied and removes it
|
|
106
|
+
on revocation. Keep your existing consent styles and preferences dialog.
|
|
107
|
+
|
|
108
|
+
**Nuxt**
|
|
109
|
+
|
|
110
|
+
Use the reactive snapshot from the existing consent runtime. The Nuxt module auto-imports the consent composables.
|
|
111
|
+
|
|
112
|
+
```vue title="app/components/ConsentEmbed.vue"
|
|
113
|
+
<script setup lang="ts">
|
|
114
|
+
import { embedCategory, embedURL, embedTitle, embedAspectRatio } from '../../src/embed-config';
|
|
115
|
+
|
|
116
|
+
const snapshot = useConsentSnapshot();
|
|
117
|
+
const activeUI = useConsentActiveUI();
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<template>
|
|
121
|
+
<iframe
|
|
122
|
+
v-if="snapshot.effectivePermissions[embedCategory]"
|
|
123
|
+
:src="embedURL"
|
|
124
|
+
:title="embedTitle"
|
|
125
|
+
loading="lazy"
|
|
126
|
+
allowfullscreen
|
|
127
|
+
:style="{ width: '100%', aspectRatio: embedAspectRatio, minHeight: '200px', border: 0 }"
|
|
128
|
+
/>
|
|
129
|
+
<button v-else type="button" @click="activeUI = 'manager'">
|
|
130
|
+
Open privacy settings to view this content
|
|
131
|
+
</button>
|
|
132
|
+
</template>
|
|
133
|
+
```
|
|
156
134
|
|
|
157
|
-
|
|
158
|
-
|
|
135
|
+
Use `v-if` so a denied iframe is removed from the DOM. Hiding an existing
|
|
136
|
+
iframe with `v-show` or CSS does not prevent its requests.
|
|
137
|
+
|
|
138
|
+
**Vue**
|
|
139
|
+
|
|
140
|
+
Use the reactive snapshot from the existing consent runtime. The c15t Vue plugin must already be installed on this app.
|
|
141
|
+
|
|
142
|
+
```vue title="src/ConsentEmbed.vue"
|
|
143
|
+
<script setup lang="ts">
|
|
144
|
+
import { useConsentSnapshot, useConsentActiveUI } from 'c15t/vue/vue-plugin';
|
|
145
|
+
import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
|
|
146
|
+
|
|
147
|
+
const snapshot = useConsentSnapshot();
|
|
148
|
+
const activeUI = useConsentActiveUI();
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<template>
|
|
152
|
+
<iframe
|
|
153
|
+
v-if="snapshot.effectivePermissions[embedCategory]"
|
|
154
|
+
:src="embedURL"
|
|
155
|
+
:title="embedTitle"
|
|
156
|
+
loading="lazy"
|
|
157
|
+
allowfullscreen
|
|
158
|
+
:style="{ width: '100%', aspectRatio: embedAspectRatio, minHeight: '200px', border: 0 }"
|
|
159
|
+
/>
|
|
160
|
+
<button v-else type="button" @click="activeUI = 'manager'">
|
|
161
|
+
Open privacy settings to view this content
|
|
162
|
+
</button>
|
|
163
|
+
</template>
|
|
164
|
+
```
|
|
159
165
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
166
|
+
Use `v-if` so a denied iframe is removed from the DOM. Hiding an existing
|
|
167
|
+
iframe with `v-show` or CSS does not prevent its requests.
|
|
168
|
+
|
|
169
|
+
**Astro**
|
|
170
|
+
|
|
171
|
+
Use the shared browser helper below with Astro's existing page runtime.
|
|
172
|
+
Add this component to pages using your consent-enabled base layout:
|
|
173
|
+
|
|
174
|
+
```astro title="src/components/ConsentEmbed.astro"
|
|
175
|
+
<c15t-consent-embed style="display: block"></c15t-consent-embed>
|
|
176
|
+
|
|
177
|
+
<script>
|
|
178
|
+
import { getConsentClient } from '@c15t/astro/client';
|
|
179
|
+
import { mountConsentEmbed } from '../consent-embed';
|
|
180
|
+
|
|
181
|
+
class ConsentEmbed extends HTMLElement {
|
|
182
|
+
dispose?: () => void;
|
|
183
|
+
|
|
184
|
+
connect = () => {
|
|
185
|
+
if (this.dispose) return;
|
|
186
|
+
const client = getConsentClient();
|
|
187
|
+
if (!client) return;
|
|
188
|
+
this.dispose = mountConsentEmbed(
|
|
189
|
+
this,
|
|
190
|
+
client.runtime.kernel,
|
|
191
|
+
() => { void client.openDialog(); },
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
connectedCallback() {
|
|
196
|
+
document.addEventListener('DOMContentLoaded', this.connect, { once: true });
|
|
197
|
+
this.connect();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
disconnectedCallback() {
|
|
201
|
+
document.removeEventListener('DOMContentLoaded', this.connect);
|
|
202
|
+
this.dispose?.();
|
|
203
|
+
this.dispose = undefined;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
163
206
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
center={{ lat: 40.7128, lng: -74.006 }}
|
|
167
|
-
consentCategory="measurement"
|
|
168
|
-
loadingFallback={<p>Loading map…</p>}
|
|
169
|
-
errorFallback={<p>The map could not be loaded.</p>}
|
|
170
|
-
onError={reportMapError}
|
|
171
|
-
placeholder={
|
|
172
|
-
<Frame.Root>
|
|
173
|
-
<Frame.Title>Allow measurement consent to view this map.</Frame.Title>
|
|
174
|
-
<Frame.Button category="measurement" />
|
|
175
|
-
</Frame.Root>
|
|
207
|
+
if (!customElements.get('c15t-consent-embed')) {
|
|
208
|
+
customElements.define('c15t-consent-embed', ConsentEmbed);
|
|
176
209
|
}
|
|
177
|
-
|
|
210
|
+
</script>
|
|
178
211
|
```
|
|
179
212
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
213
|
+
The first mount waits for the page's module scripts, including c15t's boot
|
|
214
|
+
script. Later `ClientRouter` mounts reuse the existing runtime. Removing the
|
|
215
|
+
component unsubscribes and removes the iframe. Do not create another consent
|
|
216
|
+
runtime for this embed.
|
|
217
|
+
|
|
218
|
+
**Svelte**
|
|
219
|
+
|
|
220
|
+
Render this component inside the existing `ConsentManagerProvider`.
|
|
221
|
+
The provider from your quickstart supplies its consent state.
|
|
222
|
+
|
|
223
|
+
```svelte title="src/ConsentEmbed.svelte"
|
|
224
|
+
<script lang="ts">
|
|
225
|
+
import { Frame } from '@c15t/svelte';
|
|
226
|
+
import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
|
|
227
|
+
</script>
|
|
228
|
+
|
|
229
|
+
<Frame category={embedCategory}>
|
|
230
|
+
<iframe
|
|
231
|
+
src={embedURL}
|
|
232
|
+
title={embedTitle}
|
|
233
|
+
loading="lazy"
|
|
234
|
+
allowfullscreen
|
|
235
|
+
style:width="100%"
|
|
236
|
+
style:aspect-ratio={embedAspectRatio}
|
|
237
|
+
style:min-height="200px"
|
|
238
|
+
style:border="0"
|
|
239
|
+
></iframe>
|
|
240
|
+
</Frame>
|
|
241
|
+
```
|
|
189
242
|
|
|
190
|
-
|
|
191
|
-
|
|
243
|
+
The Svelte `Frame` waits until the browser is mounted and the category is
|
|
244
|
+
allowed. Its default placeholder opens preferences. Revocation removes the
|
|
245
|
+
iframe.
|
|
246
|
+
|
|
247
|
+
**SvelteKit**
|
|
248
|
+
|
|
249
|
+
Render this component inside the existing `ConsentManagerProvider`.
|
|
250
|
+
Keep the SvelteKit root provider and its server prefetch unchanged.
|
|
251
|
+
|
|
252
|
+
```svelte title="src/lib/ConsentEmbed.svelte"
|
|
253
|
+
<script lang="ts">
|
|
254
|
+
import { Frame } from '@c15t/svelte';
|
|
255
|
+
import { embedCategory, embedURL, embedTitle, embedAspectRatio } from '../embed-config';
|
|
256
|
+
</script>
|
|
257
|
+
|
|
258
|
+
<Frame category={embedCategory}>
|
|
259
|
+
<iframe
|
|
260
|
+
src={embedURL}
|
|
261
|
+
title={embedTitle}
|
|
262
|
+
loading="lazy"
|
|
263
|
+
allowfullscreen
|
|
264
|
+
style:width="100%"
|
|
265
|
+
style:aspect-ratio={embedAspectRatio}
|
|
266
|
+
style:min-height="200px"
|
|
267
|
+
style:border="0"
|
|
268
|
+
></iframe>
|
|
269
|
+
</Frame>
|
|
270
|
+
```
|
|
192
271
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
272
|
+
The Svelte `Frame` waits until the browser is mounted and the category is
|
|
273
|
+
allowed. Its default placeholder opens preferences. Revocation removes the
|
|
274
|
+
iframe.
|
|
196
275
|
|
|
197
|
-
|
|
198
|
-
const [retryKey, setRetryKey] = useState(0);
|
|
276
|
+
**JavaScript**
|
|
199
277
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
<GoogleMap
|
|
203
|
-
apiKey={apiKey}
|
|
204
|
-
center={{ lat: 40.7128, lng: -74.006 }}
|
|
205
|
-
retryKey={retryKey}
|
|
206
|
-
/>
|
|
278
|
+
Use the shared browser helper below with your existing kernel. Put an
|
|
279
|
+
empty container where the embed should appear:
|
|
207
280
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
</button>
|
|
211
|
-
</>
|
|
212
|
-
);
|
|
213
|
-
}
|
|
281
|
+
```html
|
|
282
|
+
<div id="consent-embed"></div>
|
|
214
283
|
```
|
|
215
284
|
|
|
216
|
-
|
|
217
|
-
`scriptId`. A successful page-level SDK registration remains shared and is not
|
|
218
|
-
loaded again.
|
|
285
|
+
In your browser entry point, after creating the kernel:
|
|
219
286
|
|
|
220
|
-
|
|
287
|
+
```ts
|
|
288
|
+
import { mountConsentEmbed } from './consent-embed';
|
|
221
289
|
|
|
222
|
-
|
|
290
|
+
const container = document.querySelector<HTMLElement>('#consent-embed');
|
|
291
|
+
if (!container) throw new Error('Missing consent embed container');
|
|
223
292
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
and production origin.
|
|
227
|
-
3. Restrict the key to the **Maps JavaScript API** and any additional APIs used
|
|
228
|
-
by requested libraries.
|
|
229
|
-
4. Keep the value in an untracked environment file and expose it through the
|
|
230
|
-
browser environment variable required by your framework.
|
|
231
|
-
5. Monitor key usage and rotate the key if you see unexpected traffic.
|
|
293
|
+
const disposeEmbed = mountConsentEmbed(container, kernel, openPreferences);
|
|
294
|
+
```
|
|
232
295
|
|
|
233
|
-
|
|
234
|
-
|
|
296
|
+
`kernel` is the instance from your quickstart. `openPreferences` is your
|
|
297
|
+
application's function for showing its consent preferences UI. Call
|
|
298
|
+
`disposeEmbed()` when the page or component is destroyed. The helper observes
|
|
299
|
+
both the current snapshot and later changes.
|
|
300
|
+
|
|
301
|
+
## Browser helper for Astro and JavaScript
|
|
302
|
+
|
|
303
|
+
Only the Astro and JavaScript examples need this helper. It creates the iframe
|
|
304
|
+
when permission allows it, keeps an existing player mounted across unrelated
|
|
305
|
+
snapshot updates, and removes it on revocation.
|
|
306
|
+
|
|
307
|
+
```ts title="src/consent-embed.ts"
|
|
308
|
+
import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
|
|
309
|
+
|
|
310
|
+
type EmbedKernel = {
|
|
311
|
+
getSnapshot: () => {
|
|
312
|
+
effectivePermissions: Record<typeof embedCategory, boolean>;
|
|
313
|
+
};
|
|
314
|
+
subscribe: (listener: () => void) => () => void;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
export function mountConsentEmbed(
|
|
318
|
+
container: HTMLElement,
|
|
319
|
+
kernel: EmbedKernel,
|
|
320
|
+
openPreferences: () => void,
|
|
321
|
+
) {
|
|
322
|
+
const render = () => {
|
|
323
|
+
if (kernel.getSnapshot().effectivePermissions[embedCategory]) {
|
|
324
|
+
if (container.querySelector('iframe')) return;
|
|
325
|
+
const frame = document.createElement('iframe');
|
|
326
|
+
frame.src = embedURL;
|
|
327
|
+
frame.title = embedTitle;
|
|
328
|
+
frame.loading = 'lazy';
|
|
329
|
+
frame.allowFullscreen = true;
|
|
330
|
+
Object.assign(frame.style, {
|
|
331
|
+
width: '100%',
|
|
332
|
+
aspectRatio: embedAspectRatio,
|
|
333
|
+
minHeight: '200px',
|
|
334
|
+
border: '0',
|
|
335
|
+
});
|
|
336
|
+
container.replaceChildren(frame);
|
|
337
|
+
} else {
|
|
338
|
+
if (container.querySelector('button')) return;
|
|
339
|
+
const button = document.createElement('button');
|
|
340
|
+
button.type = 'button';
|
|
341
|
+
button.textContent = 'Open privacy settings to view this content';
|
|
342
|
+
button.onclick = openPreferences;
|
|
343
|
+
container.replaceChildren(button);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
render();
|
|
348
|
+
const unsubscribe = kernel.subscribe(render);
|
|
349
|
+
return () => {
|
|
350
|
+
unsubscribe();
|
|
351
|
+
container.replaceChildren();
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
```
|
|
235
355
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
356
|
+
Keep a transcript, address or other useful alternative outside the embed.
|
|
357
|
+
`loading="lazy"` is a performance hint; the consent condition controls whether
|
|
358
|
+
the iframe exists at all. A denied category may be fixed by policy, so opening
|
|
359
|
+
preferences does not guarantee that the visitor can grant it.
|
|
239
360
|
|
|
240
|
-
##
|
|
361
|
+
## Gate existing iframe markup
|
|
241
362
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
6. Test a rejected key or referrer and confirm `errorFallback` and `onError`
|
|
250
|
-
receive the failure.
|
|
251
|
-
7. Fix the rejected configuration, change `retryKey`, and confirm the map can
|
|
252
|
-
recover without changing `scriptId`.
|
|
363
|
+
For markup that uses `data-category` and `data-src`, the
|
|
364
|
+
`createIframeBlocker` module from `c15t/modules/iframe-blocker` promotes
|
|
365
|
+
`data-src` only when the category has consent and the URL resolves to HTTP or
|
|
366
|
+
HTTPS. Relative URLs resolve against the iframe document's base URL.
|
|
367
|
+
Malformed URLs and other schemes, including `javascript:`, `data:` and `blob:`,
|
|
368
|
+
remain in `data-src` without loading. Correct the URL and call
|
|
369
|
+
`processAllIframes()` to retry.
|
|
253
370
|
|
|
254
|
-
|
|
371
|
+
Keep the initial URL in `data-src`, without `src`, so the browser cannot load
|
|
372
|
+
it before the blocker runs. The module does not sanitize arbitrary HTML or
|
|
373
|
+
modify iframes without `data-category`.
|
|
255
374
|
|
|
256
|
-
|
|
375
|
+
## Verify before and after permission
|
|
257
376
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
377
|
+
On a fresh opt-in session, inspect Network and confirm the iframe has not
|
|
378
|
+
requested the map. Grant the category and confirm the map mounts. Revoke it and
|
|
379
|
+
check the frame returns to its blocked state. Keep useful fallback information,
|
|
380
|
+
such as the address and directions link, available outside the map.
|
|
262
381
|
|
|
263
|
-
|
|
382
|
+
For a JavaScript Maps SDK instead of an iframe, register a category-gated script
|
|
383
|
+
and manage the widget's cleanup. Do not use this iframe recipe as evidence that
|
|
384
|
+
an independently loaded Maps SDK is blocked.
|