@emplugins/emprivacy 2.0.0 → 3.0.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/README.md +98 -52
- package/dist/index.d.mts +205 -30
- package/dist/index.mjs +1746 -290
- package/docs/DEVELOPMENT.md +3 -3
- package/docs/GETTING_STARTED.md +81 -77
- package/docs/PLUGIN_SETTINGS.md +121 -117
- package/docs/RELEASES.md +2 -2
- package/docs/RELEASE_NOTES.md +20 -0
- package/docs/TESTING.md +19 -21
- package/package.json +12 -10
- package/src/astro/GatedEmbed.astro +70 -0
- package/src/astro/embed-resolve.ts +227 -0
- package/src/astro/index.ts +24 -0
package/README.md
CHANGED
|
@@ -2,13 +2,34 @@
|
|
|
2
2
|

|
|
3
3
|
# EmPrivacy
|
|
4
4
|
|
|
5
|
-
**EmPrivacy** is an open source [EmDash](https://github.com/emdash-cms/emdash) plugin that adds a cookie consent banner, category-based choices (essential / analytics / marketing),
|
|
5
|
+
**EmPrivacy** is an open source [EmDash](https://github.com/emdash-cms/emdash) plugin that adds a cookie consent banner, category-based choices (essential / functional / analytics / marketing), **client-side** loading of configured third-party scripts only after consent, and optional placeholders for official EmDash embed blocks. It aligns with common regulatory expectations when you configure it honestly and pair it with proper legal documents—but **this software is not legal advice** (see [Disclaimer](#legal-disclaimer)).
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Shows a first-visit banner (and a reopen control) with **Accept all**, **Reject non-essential**, and **Customize**.
|
|
10
|
+
- Stores choices in the first-party `emprivacy_cc` cookie as `{ v, f, a, m }` (policy version, functional, analytics, marketing).
|
|
11
|
+
- Loads **only the analytics and marketing scripts you configure** after the matching category is allowed.
|
|
12
|
+
- Optionally replaces official EmDash embed blocks (YouTube, Vimeo, X, Bluesky, Mastodon, Gist, link preview) with a placeholder until the chosen category is allowed.
|
|
13
|
+
- Exposes `window.emprivacy` so themes and other plugins can read consent without parsing the cookie.
|
|
14
|
+
- Builds a **vendor list** from your settings (admin + `GET /_emdash/api/plugins/emprivacy/vendors`).
|
|
15
|
+
- Localizes banner chrome for `en`, `de`, `fr`, `es`, `it`, `nl`, `pt`, and `pl`, with optional JSON overrides for title and message.
|
|
16
|
+
- Lets you theme the banner with hex CSS variables.
|
|
17
|
+
|
|
18
|
+
## What it does not do
|
|
19
|
+
|
|
20
|
+
- **Does not write or generate a privacy policy, cookie policy, or DPA.** You create those as EmDash Pages (or host them elsewhere) and paste the path or `https` URL.
|
|
21
|
+
- **Does not scan the theme** for leftover `gtag`, pixels, or iframes you pasted yourself. Scripts and iframes outside EmPrivacy still load.
|
|
22
|
+
- **Does not geo-locate visitors** or switch EU/US banners automatically.
|
|
23
|
+
- **Does not block first-party EmDash comments**, sessions, or other essential site cookies.
|
|
24
|
+
- **Does not iframe arbitrary hosts.** Mastodon, Gist, Bluesky, X, and link-preview blocks become **https links** (or a YouTube/Vimeo iframe from an allowlisted player host) after consent.
|
|
25
|
+
- **Is not a sandbox.** It is a **native** plugin (`plugins: []`) and runs with the same trust as your site code.
|
|
26
|
+
- **Is not legal advice** and does not certify GDPR/CCPA/CIPA compliance.
|
|
6
27
|
|
|
7
28
|
## Requirements
|
|
8
29
|
|
|
9
|
-
- **EmDash** `^0.
|
|
30
|
+
- **EmDash** `^1.0.1` (**tested with `1.0.1`**). EmDash 0.x is not supported.
|
|
10
31
|
- **Node.js** `>= 22.16` (matches EmDash’s engine requirement).
|
|
11
|
-
- Register as a **native** plugin in **`astro.config`** via `plugins: []` (not `sandboxed: []`). EmPrivacy uses `page:fragments` for the public banner and
|
|
32
|
+
- Register as a **native** plugin in **`astro.config`** via `plugins: []` (not `sandboxed: []`). EmPrivacy uses `page:fragments` for the public banner and `componentsEntry` for embed placeholders. See [Page fragments](https://docs.emdashcms.com/plugins/creating-native-plugins/page-fragments/) and [Plugin overview](https://docs.emdashcms.com/plugins/overview/).
|
|
12
33
|
|
|
13
34
|
## Install
|
|
14
35
|
|
|
@@ -20,13 +41,13 @@ npm install @emplugins/emprivacy
|
|
|
20
41
|
|
|
21
42
|
Published under the [emplugins](https://www.npmjs.com/org/emplugins) npm org. Source: [github.com/EmPlugins/EmPrivacy](https://github.com/EmPlugins/EmPrivacy). See [EMDASH_COMPAT.md](./EMDASH_COMPAT.md) for EmDash version mapping.
|
|
22
43
|
|
|
23
|
-
Use a **semver range** if you want controlled upgrades, for example `@emplugins/emprivacy@^
|
|
44
|
+
Use a **semver range** if you want controlled upgrades, for example `@emplugins/emprivacy@^3.0.0`. Versioning: [docs/RELEASES.md](docs/RELEASES.md). Org publish: [docs/NPM_ORG_PUBLISH.md](docs/NPM_ORG_PUBLISH.md).
|
|
24
45
|
|
|
25
46
|
The legacy unscoped package `emprivacy` is deprecated; migrate imports to `@emplugins/emprivacy`.
|
|
26
47
|
|
|
27
|
-
## Quick start
|
|
48
|
+
## Quick start (site managers)
|
|
28
49
|
|
|
29
|
-
1. **Register the plugin** in `astro.config.mjs` (or `.ts`)
|
|
50
|
+
1. **Register the plugin** in `astro.config.mjs` (or `.ts`):
|
|
30
51
|
|
|
31
52
|
```ts
|
|
32
53
|
import { defineConfig } from "astro/config";
|
|
@@ -36,7 +57,10 @@ The legacy unscoped package `emprivacy` is deprecated; migrate imports to `@empl
|
|
|
36
57
|
export default defineConfig({
|
|
37
58
|
integrations: [
|
|
38
59
|
emdash({
|
|
39
|
-
plugins: [
|
|
60
|
+
plugins: [
|
|
61
|
+
emprivacyPlugin(),
|
|
62
|
+
// other native plugins…
|
|
63
|
+
],
|
|
40
64
|
}),
|
|
41
65
|
],
|
|
42
66
|
});
|
|
@@ -44,29 +68,61 @@ The legacy unscoped package `emprivacy` is deprecated; migrate imports to `@empl
|
|
|
44
68
|
|
|
45
69
|
Native descriptors belong in `plugins`, not `sandboxed`. EmDash loads the named `createPlugin()` export from the package at runtime.
|
|
46
70
|
|
|
47
|
-
2. **
|
|
71
|
+
2. **Put EmPrivacy first** when other plugins inject `gtag` / analytics in `<head>` (Google Consent Mode must run first).
|
|
72
|
+
|
|
73
|
+
3. **Put EmPrivacy last** when you want automatic YouTube/Vimeo placeholders (later `blockComponents` win). If you need **both** Consent Mode-first **and** gated embeds, keep EmPrivacy first and pass the wrappers on your Portable Text renderer:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { PortableText } from "emdash/ui";
|
|
77
|
+
import { blockComponents as emprivacyEmbeds } from "@emplugins/emprivacy/astro";
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```astro
|
|
81
|
+
<PortableText value={entry.data.content} components={{ type: emprivacyEmbeds }} />
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Site-level `components` always win over every plugin.
|
|
48
85
|
|
|
49
|
-
|
|
50
|
-
- **Privacy policy** — Point to your existing EmDash **Page** using its **public path** (what you see in the address bar when that Page is open, e.g. `/privacy`), **or** a full `https://…` URL if the policy is hosted elsewhere. Root-relative paths are resolved with EmDash `ctx.url()` so links stay correct across environments.
|
|
51
|
-
- **Cookie policy** (optional) — Same rules: EmDash Page path or `https://…`.
|
|
52
|
-
- Set banner copy, **Policy / consent version**, **Analytics** (Cloudflare site token, **None**, or **Custom** `https` URLs), and **Marketing** script URLs (`https`, one per line). See [GETTING_STARTED](docs/GETTING_STARTED.md#step-5--configure-emprivacy-in-the-admin).
|
|
86
|
+
4. **Use EmDash page integration** in your layout: `<EmDashHead />`, `<EmDashBodyStart />`, `<EmDashBodyEnd />` with `createPublicPageContext({ Astro, … })`.
|
|
53
87
|
|
|
54
|
-
|
|
88
|
+
5. **Create a Privacy Policy Page** (and optional Cookie Policy Page) in EmDash, then in admin (shield → **EmPrivacy**) set the public path (`/privacy`) or an `https://` URL.
|
|
55
89
|
|
|
56
|
-
|
|
90
|
+
6. Pick an analytics preset (Cloudflare, Plausible, Fathom, Umami, Simple Analytics, GA4, GTM, None, or Custom), optional marketing script URLs, and **Save**.
|
|
91
|
+
|
|
92
|
+
Visitors see a banner on first visit, when **Policy / consent version** changes, or when an older cookie is missing the functional flag (`f`). Existing `{ v, a, m }` cookies are treated as expired so visitors choose the new category.
|
|
93
|
+
|
|
94
|
+
## Public JavaScript API
|
|
95
|
+
|
|
96
|
+
After the banner script runs:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
window.emprivacy.get();
|
|
100
|
+
// { v, essential: true, functional, analytics, marketing } | null
|
|
101
|
+
|
|
102
|
+
window.emprivacy.has("analytics"); // boolean
|
|
103
|
+
window.emprivacy.open(); // reopen the panel
|
|
104
|
+
const stop = window.emprivacy.onChange((state) => { /* … */ });
|
|
105
|
+
stop();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`document` also receives `emprivacy:change` with the same state in `event.detail`. Other plugins should gate their own scripts with this API instead of adding a second banner.
|
|
57
109
|
|
|
58
110
|
## Features
|
|
59
111
|
|
|
60
112
|
| Feature | Description |
|
|
61
113
|
|--------|-------------|
|
|
62
|
-
| Categories | Essential (
|
|
63
|
-
| Privacy / cookie links | EmDash **Page**
|
|
64
|
-
| Analytics
|
|
65
|
-
| Marketing scripts | Only **https** URLs you list
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
114
|
+
| Categories | Essential (always on), **Functional**, **Analytics**, **Marketing** |
|
|
115
|
+
| Privacy / cookie links | EmDash **Page** path or `https://` URL; resolved with `ctx.url()` |
|
|
116
|
+
| Analytics presets | Cloudflare Web Analytics, Plausible, Fathom, Umami, Simple Analytics, GA4, **GTM (Marketing)**, None, Custom `https` URLs (+ optional SRI / host allowlist) |
|
|
117
|
+
| Marketing scripts | Only **https** URLs you list, injected as `<script src>` after marketing consent |
|
|
118
|
+
| Official embeds | Optional placeholders for `@emdash-cms/plugin-embeds` block types; YouTube/Vimeo use allowlisted player URLs (`youtube-nocookie`, `player.vimeo.com`); client re-checks URLs before mount |
|
|
119
|
+
| Re-open preferences | Cookie button (bottom-left); saving reloads so script loading matches the latest choice |
|
|
120
|
+
| Consent cookie | `emprivacy_cc` (`path=/`, `SameSite=Lax`, `Secure` on HTTPS, configurable `Max-Age`, default 180 days), JSON `{ v, f, a, m }` |
|
|
121
|
+
| Vendor list | Derived from settings; shown under Customize; JSON at `/_emdash/api/plugins/emprivacy/vendors` (no tokens) |
|
|
122
|
+
| Optional server log | POST `{ policyVersion, functional, analytics, marketing }` to `/_emdash/api/plugins/emprivacy/record` (Origin / Sec-Fetch-Site, rate-limited, no IP) |
|
|
123
|
+
| Google Consent Mode v2 | Optional denied defaults in `<head>` + `gtag('consent','update',…)` including `functionality_storage` |
|
|
124
|
+
| i18n | Built-in chrome for common locales; optional JSON title/message overrides |
|
|
125
|
+
| Theme | Hex CSS variables (`--emprivacy-bg`, `--emprivacy-text`, `--emprivacy-accent`, `--emprivacy-radius`) |
|
|
70
126
|
|
|
71
127
|
## Capabilities
|
|
72
128
|
|
|
@@ -80,15 +136,18 @@ Settings, KV, and declared plugin storage need no extra capability. Native plugi
|
|
|
80
136
|
|
|
81
137
|
EmPrivacy helps you implement **technical** consent UX and script loading patterns used for regulations such as **GDPR** and **CCPA/CIPA**. **You** remain responsible for:
|
|
82
138
|
|
|
83
|
-
- Privacy notices, cookie policies, and lawful bases
|
|
84
|
-
- What data your analytics/marketing vendors collect
|
|
85
|
-
- Whether your configuration meets obligations in your jurisdictions
|
|
139
|
+
- Privacy notices, cookie policies, and lawful bases
|
|
140
|
+
- What data your analytics/marketing vendors collect
|
|
141
|
+
- Whether your configuration meets obligations in your jurisdictions
|
|
142
|
+
- Any scripts, pixels, or iframes you add outside this plugin
|
|
86
143
|
|
|
87
144
|
**This project does not provide legal advice.** If you need certainty, consult a qualified attorney or privacy professional.
|
|
88
145
|
|
|
89
146
|
## Registration order
|
|
90
147
|
|
|
91
|
-
|
|
148
|
+
- **Consent Mode / other head scripts:** `emprivacyPlugin()` **first** in `plugins`.
|
|
149
|
+
- **Automatic embed placeholders:** `emprivacyPlugin()` **after** `@emdash-cms/plugin-embeds`.
|
|
150
|
+
- **Both:** EmPrivacy first + site-level `PortableText` `components` (see Quick start).
|
|
92
151
|
|
|
93
152
|
## Development
|
|
94
153
|
|
|
@@ -100,7 +159,7 @@ pnpm test # Unit tests (see Testing)
|
|
|
100
159
|
pnpm run test:watch # Vitest in watch mode (optional)
|
|
101
160
|
```
|
|
102
161
|
|
|
103
|
-
Outputs ESM under `dist/` with typings. Layout follows EmDash’s [native plugin packaging](https://docs.emdashcms.com/plugins/creating-native-plugins/distributing/) (`emprivacyPlugin()` descriptor + named `createPlugin()`).
|
|
162
|
+
Outputs ESM under `dist/` with typings. Layout follows EmDash’s [native plugin packaging](https://docs.emdashcms.com/plugins/creating-native-plugins/distributing/) (`emprivacyPlugin()` descriptor + named `createPlugin()`, plus `./astro` for Portable Text).
|
|
104
163
|
|
|
105
164
|
Dependency, audit, and deprecation context: [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md).
|
|
106
165
|
|
|
@@ -110,45 +169,32 @@ Dependency, audit, and deprecation context: [docs/DEVELOPMENT.md](docs/DEVELOPME
|
|
|
110
169
|
|
|
111
170
|
| Command | Purpose |
|
|
112
171
|
|---------|---------|
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
172
|
+
| `pnpm run typecheck` | Types against `emdash` and this package |
|
|
173
|
+
| `pnpm run build` | `tsdown` → `dist/` and `exports` |
|
|
174
|
+
| `pnpm test` | Vitest: config validation, embed URL allowlists, i18n, vendors, JSON escaping |
|
|
116
175
|
|
|
117
176
|
Automation does **not** start EmDash or a browser; it guards packaging and validation logic.
|
|
118
177
|
|
|
119
178
|
**Suggested CI:**
|
|
120
179
|
|
|
121
180
|
```bash
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
181
|
+
pnpm install --frozen-lockfile
|
|
182
|
+
pnpm run typecheck
|
|
183
|
+
pnpm run build
|
|
184
|
+
pnpm test
|
|
126
185
|
```
|
|
127
186
|
|
|
128
187
|
### Staging / manual (before production)
|
|
129
188
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
- **[docs/TESTING.md](docs/TESTING.md)** — Full pre-deploy checklist, flows to exercise, limits of automation.
|
|
133
|
-
- **No E2E in this repo** — nothing here launches EmDash or drives a browser.
|
|
134
|
-
|
|
135
|
-
### Acceptance checklist (manual QA)
|
|
136
|
-
|
|
137
|
-
Details: [docs/TESTING.md](docs/TESTING.md).
|
|
189
|
+
Validate on a **real EmDash site** (staging) at the EmDash version you deploy.
|
|
138
190
|
|
|
139
|
-
- [
|
|
140
|
-
- [
|
|
141
|
-
- [
|
|
142
|
-
- [ ] Strict mode: no analytics/marketing scripts until consent (Network tab)
|
|
143
|
-
- [ ] Cookie persists; bumping policy version shows the banner again
|
|
144
|
-
- [ ] Admin save updates the public banner
|
|
145
|
-
- [ ] Optional: server logging + recent records in admin
|
|
146
|
-
- [ ] Optional: Google Consent Mode (Tag Assistant / [Google](https://support.google.com/tagmanager/answer/13695607))
|
|
147
|
-
- [ ] Native `plugins: []` registration (see [Requirements](#requirements))
|
|
191
|
+
- **[docs/TESTING.md](docs/TESTING.md)** — Full pre-deploy checklist.
|
|
192
|
+
- **[docs/GETTING_STARTED.md](docs/GETTING_STARTED.md)** — Install and configure as a site manager.
|
|
193
|
+
- **[docs/PLUGIN_SETTINGS.md](docs/PLUGIN_SETTINGS.md)** — Every admin field.
|
|
148
194
|
|
|
149
195
|
## Releases
|
|
150
196
|
|
|
151
|
-
npm releases follow **[Semantic Versioning](https://semver.org/)** (`MAJOR.MINOR.PATCH`). Tags are `v`-prefixed (e.g. `
|
|
197
|
+
npm releases follow **[Semantic Versioning](https://semver.org/)** (`MAJOR.MINOR.PATCH`). Tags are `v`-prefixed (e.g. `v1.0.0`). See [docs/RELEASES.md](docs/RELEASES.md).
|
|
152
198
|
|
|
153
199
|
## License
|
|
154
200
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,25 +1,99 @@
|
|
|
1
|
-
import * as emdash from "emdash";
|
|
2
1
|
import { PluginDescriptor } from "emdash";
|
|
3
|
-
|
|
4
2
|
//#region src/runtime.d.ts
|
|
5
|
-
|
|
6
|
-
* Native EmDash runtime — loaded via the named `createPlugin` export.
|
|
7
|
-
* Uses `page:fragments`, which requires trusted in-process (native) execution.
|
|
8
|
-
*/
|
|
9
|
-
declare function createPlugin(): emdash.ResolvedPlugin<{
|
|
3
|
+
export declare function createPlugin(): import("emdash").ResolvedPlugin<{
|
|
10
4
|
consentEvents: {
|
|
11
5
|
indexes: string[];
|
|
12
6
|
};
|
|
13
7
|
}>;
|
|
14
8
|
//#endregion
|
|
9
|
+
//#region src/vendors.d.ts
|
|
10
|
+
type ConsentCategory = "essential" | "functional" | "analytics" | "marketing";
|
|
11
|
+
interface VendorRow {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
category: ConsentCategory;
|
|
15
|
+
purpose: string;
|
|
16
|
+
/** First-party docs we control the URL for — never taken from admin HTML */
|
|
17
|
+
policyUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
type AnalyticsLoader = {
|
|
20
|
+
type: "none";
|
|
21
|
+
} | {
|
|
22
|
+
type: "cloudflare";
|
|
23
|
+
token: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: "custom";
|
|
26
|
+
scripts: {
|
|
27
|
+
src: string;
|
|
28
|
+
integrity: string | null;
|
|
29
|
+
}[];
|
|
30
|
+
} | {
|
|
31
|
+
type: "plausible";
|
|
32
|
+
src: string;
|
|
33
|
+
domain: string;
|
|
34
|
+
} | {
|
|
35
|
+
type: "fathom";
|
|
36
|
+
src: string;
|
|
37
|
+
siteId: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: "umami";
|
|
40
|
+
src: string;
|
|
41
|
+
websiteId: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: "simpleanalytics";
|
|
44
|
+
src: string;
|
|
45
|
+
} | {
|
|
46
|
+
type: "ga4";
|
|
47
|
+
measurementId: string;
|
|
48
|
+
} |
|
|
49
|
+
/** Loaded only after Marketing consent — GTM can fire advertising tags. */
|
|
50
|
+
{
|
|
51
|
+
type: "gtm";
|
|
52
|
+
containerId: string;
|
|
53
|
+
};
|
|
54
|
+
export declare function buildAnalyticsLoader(cfg: EmprivacyConfig): AnalyticsLoader;
|
|
55
|
+
export declare function buildVendorList(cfg: EmprivacyConfig): VendorRow[];
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/i18n.d.ts
|
|
58
|
+
interface EmprivacyChromeStrings {
|
|
59
|
+
acceptAll: string;
|
|
60
|
+
rejectNonEssential: string;
|
|
61
|
+
customize: string;
|
|
62
|
+
saveChoices: string;
|
|
63
|
+
close: string;
|
|
64
|
+
essential: string;
|
|
65
|
+
functional: string;
|
|
66
|
+
analytics: string;
|
|
67
|
+
marketing: string;
|
|
68
|
+
essentialNote: string;
|
|
69
|
+
privacyPolicy: string;
|
|
70
|
+
cookiePolicy: string;
|
|
71
|
+
cookieSettings: string;
|
|
72
|
+
loadEmbed: string;
|
|
73
|
+
embedBlocked: string;
|
|
74
|
+
embedNeedConsent: string;
|
|
75
|
+
vendorsHeading: string;
|
|
76
|
+
whatWeUse: string;
|
|
77
|
+
}
|
|
78
|
+
interface EmprivacyLocaleCopy {
|
|
79
|
+
bannerTitle: string;
|
|
80
|
+
bannerMessage: string;
|
|
81
|
+
}
|
|
82
|
+
type LocaleOverrides = Record<string, Partial<EmprivacyLocaleCopy>>;
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/theme.d.ts
|
|
85
|
+
interface EmprivacyTheme {
|
|
86
|
+
bg: string;
|
|
87
|
+
text: string;
|
|
88
|
+
accent: string;
|
|
89
|
+
radiusPx: number;
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
15
92
|
//#region src/config.d.ts
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
declare const COOKIE_NAME: "emprivacy_cc";
|
|
21
|
-
/** How analytics scripts are chosen after the user consents to analytics. */
|
|
22
|
-
type AnalyticsProvider = "cloudflare" | "none" | "custom";
|
|
93
|
+
export declare const KV_KEY: "consent:config";
|
|
94
|
+
export declare const COOKIE_NAME: "emprivacy_cc";
|
|
95
|
+
type AnalyticsProvider = "cloudflare" | "none" | "custom" | "plausible" | "fathom" | "umami" | "simpleanalytics" | "ga4" | "gtm";
|
|
96
|
+
type EmbedCategory = "functional" | "marketing";
|
|
23
97
|
interface EmprivacyConfig {
|
|
24
98
|
bannerTitle: string;
|
|
25
99
|
bannerMessage: string;
|
|
@@ -30,7 +104,7 @@ interface EmprivacyConfig {
|
|
|
30
104
|
privacyPolicyUrl: string;
|
|
31
105
|
/** Optional cookie policy: same rules as `privacyPolicyUrl` (https URL or path like `/cookies`) */
|
|
32
106
|
cookiePolicyUrl: string;
|
|
33
|
-
/** When true, analytics/marketing require explicit opt-in in the UI */
|
|
107
|
+
/** When true, analytics/marketing/functional require explicit opt-in in the UI */
|
|
34
108
|
strictDefaults: boolean;
|
|
35
109
|
/** Bump to invalidate client consent cookies and re-show the banner */
|
|
36
110
|
policyVersion: string;
|
|
@@ -39,49 +113,150 @@ interface EmprivacyConfig {
|
|
|
39
113
|
* Public in the same way as a normal site embed. Empty means no Cloudflare script until a token is saved.
|
|
40
114
|
*/
|
|
41
115
|
cloudflareWebAnalyticsToken: string;
|
|
42
|
-
/**
|
|
43
|
-
* Which analytics load path to use after consent. `cloudflare` = beacon + `cloudflareWebAnalyticsToken`;
|
|
44
|
-
* `custom` = `analyticsScriptUrls` only; `none` = no third-party analytics scripts.
|
|
45
|
-
*/
|
|
46
116
|
analyticsProvider: AnalyticsProvider;
|
|
117
|
+
/** Provider-specific id (domain, site id, G-…, GTM-…). */
|
|
118
|
+
analyticsId: string;
|
|
119
|
+
/** Required when `analyticsProvider` is `umami` — https script src only. */
|
|
120
|
+
umamiScriptUrl: string;
|
|
47
121
|
/** When `analyticsProvider` is `custom`, these https script URLs load after analytics consent. */
|
|
48
122
|
analyticsScriptUrls: string[];
|
|
49
123
|
/** Third-party script URLs loaded only after marketing consent (https only) */
|
|
50
124
|
marketingScriptUrls: string[];
|
|
125
|
+
/** Optional SRI hashes keyed by script src (sha256/384/512-…) */
|
|
126
|
+
scriptIntegrity: Record<string, string>;
|
|
127
|
+
/**
|
|
128
|
+
* When non-empty, Custom analytics and marketing script hosts must be on this list.
|
|
129
|
+
* Preset CDN hosts remain allowed for built-in providers.
|
|
130
|
+
*/
|
|
131
|
+
scriptHostAllowlist: string[];
|
|
132
|
+
/** Consent cookie Max-Age in days (1–365, default 180) */
|
|
133
|
+
cookieMaxAgeDays: number;
|
|
51
134
|
/** Emit Google Consent Mode v2 defaults (denied) in head; updates after choice */
|
|
52
135
|
googleConsentMode: boolean;
|
|
53
136
|
/** POST consent snapshots to the plugin record route and optional storage */
|
|
54
137
|
logConsentToServer: boolean;
|
|
138
|
+
/** Category that official embed placeholders require */
|
|
139
|
+
embedCategory: EmbedCategory;
|
|
140
|
+
/** When true, EmPrivacy Portable Text components gate official embed blocks */
|
|
141
|
+
gateEmbeds: boolean;
|
|
142
|
+
/** Hide the first-visit banner on privacy/cookie policy paths */
|
|
143
|
+
hideBannerOnPolicyPages: boolean;
|
|
144
|
+
defaultLocale: string;
|
|
145
|
+
localeOverrides: LocaleOverrides;
|
|
146
|
+
theme: EmprivacyTheme;
|
|
55
147
|
}
|
|
56
|
-
declare const DEFAULT_CONFIG: EmprivacyConfig;
|
|
148
|
+
export declare const DEFAULT_CONFIG: EmprivacyConfig;
|
|
57
149
|
/** Reject characters that would break JSON or attributes; allow typical Cloudflare site token strings. */
|
|
58
|
-
declare function assertValidCloudflareToken(t: string): void;
|
|
150
|
+
export declare function assertValidCloudflareToken(t: string): void;
|
|
59
151
|
/**
|
|
60
152
|
* Root-relative public path (EmDash page route), e.g. `/privacy`.
|
|
61
153
|
* Rejects protocol-relative URLs (`//…`) and whitespace.
|
|
62
154
|
*/
|
|
63
|
-
declare function isRootRelativeSitePath(s: string): boolean;
|
|
155
|
+
export declare function isRootRelativeSitePath(s: string): boolean;
|
|
64
156
|
/** For hooks: turn stored path or absolute URL into a public absolute URL. */
|
|
65
|
-
declare function resolvePolicyHref(stored: string, ctx: {
|
|
157
|
+
export declare function resolvePolicyHref(stored: string, ctx: {
|
|
66
158
|
url: (path: string) => string;
|
|
67
159
|
}): string;
|
|
68
|
-
declare function isValidPolicyHrefInput(s: string): boolean;
|
|
69
|
-
|
|
160
|
+
export declare function isValidPolicyHrefInput(s: string): boolean;
|
|
161
|
+
/** True when the public page is the configured privacy or cookie policy. */
|
|
162
|
+
export declare function isPolicyPagePath(pagePath: string, cfg: Pick<EmprivacyConfig, "privacyPolicyUrl" | "cookiePolicyUrl">): boolean;
|
|
163
|
+
export declare function normalizeConfig(raw: unknown): EmprivacyConfig;
|
|
70
164
|
interface ConsentRecordPayload {
|
|
71
165
|
createdAt: string;
|
|
72
166
|
policyVersion: string;
|
|
167
|
+
functional: boolean;
|
|
168
|
+
analytics: boolean;
|
|
169
|
+
marketing: boolean;
|
|
170
|
+
}
|
|
171
|
+
interface ConsentState {
|
|
172
|
+
v: string;
|
|
173
|
+
essential: true;
|
|
174
|
+
functional: boolean;
|
|
73
175
|
analytics: boolean;
|
|
74
176
|
marketing: boolean;
|
|
75
177
|
}
|
|
178
|
+
interface VendorPublicRow {
|
|
179
|
+
id: string;
|
|
180
|
+
name: string;
|
|
181
|
+
category: "essential" | "functional" | "analytics" | "marketing";
|
|
182
|
+
purpose: string;
|
|
183
|
+
policyUrl?: string;
|
|
184
|
+
}
|
|
185
|
+
interface EmprivacyPublicRuntimeConfig {
|
|
186
|
+
bannerTitle: string;
|
|
187
|
+
bannerMessage: string;
|
|
188
|
+
privacyPolicyUrl: string;
|
|
189
|
+
cookiePolicyUrl: string;
|
|
190
|
+
strictDefaults: boolean;
|
|
191
|
+
policyVersion: string;
|
|
192
|
+
googleConsentMode: boolean;
|
|
193
|
+
logConsent: boolean;
|
|
194
|
+
recordPath: string;
|
|
195
|
+
embedCategory: EmbedCategory;
|
|
196
|
+
gateEmbeds: boolean;
|
|
197
|
+
hideBanner: boolean;
|
|
198
|
+
theme: EmprivacyTheme;
|
|
199
|
+
ui: EmprivacyChromeStrings;
|
|
200
|
+
loader: AnalyticsLoader;
|
|
201
|
+
marketingScripts: {
|
|
202
|
+
src: string;
|
|
203
|
+
integrity: string | null;
|
|
204
|
+
}[];
|
|
205
|
+
scriptHostAllowlist: string[];
|
|
206
|
+
scriptIntegrity: Record<string, string>;
|
|
207
|
+
cookieMaxAge: number;
|
|
208
|
+
vendors: VendorPublicRow[];
|
|
209
|
+
}
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/public-api.d.ts
|
|
212
|
+
type EmprivacyCategory = "essential" | "functional" | "analytics" | "marketing";
|
|
213
|
+
/**
|
|
214
|
+
* Public browser API attached to `window.emprivacy`.
|
|
215
|
+
* Other plugins and themes should use this instead of reading the cookie directly.
|
|
216
|
+
*/
|
|
217
|
+
interface EmprivacyBrowserApi {
|
|
218
|
+
get(): ConsentState | null;
|
|
219
|
+
has(category: EmprivacyCategory): boolean;
|
|
220
|
+
onChange(cb: (state: ConsentState) => void): () => void;
|
|
221
|
+
open(): void;
|
|
222
|
+
}
|
|
223
|
+
declare global {
|
|
224
|
+
interface Window {
|
|
225
|
+
emprivacy?: EmprivacyBrowserApi;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/astro/embed-resolve.d.ts
|
|
230
|
+
/**
|
|
231
|
+
* Validate official EmDash embed block payloads and produce a first-party
|
|
232
|
+
* placeholder plan. Never returns a URL we did not construct or allowlist.
|
|
233
|
+
*/
|
|
234
|
+
export declare const EMBED_BLOCK_TYPES: readonly ["youtube", "vimeo", "tweet", "bluesky", "mastodon", "linkPreview", "gist"];
|
|
235
|
+
type EmbedBlockType = (typeof EMBED_BLOCK_TYPES)[number];
|
|
236
|
+
type EmbedRenderMode = "iframe" | "link";
|
|
237
|
+
interface ResolvedEmbed {
|
|
238
|
+
kind: EmbedBlockType;
|
|
239
|
+
mode: EmbedRenderMode;
|
|
240
|
+
/** https iframe src or link href — always produced by this module */
|
|
241
|
+
src: string;
|
|
242
|
+
label: string;
|
|
243
|
+
poster: string | null;
|
|
244
|
+
}
|
|
245
|
+
export declare function resolveEmbed(node: {
|
|
246
|
+
_type?: unknown;
|
|
247
|
+
id?: unknown;
|
|
248
|
+
poster?: unknown;
|
|
249
|
+
title?: unknown;
|
|
250
|
+
}): ResolvedEmbed | null;
|
|
76
251
|
//#endregion
|
|
77
252
|
//#region src/index.d.ts
|
|
78
253
|
/**
|
|
79
254
|
* EmDash native plugin descriptor — add to `plugins: []` in `emdash({ ... })` inside `astro.config`.
|
|
80
255
|
*
|
|
81
|
-
* EmPrivacy uses `page:fragments` (banner + consent scripts)
|
|
82
|
-
*
|
|
83
|
-
*
|
|
256
|
+
* EmPrivacy uses `page:fragments` (banner + consent scripts) and optional Portable Text
|
|
257
|
+
* embed placeholders, which only run for **native** plugins in `plugins: []`.
|
|
258
|
+
* Do not place this descriptor in `sandboxed: []`.
|
|
84
259
|
*/
|
|
85
|
-
declare function emprivacyPlugin(): PluginDescriptor;
|
|
260
|
+
export declare function emprivacyPlugin(): PluginDescriptor;
|
|
86
261
|
//#endregion
|
|
87
|
-
export { type
|
|
262
|
+
export { type AnalyticsLoader, type AnalyticsProvider, type ConsentRecordPayload, type ConsentState, type EmbedCategory, type EmprivacyBrowserApi, type EmprivacyCategory, type EmprivacyConfig, type EmprivacyPublicRuntimeConfig, type ResolvedEmbed, type VendorPublicRow, type VendorRow, createPlugin as default };
|