@dotrino/remote-agent 0.16.0 → 0.18.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 +3 -3
- package/src/agent.js +41 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/remote-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Middleware compartido del ecosistema Dotrino para apps de agente remoto cifrado punto a punto (handshake E2E, emparejamiento SAS con vault, revocación, descubrimiento). Lo consumen dotrino-terminal (PTY) y dotrino-ia (chat con IA).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
],
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@dotrino/identity": ">=0.92.0",
|
|
50
|
-
"@dotrino/proxy-client": ">=0.
|
|
50
|
+
"@dotrino/proxy-client": ">=0.30.0",
|
|
51
51
|
"@dotrino/vault": ">=0.64.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@dotrino/identity": "0.105.0",
|
|
55
|
-
"@dotrino/proxy-client": "0.
|
|
55
|
+
"@dotrino/proxy-client": "0.30.0",
|
|
56
56
|
"@dotrino/vault": "0.75.0"
|
|
57
57
|
}
|
|
58
58
|
}
|
package/src/agent.js
CHANGED
|
@@ -32,12 +32,13 @@ import { loadLink, saveLink, dataDir } from './link.js'
|
|
|
32
32
|
|
|
33
33
|
/** Sesión cifrada con un dispositivo cliente. La app recibe esto en `onSession`. */
|
|
34
34
|
class AgentSession {
|
|
35
|
-
constructor ({ sid, key, from, device, send }) {
|
|
35
|
+
constructor ({ sid, key, from, device, send, isDirect = null }) {
|
|
36
36
|
this.sid = sid
|
|
37
37
|
this.key = key
|
|
38
38
|
this.from = from // token del cliente en el proxy
|
|
39
39
|
this.device = device // pubkey del dispositivo cliente
|
|
40
40
|
this._send = send // (to, obj) => void
|
|
41
|
+
this._isDirect = isDirect // (token) => boolean, o null si el transporte no lo sabe
|
|
41
42
|
this._h = { message: [], close: [] }
|
|
42
43
|
this._closed = false
|
|
43
44
|
}
|
|
@@ -49,6 +50,33 @@ class AgentSession {
|
|
|
49
50
|
if (env) this._send(this.from, { type: DATA, sid: this.sid, env })
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
/**
|
|
54
|
+
* ¿VA YA POR EL CANAL DIRECTO (WebRTC) lo que se le mande a este cliente? Mientras sea
|
|
55
|
+
* `false`, cada `send` da la vuelta por el proxio. Sirve para que la app decida qué manda
|
|
56
|
+
* YA y qué puede esperar: nunca para dejar de mandar (subir de escalón no bloquea).
|
|
57
|
+
*/
|
|
58
|
+
direct () {
|
|
59
|
+
if (this._closed || !this._isDirect) return false
|
|
60
|
+
try { return !!this._isDirect(this.from) } catch (_) { return false }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Espera a que abra el canal directo, como mucho `ms`. Resuelve `true` si abrió y `false`
|
|
65
|
+
* si se acabó el plazo o la sesión se cerró — en los dos casos se sigue, por donde haya.
|
|
66
|
+
*/
|
|
67
|
+
whenDirect (ms = 8000) {
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
const until = Date.now() + ms
|
|
70
|
+
const look = () => {
|
|
71
|
+
if (this.direct()) return resolve(true)
|
|
72
|
+
if (this._closed || Date.now() >= until) return resolve(false)
|
|
73
|
+
const t = setTimeout(look, 100)
|
|
74
|
+
t.unref?.()
|
|
75
|
+
}
|
|
76
|
+
look()
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
52
80
|
on (ev, cb) { this._h[ev]?.push(cb); return this }
|
|
53
81
|
|
|
54
82
|
async _ingest (env) {
|
|
@@ -77,6 +105,7 @@ class AgentSession {
|
|
|
77
105
|
* @param {()=>void} [opts.onRevoked] se llamó al auto-borrarse por revocación.
|
|
78
106
|
* @param {()=>void} [opts.onReady] agente listo y escuchando.
|
|
79
107
|
* @param {boolean} [opts.quiet] sin logs.
|
|
108
|
+
* @param {number} [opts.netLogMs] cada cuánto van al log las estadísticas de red (5 min).
|
|
80
109
|
* @param {object} [opts.client] transporte ya conectado (SOLO pruebas).
|
|
81
110
|
*
|
|
82
111
|
* Devuelve además el **`client`** (el `WebSocketProxyClient` ya conectado e
|
|
@@ -374,7 +403,7 @@ export async function startRemoteAgent (opts = {}) {
|
|
|
374
403
|
const ack = { op: ACK, sid, seph: eph.pub, ceph: data.eph, machine: myPub, publickey: myPub, ts: Date.now() }
|
|
375
404
|
const { signature: ackSig } = await signWithDevice({ privateJwk: link.device.privateJwk, data: ack })
|
|
376
405
|
|
|
377
|
-
const session = new AgentSession({ sid, key, from, device: chk.device, send })
|
|
406
|
+
const session = new AgentSession({ sid, key, from, device: chk.device, send, isDirect: typeof client.isWebRTCOpen === 'function' ? (t) => client.isWebRTCOpen(t) : null })
|
|
378
407
|
session._exp = Date.now() + SESSION_TTL_MS
|
|
379
408
|
sessions.set(sid, session)
|
|
380
409
|
audit('session-open', { sid: sid.slice(0, 8), device: (await pubkeyId(chk.device)).slice(0, 8).toUpperCase() })
|
|
@@ -394,8 +423,18 @@ export async function startRemoteAgent (opts = {}) {
|
|
|
394
423
|
s._ingest(p.env).catch(() => send(from, { type: ERROR, error: 'sobre inválido' }))
|
|
395
424
|
}
|
|
396
425
|
|
|
426
|
+
// POR DÓNDE VA EL TRÁFICO, AL LOG. Un agente no tiene topbar: sin esto, saber si una
|
|
427
|
+
// sesión subió a WebRTC o sigue dando la vuelta por el proxio pedía una prueba aparte.
|
|
428
|
+
// Lo escribe el pilar (`logStats`), que lee los clientes del proceso; aquí solo se enciende.
|
|
429
|
+
let net = null
|
|
430
|
+
if (!opts.quiet) {
|
|
431
|
+
const { logStats } = await import('@dotrino/proxy-client')
|
|
432
|
+
net = logStats({ label: 'remote-agent', everyMs: opts.netLogMs })
|
|
433
|
+
}
|
|
434
|
+
|
|
397
435
|
const stop = () => {
|
|
398
436
|
clearInterval(revTimer); clearInterval(sweeper)
|
|
437
|
+
if (net) net.stop().catch(() => {})
|
|
399
438
|
for (const s of sessions.values()) s.close()
|
|
400
439
|
try { client.close() } catch {}
|
|
401
440
|
}
|