@emplugins/emprivacy 1.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.
@@ -6,6 +6,8 @@
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`. The `./astro` export ships Portable Text embed placeholders (compiled by the host Astro app, not by `tsdown`).
10
+
9
11
  ## Package manager
10
12
 
11
13
  This repo uses **pnpm** (`packageManager` in `package.json`). Install and CI:
@@ -19,18 +21,12 @@ pnpm run build
19
21
 
20
22
  ## `pnpm audit` and the Kysely override
21
23
 
22
- The `emdash` dev dependency pulls in a SQL stack that historically resolved **`kysely@0.27.x`**, which is affected by advisories (for example [GHSA-wmrf-hv6w-mr66](https://github.com/advisories/GHSA-wmrf-hv6w-mr66) and [GHSA-8cpq-38p9-67gx](https://github.com/advisories/GHSA-8cpq-38p9-67gx)) until patched releases.
24
+ `pnpm run audit` runs **`pnpm audit --prod`**, so it reflects **published runtime** dependencies only (currently `zod` 4.6.5).
23
25
 
24
- **This repository pins a safe Kysely** via `pnpm.overrides` in `package.json` (currently `0.29.2`, above the patched range for known advisories). Regenerate the lockfile with `pnpm install` after changing overrides.
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.
25
27
 
26
- - Full dev tree: `pnpm audit` should report **0 vulnerabilities** after a clean `pnpm install --frozen-lockfile`. If upstream `emdash` introduces new transitive advisories, track them here and adjust `pnpm.overrides` when a patched release exists.
27
- - Production install only: `pnpm audit --omit=dev` reflects **only** published runtime deps (currently `zod`).
28
+ **This repository pins Kysely** via `pnpm.overrides` in `package.json` (currently `0.29.6`, aligned with EmDash 0.38). Regenerate the lockfile with `pnpm install` after changing overrides.
28
29
 
29
30
  ## Deprecation warnings you may still see
30
31
 
31
- `pnpm install` can still print **deprecated** messages for **transitive** packages **not** maintained by this repo, for example:
32
-
33
- - **`prebuild-install`** — pulled in by `better-sqlite3` (used by `emdash`). The [package is deprecated](https://www.npmjs.com/package/prebuild-install) pending upstream changes in native addons.
34
- - **`node-domexception`** — used under the [libsql / fetch](https://www.npmjs.com/package/node-domexception) client stack; the package is deprecated in favor of native `DOMException` in newer Node.
35
-
36
- Those warnings do not imply that EmPrivacy’s own source is outdated; they track **upstream** `emdash` and its dependencies. Re-run `pnpm audit` to confirm there are no known vulnerabilities for your install.
32
+ `pnpm install` can still print **deprecated** messages for **transitive** packages **not** maintained by this repo (for example native-addon helpers under EmDash’s SQLite stack). Those warnings do not imply that EmPrivacy’s own source is outdated; they track **upstream** `emdash` and its dependencies.
@@ -2,119 +2,128 @@
2
2
 
3
3
  # Getting started with EmPrivacy
4
4
 
5
- This guide is for site builders who want cookie consent working on an **EmDash** site without reading plugin source.
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** and **Marketing** (essential/theme cookies are not blocked by this plugin—see the main README).
11
- - **Privacy and cookie policy links** you control: usually an EmDash **Page** via a short **path** (e.g. `/privacy`) or a full `https://` link. Paths are turned into full URLs using EmDash’s URL helper so they work in dev and production.
12
- - **Analytics** — choose a **platform** in admin (e.g. **Cloudflare Web Analytics** and your **site token**, or **Custom** with one `https://` script URL per line, or **None**). **Marketing** — `https` script URLs, one per line. Scripts load **after** consent for that category.
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 with `emdash` in `astro.config` ([plugins overview](https://docs.emdashcms.com/plugins/overview/)).
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).
26
+ 3. Register EmPrivacy as a **native** plugin (`plugins: []`, not `sandboxed: []`).
18
27
 
19
28
  ---
20
29
 
21
30
  ## Step 1 — Install the package
22
31
 
23
32
  ```bash
33
+ pnpm add @emplugins/emprivacy
34
+ # or
24
35
  npm install @emplugins/emprivacy
25
36
  ```
26
37
 
27
- The package is versioned with **semver** on npm; to pin a line, use a range such as `@emplugins/emprivacy@^1.0.0`. See [RELEASES.md](./RELEASES.md) for how releases are numbered.
38
+ Pin a line with a range such as `@emplugins/emprivacy@^1.0.0`. See [RELEASES.md](./RELEASES.md).
28
39
 
29
40
  ---
30
41
 
31
42
  ## Step 2 — Add the plugin to Astro
32
43
 
33
- Open `astro.config.mjs` (or `.ts`).
34
-
35
- 1. Import the plugin:
36
-
37
- ```ts
38
- import { emprivacyPlugin } from "@emplugins/emprivacy";
39
- ```
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
+ ```
40
59
 
41
- 2. Add **`emprivacyPlugin()`** to **`emdash({ plugins: [...] })`**. Put **EmPrivacy first** if you use other plugins that inject scripts or metadata.
60
+ Do **not** put it in `sandboxed: []`.
42
61
 
43
- ```ts
44
- emdash({
45
- plugins: [
46
- emprivacyPlugin(),
47
- // ...other plugins
48
- ],
49
- }),
50
- ```
62
+ ### Registration order
51
63
 
52
- 3. Save and restart the dev server (or rebuild).
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) |
53
69
 
54
70
  ---
55
71
 
56
72
  ## Step 3 — Layout must include EmDash hooks
57
73
 
58
- Your base layout must include EmDash’s head/body injection points (e.g. `<EmDashHead />`, `<EmDashBodyStart />`, `<EmDashBodyEnd />` or your theme’s equivalents). Otherwise the banner may not render. See [EmDash docs](https://docs.emdashcms.com/).
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.
59
75
 
60
76
  ---
61
77
 
62
- ## Step 4 — Create or pick your Privacy Policy Page (EmDash)
63
-
64
- If you already have a **Page** for your privacy policy:
78
+ ## Step 4 — Create the legal Pages
65
79
 
66
- 1. Open that Page on the **public site** (or preview) and look at the **path** in the address bar — for example `https://yoursite.com/privacy` → use **`/privacy`**.
67
- 2. In **EmPrivacy** settings, paste that path into **Privacy policy — EmDash Page path or https URL** (or paste a full `https://…` URL if the policy is not an EmDash Page).
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).
68
83
 
