@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 CHANGED
@@ -2,13 +2,34 @@
2
2
  ![EmPrivacy Logo](./artwork/logo/EmPrivacy.png)
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), and **client-side** loading of third-party scripts only after consent. 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)).
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
30
  - **EmDash** `^0.38.0` (**tested with `0.38.0`**; re-test on your deployed minor when upgrading).
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 scripts; that hook only runs for trusted in-process plugins. See [Page fragments](https://docs.emdashcms.com/plugins/creating-native-plugins/page-fragments/) and [Plugin overview](https://docs.emdashcms.com/plugins/overview/).
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
 
@@ -24,9 +45,9 @@ Use a **semver range** if you want controlled upgrades, for example `@emplugins/
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`) **before** other plugins that inject marketing or analytics, so consent runs first:
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: [emprivacyPlugin()],
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. **Use EmDash page integration** in your layout so fragments render: include `<EmDashHead />`, `<EmDashBodyStart />`, `<EmDashBodyEnd />` (or your theme’s equivalents), typically with a shared `createPublicPageContext({ Astro, … })`. See [EmDash page fragments](https://docs.emdashcms.com/plugins/creating-native-plugins/page-fragments/).
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
- 3. **Configure EmPrivacy in the admin** (shield → **EmPrivacy**):
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
- See [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md) for a step-by-step path from “create a Page” to “verify the link in the banner.”
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
- Visitors see a banner on first visit (or when you bump **Policy / consent version**). Configured analytics (e.g. Cloudflare beacon) and marketing scripts load only **after** the user consents to the matching category.
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 (informational), **Analytics**, **Marketing** |
63
- | Privacy / cookie links | EmDash **Page** as a root path (e.g. `/privacy`) or external `https://` URL; paths resolve via `ctx.url()` in hooks so public URLs stay correct when the site origin changes |
64
- | Analytics platforms | **Cloudflare Web Analytics** — enter your site token; the beacon is injected with `data-cf-beacon` after consent. **None** / **Custom** (`https` URLs) also supported; more platforms can be added over time |
65
- | Marketing scripts | Only **https** URLs you list are injected as `<script src>` after marketing consent—no arbitrary admin HTML |
66
- | Re-open preferences | After the first choice, a **cookie button** (bottom-left) re-opens the same options; saving from that panel **reloads** the page so script loading matches the latest consent (including revoking) |
67
- | Consent cookie | `emprivacy_cc` (`path=/`, `SameSite=Lax`, `Secure` on HTTPS), JSON `{ v, a, m }` (version, analytics, marketing) |
68
- | Optional server log | POST consent snapshots to `/_emdash/api/plugins/emprivacy/record` + optional storage rows (no IP in v1) |
69
- | Google Consent Mode v2 | Optional denied defaults in `<head>` + `gtag('consent','update',…)` — validate with [Google’s docs](https://support.google.com/tagmanager/answer/13695607) |
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
- Put **`emprivacyPlugin()` early** in the `plugins` array so `page:metadata` / `page:fragments` run before other plugins that add trackers or metadata.
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
- | `npm run typecheck` | Types against `emdash` and this package |
114
- | `npm run build` | `tsdown` → `dist/` and `exports` |
115
- | `npm test` | [Vitest](https://vitest.dev/) tests for `src/config.ts`: URL lists, `normalizeConfig`, policy **path vs https** validation, `resolvePolicyHref`, `jsonForHtmlScript` |
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
- npm ci
123
- npm run typecheck
124
- npm run build
125
- npm test
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
- EmPrivacy uses EmDash KV, admin Block Kit, `page:fragments`, and plugin routes. Validate on a **real EmDash site** (staging) at the EmDash version you deploy.
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
- - [ ] `typecheck`, `build`, and `npm test` pass
140
- - [ ] Banner when no cookie or **Policy / consent version** mismatch
141
- - [ ] **Privacy (and cookie) links** open the right EmDash Page or external URL; paths match what you see in the browser for that Page
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. `v0.2.1`). See [docs/RELEASES.md](docs/RELEASES.md) for bump rules, `npm version` / `npm run release:*`, and publish checklist.
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
- * EmPrivacy KV document (`consent:config`) and shared helpers.
18
- */
19
- declare const KV_KEY: "consent:config";
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
- declare function normalizeConfig(raw: unknown): EmprivacyConfig;
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), which only runs for **native**
82
- * plugins registered in `plugins: []`. Do not place this descriptor in `sandboxed: []`.
83
- * See [Page fragments](https://docs.emdashcms.com/plugins/creating-native-plugins/page-fragments/).
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 AnalyticsProvider, COOKIE_NAME, type ConsentRecordPayload, DEFAULT_CONFIG, type EmprivacyConfig, KV_KEY, assertValidCloudflareToken, createPlugin, createPlugin as default, emprivacyPlugin, isRootRelativeSitePath, isValidPolicyHrefInput, normalizeConfig, resolvePolicyHref };
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 };