@dotrino/identity 0.37.0 → 0.37.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.37.
|
|
3
|
+
"version": "0.37.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",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"url": "git+https://github.com/imdotrino/dotrino-identity.git"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@dotrino/proxy-client": "0.
|
|
58
|
+
"@dotrino/proxy-client": "0.10.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"fake-indexeddb": "^6.2.5"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Copia vendorizada de @dotrino/proxy-client@0.
|
|
1
|
+
Copia vendorizada de @dotrino/proxy-client@0.10.0 (src/, sin dependencias).
|
|
2
2
|
Ver dotrino-identity: el iframe se sirve estatico, transporte self-hosted (no CDN).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildSignedChannel, getPublicKeyJwk, signData } from './signature.js'
|
|
2
|
-
import { WebRTCManager, RTC_TAG } from './webrtc.js'
|
|
2
|
+
import { WebRTCManager, RTC_TAG, DEFAULT_ICE_SERVERS } from './webrtc.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Dotrino WebSocket proxy client.
|
|
@@ -76,6 +76,10 @@ export class WebSocketProxyClient {
|
|
|
76
76
|
clearTimeout(this._reconnectTimer)
|
|
77
77
|
this._reconnectTimer = null
|
|
78
78
|
}
|
|
79
|
+
if (this._turnTimer) {
|
|
80
|
+
clearTimeout(this._turnTimer)
|
|
81
|
+
this._turnTimer = null
|
|
82
|
+
}
|
|
79
83
|
if (this._rtc) this._rtc.closeAll()
|
|
80
84
|
if (this.ws) {
|
|
81
85
|
try { this.ws.close(1000) } catch (_) {}
|
|
@@ -219,12 +223,57 @@ export class WebSocketProxyClient {
|
|
|
219
223
|
* @param {string|string[]} toPubkeys publickey JWK string o array
|
|
220
224
|
* @param {any} payload
|
|
221
225
|
*/
|
|
222
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Enviar a una o varias pubkeys.
|
|
228
|
+
*
|
|
229
|
+
* `opts.ephemeral` marca el mensaje como de TIEMPO REAL: si el destinatario no
|
|
230
|
+
* está conectado en ese momento, se descarta en vez de guardarse en la cola
|
|
231
|
+
* offline de 24 h. Úsalo para lo que caduca —jugadas de una partida,
|
|
232
|
+
* señalización WebRTC, presencia—: entregar eso mañana no es tarde, es
|
|
233
|
+
* incorrecto (reinicia negociaciones imposibles y muestra movimientos fuera de
|
|
234
|
+
* contexto). NO lo uses para mensajes de chat, que sí quieren esperar.
|
|
235
|
+
*/
|
|
236
|
+
sendByPubkey (toPubkeys, payload, opts = {}) {
|
|
223
237
|
const list = Array.isArray(toPubkeys) ? toPubkeys : [toPubkeys]
|
|
224
|
-
|
|
238
|
+
const msg = {
|
|
225
239
|
to_publickey: list,
|
|
226
240
|
message: typeof payload === 'string' ? payload : JSON.stringify(payload)
|
|
227
|
-
}
|
|
241
|
+
}
|
|
242
|
+
if (opts.ephemeral) msg.ephemeral = true
|
|
243
|
+
this._sendRaw(msg)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Pedir una CITA: el código corto que una persona lee, dicta o escanea para
|
|
248
|
+
* emparejarse con esta conexión.
|
|
249
|
+
*
|
|
250
|
+
* Es un código de 6 caracteres (los 2 primeros dicen qué proxio lo emitió),
|
|
251
|
+
* que **caduca en minutos y se quema al usarse**. Esa es la diferencia con el
|
|
252
|
+
* token de 4 caracteres de antes: aquel era permanente mientras durara la
|
|
253
|
+
* conexión Y era la dirección de ruteo, así que tenía que ser adivinable-seguro
|
|
254
|
+
* y corto a la vez, dos cosas incompatibles.
|
|
255
|
+
*
|
|
256
|
+
* @param {{ttlMs?:number}} [opts] vida del código (30s–30min, por defecto 5min)
|
|
257
|
+
* @returns {Promise<{code:string, expiresAt:number, node:string}>}
|
|
258
|
+
*/
|
|
259
|
+
requestPairingCode (opts = {}) {
|
|
260
|
+
const msg = { type: 'pair-code' }
|
|
261
|
+
if (opts.ttlMs) msg.ttlMs = opts.ttlMs
|
|
262
|
+
return this._request(msg, 'pair-code')
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Canjear una cita ajena: devuelve a qué conexión (y a qué identidad) apunta.
|
|
267
|
+
* Acepta el código en minúsculas y con espacios o guiones.
|
|
268
|
+
*
|
|
269
|
+
* Si el código lo emitió otro proxio, este le pregunta a ESE proxio — no a
|
|
270
|
+
* toda la malla: un pregón se lo queda el primero que conteste, y así es como
|
|
271
|
+
* un nodo hostil se mete en emparejamientos ajenos.
|
|
272
|
+
*
|
|
273
|
+
* @returns {Promise<{ok:boolean, instance?:string, publickey?:string, error?:string}>}
|
|
274
|
+
*/
|
|
275
|
+
redeemPairingCode (code) {
|
|
276
|
+
return this._request({ type: 'pair-redeem', code }, 'pair-redeem')
|
|
228
277
|
}
|
|
229
278
|
|
|
230
279
|
/**
|
|
@@ -233,13 +282,79 @@ export class WebSocketProxyClient {
|
|
|
233
282
|
* (típicamente por el identity vault). Devuelve la respuesta del proxy con
|
|
234
283
|
* `queued_delivered` (mensajes offline despachados al instante).
|
|
235
284
|
*/
|
|
236
|
-
identify ({ data, signature, cert }) {
|
|
285
|
+
identify ({ data, signature, cert, acta }) {
|
|
237
286
|
if (!data || !signature) throw new Error('identify requires {data, signature}')
|
|
238
287
|
const msg = { type: 'identify', data, signature }
|
|
239
288
|
if (cert) msg.cert = cert // "una identidad": el proxy bindea este token también bajo tu maestra M
|
|
289
|
+
// Acta de perfil: el proxy la verifica (va firmada) y bindea también el `profileId`, así
|
|
290
|
+
// escribirle a la PERSONA llega a cualquiera de sus dispositivos. Ver acta-de-perfil.md.
|
|
291
|
+
if (acta) msg.acta = acta
|
|
240
292
|
return this._request(msg, 'identified')
|
|
241
293
|
}
|
|
242
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Pedir al proxy credenciales TURN temporales (Cloudflare) para WebRTC.
|
|
297
|
+
* Requiere haber llamado antes a `identify` en ESTA conexión con la misma
|
|
298
|
+
* pubkey: el proxy solo emite a conexiones identificadas (así el TURN se
|
|
299
|
+
* usa solo desde apps Dotrino y no como relay abierto). Las credenciales
|
|
300
|
+
* expiran solas (TTL corto) y hay cuota por pubkey/hora.
|
|
301
|
+
*
|
|
302
|
+
* @param {Object} opts
|
|
303
|
+
* @param {string} opts.publicKey Pubkey JWK string del vault (la de identify).
|
|
304
|
+
* @param {(data:any)=>Promise<string|{signature:string}>} opts.sign Firma del vault (id.signData).
|
|
305
|
+
* @returns {Promise<{enabled:boolean, iceServers:any[]|null, expiresAt:number|null}>}
|
|
306
|
+
*/
|
|
307
|
+
async getTurnCredentials ({ publicKey, sign } = {}) {
|
|
308
|
+
if (!publicKey || typeof sign !== 'function') {
|
|
309
|
+
throw new Error('getTurnCredentials requires { publicKey, sign }')
|
|
310
|
+
}
|
|
311
|
+
const data = { op: 'turn-credentials', publickey: publicKey, ts: Date.now() }
|
|
312
|
+
const signature = await normalizeSignature(sign, data)
|
|
313
|
+
const res = await this._request({ type: 'turn-credentials', data, signature }, 'turn-credentials')
|
|
314
|
+
return {
|
|
315
|
+
enabled: !!res.enabled,
|
|
316
|
+
iceServers: res.iceServers || null,
|
|
317
|
+
expiresAt: res.expiresAt || null
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Activar TURN para WebRTC: obtiene credenciales temporales del proxy, las
|
|
323
|
+
* inyecta como ICE servers (junto a los STUN por defecto) y las renueva
|
|
324
|
+
* sola antes de expirar. Si el proxy no tiene TURN configurado, no cambia
|
|
325
|
+
* nada (los peers siguen STUN-only con fallback al proxy).
|
|
326
|
+
*
|
|
327
|
+
* Afecta a las conexiones P2P NUEVAS (los peers ya negociados conservan su
|
|
328
|
+
* configuración). Llamalo después de `identify`.
|
|
329
|
+
*
|
|
330
|
+
* @param {Object} opts Igual que getTurnCredentials ({ publicKey, sign }).
|
|
331
|
+
* @returns {Promise<boolean>} true si TURN quedó activo.
|
|
332
|
+
*/
|
|
333
|
+
async enableTurn ({ publicKey, sign } = {}) {
|
|
334
|
+
if (!this._rtc) throw new Error('WebRTC está deshabilitado en este cliente')
|
|
335
|
+
if (this._turnTimer) {
|
|
336
|
+
clearTimeout(this._turnTimer)
|
|
337
|
+
this._turnTimer = null
|
|
338
|
+
}
|
|
339
|
+
const res = await this.getTurnCredentials({ publicKey, sign })
|
|
340
|
+
if (!res.enabled || !res.iceServers) return false
|
|
341
|
+
this._rtc.setIceServers([...res.iceServers, ...DEFAULT_ICE_SERVERS])
|
|
342
|
+
// Renovar con margen antes de expirar (mínimo 30 s entre renovaciones)
|
|
343
|
+
const refreshIn = Math.max(30000, (res.expiresAt || 0) - Date.now() - 60000)
|
|
344
|
+
this._turnTimer = setTimeout(() => {
|
|
345
|
+
this._turnTimer = null
|
|
346
|
+
this.enableTurn({ publicKey, sign }).catch(() => {
|
|
347
|
+
// Sin conexión o cuota: reintento suave en 60 s; mientras tanto los
|
|
348
|
+
// peers nuevos usan los STUN por defecto.
|
|
349
|
+
this._turnTimer = setTimeout(() => {
|
|
350
|
+
this._turnTimer = null
|
|
351
|
+
this.enableTurn({ publicKey, sign }).catch(() => {})
|
|
352
|
+
}, 60000)
|
|
353
|
+
})
|
|
354
|
+
}, refreshIn)
|
|
355
|
+
return true
|
|
356
|
+
}
|
|
357
|
+
|
|
243
358
|
/**
|
|
244
359
|
* Consultar la config de Web Push del proxy.
|
|
245
360
|
* @returns {Promise<{enabled:boolean, vapidPublicKey:string|null}>}
|
|
@@ -451,7 +566,12 @@ export class WebSocketProxyClient {
|
|
|
451
566
|
const wasConnected = this._connected
|
|
452
567
|
this._connected = false
|
|
453
568
|
this._emit('disconnect', { code: ev.code, reason: ev.reason })
|
|
454
|
-
|
|
569
|
+
// Reconectar ante cualquier cierre del servidor (incluido code 1000 de un
|
|
570
|
+
// restart limpio del proxy): los cierres INTENCIONALES del cliente ya
|
|
571
|
+
// tienen autoReconnect=false (puesto por close()), así que este guard no
|
|
572
|
+
// filtra desconexiones pedidas por la app. Sin esto, un restart del proxy
|
|
573
|
+
// deja a clientes de larga duración (bots, apps abiertas) zombis para siempre.
|
|
574
|
+
if (wasConnected && this.autoReconnect) {
|
|
455
575
|
this._scheduleReconnect()
|
|
456
576
|
}
|
|
457
577
|
})
|
|
@@ -516,7 +636,20 @@ export class WebSocketProxyClient {
|
|
|
516
636
|
const { type } = data
|
|
517
637
|
switch (type) {
|
|
518
638
|
case 'connected':
|
|
519
|
-
|
|
639
|
+
// `instance` es el identificador de esta conexión, ya cualificado por
|
|
640
|
+
// nodo: se le puede escribir desde cualquier proxio de la malla. Es el
|
|
641
|
+
// mismo valor que `token` (el nombre histórico), pero ya NO es un código
|
|
642
|
+
// de 4 caracteres para dictar — para eso está `requestPairingCode()`.
|
|
643
|
+
this.instance = data.instance || data.token
|
|
644
|
+
this.node = data.node || null
|
|
645
|
+
// Los nodos que conoce este proxio. Sirve para los descubrimientos que
|
|
646
|
+
// son de TODO el ecosistema y no tienen dueño natural (la lista pública
|
|
647
|
+
// de salas): se pregunta en cada nodo y se mezcla, en vez de designar a
|
|
648
|
+
// uno como árbitro. Son públicos: van en cada instancia y en /peers.
|
|
649
|
+
this.peers = Array.isArray(data.peers) ? data.peers : []
|
|
650
|
+
/** Este nodo + los que conoce, sin repetidos. */
|
|
651
|
+
this.knownNodes = [this.node, ...this.peers].filter((n, i, a) => n && a.indexOf(n) === i)
|
|
652
|
+
this.token = this.instance
|
|
520
653
|
this._emit('token', this.token)
|
|
521
654
|
if (this._connectResolve) {
|
|
522
655
|
this._connectResolve(this.token)
|
|
@@ -569,6 +702,9 @@ export class WebSocketProxyClient {
|
|
|
569
702
|
case 'push-scheduled':
|
|
570
703
|
case 'push-canceled':
|
|
571
704
|
case 'push-list':
|
|
705
|
+
case 'turn-credentials':
|
|
706
|
+
case 'pair-code':
|
|
707
|
+
case 'pair-redeem':
|
|
572
708
|
this._resolvePending(data, type)
|
|
573
709
|
break
|
|
574
710
|
case 'error':
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* the "polite" one (rolls back on collision).
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const DEFAULT_ICE_SERVERS = [
|
|
15
|
+
export const DEFAULT_ICE_SERVERS = [
|
|
16
16
|
{ urls: 'stun:stun.l.google.com:19302' },
|
|
17
17
|
{ urls: 'stun:stun1.l.google.com:19302' },
|
|
18
18
|
{ urls: 'stun:global.stun.twilio.com:3478' }
|
|
@@ -93,6 +93,15 @@ export class WebRTCManager {
|
|
|
93
93
|
for (const t of Array.from(this.peers.keys())) this.closePeer(t)
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Reemplaza la lista de ICE servers para las PRÓXIMAS conexiones (los peers
|
|
98
|
+
* ya negociados conservan la suya). Lo usa `client.enableTurn()` para
|
|
99
|
+
* inyectar las credenciales TURN temporales del proxy.
|
|
100
|
+
*/
|
|
101
|
+
setIceServers (list) {
|
|
102
|
+
if (Array.isArray(list) && list.length) this.iceServers = list
|
|
103
|
+
}
|
|
104
|
+
|
|
96
105
|
isOpen (to) {
|
|
97
106
|
const p = this.peers.get(to)
|
|
98
107
|
return !!(p && p.dc && p.dc.readyState === 'open')
|