69
- If you still need a Page: create and publish a Page in EmDash, note its URL path, then enter that path in EmPrivacy as above.
70
-
71
- Same idea for an optional **Cookie policy** field: path (e.g. `/cookies`) or `https://…`.
72
-
73
- **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/...`.
74
85
 
75
86
  ---
76
87
 
77
- ## Step 5 — Configure EmPrivacy in the admin
78
-
79
- 1. Log into the **site admin**.
80
- 2. Open **EmPrivacy** (shield icon).
81
- 3. Set:
82
- - **Privacy policy** — path or `https` (see Step 4).
83
- - **Cookie policy** — optional; same rules.
84
- - **Banner title** and **Short notice**.
85
- - **Policy / consent version** — bump when you change legal text so visitors see the banner again.
86
- - **Analytics platform** — *Cloudflare Web Analytics* (default), *None*, or *Custom* (see below).
87
- - **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.
88
- - **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.
89
- - **Marketing script URLs** — one **`https://`** URL per line, or leave blank.
90
- 4. Optional: **Strict defaults**, **Google Consent Mode v2**, **Log consent to server** (see main README).
91
- 5. **Save settings**.
88
+ ## Step 5 — Configure in the admin
92
89
 
93
- **Custom script URLs (not HTML)** (only when **Analytics** is set to **Custom**)
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**.
94
97
 
95
- Each line must be a **full https URL** to a `.js` file (the script `src`). **Do not** paste HTML comments, tags, or attributes. EmPrivacy injects with `<script src="…">` only (no `data-*` for custom mode).
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.
96
99
 
97
100
  ---
98
101
 
99
102
  ## Step 6 — Verify on the public site
100
103
 
101
- 1. Use a **private/incognito** window (or clear site cookies).
102
- 2. Confirm the **banner** appears. After you save a choice, a small **cookie button** stays in the bottom-left; click it to **change** analytics/marketing choices (the page reloads when you save from that panel so consent stays in sync with loaded scripts).
103
- 3. Click **Privacy policy** (and **Cookie policy** if set): they should open the EmDash Page or external URL you configured.
104
- 4. In **Developer tools → Network**: before accepting, listed third-party scripts should not load; after consent, they should load only for the categories you allowed.
105
- 5. Reload: the banner should stay dismissed if the cookie matches your **Policy / consent version**.
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.
106
110
 
107
111
  ---
108
112
 
109
- ## Step 7 — Testing before go-live
110
-
111
- Run automated checks if you develop the plugin or CI:
112
-
113
- ```bash
114
- npm run typecheck && npm run build && npm test
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
+ });
115
124
  ```
