@dotrino/identity 0.15.0 → 0.16.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/identity",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "description": "Identidad y rating de usuarios compartidos entre apps de Dotrino (vault iframe + postMessage)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/node.js CHANGED
@@ -50,6 +50,16 @@ function filePeers (filePath) {
50
50
  }
51
51
  return {
52
52
  setProfile (p) { pid = p || null },
53
+ adoptLegacy (newPid) {
54
+ // Migración: copia el peers.json viejo (sin namespace) al perfil newPid.
55
+ try {
56
+ if (!fs.existsSync(filePath)) return
57
+ const legacy = JSON.parse(fs.readFileSync(filePath, 'utf8'))
58
+ if (legacy && typeof legacy === 'object' && Object.keys(legacy).length) {
59
+ fs.writeFileSync(path.join(path.dirname(filePath), `peers.${newPid}.json`), JSON.stringify(legacy))
60
+ }
61
+ } catch (_) { /* sin peers viejos */ }
62
+ },
53
63
  async initPeerStorage () {
54
64
  const f = fileFor()
55
65
  try {
package/vault/core.js CHANGED
@@ -195,6 +195,16 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null })
195
195
  // Canal de eventos 'vault' (p.ej. el SAS a comparar durante el emparejamiento).
196
196
  const vaultListeners = new Set()
197
197
  const emitVault = (p) => { for (const fn of vaultListeners) { try { fn(p) } catch (_) {} } }
198
+ // Si el vault RECHAZA por cert revocado, este dispositivo perdió el acceso: limpiamos el
199
+ // cert local (ya no sirve) y emitimos 'revoked' → @dotrino/store borra SOLO el store de
200
+ // ESTE perfil (los demás perfiles quedan intactos). Cualquier otro error se propaga igual.
201
+ const handleVaultError = (e) => {
202
+ if (e && /\brevoked\b/.test(e.message || '')) {
203
+ try { kv.removeItem(VAULT_CERT_STORAGE); kv.removeItem(VAULT_DEVICE_STORAGE) } catch (_) {}
204
+ emitVault({ phase: 'revoked' })
205
+ }
206
+ throw e
207
+ }
198
208
  const loadVaultCert = () => { try { return JSON.parse(kv.getItem(VAULT_CERT_STORAGE) || 'null') } catch (_) { return null } }
199
209
  const loadVaultDevice = () => { try { return JSON.parse(kv.getItem(VAULT_DEVICE_STORAGE) || 'null') } catch (_) { return null } }
200
210
 
@@ -613,7 +623,8 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null })
613
623
  async vaultSign ({ payload }) {
614
624
  const v = loadVaultCert(); const device = loadVaultDevice()
615
625
  if (!v?.cert || !device) throw new Error('este dispositivo no está emparejado con un vault')
616
- return remoteSign({ master: v.master, proxy: v.proxy, device, cert: v.cert, payload })
626
+ try { return await remoteSign({ master: v.master, proxy: v.proxy, device, cert: v.cert, payload }) }
627
+ catch (e) { return handleVaultError(e) }
617
628
  },
618
629
 
619
630
  // Store DELEGADO: lee/escribe el store de hilos+aperturas EN tu vault (con el cert).
@@ -621,14 +632,16 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null })
621
632
  async vaultStore ({ method, args }) {
622
633
  const v = loadVaultCert(); const device = loadVaultDevice()
623
634
  if (!v?.cert || !device) throw new Error('este dispositivo no está emparejado con un vault')
624
- return remoteStore({ master: v.master, proxy: v.proxy, device, cert: v.cert, method, args })
635
+ try { return await remoteStore({ master: v.master, proxy: v.proxy, device, cert: v.cert, method, args }) }
636
+ catch (e) { return handleVaultError(e) }
625
637
  },
626
638
 
627
639
  // Lista (solo lectura) de dispositivos enrolados en tu vault.
