@dotrino/vault 0.12.0 → 0.14.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/README.md +29 -3
- package/bin/dotrino-env.js +23 -4
- package/package.json +2 -2
- package/src/config.js +17 -5
- package/src/enroll.js +12 -6
- package/src/env.js +49 -7
- package/src/invite.js +10 -4
- package/src/service.js +25 -1
package/README.md
CHANGED
|
@@ -97,12 +97,38 @@ Para procesos que no son Node, el CLI los inyecta en el entorno de un hijo:
|
|
|
97
97
|
dotrino-env run --ns miapp -- ./mi-binario
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
### Precedencia: el vault MANDA
|
|
101
|
+
|
|
102
|
+
Los valores del vault **pisan** los del `.env` y los del entorno. El vault no
|
|
103
|
+
reemplaza al `.env` —que sigue siendo lo que arranca una máquina sin enrolar— pero
|
|
104
|
+
sí tiene la última palabra sobre las claves que administra.
|
|
105
|
+
|
|
106
|
+
Esa es la pieza que hace barata la **rotación**: cambias el valor en un solo lugar y
|
|
107
|
+
ningún `.env` viejo olvidado en un VPS puede seguir ganando. Con la precedencia al
|
|
108
|
+
revés (como estaba hasta la 0.14.0) rotar exigía además ir a limpiar cada copia
|
|
109
|
+
rancia —el trabajo que se quería evitar— y, peor, el servicio arrancaba con la llave
|
|
110
|
+
vieja **sin decir nada**.
|
|
111
|
+
|
|
112
|
+
Lo que sí se dice en voz alta: al arrancar se listan las claves que el vault tuvo que
|
|
113
|
+
pisar. Es la señal de que en esa máquina quedó un `.env` por limpiar.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
DOTRINO_ENV_OVERRIDE=0 node server.js # escotilla: por esta corrida, gana el entorno
|
|
117
|
+
dotrino-env check # dice qué claves pisaría en esta máquina
|
|
118
|
+
```
|
|
119
|
+
|
|
100
120
|
### API `@dotrino/vault/env`
|
|
101
121
|
|
|
102
|
-
- `loadEnv({ ns?, dir?, override?, wait?, required?, onRetry? }) → { ns, secrets, injected, skipped }`
|
|
103
|
-
(por defecto **
|
|
122
|
+
- `loadEnv({ ns?, dir?, override?, wait?, required?, onRetry? }) → { ns, secrets, injected, overridden, skipped }`
|
|
123
|
+
(por defecto **pisa** lo que ya esté en el entorno; `override: false` invierte la regla).
|
|
124
|
+
`overridden` son las claves que tenían otro valor y el vault reemplazó.
|
|
125
|
+
- `applyEnv(secrets, override?) → { injected, overridden, skipped }` — vuelca un bundle
|
|
126
|
+
ya obtenido, sin pedirlo. Para servicios que **no pueden bloquear su arranque**
|
|
127
|
+
esperando al vault y lo aplican cuando llega (el caso del proxy: el vault le habla
|
|
128
|
+
*por* el proxy, así que esperarlo sería un abrazo mortal).
|
|
104
129
|
- `serviceDir(ns)`, `serviceRoot()`, `listEnrolled()`, `resolveNs(ns?)`
|
|
105
|
-
- Entorno: `DOTRINO_NS` · `DOTRINO_ENV_DIR` · `DOTRINO_ENV_HOME` · `DOTRINO_ENV_QUIET`
|
|
130
|
+
- Entorno: `DOTRINO_NS` · `DOTRINO_ENV_DIR` · `DOTRINO_ENV_HOME` · `DOTRINO_ENV_QUIET` ·
|
|
131
|
+
`DOTRINO_ENV_OVERRIDE`
|
|
106
132
|
- CLI: `dotrino-env enroll|status|check|run` (`check` lista **nombres** de secretos, nunca valores)
|
|
107
133
|
|
|
108
134
|
Bajo el capó es `@dotrino/vault/service` (`enrollService` / `waitForSecrets`): petición
|
package/bin/dotrino-env.js
CHANGED
|
@@ -16,8 +16,13 @@ import fs from 'node:fs'
|
|
|
16
16
|
import path from 'node:path'
|
|
17
17
|
import readline from 'node:readline/promises'
|
|
18
18
|
import { spawn } from 'node:child_process'
|
|
19
|
-
import { enrollService, readServiceIdentity } from '../src/service.js'
|
|
19
|
+
import { enrollService, readServiceIdentity, fetchSecrets } from '../src/service.js'
|
|
20
20
|
import { loadEnv, serviceDir, serviceRoot, listEnrolled, resolveNs } from '../src/env.js'
|
|
21
|
+
// Se usaba sin importarlo: `enroll` —el comando principal— moría con
|
|
22
|
+
// «sharedParseInvite is not defined» en cuanto tocaba una invitación. Nunca
|
|
23
|
+
// llegó a funcionar, y por eso el único servicio enrolado del ecosistema (el
|
|
24
|
+
// proxy) lo hizo con su propio `enroll-vault.js` en vez de con esta CLI.
|
|
25
|
+
import { parseInvite as sharedParseInvite } from '../src/invite.js'
|
|
21
26
|
|
|
22
27
|
const argv = process.argv.slice(2)
|
|
23
28
|
const flag = (name) => { const i = argv.indexOf('--' + name); return i >= 0 ? argv[i + 1] : undefined }
|
|
@@ -40,7 +45,11 @@ En tu código:
|
|
|
40
45
|
import '@dotrino/vault/config' // ns por DOTRINO_NS
|
|
41
46
|
import { loadEnv } from '@dotrino/vault/env'; await loadEnv({ ns: '<ns>' })
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
El vault MANDA: sus valores pisan los del .env y los del entorno. Para una
|
|
49
|
+
corrida suelta sin que pise nada: DOTRINO_ENV_OVERRIDE=0
|
|
50
|
+
|
|
51
|
+
Entorno: DOTRINO_NS · DOTRINO_ENV_DIR · DOTRINO_ENV_HOME · DOTRINO_ENV_QUIET
|
|
52
|
+
DOTRINO_ENV_OVERRIDE`)
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
/**
|
|
@@ -106,17 +115,27 @@ function cmdStatus () {
|
|
|
106
115
|
|
|
107
116
|
async function cmdCheck () {
|
|
108
117
|
const ns = resolveNs(flag('ns'))
|
|
109
|
-
|
|
118
|
+
// `fetchSecrets` y no `loadEnv`: listar NO debe tener efectos secundarios. Con
|
|
119
|
+
// `loadEnv` esto inyectaría el bundle en el entorno del propio `check`, que es
|
|
120
|
+
// justo lo que un comando de diagnóstico no tiene por qué hacer.
|
|
121
|
+
const secrets = await fetchSecrets({ dir: serviceDir(ns), ns })
|
|
110
122
|
const keys = Object.keys(secrets)
|
|
111
123
|
console.log('ns "%s": %d secreto(s)%s', ns, keys.length, keys.length ? ':' : '')
|
|
112
124
|
for (const k of keys) console.log(' ' + k) // NUNCA los valores
|
|
125
|
+
// Delata el `.env` rancio: qué claves de esta máquina el vault pisaría.
|
|
126
|
+
const chocan = keys.filter((k) => k in process.env && process.env[k] !== String(secrets[k]))
|
|
127
|
+
if (chocan.length) {
|
|
128
|
+
console.log('\nEl vault PISA estos valores del entorno de esta máquina:\n %s', chocan.join(', '))
|
|
129
|
+
}
|
|
113
130
|
}
|
|
114
131
|
|
|
115
132
|
async function cmdRun () {
|
|
116
133
|
const sep = argv.indexOf('--')
|
|
117
134
|
const cmd = sep >= 0 ? argv.slice(sep + 1) : []
|
|
118
135
|
if (!cmd.length) { console.error('uso: dotrino-env run [--ns <ns>] -- <cmd> [args…]'); process.exit(2) }
|
|
119
|
-
await loadEnv({ ns: flag('ns') })
|
|
136
|
+
const { injected, overridden } = await loadEnv({ ns: flag('ns') })
|
|
137
|
+
console.error('[dotrino-env] %d valor(es) en el entorno de %s%s', injected.length, cmd[0],
|
|
138
|
+
overridden.length ? ` (pisados: ${overridden.join(', ')})` : '')
|
|
120
139
|
const child = spawn(cmd[0], cmd.slice(1), { stdio: 'inherit', env: process.env })
|
|
121
140
|
child.on('exit', (code, signal) => process.exit(signal ? 1 : (code ?? 0)))
|
|
122
141
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/vault",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Usa ESTE dispositivo (navegador) como bóveda/CA del ecosistema Dotrino: atiende enrolamientos por el proxy y firma certificados de delegación a tus máquinas. Incluye el cliente de SERVICIO (Node): un proyecto se enrola una vez y jala sus credenciales del vault en vez del .env (`import '@dotrino/vault/config'`).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
],
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@dotrino/identity": ">=0.30.0",
|
|
46
|
-
"@dotrino/proxy-client": ">=0.
|
|
46
|
+
"@dotrino/proxy-client": ">=0.9.0"
|
|
47
47
|
},
|
|
48
48
|
"license": "MIT",
|
|
49
49
|
"repository": {
|
package/src/config.js
CHANGED
|
@@ -8,19 +8,31 @@
|
|
|
8
8
|
* secretos viejos ni vacíos. Un fallo NO transitorio (sin enrolar, cert revocado,
|
|
9
9
|
* scope equivocado) sí aborta el proceso.
|
|
10
10
|
*
|
|
11
|
+
* EL VAULT MANDA: lo que venga de aquí PISA lo que ya hubiera en el entorno
|
|
12
|
+
* (incluido un `.env` cargado antes). Ver `env.js` para el porqué.
|
|
13
|
+
*
|
|
11
14
|
* Config por entorno:
|
|
12
|
-
* DOTRINO_NS
|
|
13
|
-
* DOTRINO_ENV_DIR
|
|
14
|
-
* DOTRINO_ENV_QUIET
|
|
15
|
+
* DOTRINO_NS namespace de secretos (si no, el único enrolado en la máquina)
|
|
16
|
+
* DOTRINO_ENV_DIR directorio de la identidad del servicio (si no, ~/.dotrino/service/<ns>)
|
|
17
|
+
* DOTRINO_ENV_QUIET '1' para no imprimir la línea de arranque
|
|
18
|
+
* DOTRINO_ENV_OVERRIDE '0' para NO pisar el entorno en esta corrida (depuración)
|
|
15
19
|
*/
|
|
16
20
|
import { loadEnv } from './env.js'
|
|
17
21
|
|
|
18
22
|
const quiet = process.env.DOTRINO_ENV_QUIET === '1'
|
|
19
23
|
|
|
20
|
-
const { ns, injected } = await loadEnv({
|
|
24
|
+
const { ns, injected, overridden } = await loadEnv({
|
|
21
25
|
onRetry: (e, ms) => {
|
|
22
26
|
if (!quiet) console.error('[dotrino-env] vault no disponible (%s); reintentando en %ds…', e.message, Math.round(ms / 1000))
|
|
23
27
|
}
|
|
24
28
|
})
|
|
25
29
|
|
|
26
|
-
if (!quiet)
|
|
30
|
+
if (!quiet) {
|
|
31
|
+
console.error('[dotrino-env] %d valor(es) del ns "%s" cargados en process.env', injected.length, ns)
|
|
32
|
+
// Se dice en voz alta: que el vault haya tenido que pisar algo significa que
|
|
33
|
+
// en esta máquina hay un `.env` con valores viejos. Ganó el vault, pero el
|
|
34
|
+
// operador quiere enterarse — es la señal de que quedó basura por limpiar.
|
|
35
|
+
if (overridden.length) {
|
|
36
|
+
console.error('[dotrino-env] pisaron un valor previo del entorno: %s', overridden.join(', '))
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/enroll.js
CHANGED
|
@@ -146,14 +146,20 @@ export function createEnrollDesk ({
|
|
|
146
146
|
* aviso. Es ORIENTATIVO (un nombre que puso su dueño); la
|
|
147
147
|
* identidad de verdad de la cuenta es `iss`.
|
|
148
148
|
*/
|
|
149
|
-
function startPairing ({ scope = defaultScope, ttlMs = defaultTtlMs, label = '', mode = 'join', account = '' } = {}) {
|
|
149
|
+
async function startPairing ({ scope = defaultScope, ttlMs = defaultTtlMs, label = '', mode = 'join', account = '' } = {}) {
|
|
150
150
|
pending.clear() // uno a la vez: una sesión nueva supersede a la anterior
|
|
151
151
|
const acct = String(account || '').slice(0, 40)
|
|
152
|
-
// INVITACIÓN CORTA: si sabemos
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
152
|
+
// INVITACIÓN CORTA: si sabemos cómo alcanzarnos, el QR lleva solo eso y el
|
|
153
|
+
// nonce de la sesión. La llave, el proxy y el nombre de la cuenta los pide el
|
|
154
|
+
// aparato por la red presentando el `sn`. El nonce hace de identificador de
|
|
155
|
+
// sesión: no hace falta un token de emparejamiento aparte.
|
|
156
|
+
//
|
|
157
|
+
// `conn` es una CITA del proxio (6 caracteres, un solo uso, caduca en
|
|
158
|
+
// minutos), no la dirección de la conexión: esa pasó a ser una instancia de
|
|
159
|
+
// 24 caracteres, que ni entra cómoda en un QR ni tiene por qué quedar impresa
|
|
160
|
+
// en algo que circula. Por eso se pide una nueva por emparejamiento, y por
|
|
161
|
+
// eso esto es asíncrono.
|
|
162
|
+
const conn = typeof connToken === 'function' ? await connToken() : connToken
|
|
157
163
|
if (conn) {
|
|
158
164
|
const sn = randToken(8)
|
|
159
165
|
pending.set(sn, { token: sn, exp: Date.now() + PAIRING_TTL_MS, scope, ttlMs, label, sn, mode, account: acct, state: 'AWAITING_ENROLL' })
|
package/src/env.js
CHANGED
|
@@ -61,19 +61,42 @@ export function resolveNs (ns) {
|
|
|
61
61
|
throw new Error(`hay varios servicios enrolados (${found.join(', ')}): elige uno con DOTRINO_NS=<ns> o loadEnv({ ns })`)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* EL VAULT MANDA: sus valores PISAN los del `.env` y los del entorno.
|
|
66
|
+
*
|
|
67
|
+
* No es el default de `dotenv` y es a propósito. El vault no viene a reemplazar
|
|
68
|
+
* al `.env` —que sigue ahí y sigue siendo el arranque de cualquier máquina sin
|
|
69
|
+
* enrolar—, viene a ser la ÚLTIMA palabra sobre las claves que administra. Esa
|
|
70
|
+
* es justamente la pieza que hace barata la rotación: se cambia el valor en un
|
|
71
|
+
* solo lugar y ningún `.env` viejo, olvidado en un VPS, puede seguir ganando.
|
|
72
|
+
* Con la precedencia al revés, rotar exigía además ir a limpiar cada copia
|
|
73
|
+
* rancia —que es exactamente el trabajo que se quería evitar—, y peor: el
|
|
74
|
+
* servicio arrancaba con la llave vieja SIN decir nada.
|
|
75
|
+
*
|
|
76
|
+
* Escotilla para depurar: `DOTRINO_ENV_OVERRIDE=0` devuelve la precedencia
|
|
77
|
+
* clásica (gana lo que ya está en el entorno) para una corrida suelta, sin
|
|
78
|
+
* tocar el vault ni el código.
|
|
79
|
+
*/
|
|
80
|
+
function overrideByDefault () {
|
|
81
|
+
return process.env.DOTRINO_ENV_OVERRIDE !== '0'
|
|
82
|
+
}
|
|
83
|
+
|
|
64
84
|
/**
|
|
65
85
|
* Trae los secretos del ns desde el vault y los pone en `process.env`.
|
|
66
86
|
*
|
|
67
87
|
* @param {Object} [opts]
|
|
68
88
|
* @param {string} [opts.ns] Namespace (por defecto: `DOTRINO_NS` o el único enrolado).
|
|
69
89
|
* @param {string} [opts.dir] Dónde está `service-identity.json` (por defecto: `serviceDir(ns)`).
|
|
70
|
-
* @param {boolean} [opts.override]
|
|
90
|
+
* @param {boolean} [opts.override] El vault pisa lo que ya esté en el entorno (default: SÍ, ver arriba).
|
|
71
91
|
* @param {boolean} [opts.wait] `true` (default) = si el vault no está, ESPERA (reintenta) en vez de fallar.
|
|
72
92
|
* @param {string[]} [opts.required] Claves que deben venir; si falta alguna, lanza.
|
|
73
93
|
* @param {(e:Error, ms:number)=>void} [opts.onRetry]
|
|
74
|
-
* @returns {Promise<{ns
|
|
94
|
+
* @returns {Promise<{ns, secrets, injected:string[], overridden:string[], skipped:string[]}>}
|
|
95
|
+
* `overridden` = las que YA tenían otro valor en el entorno y el vault pisó. Es
|
|
96
|
+
* el dato que delata un `.env` rancio, así que se reporta en vez de callarse.
|
|
75
97
|
*/
|
|
76
|
-
export async function loadEnv ({ ns, dir, override
|
|
98
|
+
export async function loadEnv ({ ns, dir, override, wait = true, required = [], onRetry } = {}) {
|
|
99
|
+
if (override === undefined) override = overrideByDefault()
|
|
77
100
|
ns = resolveNs(ns)
|
|
78
101
|
dir = dir || serviceDir(ns)
|
|
79
102
|
const load = wait ? waitForSecrets : fetchSecrets
|
|
@@ -84,12 +107,31 @@ export async function loadEnv ({ ns, dir, override = false, wait = true, require
|
|
|
84
107
|
throw new Error(`faltan secretos en el ns "${ns}": ${missing.join(', ')} (agrégalos con \`dotrino-vault secret set ${ns} <CLAVE> <valor>\`)`)
|
|
85
108
|
}
|
|
86
109
|
|
|
110
|
+
return { ns, secrets, ...applyEnv(secrets, override) }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Vuelca un bundle de secretos en `process.env` y cuenta qué cambió.
|
|
115
|
+
*
|
|
116
|
+
* Separado de `loadEnv` porque un servicio que NO puede bloquear su arranque
|
|
117
|
+
* esperando al vault (el proxy: el vault le habla POR el proxy, así que
|
|
118
|
+
* esperarlo sería un abrazo mortal) igual necesita aplicar el bundle cuando
|
|
119
|
+
* llegue, tarde y por su cuenta.
|
|
120
|
+
*
|
|
121
|
+
* @returns {{injected:string[], overridden:string[], skipped:string[]}}
|
|
122
|
+
*/
|
|
123
|
+
export function applyEnv (secrets, override = overrideByDefault()) {
|
|
87
124
|
const injected = []
|
|
125
|
+
const overridden = []
|
|
88
126
|
const skipped = []
|
|
89
|
-
for (const [k, v] of Object.entries(secrets)) {
|
|
90
|
-
|
|
91
|
-
process.env
|
|
127
|
+
for (const [k, v] of Object.entries(secrets || {})) {
|
|
128
|
+
const previo = process.env[k]
|
|
129
|
+
const tenia = k in process.env
|
|
130
|
+
if (!override && tenia) { skipped.push(k); continue }
|
|
131
|
+
const valor = String(v)
|
|
132
|
+
process.env[k] = valor
|
|
92
133
|
injected.push(k)
|
|
134
|
+
if (tenia && previo !== valor) overridden.push(k)
|
|
93
135
|
}
|
|
94
|
-
return {
|
|
136
|
+
return { injected, overridden, skipped }
|
|
95
137
|
}
|
package/src/invite.js
CHANGED
|
@@ -56,9 +56,12 @@ export const FMT_COMPACT = 'c'
|
|
|
56
56
|
* que es lo único que de verdad decide. Esconderla no aportaba nada —una pública es
|
|
57
57
|
* pública— y ocupaba 44 de los ~100 caracteres.
|
|
58
58
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* Lo que lleva NO es la dirección de la bóveda: es una **cita del proxy** —un código
|
|
60
|
+
* de 6 caracteres, de un solo uso y con minutos de vida, cuyos 2 primeros dicen qué
|
|
61
|
+
* proxio la emitió—. El aparato la CANJEA (`redeemPairingCode`) y obtiene la dirección
|
|
62
|
+
* real de la conexión, que hoy son 24 caracteres para poder rutearse entre proxios y no
|
|
63
|
+
* cabría cómoda en un QR. Además, una cita caduca y se quema: no deja una dirección
|
|
64
|
+
* permanente impresa en algo que circula.
|
|
62
65
|
*/
|
|
63
66
|
export const FMT_SHORT = 't'
|
|
64
67
|
|
|
@@ -293,7 +296,10 @@ function compactDecode (text) {
|
|
|
293
296
|
* Le pasa a cualquiera con proxy propio (y lo cazó el E2E de secretos, que levanta
|
|
294
297
|
* uno local).
|
|
295
298
|
*/
|
|
296
|
-
|
|
299
|
+
// La CITA del proxio: 2 caracteres de prefijo de nodo + 4 = 6. Antes eran 4 (la
|
|
300
|
+
// dirección de la conexión). Cambió porque la dirección pasó a ser una instancia
|
|
301
|
+
// de 24 caracteres, y lo que se imprime en un QR ahora es una cita de un solo uso.
|
|
302
|
+
const CONN_TOKEN_LEN = 6
|
|
297
303
|
|
|
298
304
|
function shortEncode (qr) {
|
|
299
305
|
if (!qr || qr.v !== 2) return null
|
package/src/service.js
CHANGED
|
@@ -77,6 +77,25 @@ async function verificarHola (p, sn) {
|
|
|
77
77
|
return b
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Canjea la cita del QR y devuelve la instancia a la que apunta.
|
|
82
|
+
*
|
|
83
|
+
* Una cita se quema al usarse y caduca en minutos, así que un error acá casi
|
|
84
|
+
* siempre significa lo mismo para quien lo lee: el código ya se usó o venció, y
|
|
85
|
+
* hay que pedir otro en la bóveda. Se dice así, no con el error crudo.
|
|
86
|
+
*/
|
|
87
|
+
async function resolverCita (client, code) {
|
|
88
|
+
if (!code) throw new Error('la invitación no trae código de emparejamiento')
|
|
89
|
+
if (typeof client.redeemPairingCode !== 'function') {
|
|
90
|
+
throw new Error('el proxio no soporta códigos de emparejamiento (actualizá @dotrino/proxy-client)')
|
|
91
|
+
}
|
|
92
|
+
const r = await client.redeemPairingCode(code)
|
|
93
|
+
if (!r?.ok || !r.instance) {
|
|
94
|
+
throw new Error(`ese código no sirve: ${r?.error || 'no válido'}. Pedí uno nuevo en la bóveda.`)
|
|
95
|
+
}
|
|
96
|
+
return r.instance
|
|
97
|
+
}
|
|
98
|
+
|
|
80
99
|
async function freshClient (proxyUrl, connectTimeoutMs = 20000) {
|
|
81
100
|
installNodeGlobals()
|
|
82
101
|
const { WebSocketProxyClient } = await import('@dotrino/proxy-client')
|
|
@@ -157,6 +176,11 @@ export async function enrollService ({ qr, ns, dir, label, onCode, approveTimeou
|
|
|
157
176
|
const client = await freshClient(qr.proxy || 'wss://proxy.dotrino.com')
|
|
158
177
|
// QR CORTO: se le pregunta a la bóveda quién es, punto a punto, presentando el `sn`.
|
|
159
178
|
if (!qr.iss) {
|
|
179
|
+
// `qr.conn` es una CITA (código de 6 caracteres, un solo uso): hay que
|
|
180
|
+
// canjearla para saber a qué conexión apunta. El canje lo resuelve el proxio
|
|
181
|
+
// que la emitió —lo dice el prefijo del propio código—, así que funciona
|
|
182
|
+
// aunque la bóveda esté en otro proxio de la malla.
|
|
183
|
+
const destino = await resolverCita(client, qr.conn)
|
|
160
184
|
const hola = await new Promise((resolve, reject) => {
|
|
161
185
|
const off = client.on('message', (_f, p) => {
|
|
162
186
|
if (p?.type === MSG.HELLO_OK) { fin(); verificarHola(p, qr.sn).then(resolve, reject) }
|
|
@@ -164,7 +188,7 @@ export async function enrollService ({ qr, ns, dir, label, onCode, approveTimeou
|
|
|
164
188
|
})
|
|
165
189
|
const t = setTimeout(() => { fin(); reject(new Error('la bóveda no contestó: ese código pudo caducar')) }, 15000)
|
|
166
190
|
const fin = () => { off(); clearTimeout(t) }
|
|
167
|
-
try { client.send(
|
|
191
|
+
try { client.send(destino, { type: MSG.HELLO, sn: qr.sn }) } catch (e) { fin(); reject(e) }
|
|
168
192
|
})
|
|
169
193
|
qr = { ...qr, iss: hola.iss, proxy: hola.proxy || qr.proxy }
|
|
170
194
|
}
|