CookieStore API: El Futuro Asíncrono de la Gestión de Cookies en JavaScript
Durante décadas, los desarrolladores web hemos dependido de la antigua y limitada interfaz document.cookie para manejar cookies en el navegador. Esta API síncrona, con su sintaxis peculiar de cadenas de texto, ha sido fuente de frustración y errores. Pero eso está cambiando con la llegada de CookieStore API, una interfaz moderna y asíncrona que promete revolucionar cómo interactuamos with cookies.
El Problema con document.cookie
Antes de sumergirnos en CookieStore, recordemos los dolores de cabeza que nos ha causado document.cookie:
Sintaxis Arcaica y Propensa a Errores
// La forma "tradicional" - compleja y error-prone
document.cookie = "username=john_doe; expires=Thu, 18 Dec 2025 12:00:00 UTC; path=/; SameSite=Strict; Secure";
// Leer cookies requiere parsing manual
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
Limitaciones Fundamentales
- Interfaz síncrona: Bloquea el hilo principal
- Solo disponible en contexto de ventana: No funciona en Service Workers
- Parsing manual: Requiere código personalizado para leer valores
- Sin eventos nativos: Imposible escuchar cambios en cookies
- Propenso a errores: Sintaxis de cadena compleja
Presentando CookieStore API
CookieStore API es la respuesta moderna a estos problemas, ofreciendo:
- ✅ Interfaz asíncrona basada en Promises
- ✅ Disponible en Service Workers
- ✅ Métodos simples y claros
- ✅ Eventos de cambio nativos
- ✅ Mejor manejo de errores
- ✅ Tipado de opciones
Disponibilidad y Compatibilidad
⚠️ Importante: CookieStore API está disponible solo en contextos seguros (HTTPS) y su soporte en navegadores aún es limitado. Actualmente es compatible con:
- Chrome/Edge 87+
- Opera 73+
- Safari (experimental)
- Firefox (en desarrollo)
Métodos Principales de CookieStore
1. cookieStore.set() - Establecer Cookies
// Sintaxis simple: nombre y valor
await cookieStore.set('username', 'john_doe');
// Sintaxis completa con opciones
await cookieStore.set({
name: 'sessionToken',
value: 'abc123xyz789',
expires: Date.now() + (24 * 60 * 60 * 1000), // 24 horas
domain: 'example.com',
path: '/',
sameSite: 'strict',
secure: true,
partitioned: true // Para cookies de terceros
});
// Con manejo de errores
try {
await cookieStore.set('preferences', JSON.stringify(userPrefs));
console.log('Cookie establecida exitosamente');
} catch (error) {
console.error('Error al establecer cookie:', error);
}
2. cookieStore.get() - Obtener Una Cookie
// Obtener cookie por nombre
const userCookie = await cookieStore.get('username');
console.log(userCookie);
// Output: { name: 'username', value: 'john_doe', domain: 'example.com', ... }
// Obtener con opciones específicas
const sessionCookie = await cookieStore.get({
name: 'sessionToken',
path: '/admin'
});
// Verificar si existe
if (userCookie) {
console.log(`Usuario: ${userCookie.value}`);
} else {
console.log('Usuario no identificado');
}
3. cookieStore.getAll() - Obtener Múltiples Cookies
// Obtener todas las cookies
const allCookies = await cookieStore.getAll();
console.log(`Encontradas ${allCookies.length} cookies`);
// Filtrar por opciones
const sessionCookies = await cookieStore.getAll({
path: '/session'
});
// Procesar cookies
allCookies.forEach(cookie => {
console.log(`${cookie.name}: ${cookie.value}`);
});
4. cookieStore.delete() - Eliminar Cookies
// Eliminar por nombre
await cookieStore.delete('username');
// Eliminar con opciones específicas
await cookieStore.delete({
name: 'sessionToken',
path: '/admin'
});
// Con confirmación
try {
await cookieStore.delete('tempData');
console.log('Cookie eliminada exitosamente');
} catch (error) {
console.error('Error al eliminar cookie:', error);
}
Eventos de Cambio - La Revolución
Una de las características más potentes de CookieStore API es la capacidad de escuchar cambios en cookies:
// Escuchar cambios en cookies
cookieStore.addEventListener('change', (event) => {
console.log('Cambio en cookies detectado:', event);
// Cookies que cambiaron
event.changed.forEach(cookie => {
console.log(`Cookie modificada: ${cookie.name} = ${cookie.value}`);
});
// Cookies que fueron eliminadas
event.deleted.forEach(cookie => {
console.log(`Cookie eliminada: ${cookie.name}`);
});
});
// Ejemplo práctico: Sincronizar estado de usuario
cookieStore.addEventListener('change', (event) => {
const authChange = event.changed.find(c => c.name === 'authToken') ||
event.deleted.find(c => c.name === 'authToken');
if (authChange) {
// Actualizar UI basado en cambio de autenticación
updateAuthUI();
}
});
Casos de Uso Prácticos
1. Gestión de Sesiones Avanzada
class SessionManager {
async createSession(userId, permissions) {
const sessionData = {
userId,
permissions,
createdAt: Date.now(),
lastActive: Date.now()
};
await cookieStore.set({
name: 'session',
value: JSON.stringify(sessionData),
expires: Date.now() + (8 * 60 * 60 * 1000), // 8 horas
sameSite: 'strict',
secure: true,
httpOnly: false // Para acceso desde JS
});
}
async getSession() {
const sessionCookie = await cookieStore.get('session');
return sessionCookie ? JSON.parse(sessionCookie.value) : null;
}
async updateLastActive() {
const session = await this.getSession();
if (session) {
session.lastActive = Date.now();
await cookieStore.set('session', JSON.stringify(session));
}
}
async destroySession() {
await cookieStore.delete('session');
}
}
2. Sistema de Preferencias de Usuario
class UserPreferences {
constructor() {
this.preferences = {};
this.loadPreferences();
this.setupChangeListener();
}
async loadPreferences() {
const prefCookie = await cookieStore.get('userPrefs');
this.preferences = prefCookie ?
JSON.parse(prefCookie.value) :
this.getDefaultPreferences();
}
async updatePreference(key, value) {
this.preferences[key] = value;
await cookieStore.set({
name: 'userPrefs',
value: JSON.stringify(this.preferences),
expires: Date.now() + (365 * 24 * 60 * 60 * 1000), // 1 año
sameSite: 'lax'
});
this.applyPreference(key, value);
}
setupChangeListener() {
cookieStore.addEventListener('change', (event) => {
const prefsChange = event.changed.find(c => c.name === 'userPrefs');
if (prefsChange) {
this.preferences = JSON.parse(prefsChange.value);
this.applyAllPreferences();
}
});
}
applyPreference(key, value) {
switch(key) {
case 'theme':
document.documentElement.setAttribute('data-theme', value);
break;
case 'language':
this.updateLanguage(value);
break;
case 'fontSize':
document.documentElement.style.fontSize = `${value}px`;
break;
}
}
getDefaultPreferences() {
return {
theme: 'auto',
language: 'es',
fontSize: 16,
notifications: true
};
}
}
3. Tracking y Analytics Avanzado
class AdvancedAnalytics {
constructor() {
this.initializeTracking();
}
async initializeTracking() {
// Obtener o crear ID de visitante único
let visitorId = await cookieStore.get('visitorId');
if (!visitorId) {
visitorId = this.generateVisitorId();
await cookieStore.set({
name: 'visitorId',
value: visitorId,
expires: Date.now() + (2 * 365 * 24 * 60 * 60 * 1000), // 2 años
sameSite: 'lax'
});
}
// Tracking de sesión
await this.updateSessionTracking();
// Configurar eventos
this.setupTrackingEvents();
}
async updateSessionTracking() {
const sessionData = {
startTime: Date.now(),
pageViews: 1,
events: []
};
await cookieStore.set({
name: 'currentSession',
value: JSON.stringify(sessionData),
// Cookie de sesión - se elimina al cerrar navegador
sameSite: 'lax'
});
}
async trackEvent(eventName, eventData = {}) {
const sessionCookie = await cookieStore.get('currentSession');
if (sessionCookie) {
const session = JSON.parse(sessionCookie.value);
session.events.push({
name: eventName,
data: eventData,
timestamp: Date.now()
});
await cookieStore.set('currentSession', JSON.stringify(session));
}
}
setupTrackingEvents() {
// Escuchar cambios en cookies de tracking
cookieStore.addEventListener('change', (event) => {
event.deleted.forEach(cookie => {
if (cookie.name === 'currentSession') {
this.sendSessionData();
}
});
});
}
generateVisitorId() {
return 'visitor_' + Math.random().toString(36).substr(2, 9) + Date.now();
}
}
4. Sistema de Consentimiento GDPR
class ConsentManager {
constructor() {
this.consentTypes = ['necessary', 'analytics', 'marketing', 'personalization'];
this.initializeConsent();
}
async initializeConsent() {
const consent = await cookieStore.get('cookieConsent');
if (!consent) {
this.showConsentBanner();
} else {
this.applyConsent(JSON.parse(consent.value));
}
this.setupConsentChangeListener();
}
async updateConsent(consentData) {
await cookieStore.set({
name: 'cookieConsent',
value: JSON.stringify({
...consentData,
timestamp: Date.now(),
version: '1.0'
}),
expires: Date.now() + (365 * 24 * 60 * 60 * 1000), // 1 año
sameSite: 'strict',
secure: true
});
this.applyConsent(consentData);
this.cleanupNonConsentedCookies(consentData);
}
async cleanupNonConsentedCookies(consent) {
const allCookies = await cookieStore.getAll();
for (const cookie of allCookies) {
const cookieType = this.getCookieType(cookie.name);
if (cookieType && !consent[cookieType]) {
await cookieStore.delete(cookie.name);
}
}
}
setupConsentChangeListener() {
cookieStore.addEventListener('change', (event) => {
const consentChange = event.changed.find(c => c.name === 'cookieConsent');
if (consentChange) {
const newConsent = JSON.parse(consentChange.value);
this.applyConsent(newConsent);
}
});
}
getCookieType(cookieName) {
const cookieTypeMap = {
'analytics_': 'analytics',
'marketing_': 'marketing',
'personalization_': 'personalization',
// Cookies necesarias siempre permitidas
'session': null,
'csrf_token': null
};
for (const [prefix, type] of Object.entries(cookieTypeMap)) {
if (cookieName.startsWith(prefix)) {
return type;
}
}
return 'analytics'; // Por defecto
}
}
Uso en Service Workers
Una de las ventajas más significativas de CookieStore API es su disponibilidad en Service Workers:
// En un Service Worker
self.addEventListener('activate', async (event) => {
// Limpiar cookies obsoletas al activar SW
const oldCookies = await cookieStore.getAll({
name: 'temp_'
});
for (const cookie of oldCookies) {
await cookieStore.delete(cookie.name);
}
});
// Sincronizar datos offline
self.addEventListener('sync', async (event) => {
if (event.tag === 'sync-user-data') {
const userCookie = await cookieStore.get('userData');
if (userCookie) {
const userData = JSON.parse(userCookie.value);
await syncUserDataToServer(userData);
}
}
});
// Interceptar requests y añadir cookies automáticamente
self.addEventListener('fetch', async (event) => {
if (event.request.url.includes('/api/')) {
const authCookie = await cookieStore.get('authToken');
if (authCookie) {
const modifiedRequest = new Request(event.request, {
headers: {
...event.request.headers,
'Authorization': `Bearer ${authCookie.value}`
}
});
event.respondWith(fetch(modifiedRequest));
}
}
});
Mejores Prácticas
1. Manejo de Errores Robusto
async function safeCookieOperation(operation) {
try {
return await operation();
} catch (error) {
if (error.name === 'NotAllowedError') {
console.warn('Cookies bloqueadas por el usuario');
return null;
} else if (error.name === 'TypeError') {
console.error('Error de tipo en operación de cookie:', error);
return null;
} else {
console.error('Error inesperado:', error);
throw error;
}
}
}
// Uso
const userData = await safeCookieOperation(() =>
cookieStore.get('userData')
);
2. Validación de Datos
class SecureCookieManager {
async setSecureCookie(name, value, options = {}) {
// Validar entrada
if (!this.isValidCookieName(name)) {
throw new Error('Nombre de cookie inválido');
}
if (!this.isValidCookieValue(value)) {
throw new Error('Valor de cookie inválido');
}
// Configuración segura por defecto
const secureOptions = {
secure: true,
sameSite: 'strict',
...options
};
await cookieStore.set(name, value, secureOptions);
}
isValidCookieName(name) {
return /^[a-zA-Z0-9_-]+$/.test(name) && name.length <= 50;
}
isValidCookieValue(value) {
return typeof value === 'string' && value.length <= 4096;
}
}
3. Performance y Caching
class CachedCookieStore {
constructor() {
this.cache = new Map();
this.setupCacheInvalidation();
}
async get(name) {
if (this.cache.has(name)) {
const cached = this.cache.get(name);
if (cached.expires > Date.now()) {
return cached.value;
}
}
const cookie = await cookieStore.get(name);
if (cookie) {
this.cache.set(name, {
value: cookie,
expires: Date.now() + 30000 // Cache 30 segundos
});
}
return cookie;
}
setupCacheInvalidation() {
cookieStore.addEventListener('change', (event) => {
// Invalidar cache para cookies modificadas
event.changed.forEach(cookie => {
this.cache.delete(cookie.name);
});
event.deleted.forEach(cookie => {
this.cache.delete(cookie.name);
});
});
}
}
Migración desde document.cookie
Estrategia de Migración Gradual
class CookieAdapter {
constructor() {
this.supportsCookieStore = 'cookieStore' in window;
}
async get(name) {
if (this.supportsCookieStore) {
return await cookieStore.get(name);
} else {
// Fallback a document.cookie
return this.getLegacyCookie(name);
}
}
async set(name, value, options = {}) {
if (this.supportsCookieStore) {
await cookieStore.set(name, value, options);
} else {
this.setLegacyCookie(name, value, options);
}
}
async delete(name) {
if (this.supportsCookieStore) {
await cookieStore.delete(name);
} else {
this.deleteLegacyCookie(name);
}
}
// Métodos legacy para compatibilidad
getLegacyCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) {
const cookieValue = parts.pop().split(';').shift();
return { name, value: cookieValue };
}
return null;
}
setLegacyCookie(name, value, options) {
let cookieString = `${name}=${value}`;
if (options.expires) {
cookieString += `; expires=${new Date(options.expires).toUTCString()}`;
}
if (options.path) {
cookieString += `; path=${options.path}`;
}
if (options.secure) {
cookieString += `; secure`;
}
if (options.sameSite) {
cookieString += `; samesite=${options.sameSite}`;
}
document.cookie = cookieString;
}
deleteLegacyCookie(name) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
}
}
Consideraciones de Seguridad
1. Configuración Segura por Defecto
const SECURE_COOKIE_DEFAULTS = {
secure: true, // Solo HTTPS
sameSite: 'strict', // Protección CSRF
httpOnly: false, // Acceso desde JS (ajustar según necesidad)
partitioned: true // Para cookies de terceros
};
async function setSecureCookie(name, value, customOptions = {}) {
const options = { ...SECURE_COOKIE_DEFAULTS, ...customOptions };
await cookieStore.set(name, value, options);
}
2. Validación y Sanitización
class SecureDataHandler {
static sanitizeValue(value) {
// Remover caracteres peligrosos
return value.replace(/[<>\"'&]/g, '');
}
static encryptSensitiveData(data, key) {
// Implementar encriptación para datos sensibles
// Usar Web Crypto API para encriptación real
return btoa(JSON.stringify(data));
}
static async setEncryptedCookie(name, sensitiveData, key) {
const encrypted = this.encryptSensitiveData(sensitiveData, key);
await cookieStore.set(name, encrypted, SECURE_COOKIE_DEFAULTS);
}
}
El Futuro de CookieStore API
La evolución continúa con nuevas funcionalidades propuestas:
- Soporte para cookies HTTPOnly desde JavaScript en contextos seguros
- Integración mejorada con Web Crypto API para encriptación nativa
- Mejor control granular de políticas de cookies
- APIs de observación más avanzadas para patrones complejos
Conclusión: ¿Deberías Adoptar CookieStore API?
Ventajas claras:
- Interfaz moderna y asíncrona
- Mejor developer experience
- Funcionalidad en Service Workers
- Eventos de cambio nativos
- Menos código boilerplate
Consideraciones:
- Soporte limitado en navegadores
- Solo disponible en HTTPS
- Requiere fallbacks para compatibilidad completa
Recomendación: Comienza a experimentar con CookieStore API en proyectos nuevos que puedan tolerar soporte limitado de navegadores, pero mantén un sistema de fallback robusto hasta que la adopción sea más amplia.
La gestión de cookies está evolucionando, y CookieStore API representa el futuro de esta funcionalidad fundamental. Es momento de comenzar a prepararse para esta transición.
Enlaces útiles:
Tags: #JavaScript #CookieStore #WebAPI #Cookies #ModernWeb #ServiceWorkers #WebDevelopment






Comentarios