@dotrino/vaultd 0.145.0 → 0.146.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/ctl.js +36 -0
- package/src/daemon.js +43 -31
- package/src/notifiable.js +9 -8
- package/src/settings.js +37 -0
- package/src/tui/app.js +6 -1
- package/src/tui/i18n.js +4 -0
- package/src/vault.js +7 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/vaultd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.146.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Certificador personal de Dotrino: daemon headless que custodia la clave maestra y delega capacidades a tus dispositivos por el proxy. Tu CA propia.",
|
|
6
6
|
"bin": {
|
package/src/ctl.js
CHANGED
|
@@ -131,6 +131,11 @@ function deviceIdOf (sub) {
|
|
|
131
131
|
return pubkeyId(sub).then((id) => id.slice(0, 8).toUpperCase().replace(/(.{4})(.{4})/, '$1-$2'))
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/** Lo que dijo un aprobador sobre sus avisos, para enseñarlo. No decide nada. */
|
|
135
|
+
function avisosTxt (notifiedAt) {
|
|
136
|
+
return notifiedAt ? `dijo que le llegan los avisos (${fechaCorta(notifiedAt)})` : 'no ha dicho que le lleguen los avisos'
|
|
137
|
+
}
|
|
138
|
+
|
|
134
139
|
function cmdStatus () {
|
|
135
140
|
const s = readState()
|
|
136
141
|
const up = alive(s.pid)
|
|
@@ -158,6 +163,18 @@ function cmdStatus () {
|
|
|
158
163
|
if (s.updateAsk?.version && isNewer(s.updateAsk.version, VERSION)) {
|
|
159
164
|
console.log(' pidió permiso para instalarla %s (%d %s) y nadie lo ha aprobado · mira Pedidos en tu teléfono',
|
|
160
165
|
hace(s.updateAsk.since), s.updateAsk.asks, s.updateAsk.asks === 1 ? 'vez' : 'veces')
|
|
166
|
+
// A QUIÉN SE LE PIDIÓ, y si dijo que le llegan los avisos: el que no lo ha dicho es el
|
|
167
|
+
// primer sitio donde mirar cuando nadie contesta.
|
|
168
|
+
for (const a of s.updateAsk.approvers || []) {
|
|
169
|
+
console.log(' %s %s · %s', a.id, a.label || '(sin nombre)', avisosTxt(a.notifiedAt))
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// CÓMO SE ACTUALIZA. Va siempre, también al día: es un ajuste y se mira aquí.
|
|
173
|
+
if (up && 'updateApproval' in s) {
|
|
174
|
+
console.log(' actualizar : %s', s.updateApproval === null
|
|
175
|
+
? `${R}no se pudo leer el ajuste: no se actualiza${Z} · ponlo de nuevo con: dotrino-vault update --approval on|off`
|
|
176
|
+
: s.updateApproval ? 'pide aprobación (dotrino-vault update --approval off para que lo haga sola)'
|
|
177
|
+
: 'sola, sin pedir aprobación (dotrino-vault update --approval on para que pida)')
|
|
161
178
|
}
|
|
162
179
|
const profiles = s.profiles || []
|
|
163
180
|
if (profiles.length) {
|
|
@@ -631,6 +648,9 @@ async function cmdMembers () {
|
|
|
631
648
|
// Solo si LEE variables (`secrets`). La llave de comunicación de la bóveda entra con
|
|
632
649
|
// `cn: 'vault'` y solo `firma`, y no tiene llave de cifrado a propósito: salía marcada
|
|
633
650
|
// en rojo «NO puede leer sus variables» cuando no tiene ninguna que leer.
|
|
651
|
+
// SI LE LLEGAN LOS AVISOS, en quien aprueba. No cambia a quién se le pide —se le pide a
|
|
652
|
+
// todos—: es para ver a cuál no le suena el pedido.
|
|
653
|
+
if (m.caps.includes('approve') && 'notifiedAt' in m) console.log(' %s%s%s', D, avisosTxt(m.notifiedAt), Z)
|
|
634
654
|
if (m.cn && !m.canSeal && m.caps.includes('secrets')) console.log(' %ssin llave de cifrado: NO puede leer sus variables%s', R, Z)
|
|
635
655
|
// QUÉ CORRE, y si cuadra con esta bóveda (CONVENCIONES §14). Sin esto una
|
|
636
656
|
// incompatibilidad de versiones se ve como que ese aparato «no responde», que es el
|
|
@@ -1794,6 +1814,19 @@ async function waitForService (version, { timeoutMs = 4 * 60_000 } = {}) {
|
|
|
1794
1814
|
}
|
|
1795
1815
|
|
|
1796
1816
|
async function cmdUpdate (args = []) {
|
|
1817
|
+
// PEDIR PERMISO PARA ACTUALIZARSE es un ajuste, apagado por defecto. Se guarda aquí y el
|
|
1818
|
+
// servicio lo lee la próxima vez que mire si hay versión nueva: no hace falta reiniciarlo.
|
|
1819
|
+
const iAp = args.indexOf('--approval')
|
|
1820
|
+
if (iAp !== -1) {
|
|
1821
|
+
const { readSettings, writeSettings } = await import('./settings.js')
|
|
1822
|
+
const v = args[iAp + 1]
|
|
1823
|
+
if (v === 'on' || v === 'off') writeSettings(dir, { updateApproval: v === 'on' })
|
|
1824
|
+
else if (v !== undefined) { console.error('uso: dotrino-vault update --approval [on|off]'); process.exit(2) }
|
|
1825
|
+
console.log(readSettings(dir).updateApproval
|
|
1826
|
+
? 'Esta bóveda pide aprobación antes de actualizarse (a los aparatos con «aprueba»).'
|
|
1827
|
+
: 'Esta bóveda se actualiza sola, sin pedir aprobación.')
|
|
1828
|
+
return
|
|
1829
|
+
}
|
|
1797
1830
|
const { latestRelease, assetFor, download, verifyArtifact, isNewer, isUserInstall, installUserRelease } = await import('./update.js')
|
|
1798
1831
|
const soloMirar = args.includes('--check')
|
|
1799
1832
|
|
|
@@ -2174,6 +2207,9 @@ function help () {
|
|
|
2174
2207
|
status | info estado del servicio + fingerprint + el id de cada perfil
|
|
2175
2208
|
update [--check] trae la versión nueva (verificando la firma) y espera a que el
|
|
2176
2209
|
servicio la arranque · --check solo mira · --no-wait no espera
|
|
2210
|
+
update --approval [on|off]
|
|
2211
|
+
el servicio se actualiza solo; con «on» antes pide aprobación a los
|
|
2212
|
+
aparatos con «aprueba» · sin valor, dice cómo está
|
|
2177
2213
|
pair [--save <f>] inicia un emparejamiento (QR + espera); --save escribe la invitación (.dpair)
|
|
2178
2214
|
pair --kms <config.json>
|
|
2179
2215
|
el sitio que se cree (--adopt o --new-account) NACE con su clave
|
package/src/daemon.js
CHANGED
|
@@ -27,6 +27,7 @@ import { capScope } from '@dotrino/identity/acta'
|
|
|
27
27
|
import { watchBinary } from './selfupdate.js'
|
|
28
28
|
import { latestRelease, isNewer, CHECK_EVERY_MS, isUserInstall, installUserRelease } from './update.js'
|
|
29
29
|
import { VERSION } from './version.js'
|
|
30
|
+
import { readSettings } from './settings.js'
|
|
30
31
|
import crypto from 'node:crypto'
|
|
31
32
|
|
|
32
33
|
const readJsonSafe = (f) => ipcRead(f, null)
|
|
@@ -101,6 +102,7 @@ export async function runDaemon () {
|
|
|
101
102
|
// Los campos de la raíz (fingerprint/iss) son los del perfil ACTIVO: los leen el
|
|
102
103
|
// instalador y la web, que son anteriores al multi-perfil. La lista completa va
|
|
103
104
|
// en `profiles`.
|
|
105
|
+
const ajusteActualizar = () => { try { return readSettings(dir).updateApproval } catch (_) { return null } }
|
|
104
106
|
const writeState = () => {
|
|
105
107
|
const cur = mgr.summary().find((p) => p.current) || {}
|
|
106
108
|
ipcWrite(stateFile, {
|
|
@@ -114,6 +116,9 @@ export async function runDaemon () {
|
|
|
114
116
|
// también sin conexión.
|
|
115
117
|
...(ultimaPublicada ? { latest: ultimaPublicada } : {}),
|
|
116
118
|
...(pedidoSinRespuesta ? { updateAsk: pedidoSinRespuesta } : {}),
|
|
119
|
+
// Si para actualizarse pide permiso (`src/settings.js`). `null` = el ajuste no se
|
|
120
|
+
// pudo leer, que `status` dice distinto de «no».
|
|
121
|
+
updateApproval: ajusteActualizar(),
|
|
117
122
|
current: mgr.currentId(), profiles: mgr.summary()
|
|
118
123
|
})
|
|
119
124
|
}
|
|
@@ -1103,35 +1108,39 @@ export async function runDaemon () {
|
|
|
1103
1108
|
if (!isNewer(r.version, daemonVersion)) return
|
|
1104
1109
|
|
|
1105
1110
|
/**
|
|
1106
|
-
*
|
|
1111
|
+
* SE ACTUALIZA SOLA. PEDIR PERMISO ES UN AJUSTE (dueño, 2026-10-08).
|
|
1107
1112
|
*
|
|
1108
|
-
*
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
1111
|
-
*
|
|
1113
|
+
* Por defecto no se le pregunta a nadie: lo que se baja se verifica contra la
|
|
1114
|
+
* atestación del release antes de tocar el disco, y eso no depende de que alguien
|
|
1115
|
+
* conteste. Antes se pedía permiso según quién hubiera en el acta y según si decía que
|
|
1116
|
+
* le llegan los avisos; eran condiciones que explicar y que se desalineaban, y una
|
|
1117
|
+
* bóveda podía quedarse atrás sin que nadie lo viera.
|
|
1112
1118
|
*
|
|
1113
|
-
*
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1119
|
+
* Quien quiera decidir cada actualización lo enciende: `dotrino-vault update --approval
|
|
1120
|
+
* on` (`src/settings.js`). Entonces se le pide a TODO el que tenga `aprueba`, reciba
|
|
1121
|
+
* avisos o no, y sin un sí no se instala. Si en el acta no aprueba nadie, se actualiza
|
|
1122
|
+
* igual: esperar un sí que nadie puede dar sería no actualizarse nunca.
|
|
1123
|
+
*
|
|
1124
|
+
* Lo que cada aprobador dijo de sus avisos (`notifiedAt`, `src/notifiable.js`) no
|
|
1125
|
+
* decide nada: se enseña junto al pedido sin respuesta, para ver a cuál no le suena.
|
|
1116
1126
|
*/
|
|
1117
|
-
// SI NO SE PUEDE
|
|
1118
|
-
//
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
console.log(`[vault] version ${r.version} is out, but the approvers could not be read (${e?.message || e}) · not updating`)
|
|
1127
|
+
// SI EL AJUSTE NO SE PUEDE LEER, NO SE ACTUALIZA: el valor por defecto es no preguntar,
|
|
1128
|
+
// así que suponerlo sería saltarse la aprobación que el dueño pidió.
|
|
1129
|
+
let pedirPermiso
|
|
1130
|
+
try { pedirPermiso = readSettings(dir).updateApproval } catch (e) {
|
|
1131
|
+
console.log(`[vault] version ${r.version} is out, but the settings could not be read (${e?.message || e}) · not updating`)
|
|
1123
1132
|
return
|
|
1124
1133
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
const
|
|
1134
|
+
// Y si hay que pedir y no se puede saber a quién, tampoco: lista vacía sería «nadie
|
|
1135
|
+
// aprueba, adelante», el repliegue que abre la puerta justo cuando algo se rompió.
|
|
1136
|
+
let quienAprueba = []
|
|
1137
|
+
if (pedirPermiso) {
|
|
1138
|
+
try { quienAprueba = await mgr.current().approvers() } catch (e) {
|
|
1139
|
+
console.log(`[vault] version ${r.version} is out, but the approvers could not be read (${e?.message || e}) · not updating`)
|
|
1140
|
+
return
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
const conAvisos = quienAprueba.filter((a) => a.notifiedAt).length
|
|
1135
1144
|
|
|
1136
1145
|
// INSTALADA COMO PAQUETE DEL SISTEMA (.deb en /usr/bin): el binario es de root y este
|
|
1137
1146
|
// proceso no puede tocarlo. Se dice, con las dos salidas, y no se intenta nada.
|
|
@@ -1142,22 +1151,25 @@ export async function runDaemon () {
|
|
|
1142
1151
|
if (actualizando) return
|
|
1143
1152
|
actualizando = true
|
|
1144
1153
|
try {
|
|
1145
|
-
// PIDE PERMISO SOLO SI HAY A QUIÉN PEDÍRSELO (dueño, 2026-09-20); sin aprobadores
|
|
1146
|
-
// no se necesita aprobación (dueño, 2026-09-24).
|
|
1147
1154
|
if (quienAprueba.length) {
|
|
1148
|
-
console.log(`[vault] version ${r.version} is out (this one is ${daemonVersion}) · asking one of ${quienAprueba.length} approver(s)`)
|
|
1155
|
+
console.log(`[vault] version ${r.version} is out (this one is ${daemonVersion}) · asking one of ${quienAprueba.length} approver(s) · ${conAvisos} of them said notifications reach it`)
|
|
1149
1156
|
const si = await mgr.current().askUpdateApproval({ version: r.version })
|
|
1150
1157
|
if (!si) {
|
|
1151
1158
|
const antes = pedidoSinRespuesta?.version === r.version ? pedidoSinRespuesta : null
|
|
1152
|
-
pedidoSinRespuesta = {
|
|
1159
|
+
pedidoSinRespuesta = {
|
|
1160
|
+
version: r.version, since: antes?.since || Date.now(), asks: (antes?.asks || 0) + 1,
|
|
1161
|
+
// A QUIÉN SE LE PIDIÓ, y si dijo que le llegan los avisos: es lo que hay que mirar
|
|
1162
|
+
// cuando nadie contesta.
|
|
1163
|
+
approvers: quienAprueba.map((a) => ({ id: a.id, label: a.label, notifiedAt: a.notifiedAt }))
|
|
1164
|
+
}
|
|
1153
1165
|
writeState()
|
|
1154
1166
|
console.log(`[vault] update to ${r.version}: not approved (denied or expired) · asked ${pedidoSinRespuesta.asks} time(s) since ${new Date(pedidoSinRespuesta.since).toISOString()} · it will ask again`)
|
|
1155
1167
|
return
|
|
1156
1168
|
}
|
|
1157
|
-
} else if (
|
|
1158
|
-
console.log(`[vault] version ${r.version} is out (this one is ${daemonVersion}) ·
|
|
1169
|
+
} else if (pedirPermiso) {
|
|
1170
|
+
console.log(`[vault] version ${r.version} is out (this one is ${daemonVersion}) · approval is on but no approver in the record: updating on its own`)
|
|
1159
1171
|
} else {
|
|
1160
|
-
console.log(`[vault] version ${r.version} is out (this one is ${daemonVersion}) ·
|
|
1172
|
+
console.log(`[vault] version ${r.version} is out (this one is ${daemonVersion}) · updating on its own`)
|
|
1161
1173
|
}
|
|
1162
1174
|
if (pedidoSinRespuesta) { pedidoSinRespuesta = null; writeState() }
|
|
1163
1175
|
const res = await installUserRelease(r, { log: (m) => console.log(m) })
|
package/src/notifiable.js
CHANGED
|
@@ -2,24 +2,25 @@
|
|
|
2
2
|
* QUÉ APROBADORES PUEDEN RECIBIR UN AVISO: lo dice cada uno, y caduca.
|
|
3
3
|
*
|
|
4
4
|
* Un aparato con `approve` que no recibe avisos es un aprobador solo en el papel: el pedido
|
|
5
|
-
* no le suena y solo lo ve si abre la app por su cuenta.
|
|
6
|
-
* bloqueo mudo — la bóveda pedía cada día, nadie se enteraba y se quedaba atrás para siempre
|
|
7
|
-
* (dueño, 2026-10-07).
|
|
5
|
+
* no le suena y solo lo ve si abre la app por su cuenta.
|
|
8
6
|
*
|
|
9
7
|
* Así que el aprobador lo declara: al pedir su lista de pedidos (`op: 'approvals'`, que ya
|
|
10
8
|
* manda firmado cada vez que se abre) dice `notify: true` si tiene los avisos encendidos.
|
|
11
9
|
* Aquí se apunta CUÁNDO lo dijo por última vez, y vale `NOTIFIABLE_TTL_MS`: una app que se
|
|
12
|
-
* desinstala deja de renovarlo
|
|
10
|
+
* desinstala deja de renovarlo.
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* NO DECIDE NADA: SE ENSEÑA (dueño, 2026-10-08). Un día decidió si la bóveda pedía permiso
|
|
13
|
+
* para actualizarse —sin nadie que lo hubiera dicho, se actualizaba sola— y se quitó: era
|
|
14
|
+
* una condición más, y el silencio de un aprobador abría la puerta. Quién aprueba lo dice
|
|
15
|
+
* el acta y nada más; los avisos salen a todos los que tienen `approve`, lo hayan dicho o
|
|
16
|
+
* no. Esto sirve para VER a cuál no le está sonando el pedido (`members`, `status`, la
|
|
17
|
+
* consola).
|
|
17
18
|
*
|
|
18
19
|
* Y NO SE LE PREGUNTA AL PROXIO, que es quien guarda las suscripciones: es la pieza de la
|
|
19
20
|
* que el diseño no se fía, y creerle sería dejarle decidir cuándo no hace falta permiso.
|
|
20
21
|
*
|
|
21
22
|
* Cifrado en reposo como lo demás. Un archivo que existe y no se puede leer NO es «nadie
|
|
22
|
-
* puede recibir avisos
|
|
23
|
+
* puede recibir avisos»: se lanza.
|
|
23
24
|
*/
|
|
24
25
|
import fs from 'node:fs'
|
|
25
26
|
import path from 'node:path'
|
package/src/settings.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AJUSTES DE ESTA BÓVEDA: lo que el dueño elige distinto del comportamiento por defecto.
|
|
3
|
+
*
|
|
4
|
+
* Hoy es uno: `updateApproval`. Por defecto la bóveda se actualiza sola (lo que baja se
|
|
5
|
+
* verifica contra la atestación del release, se pida permiso o no); con `updateApproval:
|
|
6
|
+
* true` antes le pide permiso a los aparatos con `approve` (dueño, 2026-10-08).
|
|
7
|
+
*
|
|
8
|
+
* Va por el canal local (`ipc.js`): lo escribe la CLI (`dotrino-vault update --approval
|
|
9
|
+
* on|off`) y lo lee el daemon cada vez que mira si hay versión nueva, sin señal ni reinicio.
|
|
10
|
+
*
|
|
11
|
+
* UN ARCHIVO QUE EXISTE Y NO SE PUEDE LEER NO ES «EL VALOR POR DEFECTO»: el por defecto es
|
|
12
|
+
* actualizarse sin preguntar, así que tragarse el fallo sería saltarse la aprobación que el
|
|
13
|
+
* dueño pidió justo cuando algo se rompió. Se lanza.
|
|
14
|
+
*/
|
|
15
|
+
import fs from 'node:fs'
|
|
16
|
+
import path from 'node:path'
|
|
17
|
+
import { ipcRead, ipcWrite } from './ipc.js'
|
|
18
|
+
|
|
19
|
+
export const SETTINGS_FILE = 'settings.json'
|
|
20
|
+
|
|
21
|
+
const fail = (code, message) => Object.assign(new Error(message), { code })
|
|
22
|
+
|
|
23
|
+
/** @returns {{ updateApproval: boolean }} */
|
|
24
|
+
export function readSettings (dir) {
|
|
25
|
+
const file = path.join(dir, SETTINGS_FILE)
|
|
26
|
+
if (!fs.existsSync(file)) return { updateApproval: false }
|
|
27
|
+
const raw = ipcRead(file, null)
|
|
28
|
+
if (!raw || typeof raw !== 'object' || typeof raw.updateApproval !== 'boolean') {
|
|
29
|
+
throw fail('settings-unreadable', `${SETTINGS_FILE} exists but cannot be read`)
|
|
30
|
+
}
|
|
31
|
+
return { updateApproval: raw.updateApproval }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function writeSettings (dir, { updateApproval }) {
|
|
35
|
+
if (typeof updateApproval !== 'boolean') throw fail('bad-setting', 'updateApproval must be true or false')
|
|
36
|
+
ipcWrite(path.join(dir, SETTINGS_FILE), { v: 1, updateApproval })
|
|
37
|
+
}
|
package/src/tui/app.js
CHANGED
|
@@ -155,7 +155,9 @@ function mergeMembersAndCerts (members, issued) {
|
|
|
155
155
|
noAccess: !cert && !m.isMaster && !m.cn,
|
|
156
156
|
// QUÉ CORRE ESE APARATO (CONVENCIONES §14). Lo apunta la bóveda cuando le habla, así
|
|
157
157
|
// que aquí solo hay que dejarlo pasar — que es justo lo que le faltaba a `addedAt`.
|
|
158
|
-
running: m.running || null
|
|
158
|
+
running: m.running || null,
|
|
159
|
+
// SI LE LLEGAN LOS AVISOS, solo en quien aprueba (`undefined` = no aprueba, no se pinta).
|
|
160
|
+
notifiedAt: (m.caps || []).includes('approve') && 'notifiedAt' in m ? m.notifiedAt : undefined
|
|
159
161
|
}
|
|
160
162
|
})
|
|
161
163
|
}
|
|
@@ -386,6 +388,9 @@ function deviceRows (st, t) {
|
|
|
386
388
|
const extra = (d.certCount > 1 ? t.muted(` certs:${d.certCount}`) : '') +
|
|
387
389
|
(vars ? t.muted(` vars:${vars}`) : '') +
|
|
388
390
|
corre +
|
|
391
|
+
// En quien aprueba: si dijo que le llegan los avisos. No cambia a quién se le pide
|
|
392
|
+
// (a todos); deja ver a cuál no le suena el pedido. Es un dato, va en gris.
|
|
393
|
+
(d.notifiedAt === undefined ? '' : t.muted(` ${d.notifiedAt ? i.notifyYes : i.notifyUnknown}`)) +
|
|
389
394
|
// EN DEUDA: en el acta y sin poder abrir lo suyo. Va en color de aviso al lado de
|
|
390
395
|
// sus variables, que es donde se mira cuando algo no arranca.
|
|
391
396
|
(debt ? t.warn(` ${i.deviceDebt(debt)}`) : '')
|
package/src/tui/i18n.js
CHANGED
|
@@ -272,6 +272,8 @@ const es = {
|
|
|
272
272
|
revokedCount: (n) => ` Revocados: ${n}`,
|
|
273
273
|
startingPairing: 'Iniciando emparejamiento…',
|
|
274
274
|
deviceSince: 'desde',
|
|
275
|
+
notifyYes: 'le llegan avisos',
|
|
276
|
+
notifyUnknown: 'no ha dicho si le llegan avisos',
|
|
275
277
|
nameDeviceLabel: '¿Cómo se va a llamar este aparato?',
|
|
276
278
|
nameDeviceHint: 'Enter para dejar que lo diga él (usará tu apodo, y así no se distinguen)',
|
|
277
279
|
noPending: 'No hay ningún dispositivo pendiente',
|
|
@@ -719,6 +721,8 @@ const en = {
|
|
|
719
721
|
revokedCount: (n) => ` Revoked: ${n}`,
|
|
720
722
|
startingPairing: 'Starting pairing…',
|
|
721
723
|
deviceSince: 'since',
|
|
724
|
+
notifyYes: 'gets notifications',
|
|
725
|
+
notifyUnknown: 'has not said if notifications reach it',
|
|
722
726
|
nameDeviceLabel: 'What will this device be called?',
|
|
723
727
|
nameDeviceHint: 'Enter to let it say (it will use your nickname, and then they all look alike)',
|
|
724
728
|
noPending: 'No device is waiting',
|
package/src/vault.js
CHANGED
|
@@ -263,7 +263,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
263
263
|
const passwordsAtRest = atRestFor(dir)
|
|
264
264
|
// Los aparatos BLOQUEADOS por un aprobador (`src/blocked.js`). Cifrado en reposo como lo demás.
|
|
265
265
|
const blocked = openBlocked({ dir, atRest: passwordsAtRest })
|
|
266
|
-
// Qué aprobadores dijeron que pueden recibir avisos (`src/notifiable.js`)
|
|
266
|
+
// Qué aprobadores dijeron que pueden recibir avisos (`src/notifiable.js`). No decide nada: se enseña.
|
|
267
267
|
const notifiable = openNotifiable({ dir, atRest: passwordsAtRest })
|
|
268
268
|
const readPasswordsFile = () => {
|
|
269
269
|
try { return JSON.parse(passwordsAtRest.decrypt(fs.readFileSync(passwordsFile, 'utf8'))) } catch (_) { return null }
|
|
@@ -1320,7 +1320,8 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
1320
1320
|
// que arrancamos», que es distinto de «no declara» — y se dice distinto.
|
|
1321
1321
|
const devices = await Promise.all(issued.map(async (x) => ({
|
|
1322
1322
|
deviceId: x.sub ? await deviceIdOf(x.sub) : null, sub: x.sub || null, label: x.label || '', scope: x.scope, exp: x.exp, nonce: x.nonce,
|
|
1323
|
-
running: x.sub ? versionDe(x.sub) : null
|
|
1323
|
+
running: x.sub ? versionDe(x.sub) : null,
|
|
1324
|
+
notifiedAt: x.sub ? notifiable.since(x.sub) : null
|
|
1324
1325
|
})))
|
|
1325
1326
|
// LA SUBACTA VIAJA CON EL ACTA. Es la mitad de «se comparte entre todos los
|
|
1326
1327
|
// dispositivos»: sin esto, una renuncia sin sellar solo la respeta esta máquina.
|
|
@@ -4036,7 +4037,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
4036
4037
|
return (record.members || [])
|
|
4037
4038
|
.filter((m) => Acta.memberCan(record, m.pub, 'approve'))
|
|
4038
4039
|
// `notifiedAt`: cuándo dijo por última vez que le llegan los avisos (null = no lo ha
|
|
4039
|
-
// dicho, o caducó).
|
|
4040
|
+
// dicho, o caducó). No decide si se pide: se enseña junto al pedido sin respuesta.
|
|
4040
4041
|
.map((m) => ({ id: m.id, label: m.label || '', pub: m.pub, notifiedAt: notifiable.since(m.pub) }))
|
|
4041
4042
|
},
|
|
4042
4043
|
// La bóveda de contraseñas (`passwords.js`). Aquí SÍ se lista: es donde está la
|
|
@@ -4176,6 +4177,9 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
4176
4177
|
members: (r?.members || []).map((m) => ({
|
|
4177
4178
|
...m,
|
|
4178
4179
|
running: versionDe(m.pub),
|
|
4180
|
+
// Cuándo dijo que le llegan los avisos (null = no lo ha dicho, o caducó). Solo
|
|
4181
|
+
// tiene sentido en quien aprueba, y es un dato para enseñar: no decide nada.
|
|
4182
|
+
notifiedAt: notifiable.since(m.pub),
|
|
4179
4183
|
renounced: subacta.forMember(m.pub).flatMap((e) => e.caps || [])
|
|
4180
4184
|
}))
|
|
4181
4185
|
}
|