@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.
- package/README.md +28 -18
- package/dist/index.d.mts +80 -4
- package/dist/index.mjs +876 -9
- package/docs/DEVELOPMENT.md +6 -10
- package/docs/GETTING_STARTED.md +11 -7
- package/docs/RELEASE_NOTES.md +3 -18
- package/docs/TESTING.md +9 -9
- package/package.json +11 -11
- package/dist/config-BJ1LHuri.mjs +0 -166
- package/dist/config-CqOtRoQR.d.mts +0 -63
- package/dist/sandbox-entry.d.mts +0 -283
- package/dist/sandbox-entry.mjs +0 -693
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -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
|
-
|
|
24
|
+
`pnpm run audit` runs **`pnpm audit --prod`**, so it reflects **published runtime** dependencies only (currently `zod`).
|
|
23
25
|
|
|
24
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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) |
|
package/docs/RELEASE_NOTES.md
CHANGED
|
@@ -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
|
|
22
|
-
- **
|
|
23
|
-
-
|
|
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
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
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.
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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": "
|
|
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": "
|
|
40
|
+
"emdash": "^0.38.0"
|
|
42
41
|
},
|
|
43
42
|
"dependencies": {
|
|
44
|
-
"zod": "
|
|
43
|
+
"zod": "4.5.4"
|
|
45
44
|
},
|
|
46
45
|
"devDependencies": {
|
|
47
46
|
"@changesets/cli": "^2.27.11",
|
|
48
|
-
"emdash": "
|
|
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.
|
|
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
|
|
74
|
-
"dev": "tsdown src/index.ts
|
|
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": ">=
|
|
86
|
+
"node": ">=22.16"
|
|
87
87
|
}
|
|
88
88
|
}
|
package/dist/config-BJ1LHuri.mjs
DELETED
|
@@ -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 };
|