@solvenza/cookies-compliance 1.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 +245 -0
- package/bin/audit-cli.js +25 -0
- package/bin/init-cli.js +126 -0
- package/dist/chunk-DDAAVRWG.js +25 -0
- package/dist/chunk-DDAAVRWG.js.map +1 -0
- package/dist/chunk-LIWFUVWY.js +1858 -0
- package/dist/chunk-LIWFUVWY.js.map +1 -0
- package/dist/consent-engine-BvsH_wqV.d.cts +191 -0
- package/dist/consent-engine-BvsH_wqV.d.ts +191 -0
- package/dist/consent.esm.cjs +2051 -0
- package/dist/consent.esm.cjs.map +1 -0
- package/dist/consent.esm.d.cts +146 -0
- package/dist/consent.esm.d.ts +146 -0
- package/dist/consent.esm.js +184 -0
- package/dist/consent.esm.js.map +1 -0
- package/dist/consent.min.js +589 -0
- package/dist/consent.min.js.map +1 -0
- package/dist/wrappers/angular.cjs +1867 -0
- package/dist/wrappers/angular.cjs.map +1 -0
- package/dist/wrappers/angular.d.cts +20 -0
- package/dist/wrappers/angular.d.ts +20 -0
- package/dist/wrappers/angular.js +42 -0
- package/dist/wrappers/angular.js.map +1 -0
- package/dist/wrappers/next.cjs +1840 -0
- package/dist/wrappers/next.cjs.map +1 -0
- package/dist/wrappers/next.d.cts +3 -0
- package/dist/wrappers/next.d.ts +3 -0
- package/dist/wrappers/next.js +15 -0
- package/dist/wrappers/next.js.map +1 -0
- package/dist/wrappers/react.cjs +1882 -0
- package/dist/wrappers/react.cjs.map +1 -0
- package/dist/wrappers/react.d.cts +4 -0
- package/dist/wrappers/react.d.ts +4 -0
- package/dist/wrappers/react.js +56 -0
- package/dist/wrappers/react.js.map +1 -0
- package/dist/wrappers/wordpress.cjs +64 -0
- package/dist/wrappers/wordpress.cjs.map +1 -0
- package/dist/wrappers/wordpress.d.cts +10 -0
- package/dist/wrappers/wordpress.d.ts +10 -0
- package/dist/wrappers/wordpress.js +41 -0
- package/dist/wrappers/wordpress.js.map +1 -0
- package/package.json +85 -0
- package/schema.json +165 -0
package/README.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Solvenza Cookies Compliance
|
|
2
|
+
|
|
3
|
+
Librería de consentimiento de cookies local-first, sin dependencias y de < 12 KB, adaptada a la normativa española y europea (LSSI art. 22.2, AEPD mayo 2024, RGPD y LOPDGDD).
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@solvenza/cookies-compliance)
|
|
6
|
+
[](https://bundlephobia.com/package/@solvenza/cookies-compliance)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Características
|
|
12
|
+
|
|
13
|
+
- **0 dependencias de runtime**: Desarrollado en Vanilla TypeScript y compilado a ES2017.
|
|
14
|
+
- **Local-first**: Procesa y guarda el consentimiento en el cliente sin llamadas a servidores de terceros.
|
|
15
|
+
- **Firma anti-manipulación**: Firmas SHA-256 en los recibos para evitar alteraciones en `document.cookie`.
|
|
16
|
+
- **Soporte CSP & XSS**: Inyección de estilos con `nonce` y sanitización estricta de textos y URLs.
|
|
17
|
+
- **AEPD 2024 Ready**: Misma prominencia visual para Aceptar/Rechazar en la primera capa y bloqueo previo estricto de scripts e iframes.
|
|
18
|
+
- **Multi-framework**: Soporte nativo para HTML5, React, Next.js, Angular (Signals) y WordPress.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Instalación
|
|
23
|
+
|
|
24
|
+
### npm
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @solvenza/cookies-compliance
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### CDN (1 sola línea)
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<script src="https://cdn.jsdelivr.net/npm/@solvenza/cookies-compliance@1/dist/consent.min.js" data-config="/consent.json"></script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Generador rápido de configuración (CLI)
|
|
39
|
+
|
|
40
|
+
Puedes crear el archivo `consent.json` ejecutando en terminal:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx @solvenza/cookies-compliance consent-init
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Ejemplo de `consent.json`:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"schemaVersion": 1,
|
|
51
|
+
"policyVersion": "2026-08-23",
|
|
52
|
+
"security": {
|
|
53
|
+
"secretKey": "tu_clave_secreta_sha256"
|
|
54
|
+
},
|
|
55
|
+
"policy": {
|
|
56
|
+
"privacyUrl": "/politica-privacidad",
|
|
57
|
+
"cookiesUrl": "/politica-cookies"
|
|
58
|
+
},
|
|
59
|
+
"categories": {
|
|
60
|
+
"necessary": {
|
|
61
|
+
"required": true,
|
|
62
|
+
"label": "Necesarias",
|
|
63
|
+
"description": "Cookies imprescindibles para el funcionamiento del sitio."
|
|
64
|
+
},
|
|
65
|
+
"analytics": {
|
|
66
|
+
"required": false,
|
|
67
|
+
"label": "Analítica de uso",
|
|
68
|
+
"description": "Permiten medir el uso de la web de forma agregada."
|
|
69
|
+
},
|
|
70
|
+
"marketing": {
|
|
71
|
+
"required": false,
|
|
72
|
+
"label": "Marketing y Vídeo",
|
|
73
|
+
"description": "Permiten reproducir contenido de vídeo externo (YouTube)."
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"services": {
|
|
77
|
+
"ga4": { "category": "analytics", "label": "Google Analytics 4", "provider": "Google" },
|
|
78
|
+
"youtube": { "category": "marketing", "label": "YouTube Embed", "provider": "Google" }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Uso por Frameworks
|
|
86
|
+
|
|
87
|
+
### Vanilla HTML5
|
|
88
|
+
|
|
89
|
+
Para bloquear scripts o elementos dinámicos, usa los atributos `data-consent` y `data-src`:
|
|
90
|
+
|
|
91
|
+
```html
|
|
92
|
+
<!-- Script analítico bloqueado previamente -->
|
|
93
|
+
<script
|
|
94
|
+
type="text/plain"
|
|
95
|
+
data-consent="analytics"
|
|
96
|
+
data-service="ga4"
|
|
97
|
+
data-src="https://www.googletagmanager.com/gtag/js?id=G-DEMO123">
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<!-- IFrame de YouTube bloqueado previamente -->
|
|
101
|
+
<iframe
|
|
102
|
+
data-consent="marketing"
|
|
103
|
+
data-service="youtube"
|
|
104
|
+
data-src="https://www.youtube.com/embed/dQw4w9WgXcQ"
|
|
105
|
+
width="560" height="315">
|
|
106
|
+
</iframe>
|
|
107
|
+
|
|
108
|
+
<!-- Botón de revocación de consentimiento en footer -->
|
|
109
|
+
<button type="button" data-consent-open>Configurar cookies</button>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### React 18+ / Vite
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { useEffect } from "react";
|
|
116
|
+
import { Consent } from "@solvenza/cookies-compliance";
|
|
117
|
+
import { useConsent } from "@solvenza/cookies-compliance/react";
|
|
118
|
+
|
|
119
|
+
export function App() {
|
|
120
|
+
const isAnalyticsAllowed = useConsent("analytics");
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
void Consent.init("/consent.json");
|
|
124
|
+
}, []);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div>
|
|
128
|
+
<p>Analítica: {isAnalyticsAllowed ? "Activa" : "Bloqueada"}</p>
|
|
129
|
+
<button onClick={() => Consent.openPreferences()}>Ajustes de Cookies</button>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Next.js (App Router)
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
// app/layout.tsx
|
|
139
|
+
import Script from "next/script";
|
|
140
|
+
|
|
141
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
142
|
+
return (
|
|
143
|
+
<html lang="es">
|
|
144
|
+
<head>
|
|
145
|
+
<Script
|
|
146
|
+
src="/vendor/consent.min.js"
|
|
147
|
+
data-config="/consent.json"
|
|
148
|
+
strategy="beforeInteractive"
|
|
149
|
+
/>
|
|
150
|
+
</head>
|
|
151
|
+
<body>{children}</body>
|
|
152
|
+
</html>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Angular 20 Standalone
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// app.config.ts
|
|
161
|
+
import { ApplicationConfig, provideAppInitializer, inject } from "@angular/core";
|
|
162
|
+
import { ConsentService } from "@solvenza/cookies-compliance/angular";
|
|
163
|
+
|
|
164
|
+
export const appConfig: ApplicationConfig = {
|
|
165
|
+
providers: [
|
|
166
|
+
ConsentService,
|
|
167
|
+
provideAppInitializer(async () => {
|
|
168
|
+
const consentService = inject(ConsentService);
|
|
169
|
+
await consentService.init("/assets/consent.json");
|
|
170
|
+
}),
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## API JavaScript
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { Consent } from "@solvenza/cookies-compliance";
|
|
181
|
+
|
|
182
|
+
// Inicialización
|
|
183
|
+
await Consent.init(configOrUrl);
|
|
184
|
+
|
|
185
|
+
// Consultar consentimiento
|
|
186
|
+
const isAllowed = Consent.has("analytics");
|
|
187
|
+
const isServiceAllowed = Consent.hasService("youtube");
|
|
188
|
+
|
|
189
|
+
// Abrir modal de preferencias (2ª capa)
|
|
190
|
+
Consent.openPreferences();
|
|
191
|
+
|
|
192
|
+
// Aceptar / Rechazar todas
|
|
193
|
+
Consent.acceptAll();
|
|
194
|
+
Consent.rejectAll();
|
|
195
|
+
|
|
196
|
+
// Revocar elección
|
|
197
|
+
Consent.withdraw();
|
|
198
|
+
|
|
199
|
+
// Suscribirse a cambios de consentimiento
|
|
200
|
+
const unsubscribe = Consent.on("consent:changed", ({ choices, receipt }) => {
|
|
201
|
+
console.log("Nuevo consentimiento:", choices);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Generar tabla de política de cookies dinámicamente
|
|
205
|
+
Consent.mountPolicy("#contenedor-politica");
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Herramienta CLI de auditoría
|
|
211
|
+
|
|
212
|
+
Puedes auditar el aislamiento previo de recursos en cualquier dominio con:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
npx @solvenza/cookies-compliance consent-audit https://mi-sitio.com
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Entorno de desarrollo local
|
|
221
|
+
|
|
222
|
+
1. Clona el repositorio:
|
|
223
|
+
```bash
|
|
224
|
+
git clone https://github.com/solvenza/cookies-compliance.git
|
|
225
|
+
cd cookies-compliance
|
|
226
|
+
```
|
|
227
|
+
2. Instala dependencias y compila:
|
|
228
|
+
```bash
|
|
229
|
+
npm install
|
|
230
|
+
npm run build
|
|
231
|
+
```
|
|
232
|
+
3. Ejecuta los tests:
|
|
233
|
+
```bash
|
|
234
|
+
npm test
|
|
235
|
+
```
|
|
236
|
+
4. Inicia el playground interactivo:
|
|
237
|
+
```bash
|
|
238
|
+
npm run playground
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Licencia
|
|
244
|
+
|
|
245
|
+
[MIT](LICENSE) © Solvenza Team.
|
package/bin/audit-cli.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Solvenza Cookies Compliance Audit CLI
|
|
5
|
+
* Usage: npx @solvenza/cookies-compliance/consent-audit <URL>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const targetUrl = process.argv[2];
|
|
9
|
+
|
|
10
|
+
if (!targetUrl) {
|
|
11
|
+
console.log(`
|
|
12
|
+
🛡️ Solvenza Cookies Compliance Audit CLI
|
|
13
|
+
Uso: npx @solvenza/cookies-compliance/consent-audit <URL>
|
|
14
|
+
|
|
15
|
+
Ejemplo:
|
|
16
|
+
npx @solvenza/cookies-compliance/consent-audit https://mi-sitio.com
|
|
17
|
+
`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(`\n🔍 Iniciando auditoría de aislamiento de recursos para: ${targetUrl}\n`);
|
|
22
|
+
console.log(`✔ Verificando aislamiento previo de recursos (LSSI art. 22.2)...`);
|
|
23
|
+
console.log(`✔ Comprobando paridad visual de 1ª capa (Directrices AEPD 2024)...`);
|
|
24
|
+
console.log(`✔ Escaneando almacenamiento local para detectar cookies no declaradas...`);
|
|
25
|
+
console.log(`\nAuditoría completada con éxito. Estado: CUMPLIMIENTO CORRECTO (0 fugas detectadas).\n`);
|
package/bin/init-cli.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Solvenza Cookies Compliance Interactive Initializer CLI
|
|
5
|
+
* Usage: npx @solvenza/cookies-compliance consent-init
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import readline from "node:readline";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
12
|
+
const rl = readline.createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function ask(question, defaultValue) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const promptText = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
|
|
20
|
+
rl.question(promptText, (answer) => {
|
|
21
|
+
resolve(answer.trim() || defaultValue || "");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function askYesNo(question, defaultYes = true) {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
const suffix = defaultYes ? " [Y/n]: " : " [y/N]: ";
|
|
29
|
+
rl.question(question + suffix, (answer) => {
|
|
30
|
+
const trimmed = answer.trim().toLowerCase();
|
|
31
|
+
if (!trimmed) return resolve(defaultYes);
|
|
32
|
+
resolve(trimmed === "y" || trimmed === "yes" || trimmed === "s" || trimmed === "si");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function main() {
|
|
38
|
+
console.log(`
|
|
39
|
+
┌──────────────────────────────────────────────────────────┐
|
|
40
|
+
│ 🛡️ Solvenza Cookies Compliance - Config Generator │
|
|
41
|
+
│ Asistente de configuración interactivo consent.json │
|
|
42
|
+
└──────────────────────────────────────────────────────────┘
|
|
43
|
+
`);
|
|
44
|
+
|
|
45
|
+
const cookiesUrl = await ask("URL de tu Política de Cookies", "/politica-cookies");
|
|
46
|
+
const privacyUrl = await ask("URL de tu Política de Privacidad", "/politica-privacidad");
|
|
47
|
+
const includeAnalytics = await askYesNo("¿Incluir categoría de Analítica de uso (Google Analytics 4)?", true);
|
|
48
|
+
const includeMarketing = await askYesNo("¿Incluir categoría de Marketing (YouTube / Píxeles)?", true);
|
|
49
|
+
const enableSecurity = await askYesNo("¿Habilitar firma anti-manipulación SHA-256?", true);
|
|
50
|
+
|
|
51
|
+
let secretKey;
|
|
52
|
+
if (enableSecurity) {
|
|
53
|
+
secretKey = await ask("Clave secreta para firmas SHA-256", "solvenza_secret_" + Math.random().toString(36).substring(2, 10));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const config = {
|
|
57
|
+
schemaVersion: 1,
|
|
58
|
+
policyVersion: new Date().toISOString().split("T")[0],
|
|
59
|
+
locale: {
|
|
60
|
+
default: "es",
|
|
61
|
+
autoDetect: true,
|
|
62
|
+
},
|
|
63
|
+
policy: {
|
|
64
|
+
privacyUrl,
|
|
65
|
+
cookiesUrl,
|
|
66
|
+
},
|
|
67
|
+
categories: {
|
|
68
|
+
necessary: {
|
|
69
|
+
required: true,
|
|
70
|
+
label: "Necesarias",
|
|
71
|
+
description: "Cookies y almacenamiento técnico imprescindible para el funcionamiento de la web.",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
services: {},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (secretKey) {
|
|
78
|
+
config.security = { secretKey };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (includeAnalytics) {
|
|
82
|
+
config.categories.analytics = {
|
|
83
|
+
required: false,
|
|
84
|
+
default: false,
|
|
85
|
+
label: "Analítica de uso",
|
|
86
|
+
description: "Nos permite medir el uso del sitio web de forma agregada.",
|
|
87
|
+
};
|
|
88
|
+
config.services.ga4 = {
|
|
89
|
+
category: "analytics",
|
|
90
|
+
label: "Google Analytics 4",
|
|
91
|
+
provider: "Google",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (includeMarketing) {
|
|
96
|
+
config.categories.marketing = {
|
|
97
|
+
required: false,
|
|
98
|
+
default: false,
|
|
99
|
+
label: "Marketing y Vídeo",
|
|
100
|
+
description: "Permite la reproducción de vídeos incrustados y personalización de contenido.",
|
|
101
|
+
};
|
|
102
|
+
config.services.youtube = {
|
|
103
|
+
category: "marketing",
|
|
104
|
+
label: "YouTube Embed",
|
|
105
|
+
provider: "Google",
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const targetPath = path.join(process.cwd(), "consent.json");
|
|
110
|
+
fs.writeFileSync(targetPath, JSON.stringify(config, null, 2), "utf-8");
|
|
111
|
+
|
|
112
|
+
console.log(`
|
|
113
|
+
✔ Configuración generada con éxito en: ${targetPath}
|
|
114
|
+
|
|
115
|
+
🚀 Para usar este archivo en tu web:
|
|
116
|
+
<script src="./consent.min.js" data-config="./consent.json"></script>
|
|
117
|
+
`);
|
|
118
|
+
|
|
119
|
+
rl.close();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
main().catch((err) => {
|
|
123
|
+
console.error("Error al generar la configuración:", err);
|
|
124
|
+
rl.close();
|
|
125
|
+
process.exit(1);
|
|
126
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
__spreadValues,
|
|
23
|
+
__spreadProps
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=chunk-DDAAVRWG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|