@emplugins/emprivacy 2.0.0 → 2.1.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 +96 -50
- package/dist/index.d.mts +205 -30
- package/dist/index.mjs +1746 -290
- package/docs/DEVELOPMENT.md +2 -2
- package/docs/GETTING_STARTED.md +81 -77
- package/docs/PLUGIN_SETTINGS.md +121 -117
- package/docs/RELEASE_NOTES.md +13 -0
- package/docs/TESTING.md +19 -21
- package/package.json +10 -8
- package/src/astro/GatedEmbed.astro +70 -0
- package/src/astro/embed-resolve.ts +227 -0
- package/src/astro/index.ts +24 -0
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Runtime dependencies for the published package are **`zod`** only. **EmDash** is a **peer dependency** (your site installs it). The **`emdash`** package in this repo’s `devDependencies` exists so local `typecheck` and tests can resolve `import … from "emdash"`.
|
|
8
8
|
|
|
9
|
-
EmPrivacy is a **native** EmDash plugin (`format: "native"`, named `createPlugin()` export), published as **`@emplugins/emprivacy`**. It must be registered in `plugins: []` because it uses `page:fragments`.
|
|
9
|
+
EmPrivacy is a **native** EmDash plugin (`format: "native"`, named `createPlugin()` export), published as **`@emplugins/emprivacy`**. It must be registered in `plugins: []` because it uses `page:fragments`. The `./astro` export ships Portable Text embed placeholders (compiled by the host Astro app, not by `tsdown`).
|
|
10
10
|
|
|
11
11
|
## Package manager
|
|
12
12
|
|
|
@@ -21,7 +21,7 @@ pnpm run build
|
|
|
21
21
|
|
|
22
22
|
## `pnpm audit` and the Kysely override
|
|
23
23
|
|
|
24
|
-
`pnpm run audit` runs **`pnpm audit --prod`**, so it reflects **published runtime** dependencies only (currently `zod`).
|
|
24
|
+
`pnpm run audit` runs **`pnpm audit --prod`**, so it reflects **published runtime** dependencies only (currently `zod` 4.6.5).
|
|
25
25
|
|
|
26
26
|
The full install (including the `emdash` **dev** tree) can report transitive advisories from EmDash’s tooling stack. Those are owned by the site’s EmDash upgrade path, not by EmPrivacy’s published tarball.
|
|
27
27
|
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -2,20 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
# Getting started with EmPrivacy
|
|
4
4
|
|
|
5
|
-
This guide is for site
|
|
5
|
+
This guide is for **EmDash site managers** who want cookie consent on a live site without reading plugin source.
|
|
6
6
|
|
|
7
7
|
## What you get
|
|
8
8
|
|
|
9
|
-
- A **banner** on first visit (or after you change **Policy / consent version
|
|
10
|
-
- Toggles for **Analytics
|
|
11
|
-
- **Privacy and cookie policy links** you control
|
|
12
|
-
-
|
|
9
|
+
- A **banner** on first visit (or after you change **Policy / consent version**, or after an upgrade that adds the functional category).
|
|
10
|
+
- Toggles for **Functional**, **Analytics**, and **Marketing**. Essential cookies stay on.
|
|
11
|
+
- **Privacy and cookie policy links** you control (EmDash Page path or `https://`).
|
|
12
|
+
- Analytics presets (Cloudflare, Plausible, Fathom, Umami, Simple Analytics, GA4, GTM under Marketing) plus Custom / None.
|
|
13
|
+
- Optional **gated embeds** for official EmDash YouTube / Vimeo / social / Gist blocks.
|
|
14
|
+
- A **vendor list** generated from your settings.
|
|
15
|
+
|
|
16
|
+
## What you still do yourself
|
|
17
|
+
|
|
18
|
+
- Write and publish the privacy policy (and cookie policy if you use one).
|
|
19
|
+
- Keep third-party scripts **out of the theme** unless they go through EmPrivacy or `window.emprivacy.has(...)`.
|
|
20
|
+
- Decide whether embeds are **Marketing** (default, typical for YouTube) or **Functional**.
|
|
13
21
|
|
|
14
22
|
## Before you begin
|
|
15
23
|
|
|
16
|
-
1. An EmDash site on **`emdash@^0.38.0`** with the EmDash Astro integration in `astro.config
|
|
24
|
+
1. An EmDash site on **`emdash@^0.38.0`** with the EmDash Astro integration in `astro.config`.
|
|
17
25
|
2. Ability to edit **`astro.config`** and redeploy (or run locally).
|
|
18
|
-
3.
|
|
26
|
+
3. Register EmPrivacy as a **native** plugin (`plugins: []`, not `sandboxed: []`).
|
|
19
27
|
|
|
20
28
|
---
|
|
21
29
|
|
|
@@ -27,98 +35,95 @@ pnpm add @emplugins/emprivacy
|
|
|
27
35
|
npm install @emplugins/emprivacy
|
|
28
36
|
```
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
Pin a line with a range such as `@emplugins/emprivacy@^1.0.0`. See [RELEASES.md](./RELEASES.md).
|
|
31
39
|
|
|
32
40
|
---
|
|
33
41
|
|
|
34
42
|
## Step 2 — Add the plugin to Astro
|
|
35
43
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
```ts
|
|
45
|
+
import emdash from "emdash/astro";
|
|
46
|
+
import { emprivacyPlugin } from "@emplugins/emprivacy";
|
|
47
|
+
|
|
48
|
+
export default defineConfig({
|
|
49
|
+
integrations: [
|
|
50
|
+
emdash({
|
|
51
|
+
plugins: [
|
|
52
|
+
emprivacyPlugin(),
|
|
53
|
+
// ...other native plugins
|
|
54
|
+
],
|
|
55
|
+
}),
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
```
|
|
44
59
|
|
|
45
|
-
|
|
60
|
+
Do **not** put it in `sandboxed: []`.
|
|
46
61
|
|
|
47
|
-
|
|
48
|
-
emdash({
|
|
49
|
-
plugins: [
|
|
50
|
-
emprivacyPlugin(),
|
|
51
|
-
// ...other plugins
|
|
52
|
-
],
|
|
53
|
-
}),
|
|
54
|
-
```
|
|
62
|
+
### Registration order
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
| Goal | Where to put `emprivacyPlugin()` |
|
|
65
|
+
|------|----------------------------------|
|
|
66
|
+
| Google Consent Mode before other head tags | **First** in `plugins` |
|
|
67
|
+
| Automatic placeholders for `@emdash-cms/plugin-embeds` | **After** the embeds plugin (last wins) |
|
|
68
|
+
| Both | First in `plugins`, and pass `@emplugins/emprivacy/astro` `blockComponents` into `<PortableText />` (site components always win) |
|
|
57
69
|
|
|
58
70
|
---
|
|
59
71
|
|
|
60
72
|
## Step 3 — Layout must include EmDash hooks
|
|
61
73
|
|
|
62
|
-
Your base layout must include
|
|
74
|
+
Your base layout must include `<EmDashHead page={page} />`, `<EmDashBodyStart page={page} />`, and `<EmDashBodyEnd page={page} />`, usually with `createPublicPageContext` from `emdash/page`. Otherwise the banner will not render.
|
|
63
75
|
|
|
64
76
|
---
|
|
65
77
|
|
|
66
|
-
## Step 4 — Create
|
|
67
|
-
|
|
68
|
-
If you already have a **Page** for your privacy policy:
|
|
78
|
+
## Step 4 — Create the legal Pages
|
|
69
79
|
|
|
70
|
-
1.
|
|
71
|
-
2.
|
|
80
|
+
1. Create and **publish** a Page for your privacy policy. Open it on the public site and copy the path (`/privacy`).
|
|
81
|
+
2. Optional: a cookie policy Page (`/cookies`).
|
|
82
|
+
3. In EmPrivacy settings, paste those paths (or full `https://` URLs).
|
|
72
83
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
Same idea for an optional **Cookie policy** field: path (e.g. `/cookies`) or `https://…`.
|
|
76
|
-
|
|
77
|
-
**Not accepted:** bare domains (`example.com`), `http://…` URLs, or protocol-relative links (`//…`). Use **`/your-path`** or **`https://…`**.
|
|
84
|
+
**Not accepted:** `http://`, bare hostnames, or `//example.com/...`.
|
|
78
85
|
|
|
79
86
|
---
|
|
80
87
|
|
|
81
|
-
## Step 5 — Configure
|
|
82
|
-
|
|
83
|
-
1. Log into the **site admin**.
|
|
84
|
-
2. Open **EmPrivacy** (shield icon).
|
|
85
|
-
3. Set:
|
|
86
|
-
- **Privacy policy** — path or `https` (see Step 4).
|
|
87
|
-
- **Cookie policy** — optional; same rules.
|
|
88
|
-
- **Banner title** and **Short notice**.
|
|
89
|
-
- **Policy / consent version** — bump when you change legal text so visitors see the banner again.
|
|
90
|
-
- **Analytics platform** — *Cloudflare Web Analytics* (default), *None*, or *Custom* (see below).
|
|
91
|
-
- **Cloudflare** — In the [Cloudflare dashboard](https://dash.cloudflare.com/) → **Web Analytics**, copy the **site token** for your property and paste it into **Cloudflare Web Analytics site token**. EmPrivacy injects the standard `beacon.min.js` script with `data-cf-beacon='{"token":"…"}'` after the visitor consents to analytics.
|
|
92
|
-
- **Custom analytics** — If you pick **Custom**, one **`https://`** script URL per line, `src` only (not a full `<script>` block); see [Custom script URLs (not HTML)](#custom-script-urls-not-html) below.
|
|
93
|
-
- **Marketing script URLs** — one **`https://`** URL per line, or leave blank.
|
|
94
|
-
4. Optional: **Strict defaults**, **Google Consent Mode v2**, **Log consent to server** (see main README).
|
|
95
|
-
5. **Save settings**.
|
|
88
|
+
## Step 5 — Configure in the admin
|
|
96
89
|
|
|
97
|
-
|
|
90
|
+
1. Log into **site admin** → shield → **EmPrivacy**.
|
|
91
|
+
2. Set banner title / short notice, policy paths, and **Policy / consent version**.
|
|
92
|
+
3. Choose an **Analytics platform** and fill the matching ID or token. Leave it on **None** if you do not use analytics.
|
|
93
|
+
4. Optional: marketing script URLs (one `https://` `src` per line — never a `<script>` tag).
|
|
94
|
+
5. Optional: gate embeds; pick Functional vs Marketing.
|
|
95
|
+
6. Optional: hex theme colors, locale JSON overrides, Google Consent Mode, server log.
|
|
96
|
+
7. **Save settings**.
|
|
98
97
|
|
|
99
|
-
|
|
98
|
+
The **What this site uses** table on the same page is generated from the saved config. You can copy it into the cookie policy. Tokens are never listed.
|
|
100
99
|
|
|
101
100
|
---
|
|
102
101
|
|
|
103
102
|
## Step 6 — Verify on the public site
|
|
104
103
|
|
|
105
|
-
1.
|
|
106
|
-
2.
|
|
107
|
-
3.
|
|
108
|
-
4.
|
|
109
|
-
5.
|
|
104
|
+
1. Incognito window (or clear `emprivacy_cc`).
|
|
105
|
+
2. Banner appears. **Reject non-essential**: no analytics/marketing scripts in Network; embed placeholders stay placeholders.
|
|
106
|
+
3. **Accept all** (or Customize): scripts for allowed categories load; YouTube/Vimeo iframes use allowlisted player hosts after consent.
|
|
107
|
+
4. Privacy / cookie links open the correct Page.
|
|
108
|
+
5. Cookie button (bottom-left) reopens the panel. Saving reloads.
|
|
109
|
+
6. Optional: `window.emprivacy.get()` in the console returns the current state.
|
|
110
110
|
|
|
111
111
|
---
|
|
112
112
|
|
|
113
|
-
##
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
## Using the public API in a theme
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
if (window.emprivacy?.has("analytics")) {
|
|
117
|
+
// load a first-party or plugin script you own
|
|
118
|
+
}
|
|
119
|
+
window.emprivacy?.onChange((state) => {
|
|
120
|
+
if (state.marketing) {
|
|
121
|
+
// …
|
|
122
|
+
}
|
|
123
|
+
});
|
|
119
124
|
```
|
|
120
125
|
|
|
121
|
-
|
|
126
|
+
Do not read `emprivacy_cc` yourself. The JSON shape can change with a major release.
|
|
122
127
|
|
|
123
128
|
---
|
|
124
129
|
|
|
@@ -126,19 +131,18 @@ For a full staging checklist (banner, strict mode, admin, optional logging, Goog
|
|
|
126
131
|
|
|
127
132
|
| Problem | What to check |
|
|
128
133
|
|--------|----------------|
|
|
129
|
-
| Nothing appears |
|
|
130
|
-
| Scripts load before consent |
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
| Save fails
|
|
134
|
-
|
|
|
135
|
-
|
|
|
134
|
+
| Nothing appears | Layout missing head/body components; plugin not in `plugins: []`; server not restarted |
|
|
135
|
+
| Scripts load before consent | Theme still contains tracker snippets; another plugin is listed **before** EmPrivacy and injects tags immediately |
|
|
136
|
+
| YouTube still loads immediately | Embeds plugin is registered **after** EmPrivacy, so its renderers win. Move EmPrivacy last or pass `blockComponents` on `PortableText`. |
|
|
137
|
+
| Embeds stay blocked after accept | Category is Marketing but you only enabled Functional (or the reverse). |
|
|
138
|
+
| Save fails on policy URL | Must be `/path` or `https://…` |
|
|
139
|
+
| Save fails on analytics ID | Plausible wants `example.com` (no scheme). GA4 wants `G-…`. GTM wants `GTM-…`. Umami needs an https script URL. |
|
|
140
|
+
| Privacy link 404 | Path must match the live Page route |
|
|
136
141
|
|
|
137
142
|
---
|
|
138
143
|
|
|
139
144
|
## More reading
|
|
140
145
|
|
|
141
|
-
- Main **[README](../README.md)** —
|
|
142
|
-
- **[
|
|
143
|
-
- **[
|
|
144
|
-
- **[CONTRIBUTING.md](../CONTRIBUTING.md)** — how to contribute
|
|
146
|
+
- Main **[README](../README.md)** — does / does not, disclaimer, API
|
|
147
|
+
- **[PLUGIN_SETTINGS.md](./PLUGIN_SETTINGS.md)** — every admin field
|
|
148
|
+
- **[TESTING.md](./TESTING.md)** — staging QA
|
package/docs/PLUGIN_SETTINGS.md
CHANGED
|
@@ -2,63 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
# EmPrivacy — Plugin settings reference
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Every field on **EmPrivacy** (shield → **EmPrivacy**) in the EmDash admin.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
EmPrivacy is a technical consent tool, not legal advice. You are responsible for matching categories and vendors to your privacy/cookie policies.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Plugin version
|
|
12
|
+
|
|
13
|
+
- **Installed version**: the release running on this site
|
|
14
|
+
- **Latest on npm**: current [`@emplugins/emprivacy`](https://www.npmjs.com/package/@emplugins/emprivacy)
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
The rest of the page still loads if npm cannot be reached.
|
|
14
17
|
|
|
15
18
|
---
|
|
16
19
|
|
|
17
|
-
## Banner
|
|
20
|
+
## Banner copy and language
|
|
18
21
|
|
|
19
|
-
### Banner title
|
|
22
|
+
### Banner title / Short notice (fallback locale)
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
- **Example**: `Cookies & privacy`
|
|
23
|
-
- **Behavior**: Updates visible banner UI copy only (no effect on consent logic).
|
|
24
|
+
Visible heading and body. Applied with `textContent` only (no HTML). Max 120 / 600 characters.
|
|
24
25
|
|
|
25
|
-
###
|
|
26
|
+
### Fallback locale code
|
|
26
27
|
|
|
27
|
-
-
|
|
28
|
-
- **Example**: `We use cookies to run this site and optionally for analytics and marketing. You can accept or customize your choices.`
|
|
29
|
-
- **Behavior**: Updates visible banner UI copy only (no effect on consent logic).
|
|
28
|
+
Used when the page has no locale or no built-in chrome pack. Example: `en`. Button labels ship for `en`, `de`, `fr`, `es`, `it`, `nl`, `pt`, `pl` (including `pt-BR` → `pt`).
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
### Optional title/message translations (JSON)
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"de": {
|
|
35
|
+
"bannerTitle": "Cookies & Datenschutz",
|
|
36
|
+
"bannerMessage": "Wir verwenden Cookies, um die Website zu betreiben."
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
32
40
|
|
|
33
|
-
|
|
41
|
+
Keys must look like `en` or `pt-BR`. At most 32 locales. Invalid JSON is rejected on save.
|
|
34
42
|
|
|
35
|
-
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Policy links
|
|
36
46
|
|
|
37
47
|
### Privacy policy — EmDash Page path or https URL
|
|
38
48
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- `http://…`
|
|
47
|
-
- `example.com/privacy` (missing scheme)
|
|
48
|
-
- `//example.com/privacy` (protocol-relative)
|
|
49
|
-
- paths without a leading slash like `privacy`
|
|
50
|
-
- **Behavior**:
|
|
51
|
-
- Controls where the “Privacy policy” link in the banner opens.
|
|
52
|
-
- Does not change consent cookie format or script loading.
|
|
53
|
-
|
|
54
|
-
### Cookie policy (optional) — path or https URL
|
|
55
|
-
|
|
56
|
-
- **What it is**: Optional link to a separate cookie policy.
|
|
57
|
-
- **Allowed inputs**: same rules as the privacy policy field.
|
|
58
|
-
- **Example**: `/cookies` or `https://example.com/cookies`
|
|
59
|
-
- **Behavior**:
|
|
60
|
-
- When set, the banner shows an additional “Cookie policy” link.
|
|
61
|
-
- When blank, no cookie-policy link is shown.
|
|
49
|
+
Required. `/privacy` or `https://example.com/privacy`. Not `http://`, not `example.com/privacy`, not `//…`.
|
|
50
|
+
|
|
51
|
+
### Cookie policy (optional)
|
|
52
|
+
|
|
53
|
+
Same rules. Blank = no extra link.
|
|
54
|
+
|
|
55
|
+
Paths are resolved with EmDash `ctx.url()` so they stay correct across environments.
|
|
62
56
|
|
|
63
57
|
---
|
|
64
58
|
|
|
@@ -66,116 +60,126 @@ These links are rendered as “Privacy policy” (and optionally “Cookie polic
|
|
|
66
60
|
|
|
67
61
|
### Policy / consent version
|
|
68
62
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- `2026-04-25`
|
|
73
|
-
- `v3`
|
|
74
|
-
- **Behavior**:
|
|
75
|
-
- EmPrivacy stores consent as a cookie like `{ v, a, m }` where `v` is your policy version and `a/m` represent analytics/marketing choices.
|
|
76
|
-
- If a visitor has an older cookie version (or no cookie), the banner will show again.
|
|
77
|
-
- Use this when you materially change your privacy/cookie policy text, tracking vendors, or categories.
|
|
63
|
+
Stored in the cookie as `v`. Change it when legal text or vendors change so returning visitors are asked again.
|
|
64
|
+
|
|
65
|
+
The cookie is JSON `{ v, f, a, m }`. Cookies from older EmPrivacy releases that omit `f` are treated as missing, so the banner shows once after you upgrade.
|
|
78
66
|
|
|
79
67
|
---
|
|
80
68
|
|
|
81
69
|
## Defaults and consent UX
|
|
82
70
|
|
|
83
|
-
### Strict defaults
|
|
71
|
+
### Strict defaults
|
|
72
|
+
|
|
73
|
+
On: Functional, Analytics, and Marketing start **unchecked**. Off: they start **checked** (opt-out style). Stored choices always win after the first save.
|
|
74
|
+
|
|
75
|
+
### Hide the first-visit banner on privacy and cookie policy pages
|
|
84
76
|
|
|
85
|
-
|
|
86
|
-
- **Example**:
|
|
87
|
-
- Enabled: analytics + marketing start **off**
|
|
88
|
-
- Disabled: analytics + marketing start **on**
|
|
89
|
-
- **Behavior**:
|
|
90
|
-
- **Enabled**: The initial banner loads with analytics and marketing toggles **unchecked** by default.
|
|
91
|
-
- **Disabled**: The initial banner loads with analytics and marketing toggles **checked** by default.
|
|
92
|
-
- This only affects the initial default state before the visitor makes a choice; once a cookie is set, the stored choice is used.
|
|
77
|
+
On (default): visitors who open `/privacy` from the banner can read it without another overlay. The cookie button still appears so they can change choices.
|
|
93
78
|
|
|
94
79
|
---
|
|
95
80
|
|
|
96
|
-
## Analytics
|
|
81
|
+
## Analytics platform
|
|
82
|
+
|
|
83
|
+
Scripts for **Analytics** presets load only after **Analytics** consent — except **Google Tag Manager**, which loads only after **Marketing** consent (containers commonly fire ads and remarketing).
|
|
84
|
+
|
|
85
|
+
| Value | What you enter | What loads |
|
|
86
|
+
|-------|----------------|------------|
|
|
87
|
+
| Cloudflare Web Analytics | Site token | `beacon.min.js` + `data-cf-beacon` |
|
|
88
|
+
| Plausible | Hostname `example.com` (no `https://`) | `https://plausible.io/js/script.js` with `data-domain` |
|
|
89
|
+
| Fathom | Site ID (letters/digits) | `https://cdn.usefathom.com/script.js` with `data-site` |
|
|
90
|
+
| Umami | Website ID **and** https script URL | Your tracker URL with `data-website-id` |
|
|
91
|
+
| Simple Analytics | Optional hostname | `https://scripts.simpleanalyticscdn.com/latest.js` |
|
|
92
|
+
| Google Analytics 4 | `G-…` measurement ID | `https://www.googletagmanager.com/gtag/js?id=…` then `gtag('config', …)` |
|
|
93
|
+
| Google Tag Manager | `GTM-…` | `https://www.googletagmanager.com/gtm.js?id=…` after **Marketing** consent. Tags inside the container are still your responsibility. |
|
|
94
|
+
| None | — | No analytics scripts |
|
|
95
|
+
| Custom | One https script `src` per line (optional trailing SRI) | Those URLs only |
|
|
96
|
+
|
|
97
|
+
IDs and URLs are validated on save and again in the browser before inject. Tokens are never shown in the public vendor list.
|
|
98
|
+
|
|
99
|
+
### Script host allowlist (optional)
|
|
100
|
+
|
|
101
|
+
One hostname per line (e.g. `cdn.example.com`). When non-empty, **Custom** analytics, **Umami**, and **Marketing** script hosts must appear on the list. Built-in preset CDNs (Plausible, Fathom, GTM, etc.) stay allowed for their presets.
|
|
102
|
+
|
|
103
|
+
### Optional Subresource Integrity (SRI)
|
|
97
104
|
|
|
98
|
-
|
|
105
|
+
Append a hash after the URL on Custom or Marketing lines:
|
|
99
106
|
|
|
100
|
-
|
|
107
|
+
```
|
|
108
|
+
https://cdn.example/a.js sha384-…
|
|
109
|
+
https://cdn.example/b.js integrity=sha256-…
|
|
110
|
+
```
|
|
101
111
|
|
|
102
|
-
|
|
103
|
-
- **Options**:
|
|
104
|
-
- **Cloudflare Web Analytics**: loads Cloudflare’s beacon **after Analytics consent** (requires a site token).
|
|
105
|
-
- **None**: never loads third-party analytics scripts via EmPrivacy.
|
|
106
|
-
- **Custom**: loads your provided `https://` script URLs **after Analytics consent**.
|
|
107
|
-
- **Behavior**:
|
|
108
|
-
- Changing this alters which scripts are injected when Analytics consent is granted.
|
|
109
|
-
- This does not affect Marketing scripts (those are separate).
|
|
112
|
+
The browser sets `integrity` + `crossorigin=anonymous` when the hash is valid. Prefer pinning Custom scripts; many preset CDNs do not publish stable hashes.
|
|
110
113
|
|
|
111
|
-
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Marketing scripts
|
|
112
117
|
|
|
113
|
-
|
|
114
|
-
- **Example**: a Cloudflare token string copied from your dashboard (do not add quotes).
|
|
115
|
-
- **Behavior**:
|
|
116
|
-
- When Analytics consent is granted and **Analytics platform = Cloudflare**, EmPrivacy injects Cloudflare’s standard beacon script with the `data-cf-beacon` attribute.
|
|
117
|
-
- If the token is blank, EmPrivacy will have nothing to inject for Cloudflare analytics even if consent is granted.
|
|
118
|
+
One **https** URL per line, `src` only (optional SRI as above). Loaded after **Marketing** consent. Max 50 lines, 2048 characters each. No HTML.
|
|
118
119
|
|
|
119
|
-
|
|
120
|
+
---
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
- **Format**: one full **`https://…`** URL per line (the script `src`), no HTML.
|
|
123
|
-
- **Example**:
|
|
122
|
+
## Google Consent Mode v2
|
|
124
123
|
|
|
125
|
-
|
|
126
|
-
https://cdn.example.com/analytics.js
|
|
127
|
-
https://static.example.net/vendor/tracker.min.js
|
|
128
|
-
```
|
|
124
|
+
When enabled, a denied-by-default snippet runs in `<head>` (`analytics_storage`, `ad_*`, `functionality_storage`, `personalization_storage`). After a choice, EmPrivacy calls `gtag('consent','update', …)`. `security_storage` stays granted. Validate with [Google’s docs](https://support.google.com/tagmanager/answer/13695607).
|
|
129
125
|
|
|
130
|
-
|
|
131
|
-
- When Analytics consent is granted, EmPrivacy injects each URL as `<script src="…">` into the document.
|
|
132
|
-
- When Analytics consent is denied, these scripts are not injected by EmPrivacy.
|
|
126
|
+
Put EmPrivacy **first** in `plugins` if other plugins also inject Google tags.
|
|
133
127
|
|
|
134
128
|
---
|
|
135
129
|
|
|
136
|
-
##
|
|
130
|
+
## Embeds
|
|
131
|
+
|
|
132
|
+
### Gate official EmDash embed blocks
|
|
133
|
+
|
|
134
|
+
When on (default), EmPrivacy’s Portable Text components replace YouTube, Vimeo, tweet, Bluesky, Mastodon, Gist, and link-preview blocks with a placeholder until the selected category is allowed.
|
|
137
135
|
|
|
138
|
-
|
|
136
|
+
YouTube / Vimeo become allowlisted iframes (`youtube-nocookie.com`, `player.vimeo.com`). Social posts, Gists, and link previews become **https links** — EmPrivacy will not iframe an arbitrary Mastodon or unknown host.
|
|
139
137
|
|
|
140
|
-
|
|
138
|
+
This does **not** rewrite raw HTML `<iframe>` tags you paste in the theme or in an HTML block.
|
|
141
139
|
|
|
142
|
-
|
|
143
|
-
- **Format**: one full **`https://…`** URL per line (the script `src`), no HTML.
|
|
144
|
-
- **Example**:
|
|
140
|
+
### Embeds require this category
|
|
145
141
|
|
|
146
|
-
|
|
147
|
-
https://cdn.example.com/pixel.js
|
|
148
|
-
https://static.example.net/ads/retargeting.js
|
|
149
|
-
```
|
|
142
|
+
**Marketing** (default) or **Functional**. Pick Marketing for YouTube/social if those vendors set advertising cookies.
|
|
150
143
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
144
|
+
**Allow embeds and load** opens the cookie preferences panel with a short hint. It does **not** silently grant Functional or Marketing.
|
|
145
|
+
|
|
146
|
+
Registration order: see [GETTING_STARTED](./GETTING_STARTED.md#registration-order).
|
|
154
147
|
|
|
155
148
|
---
|
|
156
149
|
|
|
157
|
-
##
|
|
150
|
+
## Consent cookie lifetime
|
|
151
|
+
|
|
152
|
+
### Consent cookie lifetime (days)
|
|
153
|
+
|
|
154
|
+
`Max-Age` for `emprivacy_cc`, **1–365**, default **180**. Keep this aligned with how long you need to remember a choice under your policy.
|
|
155
|
+
|
|
156
|
+
---
|
|
158
157
|
|
|
159
|
-
|
|
158
|
+
## Theme
|
|
160
159
|
|
|
161
|
-
|
|
162
|
-
- **Behavior**:
|
|
163
|
-
- When enabled, EmPrivacy emits a “denied by default” consent configuration early (in `<head>`).
|
|
164
|
-
- After the visitor chooses, EmPrivacy calls `gtag("consent","update", …)` to reflect Analytics/Marketing choices.
|
|
165
|
-
- This is only meaningful if your site uses `gtag`/Google tags; you should validate behavior using Google tooling/docs.
|
|
160
|
+
Hex colors only (`#111`, `#111111`). Corner radius is an integer **0–24**. Values become CSS variables on `#emprivacy-root`. Other CSS is ignored (no `url()`, no `expression`).
|
|
166
161
|
|
|
167
162
|
---
|
|
168
163
|
|
|
169
|
-
## Server-side logging
|
|
164
|
+
## Server-side logging
|
|
170
165
|
|
|
171
|
-
|
|
166
|
+
When enabled, each save POSTs `{ policyVersion, functional, analytics, marketing }` to `/_emdash/api/plugins/emprivacy/record` (same origin, JSON). The handler requires a matching `Origin` **or** `Sec-Fetch-Site: same-origin`, fails closed if storage capacity cannot be checked, and rate-limits anonymous clients (~30/hour fingerprint). No IP is stored in the consent row. The page shows up to 15 recent rows. Logging stops at 500 rows.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Content Security Policy
|
|
171
|
+
|
|
172
|
+
EmPrivacy injects an **inline** bootstrap via EmDash `page:fragments`. Sites with a strict CSP must allow that script, typically:
|
|
173
|
+
|
|
174
|
+
- `script-src 'unsafe-inline'` (or a hash/nonce of the emitted bootstrap if your host supports it), and
|
|
175
|
+
- `script-src` / `connect-src` / `frame-src` entries for any presets and gated embeds you enable (e.g. `https://plausible.io`, `https://www.youtube-nocookie.com`, `https://player.vimeo.com`).
|
|
176
|
+
|
|
177
|
+
YouTube/Vimeo placeholders use a tight iframe `sandbox` (`allow-scripts allow-same-origin allow-presentation`) after consent. Client hydration also re-checks allowlisted `data-src` values so a planted evil URL cannot become an iframe.
|
|
178
|
+
|
|
179
|
+
A future hashed static bootstrap (no `'unsafe-inline'`) depends on EmDash fragment APIs exposing nonces or external script URLs.
|
|
180
|
+
|
|
181
|
+
---
|
|
172
182
|
|
|
173
|
-
|
|
174
|
-
- **Behavior**:
|
|
175
|
-
- When enabled, the browser sends a POST to the EmPrivacy record endpoint with:
|
|
176
|
-
- the current policy version
|
|
177
|
-
- whether analytics was allowed
|
|
178
|
-
- whether marketing was allowed
|
|
179
|
-
- The settings page will additionally show **Recent consent records** (up to 15), newest first.
|
|
180
|
-
- When disabled, EmPrivacy does not attempt to log consent server-side and the “Recent consent records” section will not appear.
|
|
183
|
+
## What this site uses
|
|
181
184
|
|
|
185
|
+
Read-only table generated from the saved config. Same data as `GET /_emdash/api/plugins/emprivacy/vendors`. Use it in your cookie policy. It never includes tokens or measurement IDs.
|
package/docs/RELEASE_NOTES.md
CHANGED
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
## Changes
|
|
6
6
|
|
|
7
|
+
_(none)_
|
|
8
|
+
|
|
9
|
+
# emprivacy v2.1.0
|
|
10
|
+
|
|
11
|
+
## Changes
|
|
12
|
+
|
|
13
|
+
- **Publisher consent features** — Functional category (`f` in `emprivacy_cc`; older cookies without `f` re-prompt), `window.emprivacy` API, gated official EmDash embed placeholders (`@emplugins/emprivacy/astro`), banner i18n + hex theme tokens, analytics presets (Plausible, Fathom, Umami, Simple Analytics, GA4, GTM), generated vendor list (admin + public `/vendors` route). Cookie JSON is now `{ v, f, a, m }`. Admin shows installed vs latest npm version.
|
|
14
|
+
- **Security hardening** — Client re-validates embed `data-src` and script URLs before inject; tighter YouTube/Vimeo iframe `sandbox`; GTM loads under **Marketing**; optional script host allowlist + SRI on Custom/Marketing URLs; configurable consent cookie TTL (default 180 days); soft-consent opens preferences instead of silently granting; `/record` requires Origin or `Sec-Fetch-Site: same-origin`, fails closed on storage errors, and rate-limits writes. CSP notes in [PLUGIN_SETTINGS](./PLUGIN_SETTINGS.md#content-security-policy).
|
|
15
|
+
|
|
16
|
+
# emprivacy v2.0.0
|
|
17
|
+
|
|
18
|
+
## Changes
|
|
19
|
+
|
|
7
20
|
- Compatibility: targets **EmDash `^0.38.0`** (tested with **`0.38.0`**); Node.js **`>= 22.16`**.
|
|
8
21
|
- **Native plugin** — Switched from standard/sandbox entry packaging to EmDash’s native contract: `format: "native"`, named **`createPlugin()`** export, single-argument **`RouteContext`** route handlers, and no `./sandbox` export. Register **`emprivacyPlugin()`** from **`@emplugins/emprivacy`** in **`plugins: []`** (required for `page:fragments`).
|
|
9
22
|
- Docs/README updated for scoped package install, `import emdash from "emdash/astro"`, layout page context, and native trust boundary.
|