628
640
  async listVaultDevices () {
629
641
  const v = loadVaultCert(); const device = loadVaultDevice()
630
642
  if (!v?.cert || !device) throw new Error('este dispositivo no está emparejado con un vault')
631
- return remoteDevices({ master: v.master, proxy: v.proxy, device, cert: v.cert })
643
+ try { return await remoteDevices({ master: v.master, proxy: v.proxy, device, cert: v.cert }) }
644
+ catch (e) { return handleVaultError(e) }
632
645
  },
633
646
 
634
647
  // El cert de delegación de este dispositivo (para presentarlo al proxy en `identify`
@@ -739,17 +752,27 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null })
739
752
 
740
753
  // ----- bootstrap -----
741
754
 
742
- // Perfil activo (multi-perfil por dispositivo). Sin migración (ecosistema nuevo, no importa
743
- // perder data): si no hay perfiles, se crea uno fresco. A partir de acá `kv` está scopeado a
744
- // `currentPid` y `peers` apunta al peer book de ese perfil.
755
+ // Perfil activo (multi-perfil por dispositivo). Si no hay perfiles, se crea el primero; si
756
+ // existe una identidad ÚNICA vieja (pre-multi-perfil, claves sin namespace), se ADOPTA como
757
+ // "Perfil 1" sin pérdida. A partir de acá `kv` está scopeado a `currentPid`.
745
758
  {
746
759
  let profiles = loadProfiles()
747
760
  currentPid = rawKv.getItem(CURRENT_STORAGE)
748
761
  if (!profiles.length) {
749
- currentPid = 'p' + crypto.randomUUID().slice(0, 8)
750
- profiles = [{ id: currentPid, name: '', pubkey: null }]
762
+ const pid = 'p' + crypto.randomUUID().slice(0, 8)
763
+ // Migración: adoptar la identidad única vieja (si la hay) copiando sus claves al namespace de pid.
764
+ const legacy = rawKv.getItem(KEY_STORAGE)
765
+ if (legacy) {
766
+ for (const s of ['keypair', 'enc-keypair', 'me', 'nonces', 'delegations', 'revocations', 'vault.device', 'vault.cert']) {
767
+ const v = rawKv.getItem('dotrino.identity.' + s)
768
+ if (v != null) rawKv.setItem(`dotrino.identity.p.${pid}.${s}`, v)
769
+ }
770
+ try { await peers.adoptLegacy?.(pid) } catch (_) { /* peers viejos opcionales */ }
771
+ }
772
+ currentPid = pid
773
+ profiles = [{ id: pid, name: '', pubkey: null }]
751
774
  saveProfiles(profiles)
752
- rawKv.setItem(CURRENT_STORAGE, currentPid)
775
+ rawKv.setItem(CURRENT_STORAGE, pid)
753
776
  } else if (!currentPid || !profiles.find((p) => p.id === currentPid)) {
754
777
  currentPid = profiles[0].id
755
778
  rawKv.setItem(CURRENT_STORAGE, currentPid)
@@ -40,6 +40,20 @@ export function onDirty (fn) { _markDirty = fn }
40
40
  export function setProfile (pid) { _pid = pid || null }
41
41
  function peersKey () { return _pid ? `peers.${_pid}.v1` : IDB_PEERS_KEY }
42
42
 
43
+ /** Migración: copia el peer book VIEJO (pre-multi-perfil, sin namespace) al perfil `pid`. */
44
+ export async function adoptLegacy (pid) {
45
+ try {
46
+ _idb = _idb || await openIdb()
47
+ let legacy = await idbGet(_idb, IDB_PEERS_KEY) // 'peers.v1' (viejo)
48
+ if (!legacy || typeof legacy !== 'object' || !Object.keys(legacy).length) {
49
+ try { const raw = localStorage.getItem(PEERS_STORAGE); legacy = raw ? JSON.parse(raw) : null } catch (_) { legacy = null }
50
+ }
51
+ if (legacy && typeof legacy === 'object' && Object.keys(legacy).length) {
52
+ await idbPut(_idb, `peers.${pid}.v1`, legacy)
53
+ }
54
+ } catch (_) { /* sin peers viejos que adoptar */ }
55
+ }
56
+
43
57
  function openIdb () {
44
58
  return new Promise((resolve, reject) => {
45
59
  let req