Capítulo identidad de la guía Neblla — login SSO, scopes, guests, save() y persistencia. El contrato base está en core.txt.
Identidad y persistencia
Guests
Sin login, cada pestaña de navegador recibe un id único y un sitio en el lobby — pero solo dura mientras la pestaña esté abierta. Si el usuario cierra y vuelve mañana, o abre la app en otro dispositivo, es alguien nuevo. Lo guardado con save() sin login va a localStorage (solo ese dispositivo).
SSO en dos capas
1. El usuario tiene una sola cuenta Neblla para todas las apps Neblla. Cada app recibe un ID estable para él — pero distinto en cada app: ninguna app puede correlacionarlo con otra a través de Neblla. Ese ID llega sin pantalla de consentimiento; es el mínimo que cualquier app recibe. 2. Para cualquier dato más allá del ID, la app pide autorización vía *scopes*. La primera vez, el usuario ve una pantalla de consentimiento con la lista exacta. Mientras no cambien los scopes, los logins siguientes son silenciosos. Si se añade un scope nuevo, el popup vuelve a salir.
neblla.login({ scopes: ['email', 'friends'] });
Scopes disponibles:
identity— id estable por app. No expone datos de la cuenta Neblla. Siempre concedido.email— añadeemailal payload del eventologin.friends— desbloqueaneblla.friends,neblla.friendRequestsy los métodos de gestión de amistades.discoverable— permite que el usuario aparezca en las listas de amigos de otros usuarios *de esta app concreta*. Cada app necesita su propia concesión.
Evento 'login' (CLIENT) — { type, data, grantedScopes, email? }.
neblla.logout() cierra la sesión del usuario en tu app pero no cierra su sesión Neblla — sigue logueado para otras apps. Para cerrar sesión global, el usuario usa "Switch" en el popup SSO. Evento 'logout' — {}.
Eventos de sesión: 'sessionExpired' (CLIENT) {}; 'reconnected' (CLIENT) { room, isLobby }.
save() y neblla.me.data
neblla.save({ record: 1250, settings: { sonido: false } });
- Sin login — se guarda en
localStoragedel dispositivo actual. Local. - Con login — se guarda en el almacén de Neblla y se sincroniza entre todos los dispositivos del usuario.
La misma línea de código en ambos casos; Neblla decide el camino según el estado de sesión. En cada reconexión, neblla.me.data está cargado y listo — no hay que pedirlo.
Por defecto save() reescribe el objeto entero (last-write-wins sobre todo el documento). Para actualizar un campo conservando el resto:
neblla.save({ ...neblla.me.data, record: 1250 });
…o con estrategia de merge (solo aplica con login; la copia en localStorage siempre se reescribe entera):
neblla.save({ record: 1250 }, { merge: 'shallow' }); // Object.assign: gana lo que mandas, lo demás se conserva
neblla.save({ settings: { sonido: false } }, { merge: 'deep' }); // merge recursivo de objetos
neblla.save({ record: 1250 }, { merge: 'crdt' }); // CRDT (LWW-Map): gana el valor más nuevo por campo,
// converge entre dispositivos aunque las escrituras
// lleguen desordenadas; escribir null borra ese campo
Tras un merge, el servidor devuelve el documento resultante: neblla.me.data se actualiza y se dispara 'dataMerged' ({ data, merge }). 'shallow'/'deep' aplican en orden de llegada; 'crdt' es order-independent (ideal para varias pestañas/dispositivos).
Cuidado con el reemplazo accidental:
// ❌ Mal — pierde todo lo demás de me.data:
neblla.save({ record: 1250 });
// ✅ Bien — mantiene el resto:
neblla.save({ ...neblla.me.data, record: 1250 });
Patrón: recordar nombre entre sesiones (client.js):
let info = neblla.me.data;
if (!info.name) {
info.name = prompt('Tu nombre?') || 'Guest';
neblla.save(info);
}
neblla.setPublicInfo({ name: info.name });
Privacidad: publicInfo vs me.data
setPublicInfo() se difunde a todos los miembros de la sala; neblla.me.data no llega a otros clientes (ni al peer que ejecuta room.js).
// ❌ Mal — difunde datos sensibles a toda la sala:
neblla.setPublicInfo({ name, email: neblla.me.data.email });
// ✅ Bien — solo lo que de verdad es público:
neblla.setPublicInfo({ name, avatar });
// El email se queda en neblla.me.data, no llega a otros clientes
BBDD propia del desarrollador (BYODB)
Los datos sincronizados consumen unidades de la cuota diaria (ver publicar.txt). Para más almacenamiento — o para que los datos vivan en BBDD propia — se aportan credenciales desde el dashboard. Engines soportados:
Engine Proveedores compatibles (ejemplos) Tier free típico
───────── ───────────────────────────────────────── ────────────────
MongoDB Atlas, DocumentDB, Cosmos DB (Mongo API) Atlas M0 = 512 MB
Postgres Neon, Supabase, RDS, Aurora, Render Neon = 3 GB
MySQL PlanetScale, RDS, Cloud SQL, Aurora varía
Turso Turso.tech (libSQL distribuido) 9 GB
Qué va a tu BBDD: los blobs JSON que la app genera — save() (user.data), datos de salas (setRoomData) y comunicaciones dev↔user. Tres "tablas"/colecciones (user_data, room_data, user_communications) creadas automáticamente con sus índices la primera vez que Neblla escribe. Esquema estable y documentado.
Qué se queda en Neblla: identidad (auth, sesiones, longTokens), amistades cross-app, compras (Stripe), flags anti-cheat, configuración de la app, metadata de archivos hosting, cuotas, logs. Tu BBDD nunca ve emails ni identificadores externos del usuario; solo el userId opaco por app.
Migración transparente por fases (copia masiva → delta de cambios → swap final). El último paso bloquea la app unos segundos para asegurar zero data loss. La app puede manejar el evento 'app_maintenance' ({ reason: 'byodb_migration', retryInSeconds: 30 }): el socket se desconecta en ~2 segundos y el SDK reconecta solo — muestra un mensaje de mantenimiento mientras tanto. Tras la migración, save() y los demás blobs van directos a tu BBDD, sin consumir cuota de Neblla.