@rookdaemon/agora 0.4.0 → 0.4.2
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 +62 -0
- package/dist/chunk-2U4PZINT.js +453 -0
- package/dist/chunk-2U4PZINT.js.map +1 -0
- package/dist/chunk-D7Y66GFC.js +572 -0
- package/dist/chunk-D7Y66GFC.js.map +1 -0
- package/dist/{chunk-7RX2YC4A.js → chunk-IOHECZYT.js} +57 -554
- package/dist/chunk-IOHECZYT.js.map +1 -0
- package/dist/cli.js +41 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +44 -18
- package/dist/index.js +46 -439
- package/dist/index.js.map +1 -1
- package/dist/relay/relay-server.d.ts +2 -0
- package/dist/relay/relay-server.js +27 -0
- package/dist/relay/relay-server.js.map +1 -0
- package/package.json +3 -1
- package/dist/chunk-7RX2YC4A.js.map +0 -1
|
@@ -1,129 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
publicKey: publicKey.export({ type: "spki", format: "der" }).toString("hex"),
|
|
7
|
-
privateKey: privateKey.export({ type: "pkcs8", format: "der" }).toString("hex")
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
function signMessage(message, privateKeyHex) {
|
|
11
|
-
const messageBuffer = typeof message === "string" ? Buffer.from(message) : message;
|
|
12
|
-
const privateKey = Buffer.from(privateKeyHex, "hex");
|
|
13
|
-
const signature = sign(null, messageBuffer, {
|
|
14
|
-
key: privateKey,
|
|
15
|
-
format: "der",
|
|
16
|
-
type: "pkcs8"
|
|
17
|
-
});
|
|
18
|
-
return signature.toString("hex");
|
|
19
|
-
}
|
|
20
|
-
function verifySignature(message, signatureHex, publicKeyHex) {
|
|
21
|
-
const messageBuffer = typeof message === "string" ? Buffer.from(message) : message;
|
|
22
|
-
const signature = Buffer.from(signatureHex, "hex");
|
|
23
|
-
const publicKey = Buffer.from(publicKeyHex, "hex");
|
|
24
|
-
try {
|
|
25
|
-
return verify(null, messageBuffer, {
|
|
26
|
-
key: publicKey,
|
|
27
|
-
format: "der",
|
|
28
|
-
type: "spki"
|
|
29
|
-
}, signature);
|
|
30
|
-
} catch {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function exportKeyPair(keyPair) {
|
|
35
|
-
return {
|
|
36
|
-
publicKey: keyPair.publicKey,
|
|
37
|
-
privateKey: keyPair.privateKey
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
function importKeyPair(publicKeyHex, privateKeyHex) {
|
|
41
|
-
const hexPattern = /^[0-9a-f]+$/i;
|
|
42
|
-
if (!hexPattern.test(publicKeyHex)) {
|
|
43
|
-
throw new Error("Invalid public key: must be a hex string");
|
|
44
|
-
}
|
|
45
|
-
if (!hexPattern.test(privateKeyHex)) {
|
|
46
|
-
throw new Error("Invalid private key: must be a hex string");
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
publicKey: publicKeyHex,
|
|
50
|
-
privateKey: privateKeyHex
|
|
51
|
-
};
|
|
52
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
createEnvelope,
|
|
3
|
+
generateKeyPair,
|
|
4
|
+
verifyEnvelope
|
|
5
|
+
} from "./chunk-D7Y66GFC.js";
|
|
53
6
|
|
|
54
7
|
// src/transport/peer-config.ts
|
|
55
8
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
56
|
-
function loadPeerConfig(
|
|
57
|
-
const content = readFileSync(
|
|
9
|
+
function loadPeerConfig(path) {
|
|
10
|
+
const content = readFileSync(path, "utf-8");
|
|
58
11
|
return JSON.parse(content);
|
|
59
12
|
}
|
|
60
|
-
function savePeerConfig(
|
|
13
|
+
function savePeerConfig(path, config) {
|
|
61
14
|
const content = JSON.stringify(config, null, 2);
|
|
62
|
-
writeFileSync(
|
|
15
|
+
writeFileSync(path, content, "utf-8");
|
|
63
16
|
}
|
|
64
|
-
function initPeerConfig(
|
|
65
|
-
if (existsSync(
|
|
66
|
-
return loadPeerConfig(
|
|
17
|
+
function initPeerConfig(path) {
|
|
18
|
+
if (existsSync(path)) {
|
|
19
|
+
return loadPeerConfig(path);
|
|
67
20
|
}
|
|
68
21
|
const identity = generateKeyPair();
|
|
69
22
|
const config = {
|
|
70
23
|
identity,
|
|
71
24
|
peers: {}
|
|
72
25
|
};
|
|
73
|
-
savePeerConfig(
|
|
26
|
+
savePeerConfig(path, config);
|
|
74
27
|
return config;
|
|
75
28
|
}
|
|
76
29
|
|
|
77
|
-
// src/message/envelope.ts
|
|
78
|
-
import { createHash } from "crypto";
|
|
79
|
-
function stableStringify(value) {
|
|
80
|
-
if (value === null || value === void 0) return JSON.stringify(value);
|
|
81
|
-
if (typeof value !== "object") return JSON.stringify(value);
|
|
82
|
-
if (Array.isArray(value)) {
|
|
83
|
-
return "[" + value.map(stableStringify).join(",") + "]";
|
|
84
|
-
}
|
|
85
|
-
const keys = Object.keys(value).sort();
|
|
86
|
-
const pairs = keys.map((k) => JSON.stringify(k) + ":" + stableStringify(value[k]));
|
|
87
|
-
return "{" + pairs.join(",") + "}";
|
|
88
|
-
}
|
|
89
|
-
function canonicalize(type, sender, timestamp, payload, inReplyTo) {
|
|
90
|
-
const obj = { payload, sender, timestamp, type };
|
|
91
|
-
if (inReplyTo !== void 0) {
|
|
92
|
-
obj.inReplyTo = inReplyTo;
|
|
93
|
-
}
|
|
94
|
-
return stableStringify(obj);
|
|
95
|
-
}
|
|
96
|
-
function computeId(canonical) {
|
|
97
|
-
return createHash("sha256").update(canonical).digest("hex");
|
|
98
|
-
}
|
|
99
|
-
function createEnvelope(type, sender, privateKey, payload, timestamp = Date.now(), inReplyTo) {
|
|
100
|
-
const canonical = canonicalize(type, sender, timestamp, payload, inReplyTo);
|
|
101
|
-
const id = computeId(canonical);
|
|
102
|
-
const signature = signMessage(canonical, privateKey);
|
|
103
|
-
return {
|
|
104
|
-
id,
|
|
105
|
-
type,
|
|
106
|
-
sender,
|
|
107
|
-
timestamp,
|
|
108
|
-
...inReplyTo !== void 0 ? { inReplyTo } : {},
|
|
109
|
-
payload,
|
|
110
|
-
signature
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
function verifyEnvelope(envelope) {
|
|
114
|
-
const { id, type, sender, timestamp, payload, signature, inReplyTo } = envelope;
|
|
115
|
-
const canonical = canonicalize(type, sender, timestamp, payload, inReplyTo);
|
|
116
|
-
const expectedId = computeId(canonical);
|
|
117
|
-
if (id !== expectedId) {
|
|
118
|
-
return { valid: false, reason: "id_mismatch" };
|
|
119
|
-
}
|
|
120
|
-
const sigValid = verifySignature(canonical, signature, sender);
|
|
121
|
-
if (!sigValid) {
|
|
122
|
-
return { valid: false, reason: "signature_invalid" };
|
|
123
|
-
}
|
|
124
|
-
return { valid: true };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
30
|
// src/transport/http.ts
|
|
128
31
|
async function sendToPeer(config, peerPublicKey, type, payload, inReplyTo) {
|
|
129
32
|
const peer = config.peers.get(peerPublicKey);
|
|
@@ -149,27 +52,36 @@ async function sendToPeer(config, peerPublicKey, type, payload, inReplyTo) {
|
|
|
149
52
|
sessionKey: `agora:${envelope.sender.substring(0, 16)}`,
|
|
150
53
|
deliver: false
|
|
151
54
|
};
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
"Content-Type": "application/json"
|
|
158
|
-
},
|
|
159
|
-
body: JSON.stringify(webhookPayload)
|
|
160
|
-
});
|
|
161
|
-
return {
|
|
162
|
-
ok: response.ok,
|
|
163
|
-
status: response.status,
|
|
164
|
-
error: response.ok ? void 0 : await response.text()
|
|
165
|
-
};
|
|
166
|
-
} catch (err) {
|
|
167
|
-
return {
|
|
168
|
-
ok: false,
|
|
169
|
-
status: 0,
|
|
170
|
-
error: err instanceof Error ? err.message : String(err)
|
|
171
|
-
};
|
|
55
|
+
const headers = {
|
|
56
|
+
"Content-Type": "application/json"
|
|
57
|
+
};
|
|
58
|
+
if (peer.token) {
|
|
59
|
+
headers["Authorization"] = `Bearer ${peer.token}`;
|
|
172
60
|
}
|
|
61
|
+
const requestBody = JSON.stringify(webhookPayload);
|
|
62
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(`${peer.url}/agent`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers,
|
|
67
|
+
body: requestBody
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
ok: response.ok,
|
|
71
|
+
status: response.status,
|
|
72
|
+
error: response.ok ? void 0 : await response.text()
|
|
73
|
+
};
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (attempt === 1) {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
status: 0,
|
|
79
|
+
error: err instanceof Error ? err.message : String(err)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { ok: false, status: 0, error: "Unexpected send failure" };
|
|
173
85
|
}
|
|
174
86
|
function decodeInboundEnvelope(message, knownPeers) {
|
|
175
87
|
const prefix = "[AGORA_ENVELOPE]";
|
|
@@ -291,408 +203,10 @@ async function sendViaRelay(config, peerPublicKey, type, payload, inReplyTo) {
|
|
|
291
203
|
});
|
|
292
204
|
}
|
|
293
205
|
|
|
294
|
-
// src/relay/store.ts
|
|
295
|
-
import * as fs from "fs";
|
|
296
|
-
import * as path from "path";
|
|
297
|
-
var MessageStore = class {
|
|
298
|
-
storageDir;
|
|
299
|
-
constructor(storageDir) {
|
|
300
|
-
this.storageDir = storageDir;
|
|
301
|
-
fs.mkdirSync(storageDir, { recursive: true });
|
|
302
|
-
}
|
|
303
|
-
recipientDir(publicKey) {
|
|
304
|
-
const safe = publicKey.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
305
|
-
return path.join(this.storageDir, safe);
|
|
306
|
-
}
|
|
307
|
-
save(recipientKey, message) {
|
|
308
|
-
const dir = this.recipientDir(recipientKey);
|
|
309
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
310
|
-
const filename = `${Date.now()}-${crypto.randomUUID()}.json`;
|
|
311
|
-
fs.writeFileSync(path.join(dir, filename), JSON.stringify(message));
|
|
312
|
-
}
|
|
313
|
-
load(recipientKey) {
|
|
314
|
-
const dir = this.recipientDir(recipientKey);
|
|
315
|
-
if (!fs.existsSync(dir)) return [];
|
|
316
|
-
const files = fs.readdirSync(dir).sort();
|
|
317
|
-
const messages = [];
|
|
318
|
-
for (const file of files) {
|
|
319
|
-
if (!file.endsWith(".json")) continue;
|
|
320
|
-
try {
|
|
321
|
-
const data = fs.readFileSync(path.join(dir, file), "utf8");
|
|
322
|
-
messages.push(JSON.parse(data));
|
|
323
|
-
} catch {
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
return messages;
|
|
327
|
-
}
|
|
328
|
-
clear(recipientKey) {
|
|
329
|
-
const dir = this.recipientDir(recipientKey);
|
|
330
|
-
if (!fs.existsSync(dir)) return;
|
|
331
|
-
const files = fs.readdirSync(dir);
|
|
332
|
-
for (const file of files) {
|
|
333
|
-
if (file.endsWith(".json")) {
|
|
334
|
-
fs.unlinkSync(path.join(dir, file));
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
// src/relay/server.ts
|
|
341
|
-
import { EventEmitter } from "events";
|
|
342
|
-
import { WebSocketServer, WebSocket as WebSocket2 } from "ws";
|
|
343
|
-
var RelayServer = class extends EventEmitter {
|
|
344
|
-
wss = null;
|
|
345
|
-
agents = /* @__PURE__ */ new Map();
|
|
346
|
-
identity;
|
|
347
|
-
storagePeers = [];
|
|
348
|
-
store = null;
|
|
349
|
-
constructor(options) {
|
|
350
|
-
super();
|
|
351
|
-
if (options) {
|
|
352
|
-
if ("identity" in options && options.identity) {
|
|
353
|
-
this.identity = options.identity;
|
|
354
|
-
} else if ("publicKey" in options && "privateKey" in options) {
|
|
355
|
-
this.identity = { publicKey: options.publicKey, privateKey: options.privateKey };
|
|
356
|
-
}
|
|
357
|
-
const opts = options;
|
|
358
|
-
if (opts.storagePeers?.length && opts.storageDir) {
|
|
359
|
-
this.storagePeers = opts.storagePeers;
|
|
360
|
-
this.store = new MessageStore(opts.storageDir);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Start the relay server
|
|
366
|
-
* @param port - Port to listen on
|
|
367
|
-
* @param host - Optional host (default: all interfaces)
|
|
368
|
-
*/
|
|
369
|
-
start(port, host) {
|
|
370
|
-
return new Promise((resolve, reject) => {
|
|
371
|
-
try {
|
|
372
|
-
this.wss = new WebSocketServer({ port, host: host ?? "0.0.0.0" });
|
|
373
|
-
let resolved = false;
|
|
374
|
-
this.wss.on("error", (error) => {
|
|
375
|
-
this.emit("error", error);
|
|
376
|
-
if (!resolved) {
|
|
377
|
-
resolved = true;
|
|
378
|
-
reject(error);
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
this.wss.on("listening", () => {
|
|
382
|
-
if (!resolved) {
|
|
383
|
-
resolved = true;
|
|
384
|
-
resolve();
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
this.wss.on("connection", (socket) => {
|
|
388
|
-
this.handleConnection(socket);
|
|
389
|
-
});
|
|
390
|
-
} catch (error) {
|
|
391
|
-
reject(error);
|
|
392
|
-
}
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* Stop the relay server
|
|
397
|
-
*/
|
|
398
|
-
async stop() {
|
|
399
|
-
return new Promise((resolve, reject) => {
|
|
400
|
-
if (!this.wss) {
|
|
401
|
-
resolve();
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
for (const agent of this.agents.values()) {
|
|
405
|
-
agent.socket.close();
|
|
406
|
-
}
|
|
407
|
-
this.agents.clear();
|
|
408
|
-
this.wss.close((err) => {
|
|
409
|
-
if (err) {
|
|
410
|
-
reject(err);
|
|
411
|
-
} else {
|
|
412
|
-
this.wss = null;
|
|
413
|
-
resolve();
|
|
414
|
-
}
|
|
415
|
-
});
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* Get all connected agents
|
|
420
|
-
*/
|
|
421
|
-
getAgents() {
|
|
422
|
-
return new Map(this.agents);
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Handle incoming connection
|
|
426
|
-
*/
|
|
427
|
-
handleConnection(socket) {
|
|
428
|
-
let agentPublicKey = null;
|
|
429
|
-
socket.on("message", (data) => {
|
|
430
|
-
try {
|
|
431
|
-
const msg = JSON.parse(data.toString());
|
|
432
|
-
if (msg.type === "register" && !agentPublicKey) {
|
|
433
|
-
if (!msg.publicKey || typeof msg.publicKey !== "string") {
|
|
434
|
-
this.sendError(socket, "Invalid registration: missing or invalid publicKey");
|
|
435
|
-
socket.close();
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
const publicKey = msg.publicKey;
|
|
439
|
-
const name = msg.name;
|
|
440
|
-
agentPublicKey = publicKey;
|
|
441
|
-
const existing = this.agents.get(publicKey);
|
|
442
|
-
if (existing) {
|
|
443
|
-
existing.socket.close();
|
|
444
|
-
}
|
|
445
|
-
const agent = {
|
|
446
|
-
publicKey,
|
|
447
|
-
name,
|
|
448
|
-
socket,
|
|
449
|
-
lastSeen: Date.now()
|
|
450
|
-
};
|
|
451
|
-
this.agents.set(publicKey, agent);
|
|
452
|
-
this.emit("agent-registered", publicKey);
|
|
453
|
-
let peers = Array.from(this.agents.values()).filter((a) => a.publicKey !== publicKey).map((a) => ({ publicKey: a.publicKey, name: a.name }));
|
|
454
|
-
for (const storagePeer of this.storagePeers) {
|
|
455
|
-
if (storagePeer !== publicKey && !this.agents.has(storagePeer)) {
|
|
456
|
-
peers.push({ publicKey: storagePeer, name: void 0 });
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
socket.send(JSON.stringify({
|
|
460
|
-
type: "registered",
|
|
461
|
-
publicKey,
|
|
462
|
-
peers
|
|
463
|
-
}));
|
|
464
|
-
this.broadcastPeerEvent("peer_online", publicKey, name);
|
|
465
|
-
if (this.store && this.storagePeers.includes(publicKey)) {
|
|
466
|
-
const queued = this.store.load(publicKey);
|
|
467
|
-
for (const stored of queued) {
|
|
468
|
-
socket.send(JSON.stringify({
|
|
469
|
-
type: "message",
|
|
470
|
-
from: stored.from,
|
|
471
|
-
name: stored.name,
|
|
472
|
-
envelope: stored.envelope
|
|
473
|
-
}));
|
|
474
|
-
}
|
|
475
|
-
this.store.clear(publicKey);
|
|
476
|
-
}
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
if (!agentPublicKey) {
|
|
480
|
-
this.sendError(socket, "Not registered: send registration message first");
|
|
481
|
-
socket.close();
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
if (msg.type === "message") {
|
|
485
|
-
if (!msg.to || typeof msg.to !== "string") {
|
|
486
|
-
this.sendError(socket, 'Invalid message: missing or invalid "to" field');
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
if (!msg.envelope || typeof msg.envelope !== "object") {
|
|
490
|
-
this.sendError(socket, 'Invalid message: missing or invalid "envelope" field');
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
const envelope = msg.envelope;
|
|
494
|
-
const verification = verifyEnvelope(envelope);
|
|
495
|
-
if (!verification.valid) {
|
|
496
|
-
this.sendError(socket, `Invalid envelope: ${verification.reason || "verification failed"}`);
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
if (envelope.sender !== agentPublicKey) {
|
|
500
|
-
this.sendError(socket, "Envelope sender does not match registered public key");
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
const senderAgent = this.agents.get(agentPublicKey);
|
|
504
|
-
if (senderAgent) {
|
|
505
|
-
senderAgent.lastSeen = Date.now();
|
|
506
|
-
}
|
|
507
|
-
if (envelope.type === "peer_list_request" && this.identity && msg.to === this.identity.publicKey) {
|
|
508
|
-
this.handlePeerListRequest(envelope, socket, agentPublicKey);
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
const recipient = this.agents.get(msg.to);
|
|
512
|
-
if (!recipient || recipient.socket.readyState !== WebSocket2.OPEN) {
|
|
513
|
-
if (this.store && this.storagePeers.includes(msg.to)) {
|
|
514
|
-
const senderAgent2 = this.agents.get(agentPublicKey);
|
|
515
|
-
this.store.save(msg.to, {
|
|
516
|
-
from: agentPublicKey,
|
|
517
|
-
name: senderAgent2?.name,
|
|
518
|
-
envelope
|
|
519
|
-
});
|
|
520
|
-
this.emit("message-relayed", agentPublicKey, msg.to, envelope);
|
|
521
|
-
} else {
|
|
522
|
-
this.sendError(socket, "Recipient not connected");
|
|
523
|
-
}
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
try {
|
|
527
|
-
const senderAgent2 = this.agents.get(agentPublicKey);
|
|
528
|
-
const relayMessage = {
|
|
529
|
-
type: "message",
|
|
530
|
-
from: agentPublicKey,
|
|
531
|
-
name: senderAgent2?.name,
|
|
532
|
-
envelope
|
|
533
|
-
};
|
|
534
|
-
recipient.socket.send(JSON.stringify(relayMessage));
|
|
535
|
-
this.emit("message-relayed", agentPublicKey, msg.to, envelope);
|
|
536
|
-
} catch (err) {
|
|
537
|
-
this.sendError(socket, "Failed to relay message");
|
|
538
|
-
this.emit("error", err);
|
|
539
|
-
}
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
if (msg.type === "broadcast") {
|
|
543
|
-
if (!msg.envelope || typeof msg.envelope !== "object") {
|
|
544
|
-
this.sendError(socket, 'Invalid broadcast: missing or invalid "envelope" field');
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
const envelope = msg.envelope;
|
|
548
|
-
const verification = verifyEnvelope(envelope);
|
|
549
|
-
if (!verification.valid) {
|
|
550
|
-
this.sendError(socket, `Invalid envelope: ${verification.reason || "verification failed"}`);
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
if (envelope.sender !== agentPublicKey) {
|
|
554
|
-
this.sendError(socket, "Envelope sender does not match registered public key");
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
const senderAgentBroadcast = this.agents.get(agentPublicKey);
|
|
558
|
-
if (senderAgentBroadcast) {
|
|
559
|
-
senderAgentBroadcast.lastSeen = Date.now();
|
|
560
|
-
}
|
|
561
|
-
const senderAgent = this.agents.get(agentPublicKey);
|
|
562
|
-
const relayMessage = {
|
|
563
|
-
type: "message",
|
|
564
|
-
from: agentPublicKey,
|
|
565
|
-
name: senderAgent?.name,
|
|
566
|
-
envelope
|
|
567
|
-
};
|
|
568
|
-
const messageStr = JSON.stringify(relayMessage);
|
|
569
|
-
for (const agent of this.agents.values()) {
|
|
570
|
-
if (agent.publicKey !== agentPublicKey && agent.socket.readyState === WebSocket2.OPEN) {
|
|
571
|
-
try {
|
|
572
|
-
agent.socket.send(messageStr);
|
|
573
|
-
} catch (err) {
|
|
574
|
-
this.emit("error", err);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
if (msg.type === "ping") {
|
|
581
|
-
socket.send(JSON.stringify({ type: "pong" }));
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
this.sendError(socket, `Unknown message type: ${msg.type}`);
|
|
585
|
-
} catch (err) {
|
|
586
|
-
this.emit("error", new Error(`Message parsing failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
587
|
-
this.sendError(socket, "Invalid message format");
|
|
588
|
-
}
|
|
589
|
-
});
|
|
590
|
-
socket.on("close", () => {
|
|
591
|
-
if (agentPublicKey) {
|
|
592
|
-
const agent = this.agents.get(agentPublicKey);
|
|
593
|
-
const agentName = agent?.name;
|
|
594
|
-
this.agents.delete(agentPublicKey);
|
|
595
|
-
this.emit("agent-disconnected", agentPublicKey);
|
|
596
|
-
if (!this.storagePeers.includes(agentPublicKey)) {
|
|
597
|
-
this.broadcastPeerEvent("peer_offline", agentPublicKey, agentName);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
});
|
|
601
|
-
socket.on("error", (error) => {
|
|
602
|
-
this.emit("error", error);
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
/**
|
|
606
|
-
* Send an error message to a client
|
|
607
|
-
*/
|
|
608
|
-
sendError(socket, message) {
|
|
609
|
-
try {
|
|
610
|
-
if (socket.readyState === WebSocket2.OPEN) {
|
|
611
|
-
socket.send(JSON.stringify({ type: "error", message }));
|
|
612
|
-
}
|
|
613
|
-
} catch (err) {
|
|
614
|
-
this.emit("error", new Error(`Failed to send error message: ${err instanceof Error ? err.message : String(err)}`));
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
/**
|
|
618
|
-
* Broadcast a peer event to all connected agents
|
|
619
|
-
*/
|
|
620
|
-
broadcastPeerEvent(eventType, publicKey, name) {
|
|
621
|
-
const message = {
|
|
622
|
-
type: eventType,
|
|
623
|
-
publicKey,
|
|
624
|
-
name
|
|
625
|
-
};
|
|
626
|
-
const messageStr = JSON.stringify(message);
|
|
627
|
-
for (const agent of this.agents.values()) {
|
|
628
|
-
if (agent.publicKey !== publicKey && agent.socket.readyState === WebSocket2.OPEN) {
|
|
629
|
-
try {
|
|
630
|
-
agent.socket.send(messageStr);
|
|
631
|
-
} catch (err) {
|
|
632
|
-
this.emit("error", new Error(`Failed to send ${eventType} event: ${err instanceof Error ? err.message : String(err)}`));
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Handle peer list request from an agent
|
|
639
|
-
*/
|
|
640
|
-
handlePeerListRequest(envelope, socket, requesterPublicKey) {
|
|
641
|
-
if (!this.identity) {
|
|
642
|
-
this.sendError(socket, "Relay does not support peer discovery (no identity configured)");
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
const { filters } = envelope.payload;
|
|
646
|
-
const now = Date.now();
|
|
647
|
-
let peers = Array.from(this.agents.values());
|
|
648
|
-
peers = peers.filter((p) => p.publicKey !== requesterPublicKey);
|
|
649
|
-
if (filters?.activeWithin) {
|
|
650
|
-
peers = peers.filter((p) => now - p.lastSeen < filters.activeWithin);
|
|
651
|
-
}
|
|
652
|
-
if (filters?.limit && filters.limit > 0) {
|
|
653
|
-
peers = peers.slice(0, filters.limit);
|
|
654
|
-
}
|
|
655
|
-
const response = {
|
|
656
|
-
peers: peers.map((p) => ({
|
|
657
|
-
publicKey: p.publicKey,
|
|
658
|
-
metadata: p.name || p.metadata ? {
|
|
659
|
-
name: p.name,
|
|
660
|
-
version: p.metadata?.version,
|
|
661
|
-
capabilities: p.metadata?.capabilities
|
|
662
|
-
} : void 0,
|
|
663
|
-
lastSeen: p.lastSeen
|
|
664
|
-
})),
|
|
665
|
-
totalPeers: this.agents.size - 1,
|
|
666
|
-
// Exclude requester from count
|
|
667
|
-
relayPublicKey: this.identity.publicKey
|
|
668
|
-
};
|
|
669
|
-
const responseEnvelope = createEnvelope(
|
|
670
|
-
"peer_list_response",
|
|
671
|
-
this.identity.publicKey,
|
|
672
|
-
this.identity.privateKey,
|
|
673
|
-
response,
|
|
674
|
-
Date.now(),
|
|
675
|
-
envelope.id
|
|
676
|
-
// Reply to the request
|
|
677
|
-
);
|
|
678
|
-
const relayMessage = {
|
|
679
|
-
type: "message",
|
|
680
|
-
from: this.identity.publicKey,
|
|
681
|
-
name: "relay",
|
|
682
|
-
envelope: responseEnvelope
|
|
683
|
-
};
|
|
684
|
-
try {
|
|
685
|
-
socket.send(JSON.stringify(relayMessage));
|
|
686
|
-
} catch (err) {
|
|
687
|
-
this.emit("error", new Error(`Failed to send peer list response: ${err instanceof Error ? err.message : String(err)}`));
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
};
|
|
691
|
-
|
|
692
206
|
// src/relay/client.ts
|
|
693
|
-
import { EventEmitter
|
|
694
|
-
import
|
|
695
|
-
var RelayClient = class extends
|
|
207
|
+
import { EventEmitter } from "events";
|
|
208
|
+
import WebSocket2 from "ws";
|
|
209
|
+
var RelayClient = class extends EventEmitter {
|
|
696
210
|
ws = null;
|
|
697
211
|
config;
|
|
698
212
|
reconnectAttempts = 0;
|
|
@@ -714,7 +228,7 @@ var RelayClient = class extends EventEmitter2 {
|
|
|
714
228
|
* Connect to the relay server
|
|
715
229
|
*/
|
|
716
230
|
async connect() {
|
|
717
|
-
if (this.ws && (this.ws.readyState ===
|
|
231
|
+
if (this.ws && (this.ws.readyState === WebSocket2.CONNECTING || this.ws.readyState === WebSocket2.OPEN)) {
|
|
718
232
|
return;
|
|
719
233
|
}
|
|
720
234
|
this.shouldReconnect = true;
|
|
@@ -792,7 +306,7 @@ var RelayClient = class extends EventEmitter2 {
|
|
|
792
306
|
async doConnect() {
|
|
793
307
|
return new Promise((resolve, reject) => {
|
|
794
308
|
try {
|
|
795
|
-
this.ws = new
|
|
309
|
+
this.ws = new WebSocket2(this.config.relayUrl);
|
|
796
310
|
let resolved = false;
|
|
797
311
|
const resolveOnce = (callback) => {
|
|
798
312
|
if (!resolved) {
|
|
@@ -928,7 +442,7 @@ var RelayClient = class extends EventEmitter2 {
|
|
|
928
442
|
startPingInterval() {
|
|
929
443
|
this.stopPingInterval();
|
|
930
444
|
this.pingInterval = setInterval(() => {
|
|
931
|
-
if (this.ws && this.ws.readyState ===
|
|
445
|
+
if (this.ws && this.ws.readyState === WebSocket2.OPEN) {
|
|
932
446
|
const ping = { type: "ping" };
|
|
933
447
|
this.ws.send(JSON.stringify(ping));
|
|
934
448
|
}
|
|
@@ -957,8 +471,8 @@ var RelayClient = class extends EventEmitter2 {
|
|
|
957
471
|
};
|
|
958
472
|
|
|
959
473
|
// src/discovery/peer-discovery.ts
|
|
960
|
-
import { EventEmitter as
|
|
961
|
-
var PeerDiscoveryService = class extends
|
|
474
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
475
|
+
var PeerDiscoveryService = class extends EventEmitter2 {
|
|
962
476
|
config;
|
|
963
477
|
constructor(config) {
|
|
964
478
|
super();
|
|
@@ -1211,7 +725,7 @@ function validateRevealRecord(record) {
|
|
|
1211
725
|
}
|
|
1212
726
|
|
|
1213
727
|
// src/reputation/store.ts
|
|
1214
|
-
import { promises as
|
|
728
|
+
import { promises as fs } from "fs";
|
|
1215
729
|
import { dirname } from "path";
|
|
1216
730
|
var ReputationStore = class {
|
|
1217
731
|
filePath;
|
|
@@ -1227,7 +741,7 @@ var ReputationStore = class {
|
|
|
1227
741
|
*/
|
|
1228
742
|
async load() {
|
|
1229
743
|
try {
|
|
1230
|
-
const content = await
|
|
744
|
+
const content = await fs.readFile(this.filePath, "utf-8");
|
|
1231
745
|
const lines = content.trim().split("\n").filter((line) => line.length > 0);
|
|
1232
746
|
for (const line of lines) {
|
|
1233
747
|
try {
|
|
@@ -1283,9 +797,9 @@ var ReputationStore = class {
|
|
|
1283
797
|
* Append a record to the JSONL file
|
|
1284
798
|
*/
|
|
1285
799
|
async appendToFile(record) {
|
|
1286
|
-
await
|
|
800
|
+
await fs.mkdir(dirname(this.filePath), { recursive: true });
|
|
1287
801
|
const line = JSON.stringify(record) + "\n";
|
|
1288
|
-
await
|
|
802
|
+
await fs.appendFile(this.filePath, line, "utf-8");
|
|
1289
803
|
}
|
|
1290
804
|
/**
|
|
1291
805
|
* Add a verification record
|
|
@@ -1459,9 +973,9 @@ function verifyVerificationSignature(record) {
|
|
|
1459
973
|
}
|
|
1460
974
|
|
|
1461
975
|
// src/reputation/commit-reveal.ts
|
|
1462
|
-
import { createHash
|
|
976
|
+
import { createHash } from "crypto";
|
|
1463
977
|
function hashPrediction(prediction) {
|
|
1464
|
-
return
|
|
978
|
+
return createHash("sha256").update(prediction).digest("hex");
|
|
1465
979
|
}
|
|
1466
980
|
function createCommit(agent, privateKey, domain, prediction, timestamp, expiryMs) {
|
|
1467
981
|
const commitment = hashPrediction(prediction);
|
|
@@ -1619,23 +1133,12 @@ function computeTrustScores(agent, verifications, currentTime) {
|
|
|
1619
1133
|
var computeAllTrustScores = computeTrustScores;
|
|
1620
1134
|
|
|
1621
1135
|
export {
|
|
1622
|
-
generateKeyPair,
|
|
1623
|
-
signMessage,
|
|
1624
|
-
verifySignature,
|
|
1625
|
-
exportKeyPair,
|
|
1626
|
-
importKeyPair,
|
|
1627
1136
|
loadPeerConfig,
|
|
1628
1137
|
savePeerConfig,
|
|
1629
1138
|
initPeerConfig,
|
|
1630
|
-
canonicalize,
|
|
1631
|
-
computeId,
|
|
1632
|
-
createEnvelope,
|
|
1633
|
-
verifyEnvelope,
|
|
1634
1139
|
sendToPeer,
|
|
1635
1140
|
decodeInboundEnvelope,
|
|
1636
1141
|
sendViaRelay,
|
|
1637
|
-
MessageStore,
|
|
1638
|
-
RelayServer,
|
|
1639
1142
|
RelayClient,
|
|
1640
1143
|
PeerDiscoveryService,
|
|
1641
1144
|
DEFAULT_BOOTSTRAP_RELAYS,
|
|
@@ -1659,4 +1162,4 @@ export {
|
|
|
1659
1162
|
computeTrustScores,
|
|
1660
1163
|
computeAllTrustScores
|
|
1661
1164
|
};
|
|
1662
|
-
//# sourceMappingURL=chunk-
|
|
1165
|
+
//# sourceMappingURL=chunk-IOHECZYT.js.map
|