116
125
 
117
- For a full staging checklist (banner, strict mode, admin, optional logging, Google), see **[TESTING.md](./TESTING.md)**.
126
+ Do not read `emprivacy_cc` yourself. The JSON shape can change with a major release.
118
127
 
119
128
  ---
120
129
 
@@ -122,19 +131,18 @@ For a full staging checklist (banner, strict mode, admin, optional logging, Goog
122
131
 
123
132
  | Problem | What to check |
124
133
  |--------|----------------|
125
- | Nothing appears | EmDash layout missing head/body components; plugin not in `plugins: []`; dev server restarted after config change |
126
- | Scripts load before consent | No hard-coded third-party scripts in the theme; EmPrivacy listed **before** those plugins in `plugins` |
127
- | EmPrivacy missing in admin | Plugin not installed or `astro.config` wrong; check build errors |
128
- | Save fails on privacy/cookie | Value must be a **root-relative path** (`/something`) or **`https://…`** (not `http://` or a bare hostname) |
129
- | Save fails: “Invalid … script URL” | For **Custom** analytics or **Marketing** URLs, enter one **https URL** per line (the script `src`), not a `<script>` tag. See [Custom script URLs (not HTML)](#custom-script-urls-not-html) |
130
- | Privacy link 404 or wrong page | Path must match the live EmDash route (compare with the address bar when viewing that Page) |
131
- | Link works on staging but path looks wrong | Paths are resolved with EmDash `ctx.url()`; ensure the Page is published and the path matches production routing |
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 |
132
141
 
133
142
  ---
134
143
 
135
144
  ## More reading
136
145
 
137
- - Main **[README](../README.md)** — disclaimer, features table, registration order, full testing section
138
- - **[TESTING.md](./TESTING.md)** — pre-deploy QA and CI
139
- - **[PLUGIN_SETTINGS.md](./PLUGIN_SETTINGS.md)** — what each admin setting does (with examples)
140
- - **[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
@@ -2,63 +2,57 @@
2
2
 
3
3
  # EmPrivacy — Plugin settings reference
4
4
 
5
- This page documents every field on the **EmPrivacy** admin settings page (shield icon → **EmPrivacy**).
5
+ Every field on **EmPrivacy** (shield → **EmPrivacy**) in the EmDash admin.
6
6
 
7
- It’s written for site owners and maintainers who want to understand:
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
- - What each setting **does**
10
- - What input format is expected (with **examples**)
11
- - What public-site behavior changes when you change it
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
- > Note: EmPrivacy is a technical implementation tool, not legal advice. You are responsible for configuring categories and scripts consistently with your privacy/cookie policies and applicable laws.
16
+ The rest of the page still loads if npm cannot be reached.
14
17
 
15
18
  ---
16
19
 
17
- ## Banner settings
20
+ ## Banner copy and language
18
21
 
19
- ### Banner title
22
+ ### Banner title / Short notice (fallback locale)
20
23
 
21
- - **What it is**: The heading shown at the top of the cookie banner and the reopen panel.
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
- ### Short notice
26
+ ### Fallback locale code
26
27
 
27
- - **What it is**: The banner message text shown under the title.
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
- ## Policy links (shown in the banner)
41
+ Keys must look like `en` or `pt-BR`. At most 32 locales. Invalid JSON is rejected on save.
34
42
 
35
- These links are rendered as “Privacy policy” (and optionally “Cookie policy”) in the banner and reopen panel.
43
+ ---
44
+
45
+ ## Policy links
36
46
 
37
47
  ### Privacy policy — EmDash Page path or https URL
38
48
 
39
- - **What it is**: Where your privacy policy lives.
40
- - **Allowed inputs**:
41
- - A **root-relative path** to an EmDash Page (resolved to a full URL by EmDash at runtime), for example:
42
- - `/privacy`
43
- - A full **`https://…`** URL, for example:
44
- - `https://example.com/privacy`
45
- - **Not accepted**:
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
- - **What it is**: A version string stored into the consent cookie. When you change it, existing visitors are prompted again.
70
- - **Example values**:
71
- - `1`
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 (require opt-in for analytics & marketing)
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
- - **What it is**: Controls whether the default state of the banner toggles is **off** (opt-in) or **on** (opt-out).
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 settings
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
- Analytics scripts only load after the visitor consents to **Analytics** (unless you hard-code scripts elsewhere in your site/theme, which you should avoid).
105
+ Append a hash after the URL on Custom or Marketing lines:
99
106
 
100
- ### Analytics platform
107
+ ```
108
+ https://cdn.example/a.js sha384-…
109
+ https://cdn.example/b.js integrity=sha256-…
110
+ ```
101
111
 
102
- - **What it is**: Chooses where analytics scripts come from.
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
- ### Cloudflare Web Analytics site token
114
+ ---
115
+
116
+ ## Marketing scripts
112
117
 
113
- - **What it is**: The token Cloudflare gives you for your site under Cloudflare → Web Analytics.
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
- ### Custom analytics script URLs (one https URL per line, not a `<script>` tag)
120
+ ---
120
121
 
121
- - **What it is**: A list of third-party analytics scripts to load after Analytics consent, when **Analytics platform = Custom**.
122
- - **Format**: one full **`https://…`** URL per line (the script `src`), no HTML.
123
- - **Example**:
122
+ ## Google Consent Mode v2
124
123
 
125
- ```text
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
- - **Behavior**:
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
- ## Marketing settings
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
- Marketing scripts only load after the visitor consents to **Marketing**.
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
- ### Marketing script URLs (one https URL per line, not a `<script>` tag)
138
+ This does **not** rewrite raw HTML `<iframe>` tags you paste in the theme or in an HTML block.
141
139
 
142
- - **What it is**: A list of third-party marketing scripts to load after Marketing consent.
143
- - **Format**: one full **`https://…`** URL per line (the script `src`), no HTML.
144
- - **Example**:
140
+ ### Embeds require this category
145
141
 
146
- ```text
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
- - **Behavior**:
152
- - When Marketing consent is granted, EmPrivacy injects each URL as `<script src="…">`.
153
- - When Marketing consent is denied, these scripts are not injected by EmPrivacy.
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
- ## Google integrations (optional)
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
- ### Google Consent Mode v2 (denied defaults in head; updates after choice)
158
+ ## Theme
160
159
 
161
- - **What it is**: Enables Google Consent Mode v2 integration.
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 (optional)
164
+ ## Server-side logging
170
165
 
171
- ### Log consent choices to the server (minimal record; no IP stored)
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
- - **What it is**: If enabled, EmPrivacy will send a minimal consent snapshot to the server when a visitor saves a choice.
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.
@@ -2,26 +2,24 @@
2
2
 
3
3
  # Unreleased
4
4
 
5
- # @emplugins/emprivacy v1.0.0 (pending Version Packages)
5
+ ## Changes
6
6
 
7
- ## Breaking
7
+ _(none)_
8
8
 
9
- - **npm package renamed** — Install and import **`@emplugins/emprivacy`** ( [emplugins](https://www.npmjs.com/org/emplugins) org ). Legacy unscoped `emprivacy` is no longer the publish target.
10
- - **Sandbox entrypoint** — Descriptor uses `@emplugins/emprivacy/sandbox` (update only if you referenced the old entry string directly).
9
+ # emprivacy v2.1.0
11
10
 
12
- ## Unchanged
11
+ ## Changes
13
12
 
14
- - Runtime plugin id `emprivacy`, cookie `emprivacy_cc`, and API routes.
15
- - Source repository: [EmPlugins/EmPrivacy](https://github.com/EmPlugins/EmPrivacy).
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).
16
15
 
17
- # emprivacy v0.3.0
16
+ # emprivacy v2.0.0
18
17
 
19
18
  ## Changes
20
19
 
21
- - **EmDash `>=0.14.0` required** — peer and dev dependency minimum raised from `^0.9.0`. Sites on EmDash 0.9–0.13 should stay on **emprivacy 0.2.x**.
22
- - **Plugin entry** — `src/sandbox-entry.ts` now default-exports a bare `{ hooks, routes } satisfies SandboxedPlugin` object (types from `emdash/plugin`), matching EmDash 0.13+ standard-format plugins and `@emdash-cms/plugin-audit-log`. No runtime `definePlugin()` import.
23
- - **CI** — matrix targets **EmDash `0.14.0`**.
24
- - **Kysely override** — Bumped to **`0.29.2`** so `npm audit` stays clean with `emdash@0.14.x` (see [docs/DEVELOPMENT.md](DEVELOPMENT.md)).
20
+ - Compatibility: targets **EmDash `^0.38.0`** (tested with **`0.38.0`**); Node.js **`>= 22.16`**.
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`).
22
+ - Docs/README updated for scoped package install, `import emdash from "emdash/astro"`, layout page context, and native trust boundary.
25
23
 
26
24
  # emprivacy v0.2.1
27
25