@dotrino/identity 0.16.0 → 0.17.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/package.json +1 -1
- package/src/index.js +16 -0
- package/src/node.js +13 -0
- package/vault/core.js +75 -9
- package/vault/peerStore.js +14 -0
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -306,6 +306,22 @@ export class Identity {
|
|
|
306
306
|
return result
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Actualiza tu PERFIL (merge): `{ nickname?, avatar?, avatarVisible?, links?, fields? }`.
|
|
311
|
+
* `avatar` = data-URI 250×250 (o null para quitarla); `links`/`fields` = arrays con `visible`
|
|
312
|
+
* por ítem (oculto = no se comparte). No pisa lo que no mandes.
|
|
313
|
+
*/
|
|
314
|
+
async updateMe (patch) {
|
|
315
|
+
const result = await this._call('updateMe', { patch })
|
|
316
|
+
if (result?.me) this._me = result.me
|
|
317
|
+
return result
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Tu `me` completo (incluye ocultos). */
|
|
321
|
+
async getMe () { return this._call('getMe') }
|
|
322
|
+
/** Subconjunto PÚBLICO de tu perfil (solo lo visible) — para compartir/publicar. */
|
|
323
|
+
async publicMe () { return this._call('publicMe') }
|
|
324
|
+
|
|
309
325
|
/** Pubkey ECDH (JWK string) propio para encripción. */
|
|
310
326
|
async getEncryptionPubkey () {
|
|
311
327
|
return this._call('getEncryptionPubkey')
|
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 {
|
|
@@ -165,6 +175,9 @@ export class Identity {
|
|
|
165
175
|
const result = await this._h('setMyNickname', { nickname })
|
|
166
176
|
return result
|
|
167
177
|
}
|
|
178
|
+
async updateMe (patch) { return this._h('updateMe', { patch }) }
|
|
179
|
+
getMe () { return this._h('getMe') }
|
|
180
|
+
publicMe () { return this._h('publicMe') }
|
|
168
181
|
getEncryptionPubkey () { return this._h('getEncryptionPubkey') }
|
|
169
182
|
encrypt (recipients, plaintext) { return this._h('encrypt', { recipients, plaintext }) }
|
|
170
183
|
decrypt (senderEncryptionPubkey, myToken, envelope) {
|
package/vault/core.js
CHANGED
|
@@ -108,6 +108,33 @@ async function verifyBytes (publicJwkStr, bytes, signatureBase64) {
|
|
|
108
108
|
* @returns {Promise<{ handlers:Object, get me():Object, sync:Object|null,
|
|
109
109
|
* onSyncStatus(fn):void }>}
|
|
110
110
|
*/
|
|
111
|
+
// Sanea un patch de perfil (avatar/links/fields/nickname). Cada link/field lleva `visible`
|
|
112
|
+
// (oculto = no se comparte). Caps de tamaño para no inflar el `me`. Los ids los pone la UI.
|
|
113
|
+
function sanitizeProfilePatch (patch = {}) {
|
|
114
|
+
const out = {}
|
|
115
|
+
if (typeof patch.nickname === 'string') out.nickname = patch.nickname.slice(0, 40)
|
|
116
|
+
if (patch.avatar === null) out.avatar = null
|
|
117
|
+
else if (typeof patch.avatar === 'string') out.avatar = patch.avatar.slice(0, 120000) // ~90KB: data-URI 250x250
|
|
118
|
+
if (typeof patch.avatarVisible === 'boolean') out.avatarVisible = patch.avatarVisible
|
|
119
|
+
if (Array.isArray(patch.links)) {
|
|
120
|
+
out.links = patch.links.slice(0, 12).map((l) => ({
|
|
121
|
+
id: String(l?.id || '').slice(0, 24),
|
|
122
|
+
type: String(l?.type || 'web').slice(0, 16),
|
|
123
|
+
value: String(l?.value || '').slice(0, 200),
|
|
124
|
+
visible: l?.visible !== false
|
|
125
|
+
})).filter((l) => l.value)
|
|
126
|
+
}
|
|
127
|
+
if (Array.isArray(patch.fields)) {
|
|
128
|
+
out.fields = patch.fields.slice(0, 20).map((f) => ({
|
|
129
|
+
id: String(f?.id || '').slice(0, 24),
|
|
130
|
+
label: String(f?.label || '').slice(0, 40),
|
|
131
|
+
value: String(f?.value || '').slice(0, 280),
|
|
132
|
+
visible: f?.visible !== false
|
|
133
|
+
})).filter((f) => f.label || f.value)
|
|
134
|
+
}
|
|
135
|
+
return out
|
|
136
|
+
}
|
|
137
|
+
|
|
111
138
|
export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null }) {
|
|
112
139
|
const {
|
|
113
140
|
initPeerStorage, loadPeers, savePeers, setPeersDirect, upsertPeer, onDirty
|
|
@@ -354,6 +381,20 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null })
|
|
|
354
381
|
|
|
355
382
|
// ----- handlers (idénticos a la versión iframe) -----
|
|
356
383
|
|
|
384
|
+
// Merge de un patch de perfil en `me` (preserva lo demás), saneado. Refleja el nombre en la
|
|
385
|
+
// meta del perfil (para el switcher). Devuelve el `me` resultante.
|
|
386
|
+
function applyMeUpdate (patch) {
|
|
387
|
+
const clean = sanitizeProfilePatch(patch || {})
|
|
388
|
+
me = { ...(me || {}), ...clean, publickey: publickeyJwkStr, encryptionPubkey: encPublickeyJwkStr }
|
|
389
|
+
if (clean.avatar === null) delete me.avatar
|
|
390
|
+
saveMe(me)
|
|
391
|
+
if (typeof clean.nickname === 'string') {
|
|
392
|
+
const list = loadProfiles(); const e = list.find((p) => p.id === currentPid)
|
|
393
|
+
if (e && e.name !== clean.nickname) { e.name = clean.nickname; saveProfiles(list) }
|
|
394
|
+
}
|
|
395
|
+
return me
|
|
396
|
+
}
|
|
397
|
+
|
|
357
398
|
const handlers = {
|
|
358
399
|
async makeChallenge () {
|
|
359
400
|
const nonce = crypto.randomUUID()
|
|
@@ -656,9 +697,24 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null })
|
|
|
656
697
|
},
|
|
657
698
|
|
|
658
699
|
async setMyNickname ({ nickname }) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
700
|
+
return { me: applyMeUpdate({ nickname }) }
|
|
701
|
+
},
|
|
702
|
+
|
|
703
|
+
// Perfil completo (avatar 250x250, links de redes, datos), cada ítem con `visible`
|
|
704
|
+
// (oculto = no se comparte). Merge: no pisa lo que no venga en el patch.
|
|
705
|
+
async updateMe ({ patch } = {}) {
|
|
706
|
+
return { me: applyMeUpdate(patch || {}) }
|
|
707
|
+
},
|
|
708
|
+
async getMe () { return me },
|
|
709
|
+
// Subconjunto PÚBLICO del perfil (solo lo marcado visible) — para compartir/publicar.
|
|
710
|
+
async publicMe () {
|
|
711
|
+
const m = me || {}
|
|
712
|
+
const out = { publickey: m.publickey, encryptionPubkey: m.encryptionPubkey }
|
|
713
|
+
if (m.nickname) out.nickname = m.nickname
|
|
714
|
+
if (m.avatar && m.avatarVisible !== false) out.avatar = m.avatar
|
|
715
|
+
if (Array.isArray(m.links)) { const v = m.links.filter((l) => l.visible !== false).map(({ visible, ...r }) => r); if (v.length) out.links = v }
|
|
716
|
+
if (Array.isArray(m.fields)) { const v = m.fields.filter((f) => f.visible !== false).map(({ visible, ...r }) => r); if (v.length) out.fields = v }
|
|
717
|
+
return out
|
|
662
718
|
},
|
|
663
719
|
|
|
664
720
|
async getEncryptionPubkey () { return encPublickeyJwkStr },
|
|
@@ -752,17 +808,27 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null })
|
|
|
752
808
|
|
|
753
809
|
// ----- bootstrap -----
|
|
754
810
|
|
|
755
|
-
// Perfil activo (multi-perfil por dispositivo).
|
|
756
|
-
//
|
|
757
|
-
//
|
|
811
|
+
// Perfil activo (multi-perfil por dispositivo). Si no hay perfiles, se crea el primero; si
|
|
812
|
+
// existe una identidad ÚNICA vieja (pre-multi-perfil, claves sin namespace), se ADOPTA como
|
|
813
|
+
// "Perfil 1" — sin pérdida. A partir de acá `kv` está scopeado a `currentPid`.
|
|
758
814
|
{
|
|
759
815
|
let profiles = loadProfiles()
|
|
760
816
|
currentPid = rawKv.getItem(CURRENT_STORAGE)
|
|
761
817
|
if (!profiles.length) {
|
|
762
|
-
|
|
763
|
-
|
|
818
|
+
const pid = 'p' + crypto.randomUUID().slice(0, 8)
|
|
819
|
+
// Migración: adoptar la identidad única vieja (si la hay) copiando sus claves al namespace de pid.
|
|
820
|
+
const legacy = rawKv.getItem(KEY_STORAGE)
|
|
821
|
+
if (legacy) {
|
|
822
|
+
for (const s of ['keypair', 'enc-keypair', 'me', 'nonces', 'delegations', 'revocations', 'vault.device', 'vault.cert']) {
|
|
823
|
+
const v = rawKv.getItem('dotrino.identity.' + s)
|
|
824
|
+
if (v != null) rawKv.setItem(`dotrino.identity.p.${pid}.${s}`, v)
|
|
825
|
+
}
|
|
826
|
+
try { await peers.adoptLegacy?.(pid) } catch (_) { /* peers viejos opcionales */ }
|
|
827
|
+
}
|
|
828
|
+
currentPid = pid
|
|
829
|
+
profiles = [{ id: pid, name: '', pubkey: null }]
|
|
764
830
|
saveProfiles(profiles)
|
|
765
|
-
rawKv.setItem(CURRENT_STORAGE,
|
|
831
|
+
rawKv.setItem(CURRENT_STORAGE, pid)
|
|
766
832
|
} else if (!currentPid || !profiles.find((p) => p.id === currentPid)) {
|
|
767
833
|
currentPid = profiles[0].id
|
|
768
834
|
rawKv.setItem(CURRENT_STORAGE, currentPid)
|
package/vault/peerStore.js
CHANGED
|
@@ -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
|