@emplugins/emprivacy 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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`.
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`).
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.
@@ -13,14 +13,17 @@ This guide is for site builders who want cookie consent working on an **EmDash**
13
13
 
14
14
  ## Before you begin
15
15
 
16
- 1. An EmDash site with `emdash` in `astro.config` ([plugins overview](https://docs.emdashcms.com/plugins/overview/)).
16
+ 1. An EmDash site on **`emdash@^0.38.0`** with the EmDash Astro integration in `astro.config` ([plugins overview](https://docs.emdashcms.com/plugins/overview/)).
17
17
  2. Ability to edit **`astro.config`** and redeploy (or run locally).
18
+ 3. Plan to register EmPrivacy as a **native** plugin (`plugins: []`, not `sandboxed: []`) because it injects public page fragments.
18
19
 
19
20
  ---
20
21
 
21
22
  ## Step 1 — Install the package
22
23
 
23
24
  ```bash
25
+ pnpm add @emplugins/emprivacy
26
+ # or
24
27
  npm install @emplugins/emprivacy
25
28
  ```
26
29
 
@@ -32,13 +35,14 @@ The package is versioned with **semver** on npm; to pin a line, use a range such
32
35
 
33
36
  Open `astro.config.mjs` (or `.ts`).
34
37
 
35
- 1. Import the plugin:
38
+ 1. Import the plugin and EmDash integration:
36
39
 
37
40
  ```ts
41
+ import emdash from "emdash/astro";
38
42
  import { emprivacyPlugin } from "@emplugins/emprivacy";
39
43
  ```
40
44
 
41
- 2. Add **`emprivacyPlugin()`** to **`emdash({ plugins: [...] })`**. Put **EmPrivacy first** if you use other plugins that inject scripts or metadata.
45
+ 2. Add **`emprivacyPlugin()`** to **`emdash({ plugins: [...] })`**. Put **EmPrivacy first** if you use other plugins that inject scripts or metadata. Do **not** put it in `sandboxed: []`.
42
46
 
43
47
  ```ts
44
48
  emdash({
@@ -55,7 +59,7 @@ Open `astro.config.mjs` (or `.ts`).
55
59
 
56
60
  ## Step 3 — Layout must include EmDash hooks
57
61
 
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/).
62
+ Your base layout must include EmDash’s head/body injection points (e.g. `<EmDashHead page={page} />`, `<EmDashBodyStart page={page} />`, `<EmDashBodyEnd page={page} />`, usually with `createPublicPageContext` from `emdash/page`). Otherwise the banner may not render. See [Page fragments](https://docs.emdashcms.com/plugins/creating-native-plugins/page-fragments/).
59
63
 
60
64
  ---
61
65
 
@@ -111,7 +115,7 @@ Each line must be a **full https URL** to a `.js` file (the script `src`). **Do
111
115
  Run automated checks if you develop the plugin or CI:
112
116
 
113
117
  ```bash
114
- npm run typecheck && npm run build && npm test
118
+ pnpm run typecheck && pnpm run build && pnpm test
115
119
  ```
116
120
 
117
121
  For a full staging checklist (banner, strict mode, admin, optional logging, Google), see **[TESTING.md](./TESTING.md)**.
@@ -122,9 +126,9 @@ For a full staging checklist (banner, strict mode, admin, optional logging, Goog
122
126
 
123
127
  | Problem | What to check |
124
128
  |--------|----------------|
125
- | Nothing appears | EmDash layout missing head/body components; plugin not in `plugins: []`; dev server restarted after config change |
129
+ | Nothing appears | EmDash layout missing head/body components (with `page` context); plugin not in **`plugins: []`** (native — not `sandboxed: []`); dev server restarted after config change |
126
130
  | 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 |
131
+ | EmPrivacy missing in admin | Plugin not installed or `astro.config` wrong; check build errors; confirm `format: "native"` / `createPlugin` package version |
128
132
  | Save fails on privacy/cookie | Value must be a **root-relative path** (`/something`) or **`https://…`** (not `http://` or a bare hostname) |
129
133
  | 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
134
  | Privacy link 404 or wrong page | Path must match the live EmDash route (compare with the address bar when viewing that Page) |
@@ -2,26 +2,11 @@
2
2
 
3
3
  # Unreleased
4
4
 
5
- # @emplugins/emprivacy v1.0.0 (pending Version Packages)
6
-
7
- ## Breaking
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).
11
-
12
- ## Unchanged
13
-
14
- - Runtime plugin id `emprivacy`, cookie `emprivacy_cc`, and API routes.
15
- - Source repository: [EmPlugins/EmPrivacy](https://github.com/EmPlugins/EmPrivacy).
16
-
17
- # emprivacy v0.3.0
18
-
19
5
  ## Changes
20
6
 
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)).
7
+ - Compatibility: targets **EmDash `^0.38.0`** (tested with **`0.38.0`**); Node.js **`>= 22.16`**.
8
+ - **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
+ - Docs/README updated for scoped package install, `import emdash from "emdash/astro"`, layout page context, and native trust boundary.
25
10
 
26
11
  # emprivacy v0.2.1
27
12
 
package/docs/TESTING.md CHANGED
@@ -8,15 +8,15 @@ Testing is split into **automated** checks (CI-friendly) and **manual** checks t
8
8
 
9
9
  | Command | What it verifies |
10
10
  |--------|-------------------|
11
- | `npm run typecheck` | TypeScript types against `emdash` APIs |
12
- | `npm run build` | Bundles ESM + declarations (`dist/`) |
13
- | `npm test` | Unit tests for **pure** helpers in `src/config.ts` (validation, normalization, URL list parsing, JSON escaping) |
11
+ | `pnpm run typecheck` | TypeScript types against `emdash` APIs |
12
+ | `pnpm run build` | Bundles ESM + declarations (`dist/`) |
13
+ | `pnpm test` | Unit tests for **pure** helpers in `src/config.ts` (validation, normalization, URL list parsing, JSON escaping) |
14
14
 
15
15
  These scripts do **not** start EmDash or exercise `page:fragments`, admin routes, or the consent cookie in a browser. They catch regressions in config/save validation and packaging early.
16
16
 
17
17
  ## Manual integration QA (required before production)
18
18
 
19
- Use a **staging** EmDash **`0.14.0+`** Astro app with `emprivacyPlugin()` in `plugins: []`, layout wires (`EmDashHead` / body components), and the same EmDash version you plan to ship.
19
+ Use a **staging** EmDash Astro app on **`emdash@^0.38.0`** with `emprivacyPlugin()` in **`plugins: []`** (native, not `sandboxed`), layout wires (`EmDashHead` / body components with page context), and the same EmDash version you plan to ship.
20
20
 
21
21
  Work through the checklist from the main [README](../README.md) (also copied below). Record pass/fail and browser + EmDash versions.
22
22
 
@@ -38,15 +38,15 @@ Work through the checklist from the main [README](../README.md) (also copied bel
38
38
  ## CI suggestion
39
39
 
40
40
  ```bash
41
- npm ci
42
- npm run typecheck
43
- npm run build
44
- npm test
41
+ pnpm install --frozen-lockfile
42
+ pnpm run typecheck
43
+ pnpm run build
44
+ pnpm test
45
45
  ```
46
46
 
47
47
  ## Future improvements (not in v1)
48
48
 
49
- - **Integration tests** against EmDash’s `adaptSandboxEntry` + mock `PluginContext` (heavy; depends on EmDash test utilities if exposed).
49
+ - **Integration tests** against EmDash’s native plugin loader + mock `PluginContext` (heavy; depends on EmDash test utilities if exposed).
50
50
  - **E2E** (Playwright) against a minimal EmDash fixture app in CI (slower, higher maintenance).
51
51
 
52
52
  For most teams, **automated unit tests + staging manual QA** is the right balance.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "//": "SPDX-License-Identifier: MIT",
3
3
  "name": "@emplugins/emprivacy",
4
- "version": "1.0.0",
4
+ "version": "2.0.0",
5
5
  "description": "Cookie consent and privacy notices for EmDash (GDPR, CCPA/CIPA-oriented configuration)",
6
6
  "type": "module",
7
7
  "main": "dist/index.mjs",
@@ -9,8 +9,7 @@
9
9
  ".": {
10
10
  "import": "./dist/index.mjs",
11
11
  "types": "./dist/index.d.mts"
12
- },
13
- "./sandbox": "./dist/sandbox-entry.mjs"
12
+ }
14
13
  },
15
14
  "files": [
16
15
  "dist",
@@ -38,14 +37,14 @@
38
37
  "license": "MIT",
39
38
  "packageManager": "pnpm@9.15.4",
40
39
  "peerDependencies": {
41
- "emdash": ">=0.14.0"
40
+ "emdash": "^0.38.0"
42
41
  },
43
42
  "dependencies": {
44
- "zod": "^4.3.5"
43
+ "zod": "4.5.4"
45
44
  },
46
45
  "devDependencies": {
47
46
  "@changesets/cli": "^2.27.11",
48
- "emdash": "^0.29.0",
47
+ "emdash": "0.38.0",
49
48
  "tsdown": "^0.20.3",
50
49
  "typescript": "^5.9.3",
51
50
  "vitest": "^3.2.4"
@@ -64,16 +63,17 @@
64
63
  },
65
64
  "pnpm": {
66
65
  "overrides": {
67
- "kysely": "0.29.2"
66
+ "kysely": "0.29.6",
67
+ "zod": "4.5.4"
68
68
  }
69
69
  },
70
70
  "scripts": {
71
71
  "sync:version": "node scripts/write-version.mjs",
72
72
  "verify:exports": "node scripts/verify-exports.mjs",
73
- "build": "tsdown src/index.ts src/sandbox-entry.ts --format esm --dts --clean",
74
- "dev": "tsdown src/index.ts src/sandbox-entry.ts --format esm --dts --watch",
73
+ "build": "tsdown src/index.ts --format esm --dts --clean",
74
+ "dev": "tsdown src/index.ts --format esm --dts --watch",
75
75
  "typecheck": "tsc --noEmit",
76
- "audit": "pnpm audit",
76
+ "audit": "pnpm audit --prod",
77
77
  "test": "vitest run",
78
78
  "test:watch": "vitest",
79
79
  "pack:check": "npm pack --dry-run",
@@ -83,6 +83,6 @@
83
83
  "prepublishOnly": "pnpm run sync:version && pnpm run typecheck && pnpm run build && pnpm test && pnpm run verify:exports && pnpm run audit"
84
84
  },
85
85
  "engines": {
86
- "node": ">=20"
86
+ "node": ">=22.16"
87
87
  }
88
88
  }
@@ -1,166 +0,0 @@
1
- //#region src/config.ts
2
- /**
3
- * EmPrivacy KV document (`consent:config`) and shared helpers.
4
- */
5
- const KV_KEY = "consent:config";
6
- const COOKIE_NAME = "emprivacy_cc";
7
- const PLUGIN_ID = "emprivacy";
8
- const DEFAULT_CONFIG = {
9
- bannerTitle: "Cookies & privacy",
10
- bannerMessage: "We use cookies to run this site and optionally for analytics and marketing. You can accept or customize your choices.",
11
- privacyPolicyUrl: "/privacy",
12
- cookiePolicyUrl: "",
13
- strictDefaults: true,
14
- policyVersion: "1",
15
- cloudflareWebAnalyticsToken: "",
16
- analyticsProvider: "cloudflare",
17
- analyticsScriptUrls: [],
18
- marketingScriptUrls: [],
19
- googleConsentMode: false,
20
- logConsentToServer: false
21
- };
22
- function hasUnsafeUrlChars(s) {
23
- return /[\u0000-\u001F\u007F\s]/.test(s);
24
- }
25
- function isHttpsUrl(s) {
26
- try {
27
- const t = s.trim();
28
- if (!t) return false;
29
- if (hasUnsafeUrlChars(t)) return false;
30
- if (/%0d|%0a|%09|%0b|%0c|%20/i.test(t)) return false;
31
- const u = new URL(t);
32
- if (u.protocol !== "https:") return false;
33
- if (u.username || u.password) return false;
34
- return true;
35
- } catch {
36
- return false;
37
- }
38
- }
39
- function invalidScriptUrlError(kind, u) {
40
- if (u.includes("<") || u.includes(">")) return `Invalid ${kind} script URL (https only). Paste one full https:// URL per line (the script src only), not a <script> tag or HTML comment. Example: https://static.cloudflareinsights.com/beacon.min.js. The invalid line was: ${u}`;
41
- return `Invalid ${kind} script URL (https only): ${u}`;
42
- }
43
- function isAnalyticsProvider(s) {
44
- return s === "cloudflare" || s === "none" || s === "custom";
45
- }
46
- /** Reject characters that would break JSON or attributes; allow typical Cloudflare site token strings. */
47
- function assertValidCloudflareToken(t) {
48
- const s = t.trim();
49
- if (s.length === 0) return;
50
- if (s.length > 256) throw new Error("Cloudflare site token is too long.");
51
- if (/[\s<>'"&]/.test(s)) throw new Error("Cloudflare site token contains invalid characters.");
52
- }
53
- /**
54
- * Root-relative public path (EmDash page route), e.g. `/privacy`.
55
- * Rejects protocol-relative URLs (`//…`) and whitespace.
56
- */
57
- function isRootRelativeSitePath(s) {
58
- const t = s.trim();
59
- if (t.length === 0) return false;
60
- if (!t.startsWith("/")) return false;
61
- if (t.startsWith("//")) return false;
62
- if (hasUnsafeUrlChars(t)) return false;
63
- return true;
64
- }
65
- /** For hooks: turn stored path or absolute URL into a public absolute URL. */
66
- function resolvePolicyHref(stored, ctx) {
67
- const t = stored.trim();
68
- if (!t) return t;
69
- if (isRootRelativeSitePath(t)) return ctx.url(t);
70
- return t;
71
- }
72
- function isValidPolicyHrefInput(s) {
73
- const t = s.trim();
74
- if (!t) return false;
75
- return isHttpsUrl(t) || isRootRelativeSitePath(t);
76
- }
77
- function parseUrlList(text) {
78
- const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
79
- return [...new Set(lines)];
80
- }
81
- function assertLength(label, s, min, max) {
82
- const t = s.trim();
83
- if (t.length < min) throw new Error(`${label} is required.`);
84
- if (t.length > max) throw new Error(`${label} is too long.`);
85
- }
86
- function assertOptionalLength(label, s, max) {
87
- if (s.trim().length > max) throw new Error(`${label} is too long.`);
88
- }
89
- function normalizeConfig(raw) {
90
- if (typeof raw !== "object" || raw === null) return { ...DEFAULT_CONFIG };
91
- const o = raw;
92
- const analytics = Array.isArray(o.analyticsScriptUrls) ? o.analyticsScriptUrls.filter((x) => typeof x === "string") : DEFAULT_CONFIG.analyticsScriptUrls;
93
- const marketing = Array.isArray(o.marketingScriptUrls) ? o.marketingScriptUrls.filter((x) => typeof x === "string") : DEFAULT_CONFIG.marketingScriptUrls;
94
- const cfTok = typeof o.cloudflareWebAnalyticsToken === "string" ? o.cloudflareWebAnalyticsToken : "";
95
- const apCandidate = typeof o.analyticsProvider === "string" ? o.analyticsProvider.trim() : "";
96
- let analyticsProvider;
97
- if (isAnalyticsProvider(apCandidate)) analyticsProvider = apCandidate;
98
- else if (analytics.length > 0) analyticsProvider = "custom";
99
- else analyticsProvider = DEFAULT_CONFIG.analyticsProvider;
100
- return {
101
- bannerTitle: typeof o.bannerTitle === "string" ? o.bannerTitle : DEFAULT_CONFIG.bannerTitle,
102
- bannerMessage: typeof o.bannerMessage === "string" ? o.bannerMessage : DEFAULT_CONFIG.bannerMessage,
103
- privacyPolicyUrl: typeof o.privacyPolicyUrl === "string" ? o.privacyPolicyUrl : DEFAULT_CONFIG.privacyPolicyUrl,
104
- cookiePolicyUrl: typeof o.cookiePolicyUrl === "string" ? o.cookiePolicyUrl : DEFAULT_CONFIG.cookiePolicyUrl,
105
- strictDefaults: typeof o.strictDefaults === "boolean" ? o.strictDefaults : DEFAULT_CONFIG.strictDefaults,
106
- policyVersion: typeof o.policyVersion === "string" && o.policyVersion.length > 0 ? o.policyVersion : DEFAULT_CONFIG.policyVersion,
107
- cloudflareWebAnalyticsToken: cfTok,
108
- analyticsProvider,
109
- analyticsScriptUrls: analytics,
110
- marketingScriptUrls: marketing,
111
- googleConsentMode: typeof o.googleConsentMode === "boolean" ? o.googleConsentMode : DEFAULT_CONFIG.googleConsentMode,
112
- logConsentToServer: typeof o.logConsentToServer === "boolean" ? o.logConsentToServer : DEFAULT_CONFIG.logConsentToServer
113
- };
114
- }
115
- /** Validate admin-submitted fields; throws an Error with a short message on failure */
116
- function assertValidSavedConfig(input) {
117
- assertLength("Policy version", input.policyVersion, 1, 64);
118
- assertOptionalLength("Banner title", input.bannerTitle, 120);
119
- assertOptionalLength("Short notice", input.bannerMessage, 600);
120
- const privacyPolicyUrl = input.privacyPolicyUrl.trim();
121
- if (!isValidPolicyHrefInput(privacyPolicyUrl)) throw new Error("Privacy policy must be a full https:// URL or a site path to your EmDash Page (e.g. /privacy).");
122
- if (privacyPolicyUrl.length > 2048) throw new Error("Privacy policy URL/path is too long.");
123
- let cookiePolicyUrl = input.cookiePolicyUrl.trim();
124
- if (cookiePolicyUrl && !isValidPolicyHrefInput(cookiePolicyUrl)) throw new Error("Cookie policy must be a valid https:// URL or a root-relative path (e.g. /cookies), or empty.");
125
- if (cookiePolicyUrl.length > 2048) throw new Error("Cookie policy URL/path is too long.");
126
- if (!cookiePolicyUrl) cookiePolicyUrl = DEFAULT_CONFIG.cookiePolicyUrl;
127
- const ap = input.analyticsPlatform.trim() || "cloudflare";
128
- if (!isAnalyticsProvider(ap)) throw new Error("Analytics: choose a supported option (Cloudflare, None, or Custom).");
129
- const tokenTrim = input.cloudflareToken.trim();
130
- if (ap === "cloudflare") assertValidCloudflareToken(tokenTrim);
131
- const analyticsScriptUrls = [];
132
- if (ap === "custom") {
133
- const list = parseUrlList(input.analyticsUrlsText);
134
- if (list.length > 50) throw new Error("Analytics script URL list is too long (max 50).");
135
- for (const u of list) {
136
- if (u.length > 2048) throw new Error(invalidScriptUrlError("analytics", u));
137
- if (!isHttpsUrl(u)) throw new Error(invalidScriptUrlError("analytics", u));
138
- analyticsScriptUrls.push(u);
139
- }
140
- }
141
- const marketingScriptUrls = [];
142
- const mlist = parseUrlList(input.marketingUrlsText);
143
- if (mlist.length > 50) throw new Error("Marketing script URL list is too long (max 50).");
144
- for (const u of mlist) {
145
- if (u.length > 2048) throw new Error(invalidScriptUrlError("marketing", u));
146
- if (!isHttpsUrl(u)) throw new Error(invalidScriptUrlError("marketing", u));
147
- marketingScriptUrls.push(u);
148
- }
149
- return normalizeConfig({
150
- bannerTitle: input.bannerTitle.trim() || DEFAULT_CONFIG.bannerTitle,
151
- bannerMessage: input.bannerMessage.trim() || DEFAULT_CONFIG.bannerMessage,
152
- privacyPolicyUrl,
153
- cookiePolicyUrl,
154
- strictDefaults: input.strictDefaults,
155
- policyVersion: input.policyVersion.trim(),
156
- analyticsProvider: ap,
157
- cloudflareWebAnalyticsToken: ap === "cloudflare" ? tokenTrim : "",
158
- analyticsScriptUrls: ap === "custom" ? analyticsScriptUrls : [],
159
- marketingScriptUrls,
160
- googleConsentMode: input.googleConsentMode,
161
- logConsentToServer: input.logConsentToServer
162
- });
163
- }
164
-
165
- //#endregion
166
- export { assertValidCloudflareToken as a, isValidPolicyHrefInput as c, PLUGIN_ID as i, normalizeConfig as l, DEFAULT_CONFIG as n, assertValidSavedConfig as o, KV_KEY as r, isRootRelativeSitePath as s, COOKIE_NAME as t, resolvePolicyHref as u };
@@ -1,63 +0,0 @@
1
- //#region src/config.d.ts
2
- /**
3
- * EmPrivacy KV document (`consent:config`) and shared helpers.
4
- */
5
- declare const KV_KEY: "consent:config";
6
- declare const COOKIE_NAME: "emprivacy_cc";
7
- /** How analytics scripts are chosen after the user consents to analytics. */
8
- type AnalyticsProvider = "cloudflare" | "none" | "custom";
9
- interface EmprivacyConfig {
10
- bannerTitle: string;
11
- bannerMessage: string;
12
- /**
13
- * Privacy policy location: full `https://…` URL **or** a root-relative path to an EmDash **Page**
14
- * (e.g. `/privacy`) resolved with `ctx.url()` on the server.
15
- */
16
- privacyPolicyUrl: string;
17
- /** Optional cookie policy: same rules as `privacyPolicyUrl` (https URL or path like `/cookies`) */
18
- cookiePolicyUrl: string;
19
- /** When true, analytics/marketing require explicit opt-in in the UI */
20
- strictDefaults: boolean;
21
- /** Bump to invalidate client consent cookies and re-show the banner */
22
- policyVersion: string;
23
- /**
24
- * Analytics when `cloudflare` — **site token** from Cloudflare → Web Analytics (injected with `data-cf-beacon`).
25
- * Public in the same way as a normal site embed. Empty means no Cloudflare script until a token is saved.
26
- */
27
- cloudflareWebAnalyticsToken: string;
28
- /**
29
- * Which analytics load path to use after consent. `cloudflare` = beacon + `cloudflareWebAnalyticsToken`;
30
- * `custom` = `analyticsScriptUrls` only; `none` = no third-party analytics scripts.
31
- */
32
- analyticsProvider: AnalyticsProvider;
33
- /** When `analyticsProvider` is `custom`, these https script URLs load after analytics consent. */
34
- analyticsScriptUrls: string[];
35
- /** Third-party script URLs loaded only after marketing consent (https only) */
36
- marketingScriptUrls: string[];
37
- /** Emit Google Consent Mode v2 defaults (denied) in head; updates after choice */
38
- googleConsentMode: boolean;
39
- /** POST consent snapshots to the plugin record route and optional storage */
40
- logConsentToServer: boolean;
41
- }
42
- declare const DEFAULT_CONFIG: EmprivacyConfig;
43
- /** Reject characters that would break JSON or attributes; allow typical Cloudflare site token strings. */
44
- declare function assertValidCloudflareToken(t: string): void;
45
- /**
46
- * Root-relative public path (EmDash page route), e.g. `/privacy`.
47
- * Rejects protocol-relative URLs (`//…`) and whitespace.
48
- */
49
- declare function isRootRelativeSitePath(s: string): boolean;
50
- /** For hooks: turn stored path or absolute URL into a public absolute URL. */
51
- declare function resolvePolicyHref(stored: string, ctx: {
52
- url: (path: string) => string;
53
- }): string;
54
- declare function isValidPolicyHrefInput(s: string): boolean;
55
- declare function normalizeConfig(raw: unknown): EmprivacyConfig;
56
- interface ConsentRecordPayload {
57
- createdAt: string;
58
- policyVersion: string;
59
- analytics: boolean;
60
- marketing: boolean;
61
- }
62
- //#endregion
63
- export { EmprivacyConfig as a, isRootRelativeSitePath as c, resolvePolicyHref as d, DEFAULT_CONFIG as i, isValidPolicyHrefInput as l, COOKIE_NAME as n, KV_KEY as o, ConsentRecordPayload as r, assertValidCloudflareToken as s, AnalyticsProvider as t, normalizeConfig as u };