@krysmalskipl/astro-consent-mode 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +226 -2
- package/dist/client.js +71 -0
- package/dist/index.d.ts +231 -0
- package/dist/index.js +499 -0
- package/package.json +68 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## 0.1.0 - 2026-10-12
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- Initial release of `@krysmalski/astro-consent-mode`.
|
|
10
|
+
- Astro integration supporting Google Consent Mode v2 with both `basic` and `advanced` loading strategies.
|
|
11
|
+
- Zero-config integration with vanilla-cookieconsent v3.
|
|
12
|
+
- Native tag loaders for Google Tag Manager (GTM), Google Analytics 4 (GA4), Meta Pixel, and Microsoft Clarity.
|
|
13
|
+
- Support for Microsoft Clarity Consent API v2 (`clarity('consentv2', ...)`).
|
|
14
|
+
- Synchronous cookie inspection in `<head>` for returning visitors to eliminate consent lag.
|
|
15
|
+
- Auto-cleaning of analytics and marketing cookies when consent is revoked.
|
|
16
|
+
- 8 ready-made banner layout presets (`box-left`, `box-right`, `box-center`, `box-wide`, `cloud`, `bar-bottom`, `bar-top`, `modal`) plus optional `overlay` backdrop dimming.
|
|
17
|
+
- Dynamic categorization based on configured service IDs.
|
|
18
|
+
- Full Polish (`pl`) and English (`en`) built-in translations with deep customization support.
|
|
19
|
+
- Fully typed configuration with TypeScript declarations and JSDoc annotations.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Krysmalski (https://krysmalski.pl)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,227 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @krysmalskipl/astro-consent-mode
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Google Consent Mode v2 for Astro - cookie banner, GTM, GA4, Meta Pixel and Microsoft Clarity with zero config.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Google Consent Mode v2**: Full support for `ad_storage`, `ad_user_data`, `ad_personalization`, and `analytics_storage`.
|
|
8
|
+
- **Integrated Cookie Banner**: Powered by vanilla-cookieconsent v3 with an accessible and customizable UI.
|
|
9
|
+
- **Multi-Service Loaders**: First-class support for Google Tag Manager, Google Analytics 4, Meta Pixel (Facebook Pixel), and Microsoft Clarity.
|
|
10
|
+
- **Microsoft Clarity Consent API v2**: Implements `clarity('consentv2', ...)` for proper European Economic Area compliance.
|
|
11
|
+
- **Two Consent Modes**: Choose between `basic` (strict tag blocking until consent) and `advanced` (cookieless pings with default denied).
|
|
12
|
+
- **Synchronous Returning Visitor Handling**: Reads consent synchronously in `<head>` to prevent tagging delays on subsequent page visits.
|
|
13
|
+
- **Auto Cookie Cleanup**: Removes tracking cookies (`_ga`, `_fbp`, `_clck`, etc.) when consent is withdrawn.
|
|
14
|
+
- **Bilingual by Default**: Built-in Polish (`pl`) and English (`en`) translations with deep customization.
|
|
15
|
+
- **Fully Typed**: Includes comprehensive TypeScript definitions and JSDoc documentation.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Add the integration to your Astro project:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx astro add @krysmalskipl/astro-consent-mode
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or install manually via npm:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @krysmalskipl/astro-consent-mode vanilla-cookieconsent
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Then register the integration in your `astro.config.mjs`:
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
import { defineConfig } from 'astro/config';
|
|
35
|
+
import consent from '@krysmalskipl/astro-consent-mode';
|
|
36
|
+
|
|
37
|
+
export default defineConfig({
|
|
38
|
+
integrations: [
|
|
39
|
+
consent({
|
|
40
|
+
gtmId: 'GTM-XXXXXX',
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
import { defineConfig } from 'astro/config';
|
|
50
|
+
import consent from '@krysmalskipl/astro-consent-mode';
|
|
51
|
+
|
|
52
|
+
export default defineConfig({
|
|
53
|
+
integrations: [
|
|
54
|
+
consent({
|
|
55
|
+
// Tracking IDs
|
|
56
|
+
gtmId: 'GTM-XXXXXX', // Optional: Google Tag Manager ID
|
|
57
|
+
ga4Id: 'G-XXXXXXXXXX', // Optional: Used if gtmId is omitted
|
|
58
|
+
metaPixelId: '1234567890', // Optional: Meta Pixel ID
|
|
59
|
+
clarityId: 'abcdef1234', // Optional: Microsoft Clarity ID
|
|
60
|
+
|
|
61
|
+
// Strategy and Language
|
|
62
|
+
mode: 'basic', // 'basic' | 'advanced' (default: 'basic')
|
|
63
|
+
lang: 'pl', // 'pl' | 'en' (default: 'pl')
|
|
64
|
+
privacyUrl: '/polityka-prywatnosci/', // Link to Privacy Policy
|
|
65
|
+
|
|
66
|
+
// Revisions & Customizations
|
|
67
|
+
revision: 0, // Increment to force re-consent
|
|
68
|
+
texts: { // Custom text overrides
|
|
69
|
+
consentModal: {
|
|
70
|
+
title: 'Dbamy o Twoją prywatność',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
gui: {
|
|
74
|
+
consentModal: {
|
|
75
|
+
layout: 'box',
|
|
76
|
+
position: 'bottom left',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Options
|
|
85
|
+
|
|
86
|
+
| Option | Type | Default | Description |
|
|
87
|
+
| --- | --- | --- | --- |
|
|
88
|
+
| `gtmId` | `string` | `undefined` | Google Tag Manager ID (e.g. `'GTM-XXXXXX'`). Takes precedence over `ga4Id`. |
|
|
89
|
+
| `ga4Id` | `string` | `undefined` | Google Analytics 4 Measurement ID (e.g. `'G-XXXXXX'`). |
|
|
90
|
+
| `metaPixelId` | `string` | `undefined` | Meta Pixel ID (numeric string, e.g. `'1234567890'`). |
|
|
91
|
+
| `clarityId` | `string` | `undefined` | Microsoft Clarity Project ID (alphanumeric string). |
|
|
92
|
+
| `mode` | `'basic' \| 'advanced'` | `'basic'` | Tag loading strategy. In `basic`, tags are blocked until consent. In `advanced`, Google and Clarity load immediately with default `denied`. |
|
|
93
|
+
| `lang` | `'pl' \| 'en'` | `'pl'` | Default language for cookie consent modal. |
|
|
94
|
+
| `privacyUrl` | `string` | `'/polityka-prywatnosci/'` (pl) or `'/privacy-policy/'` (en) | Target URL for privacy policy link. |
|
|
95
|
+
| `texts` | `object` | `undefined` | DeepPartial object overriding default modal translations. |
|
|
96
|
+
| `banner` | `string` | `'box-left'` | Banner layout preset - see [Banner layouts](#banner-layouts). |
|
|
97
|
+
| `overlay` | `boolean` | `true` for `'modal'`, otherwise `false` | Dim the page and block interaction until the visitor decides. |
|
|
98
|
+
| `gui` | `object` | `{}` | Low-level vanilla-cookieconsent `guiOptions`, merged on top of the `banner` preset. |
|
|
99
|
+
| `revision` | `number` | `0` | Revision counter. Incrementing invalidates stored cookies and re-prompts visitors. |
|
|
100
|
+
| `urlPassthrough` | `boolean` | `true` | Enables `url_passthrough` in Google tag to retain ad click info across pages without cookies. |
|
|
101
|
+
| `adsDataRedaction` | `boolean` | `true` | Enables `ads_data_redaction` in Google tag when ad consent is denied. |
|
|
102
|
+
| `waitForUpdate` | `number` | `500` | Milliseconds Google tag waits for consent update before firing. |
|
|
103
|
+
| `enabled` | `boolean` | `true` (when any ID is provided) | Explicit toggle to enable or disable the integration completely. |
|
|
104
|
+
|
|
105
|
+
## How it works
|
|
106
|
+
|
|
107
|
+
### Lifecycle & Loading Flow
|
|
108
|
+
|
|
109
|
+
1. **Default Denied (`<head>`)**:
|
|
110
|
+
Before any tags load, an inline script in `<head>` sets Google Consent Mode v2 default signals to `denied` for all storage categories (`ad_storage`, `ad_user_data`, `ad_personalization`, `analytics_storage`).
|
|
111
|
+
2. **Synchronous Cookie Check**:
|
|
112
|
+
The script checks `document.cookie` for an existing `cc_cookie`. If valid consent is found (matching current `revision`), consent signals are updated immediately and granted tags begin loading without waiting for the client bundle.
|
|
113
|
+
3. **Cookie Banner Evaluation**:
|
|
114
|
+
If no consent is recorded, the cookie banner displays on the client.
|
|
115
|
+
4. **Consent Update & Dynamic Tag Dispatch**:
|
|
116
|
+
When the user grants or revokes consent via `onConsent` or `onChange`, `window.__acm.apply(categories)` updates Google signals, loads allowed scripts idempotently, and updates Meta Pixel and Microsoft Clarity consent states.
|
|
117
|
+
|
|
118
|
+
### Service Loading Matrix
|
|
119
|
+
|
|
120
|
+
| Service | Category | `basic` Mode (Before Consent) | `basic` Mode (After Consent) | `advanced` Mode (Before Consent) | `advanced` Mode (After Consent) |
|
|
121
|
+
| --- | --- | --- | --- | --- | --- |
|
|
122
|
+
| Google Tag Manager | Analytics / Marketing | Blocked | Loaded upon `analytics` or `marketing` consent | Loaded immediately (denied signals) | Signals updated to granted |
|
|
123
|
+
| Google Analytics 4 | Analytics | Blocked | Loaded upon `analytics` consent | Loaded immediately (denied signals) | Signals updated to granted |
|
|
124
|
+
| Meta Pixel | Marketing | Blocked | Loaded upon `marketing` consent | Blocked (always requires marketing consent) | Loaded upon `marketing` consent |
|
|
125
|
+
| Microsoft Clarity | Analytics | Blocked | Loaded upon `analytics` consent | Loaded immediately (`consentv2` denied) | `consentv2` updated to granted |
|
|
126
|
+
|
|
127
|
+
## Opening Preferences
|
|
128
|
+
|
|
129
|
+
Visitors can reopen the cookie preferences modal anytime by clicking any HTML element that includes `data-cc="show-preferencesModal"`:
|
|
130
|
+
|
|
131
|
+
```html
|
|
132
|
+
<button type="button" data-cc="show-preferencesModal">
|
|
133
|
+
Cookie settings
|
|
134
|
+
</button>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
You can also open it programmatically in your client scripts:
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
import { showPreferences } from '@krysmalskipl/astro-consent-mode/client';
|
|
141
|
+
|
|
142
|
+
showPreferences();
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Banner layouts
|
|
146
|
+
|
|
147
|
+
Choose a ready-made layout using the `banner` option:
|
|
148
|
+
|
|
149
|
+
| Preset | Description |
|
|
150
|
+
|---|---|
|
|
151
|
+
| `'box-left'` *(default)* | Compact card in the bottom-left corner |
|
|
152
|
+
| `'box-right'` | Compact card in the bottom-right corner |
|
|
153
|
+
| `'box-center'` | Compact card centered at the bottom |
|
|
154
|
+
| `'box-wide'` | Wider card in the bottom-left corner |
|
|
155
|
+
| `'cloud'` | Floating card centered at the bottom, buttons aligned beside the text |
|
|
156
|
+
| `'bar-bottom'` | Full-width bar anchored to the bottom edge |
|
|
157
|
+
| `'bar-top'` | Full-width bar anchored to the top edge |
|
|
158
|
+
| `'modal'` | Centered modal window with a dimmed page backdrop (sets `overlay: true` automatically) |
|
|
159
|
+
|
|
160
|
+
You can also dim the page background and prevent page interactions for any layout by setting `overlay: true`:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
consent({
|
|
164
|
+
gtmId: 'GTM-XXXXXX',
|
|
165
|
+
banner: 'box-right',
|
|
166
|
+
overlay: true,
|
|
167
|
+
})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Styling
|
|
171
|
+
|
|
172
|
+
The banner is styled using vanilla-cookieconsent v3 CSS variables. You can override colors, fonts, and border radius in your global CSS stylesheet:
|
|
173
|
+
|
|
174
|
+
```css
|
|
175
|
+
:root {
|
|
176
|
+
--cc-font-family: inherit;
|
|
177
|
+
--cc-btn-primary-bg: #2563eb;
|
|
178
|
+
--cc-btn-primary-hover-bg: #1d4ed8;
|
|
179
|
+
--cc-btn-primary-color: #ffffff;
|
|
180
|
+
--cc-btn-secondary-bg: #f1f5f9;
|
|
181
|
+
--cc-btn-secondary-hover-bg: #e2e8f0;
|
|
182
|
+
--cc-btn-secondary-color: #0f172a;
|
|
183
|
+
--cc-modal-border-radius: 1rem;
|
|
184
|
+
--cc-btn-border-radius: 0.5rem;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Translations
|
|
189
|
+
|
|
190
|
+
Built-in translations support Polish (`pl`) and English (`en`). You can override specific labels or add additional languages via the `texts` option:
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
consent({
|
|
194
|
+
lang: 'pl',
|
|
195
|
+
texts: {
|
|
196
|
+
consentModal: {
|
|
197
|
+
title: 'Dbamy o Twoją prywatność',
|
|
198
|
+
acceptAllBtn: 'Zezwól na wszystkie',
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Testing with Google Tag Assistant
|
|
205
|
+
|
|
206
|
+
To verify that Google Consent Mode v2 is functioning correctly:
|
|
207
|
+
|
|
208
|
+
1. Open [Google Tag Assistant](https://tagassistant.google.com/) and connect your site.
|
|
209
|
+
2. Check the **Consent** tab: you should see `Consent Default` with all storage types `denied` right after page load.
|
|
210
|
+
3. Accept cookies in the banner: Tag Assistant will record a `Consent Update` event with updated `granted` values.
|
|
211
|
+
|
|
212
|
+
## Limitations
|
|
213
|
+
|
|
214
|
+
- **Not a Google-certified CMP**: This integration provides Google Consent Mode v2 for standard tracking setups (GA4, Google Ads, Meta Pixel, Clarity). However, publishers serving ads via Google AdSense, Google Ad Manager, or AdMob in the EEA, UK, or Switzerland must use a Google-certified Consent Management Platform supporting TCF v2.2.
|
|
215
|
+
- **Legal Disclaimer**: This software is provided as a technical solution and does not constitute legal advice. Ensure your privacy policy and cookie categories comply with local regulations (such as GDPR and ePrivacy).
|
|
216
|
+
|
|
217
|
+
## Po polsku
|
|
218
|
+
|
|
219
|
+
Integracja Astro dla Google Consent Mode v2 z gotowym banerem cookies, obsługą GTM, GA4, Meta Pixel oraz Microsoft Clarity. Działa od razu po instalacji z domyślnym językiem polskim i automatycznym czyszczeniem ciasteczek po wycofaniu zgody. Więcej o nowoczesnych stronach na Astro: [krysmalski.pl](https://krysmalski.pl).
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT - Copyright (c) 2026 Krysmalski.
|
|
224
|
+
|
|
225
|
+
## Author
|
|
226
|
+
|
|
227
|
+
Built and maintained by [Krysmalski](https://krysmalski.pl) - websites and Astro development.
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import css from "vanilla-cookieconsent/dist/cookieconsent.css?inline";
|
|
3
|
+
import * as CookieConsent from "vanilla-cookieconsent";
|
|
4
|
+
import config from "virtual:astro-consent-mode/config";
|
|
5
|
+
if (typeof document !== "undefined" && !document.getElementById("acm-style")) {
|
|
6
|
+
const style = document.createElement("style");
|
|
7
|
+
style.id = "acm-style";
|
|
8
|
+
style.textContent = css;
|
|
9
|
+
document.head.prepend(style);
|
|
10
|
+
}
|
|
11
|
+
function showPreferences2() {
|
|
12
|
+
if (typeof window !== "undefined") {
|
|
13
|
+
CookieConsent.showPreferences();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
var categories = {
|
|
17
|
+
necessary: {
|
|
18
|
+
enabled: true,
|
|
19
|
+
readOnly: true
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
if (config.hasAnalytics) {
|
|
23
|
+
categories.analytics = {
|
|
24
|
+
autoClear: {
|
|
25
|
+
cookies: [
|
|
26
|
+
{ name: /^_ga/ },
|
|
27
|
+
{ name: "_gid" },
|
|
28
|
+
{ name: /^_gat/ },
|
|
29
|
+
{ name: /^_clck/ },
|
|
30
|
+
{ name: /^_clsk/ },
|
|
31
|
+
{ name: "CLID" }
|
|
32
|
+
],
|
|
33
|
+
reloadPage: true
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (config.hasMarketing) {
|
|
38
|
+
categories.marketing = {
|
|
39
|
+
autoClear: {
|
|
40
|
+
cookies: [
|
|
41
|
+
{ name: /^_gcl/ },
|
|
42
|
+
{ name: /^_fbp/ },
|
|
43
|
+
{ name: /^_fbc/ }
|
|
44
|
+
],
|
|
45
|
+
reloadPage: true
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
var syncConsent = () => {
|
|
50
|
+
const preferences = CookieConsent.getUserPreferences();
|
|
51
|
+
const acceptedCategories = preferences.acceptedCategories || [];
|
|
52
|
+
window.__acm?.apply(acceptedCategories);
|
|
53
|
+
};
|
|
54
|
+
if (typeof window !== "undefined") {
|
|
55
|
+
CookieConsent.run({
|
|
56
|
+
revision: config.revision,
|
|
57
|
+
// Resolved at build time from the `banner` preset merged with `gui`.
|
|
58
|
+
guiOptions: config.gui,
|
|
59
|
+
disablePageInteraction: config.overlay,
|
|
60
|
+
categories,
|
|
61
|
+
language: {
|
|
62
|
+
default: config.lang,
|
|
63
|
+
translations: config.translations
|
|
64
|
+
},
|
|
65
|
+
onConsent: syncConsent,
|
|
66
|
+
onChange: syncConsent
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
showPreferences2 as showPreferences
|
|
71
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { AstroIntegration } from 'astro';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recursive partial utility type.
|
|
5
|
+
*/
|
|
6
|
+
type DeepPartial<T> = {
|
|
7
|
+
[P in keyof T]?: T[P] extends (infer U)[] ? DeepPartial<U>[] : T[P] extends readonly (infer U)[] ? readonly DeepPartial<U>[] : T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Text configuration for the initial consent modal banner.
|
|
11
|
+
*/
|
|
12
|
+
interface ConsentModalTranslation {
|
|
13
|
+
/** Title of the consent modal */
|
|
14
|
+
title?: string;
|
|
15
|
+
/** Main description explaining cookie usage */
|
|
16
|
+
description?: string;
|
|
17
|
+
/** Label for the "Accept all" button */
|
|
18
|
+
acceptAllBtn?: string;
|
|
19
|
+
/** Label for the "Reject non-essential / Necessary only" button */
|
|
20
|
+
acceptNecessaryBtn?: string;
|
|
21
|
+
/** Label for the "Preferences / Settings" button */
|
|
22
|
+
showPreferencesBtn?: string;
|
|
23
|
+
/** HTML footer content, e.g. link to Privacy Policy */
|
|
24
|
+
footer?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Section item in the cookie preferences modal.
|
|
28
|
+
*/
|
|
29
|
+
interface PreferencesSection {
|
|
30
|
+
/** Optional title of the preferences category/section */
|
|
31
|
+
title?: string;
|
|
32
|
+
/** Informational description of the category or policy */
|
|
33
|
+
description?: string;
|
|
34
|
+
/** Linked cookie consent category (e.g. 'necessary', 'analytics', 'marketing') */
|
|
35
|
+
linkedCategory?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Text configuration for the detailed cookie preferences modal.
|
|
39
|
+
*/
|
|
40
|
+
interface PreferencesModalTranslation {
|
|
41
|
+
/** Title of the preferences modal */
|
|
42
|
+
title?: string;
|
|
43
|
+
/** Label for the "Accept all" button inside preferences */
|
|
44
|
+
acceptAllBtn?: string;
|
|
45
|
+
/** Label for the "Necessary only" button inside preferences */
|
|
46
|
+
acceptNecessaryBtn?: string;
|
|
47
|
+
/** Label for the "Save choices" button inside preferences */
|
|
48
|
+
savePreferencesBtn?: string;
|
|
49
|
+
/** Accessibility label for the close icon */
|
|
50
|
+
closeIconLabel?: string;
|
|
51
|
+
/** Array of informational and category sections */
|
|
52
|
+
sections?: PreferencesSection[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Complete set of translations for a single language.
|
|
56
|
+
*/
|
|
57
|
+
interface Translation {
|
|
58
|
+
/** Texts for the primary cookie consent modal */
|
|
59
|
+
consentModal: ConsentModalTranslation;
|
|
60
|
+
/** Texts for the cookie preferences modal */
|
|
61
|
+
preferencesModal: PreferencesModalTranslation;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Modal layout options for vanilla-cookieconsent.
|
|
65
|
+
*/
|
|
66
|
+
interface ModalGuiOptions {
|
|
67
|
+
/** Layout style, e.g. 'box', 'bar', 'cloud' */
|
|
68
|
+
layout?: string;
|
|
69
|
+
/** Modal screen position, e.g. 'bottom left', 'bottom right', 'middle center' */
|
|
70
|
+
position?: string;
|
|
71
|
+
/** Give all primary action buttons equal visual weight */
|
|
72
|
+
equalWeightButtons?: boolean;
|
|
73
|
+
/** Invert button order */
|
|
74
|
+
flipButtons?: boolean;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* GUI configuration options for vanilla-cookieconsent modals.
|
|
78
|
+
*/
|
|
79
|
+
interface GuiOptions {
|
|
80
|
+
/** Configuration for the primary consent banner */
|
|
81
|
+
consentModal?: ModalGuiOptions;
|
|
82
|
+
/** Configuration for the preferences management modal */
|
|
83
|
+
preferencesModal?: ModalGuiOptions;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Ready-made banner layouts. See `ConsentOptions.banner`.
|
|
87
|
+
*/
|
|
88
|
+
type BannerPreset = 'box-left' | 'box-right' | 'box-center' | 'box-wide' | 'cloud' | 'bar-bottom' | 'bar-top' | 'modal';
|
|
89
|
+
/**
|
|
90
|
+
* Options for configuring @krysmalskipl/astro-consent-mode integration.
|
|
91
|
+
*/
|
|
92
|
+
interface ConsentOptions {
|
|
93
|
+
/**
|
|
94
|
+
* Google Tag Manager Container ID (e.g. 'GTM-XXXXXX').
|
|
95
|
+
* When provided, GTM handles tag deployment.
|
|
96
|
+
*/
|
|
97
|
+
gtmId?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Google Analytics 4 Measurement ID (e.g. 'G-XXXXXX').
|
|
100
|
+
* Used only when gtmId is not specified.
|
|
101
|
+
*/
|
|
102
|
+
ga4Id?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Meta Pixel (Facebook Pixel) ID (numeric string, e.g. '123456789012345').
|
|
105
|
+
*/
|
|
106
|
+
metaPixelId?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Microsoft Clarity Project ID (alphanumeric string, e.g. 'abcdef1234').
|
|
109
|
+
*/
|
|
110
|
+
clarityId?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Consent Mode loading strategy:
|
|
113
|
+
* - 'basic': All tracking tags are blocked until the visitor grants consent.
|
|
114
|
+
* - 'advanced': GTM/GA4 and Clarity load immediately with default 'denied' consent signals.
|
|
115
|
+
* Defaults to 'basic'.
|
|
116
|
+
*/
|
|
117
|
+
mode?: 'basic' | 'advanced';
|
|
118
|
+
/**
|
|
119
|
+
* Default language for cookie consent text ('pl' | 'en').
|
|
120
|
+
* Defaults to 'pl'.
|
|
121
|
+
*/
|
|
122
|
+
lang?: 'pl' | 'en';
|
|
123
|
+
/**
|
|
124
|
+
* URL to your privacy policy page.
|
|
125
|
+
* Defaults to '/polityka-prywatnosci/' for 'pl', or '/privacy-policy/' for 'en'.
|
|
126
|
+
*/
|
|
127
|
+
privacyUrl?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Custom text overrides for translations.
|
|
130
|
+
* Can be provided as a language map (e.g. { pl: { consentModal: { ... } } })
|
|
131
|
+
* or directly for the active language (e.g. { consentModal: { ... } }).
|
|
132
|
+
*/
|
|
133
|
+
texts?: DeepPartial<Record<string, Translation>> | DeepPartial<Translation>;
|
|
134
|
+
/**
|
|
135
|
+
* Banner layout preset:
|
|
136
|
+
* - 'box-left' (default): compact card in the bottom left corner
|
|
137
|
+
* - 'box-right': compact card in the bottom right corner
|
|
138
|
+
* - 'box-center': compact card at the bottom center
|
|
139
|
+
* - 'box-wide': wider card in the bottom left corner
|
|
140
|
+
* - 'cloud': wide floating card at the bottom center, buttons next to the text
|
|
141
|
+
* - 'bar-bottom': full-width bar at the bottom of the page
|
|
142
|
+
* - 'bar-top': full-width bar at the top of the page
|
|
143
|
+
* - 'modal': centered dialog with a dimmed page overlay (implies `overlay: true`)
|
|
144
|
+
*/
|
|
145
|
+
banner?: BannerPreset;
|
|
146
|
+
/**
|
|
147
|
+
* Dim the page and block interaction until the visitor makes a choice.
|
|
148
|
+
* Defaults to true for the 'modal' preset, false otherwise.
|
|
149
|
+
*/
|
|
150
|
+
overlay?: boolean;
|
|
151
|
+
/**
|
|
152
|
+
* Low-level UI configuration for consent and preferences modals.
|
|
153
|
+
* Merged on top of the `banner` preset, so you can fine-tune any of its values.
|
|
154
|
+
*/
|
|
155
|
+
gui?: GuiOptions;
|
|
156
|
+
/**
|
|
157
|
+
* Revision number of the consent configuration.
|
|
158
|
+
* Incrementing this number invalidates past user consents and re-prompts visitors.
|
|
159
|
+
* Defaults to 0.
|
|
160
|
+
*/
|
|
161
|
+
revision?: number;
|
|
162
|
+
/**
|
|
163
|
+
* Whether to enable Google Tag url_passthrough to preserve ad click identifiers across pages.
|
|
164
|
+
* Defaults to true.
|
|
165
|
+
*/
|
|
166
|
+
urlPassthrough?: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* Whether to enable Google Tag ads_data_redaction when ad storage consent is denied.
|
|
169
|
+
* Defaults to true.
|
|
170
|
+
*/
|
|
171
|
+
adsDataRedaction?: boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Milliseconds Google Consent Mode waits for an updated consent state before firing tags.
|
|
174
|
+
* Defaults to 500.
|
|
175
|
+
*/
|
|
176
|
+
waitForUpdate?: number;
|
|
177
|
+
/**
|
|
178
|
+
* Explicitly enable or disable the integration.
|
|
179
|
+
* Defaults to true when any tracking ID is present; false completely disables scripts.
|
|
180
|
+
*/
|
|
181
|
+
enabled?: boolean;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Fully resolved integration options.
|
|
185
|
+
*/
|
|
186
|
+
interface ResolvedConsentOptions {
|
|
187
|
+
gtmId?: string;
|
|
188
|
+
ga4Id?: string;
|
|
189
|
+
metaPixelId?: string;
|
|
190
|
+
clarityId?: string;
|
|
191
|
+
mode: 'basic' | 'advanced';
|
|
192
|
+
lang: 'pl' | 'en';
|
|
193
|
+
privacyUrl: string;
|
|
194
|
+
texts?: DeepPartial<Record<string, Translation>> | DeepPartial<Translation>;
|
|
195
|
+
gui: GuiOptions;
|
|
196
|
+
revision: number;
|
|
197
|
+
urlPassthrough: boolean;
|
|
198
|
+
adsDataRedaction: boolean;
|
|
199
|
+
waitForUpdate: number;
|
|
200
|
+
enabled: boolean;
|
|
201
|
+
hasAnalytics: boolean;
|
|
202
|
+
hasMarketing: boolean;
|
|
203
|
+
analyticsServices: string[];
|
|
204
|
+
marketingServices: string[];
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Client runtime configuration passed via virtual module.
|
|
208
|
+
*/
|
|
209
|
+
interface ClientConfig {
|
|
210
|
+
hasAnalytics: boolean;
|
|
211
|
+
hasMarketing: boolean;
|
|
212
|
+
analyticsServices: string[];
|
|
213
|
+
marketingServices: string[];
|
|
214
|
+
lang: 'pl' | 'en';
|
|
215
|
+
privacyUrl: string;
|
|
216
|
+
translations: Record<string, Translation>;
|
|
217
|
+
gui: GuiOptions;
|
|
218
|
+
overlay: boolean;
|
|
219
|
+
revision: number;
|
|
220
|
+
mode: 'basic' | 'advanced';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Astro integration for Google Consent Mode v2 + cookie consent banner + GTM / GA4 / Meta Pixel / Clarity.
|
|
225
|
+
*
|
|
226
|
+
* @param options Integration options
|
|
227
|
+
* @returns Astro integration definition
|
|
228
|
+
*/
|
|
229
|
+
declare function consent(options?: ConsentOptions): AstroIntegration;
|
|
230
|
+
|
|
231
|
+
export { type BannerPreset, type ClientConfig, type ConsentModalTranslation, type ConsentOptions, type DeepPartial, type GuiOptions, type ModalGuiOptions, type PreferencesModalTranslation, type PreferencesSection, type ResolvedConsentOptions, type Translation, consent, consent as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
// src/head.ts
|
|
2
|
+
function buildHeadScript(options) {
|
|
3
|
+
const {
|
|
4
|
+
gtmId = "",
|
|
5
|
+
ga4Id = "",
|
|
6
|
+
metaPixelId = "",
|
|
7
|
+
clarityId = "",
|
|
8
|
+
mode,
|
|
9
|
+
revision,
|
|
10
|
+
waitForUpdate,
|
|
11
|
+
adsDataRedaction,
|
|
12
|
+
urlPassthrough
|
|
13
|
+
} = options;
|
|
14
|
+
const hasGoogle = Boolean(gtmId || ga4Id);
|
|
15
|
+
const hasMeta = Boolean(metaPixelId);
|
|
16
|
+
const hasClarity = Boolean(clarityId);
|
|
17
|
+
return `(function(){
|
|
18
|
+
var mode=${JSON.stringify(mode)};
|
|
19
|
+
var gtmId=${JSON.stringify(gtmId)};
|
|
20
|
+
var ga4Id=${JSON.stringify(ga4Id)};
|
|
21
|
+
var metaPixelId=${JSON.stringify(metaPixelId)};
|
|
22
|
+
var clarityId=${JSON.stringify(clarityId)};
|
|
23
|
+
var rev=${revision};
|
|
24
|
+
var gLoaded=false;
|
|
25
|
+
var metaLoaded=false;
|
|
26
|
+
var clLoaded=false;
|
|
27
|
+
|
|
28
|
+
function getHead(){return document.head||document.getElementsByTagName('head')[0]||document.documentElement;}
|
|
29
|
+
|
|
30
|
+
function loadGoogle(){
|
|
31
|
+
if(gLoaded)return;
|
|
32
|
+
gLoaded=true;
|
|
33
|
+
var s=document.createElement('script');
|
|
34
|
+
s.async=true;
|
|
35
|
+
if(gtmId){
|
|
36
|
+
window.dataLayer.push({'gtm.start':Date.now(),event:'gtm.js'});
|
|
37
|
+
s.src='https://www.googletagmanager.com/gtm.js?id='+gtmId;
|
|
38
|
+
}else if(ga4Id){
|
|
39
|
+
s.src='https://www.googletagmanager.com/gtag/js?id='+ga4Id;
|
|
40
|
+
window.gtag('js',new Date());
|
|
41
|
+
window.gtag('config',ga4Id);
|
|
42
|
+
}
|
|
43
|
+
getHead().appendChild(s);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function loadMeta(){
|
|
47
|
+
if(metaLoaded||!metaPixelId)return;
|
|
48
|
+
metaLoaded=true;
|
|
49
|
+
(function(f,b,e,v,n,t,s){
|
|
50
|
+
if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
|
51
|
+
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=true;n.version='2.0';n.queue=[];
|
|
52
|
+
t=b.createElement(e);t.async=true;t.src=v;
|
|
53
|
+
s=getHead();s.appendChild(t);
|
|
54
|
+
})(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');
|
|
55
|
+
window.fbq('init',metaPixelId);
|
|
56
|
+
window.fbq('track','PageView');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function loadClarity(){
|
|
60
|
+
if(clLoaded||!clarityId)return;
|
|
61
|
+
clLoaded=true;
|
|
62
|
+
(function(c,l,a,r,i,t,y){
|
|
63
|
+
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
|
|
64
|
+
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
|
|
65
|
+
y=getHead();y.appendChild(t);
|
|
66
|
+
})(window,document,"clarity","script",clarityId);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
${hasGoogle ? `window.dataLayer=window.dataLayer||[];
|
|
70
|
+
window.gtag=function(){window.dataLayer.push(arguments);};
|
|
71
|
+
window.gtag('consent','default',{
|
|
72
|
+
ad_storage:'denied',
|
|
73
|
+
ad_user_data:'denied',
|
|
74
|
+
ad_personalization:'denied',
|
|
75
|
+
analytics_storage:'denied',
|
|
76
|
+
personalization_storage:'denied',
|
|
77
|
+
functionality_storage:'granted',
|
|
78
|
+
security_storage:'granted',
|
|
79
|
+
wait_for_update:${waitForUpdate}
|
|
80
|
+
});
|
|
81
|
+
${adsDataRedaction ? `window.gtag('set','ads_data_redaction',true);` : ""}
|
|
82
|
+
${urlPassthrough ? `window.gtag('set','url_passthrough',true);` : ""}` : ""}
|
|
83
|
+
|
|
84
|
+
window.__acm={
|
|
85
|
+
apply:function(cats){
|
|
86
|
+
cats=cats||[];
|
|
87
|
+
var an=cats.indexOf('analytics')!==-1;
|
|
88
|
+
var mk=cats.indexOf('marketing')!==-1;
|
|
89
|
+
|
|
90
|
+
${hasGoogle ? `if(window.gtag){
|
|
91
|
+
window.gtag('consent','update',{
|
|
92
|
+
analytics_storage:an?'granted':'denied',
|
|
93
|
+
ad_storage:mk?'granted':'denied',
|
|
94
|
+
ad_user_data:mk?'granted':'denied',
|
|
95
|
+
ad_personalization:mk?'granted':'denied',
|
|
96
|
+
personalization_storage:mk?'granted':'denied'
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if(mode==='advanced'||an||mk){
|
|
100
|
+
loadGoogle();
|
|
101
|
+
}` : ""}
|
|
102
|
+
|
|
103
|
+
${hasMeta ? `if(mk){
|
|
104
|
+
if(!metaLoaded){loadMeta();}
|
|
105
|
+
else if(window.fbq){window.fbq('consent','grant');}
|
|
106
|
+
}else if(metaLoaded&&window.fbq){
|
|
107
|
+
window.fbq('consent','revoke');
|
|
108
|
+
}` : ""}
|
|
109
|
+
|
|
110
|
+
${hasClarity ? `var adState=mk?'granted':'denied';
|
|
111
|
+
var anState=an?'granted':'denied';
|
|
112
|
+
if(mode==='advanced'){
|
|
113
|
+
if(!clLoaded)loadClarity();
|
|
114
|
+
if(window.clarity){window.clarity('consentv2',{ad_Storage:adState,analytics_Storage:anState});}
|
|
115
|
+
}else{
|
|
116
|
+
if(an){
|
|
117
|
+
if(!clLoaded)loadClarity();
|
|
118
|
+
if(window.clarity){window.clarity('consentv2',{ad_Storage:adState,analytics_Storage:'granted'});}
|
|
119
|
+
}else if(clLoaded&&window.clarity){
|
|
120
|
+
window.clarity('consentv2',{ad_Storage:'denied',analytics_Storage:'denied'});
|
|
121
|
+
}
|
|
122
|
+
}` : ""}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
var savedCats=null;
|
|
127
|
+
try{
|
|
128
|
+
var m=document.cookie.match(/(?:^|; *)cc_cookie=([^;]+)/);
|
|
129
|
+
if(m){
|
|
130
|
+
var parsed=JSON.parse(decodeURIComponent(m[1]));
|
|
131
|
+
var sRev=typeof parsed.revision==='number'?parsed.revision:0;
|
|
132
|
+
if(sRev===rev&&Array.isArray(parsed.categories)){
|
|
133
|
+
savedCats=parsed.categories;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}catch(e){}
|
|
137
|
+
|
|
138
|
+
if(savedCats){
|
|
139
|
+
window.__acm.apply(savedCats);
|
|
140
|
+
}else if(mode==='advanced'){
|
|
141
|
+
${hasGoogle ? `loadGoogle();` : ""}
|
|
142
|
+
${hasClarity ? `loadClarity();
|
|
143
|
+
if(window.clarity){window.clarity('consentv2',{ad_Storage:'denied',analytics_Storage:'denied'});}` : ""}
|
|
144
|
+
}
|
|
145
|
+
})();`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/translations.ts
|
|
149
|
+
function isObject(item) {
|
|
150
|
+
return item !== null && typeof item === "object" && !Array.isArray(item);
|
|
151
|
+
}
|
|
152
|
+
function deepMerge(target, source) {
|
|
153
|
+
if (!source || typeof source !== "object") return target;
|
|
154
|
+
const output = { ...target };
|
|
155
|
+
const sourceRecord = source;
|
|
156
|
+
for (const key of Object.keys(sourceRecord)) {
|
|
157
|
+
const sourceVal = sourceRecord[key];
|
|
158
|
+
const targetVal = output[key];
|
|
159
|
+
if (sourceVal === void 0) continue;
|
|
160
|
+
if (isObject(sourceVal) && isObject(targetVal)) {
|
|
161
|
+
output[key] = deepMerge(targetVal, sourceVal);
|
|
162
|
+
} else {
|
|
163
|
+
output[key] = sourceVal;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return output;
|
|
167
|
+
}
|
|
168
|
+
function buildDefaultTranslations(params) {
|
|
169
|
+
const {
|
|
170
|
+
privacyUrl,
|
|
171
|
+
hasAnalytics,
|
|
172
|
+
hasMarketing,
|
|
173
|
+
analyticsServices,
|
|
174
|
+
marketingServices
|
|
175
|
+
} = params;
|
|
176
|
+
const plAnalyticsSuffix = analyticsServices.length > 0 ? ` (np. ${analyticsServices.join(", ")})` : "";
|
|
177
|
+
const plMarketingSuffix = marketingServices.length > 0 ? ` (np. ${marketingServices.join(", ")})` : "";
|
|
178
|
+
const enAnalyticsSuffix = analyticsServices.length > 0 ? ` (e.g. ${analyticsServices.join(", ")})` : "";
|
|
179
|
+
const enMarketingSuffix = marketingServices.length > 0 ? ` (e.g. ${marketingServices.join(", ")})` : "";
|
|
180
|
+
const plSections = [
|
|
181
|
+
{
|
|
182
|
+
description: `Mo\u017Cesz w ka\u017Cdej chwili zmieni\u0107 swoje wybory. Szczeg\xF3\u0142y znajdziesz w <a href="${privacyUrl}">polityce prywatno\u015Bci</a>.`
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
title: "Niezb\u0119dne",
|
|
186
|
+
description: "Wymagane do dzia\u0142ania strony, np. zapami\u0119tanie Twoich wybor\xF3w dotycz\u0105cych cookies.",
|
|
187
|
+
linkedCategory: "necessary"
|
|
188
|
+
}
|
|
189
|
+
];
|
|
190
|
+
if (hasAnalytics) {
|
|
191
|
+
plSections.push({
|
|
192
|
+
title: "Analityczne",
|
|
193
|
+
description: `Pomagaj\u0105 nam zrozumie\u0107, jak korzystasz ze strony${plAnalyticsSuffix}.`,
|
|
194
|
+
linkedCategory: "analytics"
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (hasMarketing) {
|
|
198
|
+
plSections.push({
|
|
199
|
+
title: "Marketingowe",
|
|
200
|
+
description: `S\u0142u\u017C\u0105 do mierzenia skuteczno\u015Bci reklam i dopasowania ich do Twoich zainteresowa\u0144${plMarketingSuffix}.`,
|
|
201
|
+
linkedCategory: "marketing"
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const enSections = [
|
|
205
|
+
{
|
|
206
|
+
description: `You can change your choices at any time. For more details, see our <a href="${privacyUrl}">privacy policy</a>.`
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
title: "Necessary",
|
|
210
|
+
description: "Required for the site to function properly, e.g. remembering your cookie choices.",
|
|
211
|
+
linkedCategory: "necessary"
|
|
212
|
+
}
|
|
213
|
+
];
|
|
214
|
+
if (hasAnalytics) {
|
|
215
|
+
enSections.push({
|
|
216
|
+
title: "Analytics",
|
|
217
|
+
description: `Help us understand how you use the website${enAnalyticsSuffix}.`,
|
|
218
|
+
linkedCategory: "analytics"
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (hasMarketing) {
|
|
222
|
+
enSections.push({
|
|
223
|
+
title: "Marketing",
|
|
224
|
+
description: `Used to measure advertising effectiveness and tailor ads to your interests${enMarketingSuffix}.`,
|
|
225
|
+
linkedCategory: "marketing"
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
const pl = {
|
|
229
|
+
consentModal: {
|
|
230
|
+
title: "Szanujemy Twoj\u0105 prywatno\u015B\u0107",
|
|
231
|
+
description: "U\u017Cywamy plik\xF3w cookies, aby analizowa\u0107 ruch i ulepsza\u0107 stron\u0119. Cookies analityczne i marketingowe w\u0142\u0105czymy tylko za Twoj\u0105 zgod\u0105.",
|
|
232
|
+
acceptAllBtn: "Akceptuj\u0119 wszystkie",
|
|
233
|
+
acceptNecessaryBtn: "Tylko niezb\u0119dne",
|
|
234
|
+
showPreferencesBtn: "Ustawienia",
|
|
235
|
+
footer: `<a href="${privacyUrl}">Polityka prywatno\u015Bci</a>`
|
|
236
|
+
},
|
|
237
|
+
preferencesModal: {
|
|
238
|
+
title: "Ustawienia cookies",
|
|
239
|
+
acceptAllBtn: "Akceptuj\u0119 wszystkie",
|
|
240
|
+
acceptNecessaryBtn: "Tylko niezb\u0119dne",
|
|
241
|
+
savePreferencesBtn: "Zapisz wyb\xF3r",
|
|
242
|
+
closeIconLabel: "Zamknij",
|
|
243
|
+
sections: plSections
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
const en = {
|
|
247
|
+
consentModal: {
|
|
248
|
+
title: "We value your privacy",
|
|
249
|
+
description: "We use cookies to analyze traffic and improve the website. Analytics and marketing cookies will only be activated with your consent.",
|
|
250
|
+
acceptAllBtn: "Accept all",
|
|
251
|
+
acceptNecessaryBtn: "Necessary only",
|
|
252
|
+
showPreferencesBtn: "Preferences",
|
|
253
|
+
footer: `<a href="${privacyUrl}">Privacy policy</a>`
|
|
254
|
+
},
|
|
255
|
+
preferencesModal: {
|
|
256
|
+
title: "Cookie preferences",
|
|
257
|
+
acceptAllBtn: "Accept all",
|
|
258
|
+
acceptNecessaryBtn: "Necessary only",
|
|
259
|
+
savePreferencesBtn: "Save preferences",
|
|
260
|
+
closeIconLabel: "Close",
|
|
261
|
+
sections: enSections
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
return { pl, en };
|
|
265
|
+
}
|
|
266
|
+
function resolveTranslations(params, defaultLang, customTexts) {
|
|
267
|
+
const translations = buildDefaultTranslations(params);
|
|
268
|
+
if (!customTexts) {
|
|
269
|
+
return translations;
|
|
270
|
+
}
|
|
271
|
+
const textsRecord = customTexts;
|
|
272
|
+
if ("consentModal" in textsRecord || "preferencesModal" in textsRecord) {
|
|
273
|
+
translations[defaultLang] = deepMerge(
|
|
274
|
+
translations[defaultLang],
|
|
275
|
+
textsRecord
|
|
276
|
+
);
|
|
277
|
+
return translations;
|
|
278
|
+
}
|
|
279
|
+
for (const [langKey, langValue] of Object.entries(textsRecord)) {
|
|
280
|
+
if (!langValue || typeof langValue !== "object") continue;
|
|
281
|
+
if (translations[langKey]) {
|
|
282
|
+
translations[langKey] = deepMerge(
|
|
283
|
+
translations[langKey],
|
|
284
|
+
langValue
|
|
285
|
+
);
|
|
286
|
+
} else {
|
|
287
|
+
const base = translations[defaultLang] || translations.en;
|
|
288
|
+
translations[langKey] = deepMerge(
|
|
289
|
+
base,
|
|
290
|
+
langValue
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return translations;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/index.ts
|
|
298
|
+
function virtualConfigPlugin(clientConfig) {
|
|
299
|
+
const virtualModuleId = "virtual:astro-consent-mode/config";
|
|
300
|
+
const resolvedVirtualModuleId = "\0" + virtualModuleId;
|
|
301
|
+
return {
|
|
302
|
+
name: "vite-plugin-astro-consent-mode-config",
|
|
303
|
+
resolveId(id) {
|
|
304
|
+
if (id === virtualModuleId) {
|
|
305
|
+
return resolvedVirtualModuleId;
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
load(id) {
|
|
309
|
+
if (id === resolvedVirtualModuleId) {
|
|
310
|
+
return `export default ${JSON.stringify(clientConfig)};`;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function validateOptions(options) {
|
|
316
|
+
if (options.gtmId && !/^GTM-[A-Z0-9]+$/i.test(options.gtmId)) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`[@krysmalskipl/astro-consent-mode] Invalid "gtmId": expected format "GTM-XXXXXX", received "${options.gtmId}".`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
if (options.ga4Id && !/^G-[A-Z0-9]+$/i.test(options.ga4Id)) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
`[@krysmalskipl/astro-consent-mode] Invalid "ga4Id": expected format "G-XXXXXX", received "${options.ga4Id}".`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
if (options.metaPixelId && !/^\d+$/.test(options.metaPixelId)) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`[@krysmalskipl/astro-consent-mode] Invalid "metaPixelId": expected numeric string, received "${options.metaPixelId}".`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
if (options.clarityId && !/^[a-z0-9]+$/i.test(options.clarityId)) {
|
|
332
|
+
throw new Error(
|
|
333
|
+
`[@krysmalskipl/astro-consent-mode] Invalid "clarityId": expected alphanumeric string, received "${options.clarityId}".`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
if (options.mode && options.mode !== "basic" && options.mode !== "advanced") {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`[@krysmalskipl/astro-consent-mode] Invalid "mode": expected "basic" | "advanced", received "${options.mode}".`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
if (options.lang && options.lang !== "pl" && options.lang !== "en") {
|
|
342
|
+
throw new Error(
|
|
343
|
+
`[@krysmalskipl/astro-consent-mode] Invalid "lang": expected "pl" | "en", received "${options.lang}".`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
if (options.banner && !(options.banner in BANNER_PRESETS)) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
`[@krysmalskipl/astro-consent-mode] Invalid "banner": expected one of ${Object.keys(BANNER_PRESETS).map((k) => `"${k}"`).join(" | ")}, received "${options.banner}".`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
var BOX_PREFERENCES = { layout: "box", equalWeightButtons: true };
|
|
353
|
+
var BANNER_PRESETS = {
|
|
354
|
+
"box-left": {
|
|
355
|
+
consentModal: { layout: "box", position: "bottom left", equalWeightButtons: true },
|
|
356
|
+
preferencesModal: BOX_PREFERENCES
|
|
357
|
+
},
|
|
358
|
+
"box-right": {
|
|
359
|
+
consentModal: { layout: "box", position: "bottom right", equalWeightButtons: true },
|
|
360
|
+
preferencesModal: BOX_PREFERENCES
|
|
361
|
+
},
|
|
362
|
+
"box-center": {
|
|
363
|
+
consentModal: { layout: "box", position: "bottom center", equalWeightButtons: true },
|
|
364
|
+
preferencesModal: BOX_PREFERENCES
|
|
365
|
+
},
|
|
366
|
+
"box-wide": {
|
|
367
|
+
consentModal: { layout: "box wide", position: "bottom left", equalWeightButtons: true },
|
|
368
|
+
preferencesModal: BOX_PREFERENCES
|
|
369
|
+
},
|
|
370
|
+
cloud: {
|
|
371
|
+
consentModal: { layout: "cloud inline", position: "bottom center", equalWeightButtons: true },
|
|
372
|
+
preferencesModal: BOX_PREFERENCES
|
|
373
|
+
},
|
|
374
|
+
"bar-bottom": {
|
|
375
|
+
consentModal: { layout: "bar inline", position: "bottom", equalWeightButtons: true },
|
|
376
|
+
preferencesModal: BOX_PREFERENCES
|
|
377
|
+
},
|
|
378
|
+
"bar-top": {
|
|
379
|
+
consentModal: { layout: "bar inline", position: "top", equalWeightButtons: true },
|
|
380
|
+
preferencesModal: BOX_PREFERENCES
|
|
381
|
+
},
|
|
382
|
+
modal: {
|
|
383
|
+
consentModal: { layout: "box", position: "middle center", equalWeightButtons: true },
|
|
384
|
+
preferencesModal: BOX_PREFERENCES
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
function consent(options = {}) {
|
|
388
|
+
validateOptions(options);
|
|
389
|
+
const hasAnyId = Boolean(
|
|
390
|
+
options.gtmId || options.ga4Id || options.metaPixelId || options.clarityId
|
|
391
|
+
);
|
|
392
|
+
const isEnabled = options.enabled !== void 0 ? options.enabled : hasAnyId;
|
|
393
|
+
const mode = options.mode ?? "basic";
|
|
394
|
+
const lang = options.lang ?? "pl";
|
|
395
|
+
const defaultPrivacyUrl = lang === "en" ? "/privacy-policy/" : "/polityka-prywatnosci/";
|
|
396
|
+
const privacyUrl = options.privacyUrl ?? defaultPrivacyUrl;
|
|
397
|
+
const revision = options.revision ?? 0;
|
|
398
|
+
const urlPassthrough = options.urlPassthrough ?? true;
|
|
399
|
+
const adsDataRedaction = options.adsDataRedaction ?? true;
|
|
400
|
+
const waitForUpdate = options.waitForUpdate ?? 500;
|
|
401
|
+
const analyticsServices = [];
|
|
402
|
+
if (options.gtmId) {
|
|
403
|
+
analyticsServices.push("Google Tag Manager");
|
|
404
|
+
} else if (options.ga4Id) {
|
|
405
|
+
analyticsServices.push("Google Analytics");
|
|
406
|
+
}
|
|
407
|
+
if (options.clarityId) {
|
|
408
|
+
analyticsServices.push("Microsoft Clarity");
|
|
409
|
+
}
|
|
410
|
+
const marketingServices = [];
|
|
411
|
+
if (options.gtmId) {
|
|
412
|
+
marketingServices.push("Google Tag Manager");
|
|
413
|
+
}
|
|
414
|
+
if (options.metaPixelId) {
|
|
415
|
+
marketingServices.push("Meta Pixel");
|
|
416
|
+
}
|
|
417
|
+
const hasAnalytics = analyticsServices.length > 0;
|
|
418
|
+
const hasMarketing = marketingServices.length > 0;
|
|
419
|
+
const resolvedOptions = {
|
|
420
|
+
gtmId: options.gtmId,
|
|
421
|
+
ga4Id: options.gtmId ? void 0 : options.ga4Id,
|
|
422
|
+
metaPixelId: options.metaPixelId,
|
|
423
|
+
clarityId: options.clarityId,
|
|
424
|
+
mode,
|
|
425
|
+
lang,
|
|
426
|
+
privacyUrl,
|
|
427
|
+
texts: options.texts,
|
|
428
|
+
gui: options.gui || {},
|
|
429
|
+
revision,
|
|
430
|
+
urlPassthrough,
|
|
431
|
+
adsDataRedaction,
|
|
432
|
+
waitForUpdate,
|
|
433
|
+
enabled: isEnabled,
|
|
434
|
+
hasAnalytics,
|
|
435
|
+
hasMarketing,
|
|
436
|
+
analyticsServices,
|
|
437
|
+
marketingServices
|
|
438
|
+
};
|
|
439
|
+
const translations = resolveTranslations(
|
|
440
|
+
{
|
|
441
|
+
privacyUrl,
|
|
442
|
+
hasAnalytics,
|
|
443
|
+
hasMarketing,
|
|
444
|
+
analyticsServices,
|
|
445
|
+
marketingServices
|
|
446
|
+
},
|
|
447
|
+
lang,
|
|
448
|
+
options.texts
|
|
449
|
+
);
|
|
450
|
+
const banner = options.banner ?? "box-left";
|
|
451
|
+
const overlay = options.overlay ?? banner === "modal";
|
|
452
|
+
const preset = BANNER_PRESETS[banner];
|
|
453
|
+
const gui = {
|
|
454
|
+
consentModal: { ...preset.consentModal, ...options.gui?.consentModal },
|
|
455
|
+
preferencesModal: { ...preset.preferencesModal, ...options.gui?.preferencesModal }
|
|
456
|
+
};
|
|
457
|
+
const clientConfig = {
|
|
458
|
+
hasAnalytics,
|
|
459
|
+
hasMarketing,
|
|
460
|
+
analyticsServices,
|
|
461
|
+
marketingServices,
|
|
462
|
+
lang,
|
|
463
|
+
privacyUrl,
|
|
464
|
+
translations,
|
|
465
|
+
gui,
|
|
466
|
+
overlay,
|
|
467
|
+
revision,
|
|
468
|
+
mode
|
|
469
|
+
};
|
|
470
|
+
return {
|
|
471
|
+
name: "@krysmalskipl/astro-consent-mode",
|
|
472
|
+
hooks: {
|
|
473
|
+
"astro:config:setup"({ injectScript, updateConfig, logger }) {
|
|
474
|
+
if (!isEnabled) {
|
|
475
|
+
logger.info(
|
|
476
|
+
"Astro Consent Mode is disabled (no tracking IDs configured or enabled: false)."
|
|
477
|
+
);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
injectScript("head-inline", buildHeadScript(resolvedOptions));
|
|
481
|
+
updateConfig({
|
|
482
|
+
vite: {
|
|
483
|
+
plugins: [virtualConfigPlugin(clientConfig)],
|
|
484
|
+
optimizeDeps: {
|
|
485
|
+
exclude: ["@krysmalskipl/astro-consent-mode"],
|
|
486
|
+
include: ["vanilla-cookieconsent"]
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
injectScript("page", "import '@krysmalskipl/astro-consent-mode/client';");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
var index_default = consent;
|
|
496
|
+
export {
|
|
497
|
+
consent,
|
|
498
|
+
index_default as default
|
|
499
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,70 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krysmalskipl/astro-consent-mode",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Google Consent Mode v2 for Astro - cookie banner, GTM, GA4, Meta Pixel and Microsoft Clarity with zero config.",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Krysmalski",
|
|
9
|
+
"url": "https://krysmalski.pl"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://krysmalski.pl",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/krysmalskipl/astro-consent-mode.git"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/krysmalskipl/astro-consent-mode/issues"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"withastro",
|
|
21
|
+
"astro-integration",
|
|
22
|
+
"astro-component",
|
|
23
|
+
"astro",
|
|
24
|
+
"cookie-consent",
|
|
25
|
+
"consent-mode",
|
|
26
|
+
"consent-mode-v2",
|
|
27
|
+
"gdpr",
|
|
28
|
+
"rodo",
|
|
29
|
+
"google-tag-manager",
|
|
30
|
+
"ga4",
|
|
31
|
+
"meta-pixel",
|
|
32
|
+
"clarity"
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./client": "./dist/client.js"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"CHANGELOG.md"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"dev": "tsup --watch",
|
|
50
|
+
"prepublishOnly": "npm run build"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"astro": ">=5.0.0"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"vanilla-cookieconsent": "^3.1.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^22.10.2",
|
|
60
|
+
"astro": "^5.4.2",
|
|
61
|
+
"tsup": "^8.4.0",
|
|
62
|
+
"typescript": "^5.7.3"
|
|
63
|
+
},
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=18.20.8"
|
|
69
|
+
}
|
|
70
|
+
}
|