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. ```javascript neblla.login({ scopes: ['email', 'friends'] }); ``` Scopes disponibles: - **`identity`** — id estable por app. No expone datos de la cuenta Neblla. Siempre concedido. - **`email`** — añade `email` al payload del evento `login`. - **`friends`** — desbloquea `neblla.friends`, `neblla.friendRequests` y 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 ```javascript neblla.save({ record: 1250, settings: { sonido: false } }); ``` - **Sin login** — se guarda en `localStorage` del 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: ```javascript neblla.save({ ...neblla.me.data, record: 1250 }); ``` …o con estrategia de merge (solo aplica con login; la copia en `localStorage` siempre se reescribe entera): ```javascript 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: ```javascript // ❌ 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): ```javascript 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`). ```javascript // ❌ 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.