@rookdaemon/agora 0.7.0 → 0.8.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/dist/{chunk-HGXMAZZI.js → chunk-MJGCRX6B.js} +1 -8
- package/dist/chunk-MJGCRX6B.js.map +1 -0
- package/dist/{chunk-3PEGG7D3.js → chunk-NHTC6OZW.js} +2 -2
- package/dist/{chunk-VQ4BE6OV.js → chunk-OVDMZHTX.js} +2 -7
- package/dist/chunk-OVDMZHTX.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +0 -2
- package/dist/index.js +3 -3
- package/dist/relay/relay-server.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-HGXMAZZI.js.map +0 -1
- package/dist/chunk-VQ4BE6OV.js.map +0 -1
- /package/dist/{chunk-3PEGG7D3.js.map → chunk-NHTC6OZW.js.map} +0 -0
|
@@ -395,7 +395,6 @@ var RelayServer = class _RelayServer extends EventEmitter {
|
|
|
395
395
|
socket.send(JSON.stringify({
|
|
396
396
|
type: "message",
|
|
397
397
|
from: stored.from,
|
|
398
|
-
name: stored.name,
|
|
399
398
|
envelope: stored.envelope
|
|
400
399
|
}));
|
|
401
400
|
}
|
|
@@ -452,11 +451,8 @@ var RelayServer = class _RelayServer extends EventEmitter {
|
|
|
452
451
|
const openRecipients = recipientSessionMap ? Array.from(recipientSessionMap.values()).filter((a) => a.socket.readyState === WebSocket.OPEN) : [];
|
|
453
452
|
if (openRecipients.length === 0) {
|
|
454
453
|
if (this.store && this.storagePeers.includes(msg.to)) {
|
|
455
|
-
const senderSessionMap2 = this.sessions.get(agentPublicKey);
|
|
456
|
-
const senderAgent = senderSessionMap2?.values().next().value;
|
|
457
454
|
this.store.save(msg.to, {
|
|
458
455
|
from: agentPublicKey,
|
|
459
|
-
name: senderAgent?.name,
|
|
460
456
|
envelope
|
|
461
457
|
});
|
|
462
458
|
this.emit("message-relayed", agentPublicKey, msg.to, envelope);
|
|
@@ -466,12 +462,9 @@ var RelayServer = class _RelayServer extends EventEmitter {
|
|
|
466
462
|
return;
|
|
467
463
|
}
|
|
468
464
|
try {
|
|
469
|
-
const senderSessionMap2 = this.sessions.get(agentPublicKey);
|
|
470
|
-
const senderAgent = senderSessionMap2?.values().next().value;
|
|
471
465
|
const relayMessage = {
|
|
472
466
|
type: "message",
|
|
473
467
|
from: agentPublicKey,
|
|
474
|
-
name: senderAgent?.name,
|
|
475
468
|
envelope
|
|
476
469
|
};
|
|
477
470
|
const messageStr = JSON.stringify(relayMessage);
|
|
@@ -627,4 +620,4 @@ export {
|
|
|
627
620
|
MessageStore,
|
|
628
621
|
RelayServer
|
|
629
622
|
};
|
|
630
|
-
//# sourceMappingURL=chunk-
|
|
623
|
+
//# sourceMappingURL=chunk-MJGCRX6B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/identity/keypair.ts","../src/message/envelope.ts","../src/relay/store.ts","../src/relay/server.ts"],"sourcesContent":["import { sign, verify, generateKeyPairSync } from 'node:crypto';\r\n\r\n/**\r\n * Represents an ed25519 key pair for agent identity\r\n */\r\nexport interface KeyPair {\r\n publicKey: string; // hex-encoded\r\n privateKey: string; // hex-encoded\r\n}\r\n\r\n/**\r\n * Generates a new ed25519 key pair\r\n * @returns KeyPair with hex-encoded public and private keys\r\n */\r\nexport function generateKeyPair(): KeyPair {\r\n const { publicKey, privateKey } = generateKeyPairSync('ed25519');\r\n \r\n return {\r\n publicKey: publicKey.export({ type: 'spki', format: 'der' }).toString('hex'),\r\n privateKey: privateKey.export({ type: 'pkcs8', format: 'der' }).toString('hex'),\r\n };\r\n}\r\n\r\n/**\r\n * Signs a message with the private key\r\n * @param message - The message to sign (string or Buffer)\r\n * @param privateKeyHex - The private key in hex format\r\n * @returns Signature as hex string\r\n */\r\nexport function signMessage(message: string | Buffer, privateKeyHex: string): string {\r\n const messageBuffer = typeof message === 'string' ? Buffer.from(message) : message;\r\n const privateKey = Buffer.from(privateKeyHex, 'hex');\r\n \r\n const signature = sign(null, messageBuffer, {\r\n key: privateKey,\r\n format: 'der',\r\n type: 'pkcs8',\r\n });\r\n \r\n return signature.toString('hex');\r\n}\r\n\r\n/**\r\n * Verifies a signature with the public key\r\n * @param message - The original message (string or Buffer)\r\n * @param signatureHex - The signature in hex format\r\n * @param publicKeyHex - The public key in hex format\r\n * @returns true if signature is valid, false otherwise\r\n */\r\nexport function verifySignature(\r\n message: string | Buffer,\r\n signatureHex: string,\r\n publicKeyHex: string\r\n): boolean {\r\n const messageBuffer = typeof message === 'string' ? Buffer.from(message) : message;\r\n const signature = Buffer.from(signatureHex, 'hex');\r\n const publicKey = Buffer.from(publicKeyHex, 'hex');\r\n \r\n try {\r\n return verify(null, messageBuffer, {\r\n key: publicKey,\r\n format: 'der',\r\n type: 'spki',\r\n }, signature);\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Exports a key pair to a JSON-serializable format\r\n * @param keyPair - The key pair to export\r\n * @returns KeyPair object with hex-encoded keys\r\n */\r\nexport function exportKeyPair(keyPair: KeyPair): KeyPair {\r\n return {\r\n publicKey: keyPair.publicKey,\r\n privateKey: keyPair.privateKey,\r\n };\r\n}\r\n\r\n/**\r\n * Imports a key pair from hex strings\r\n * @param publicKeyHex - The public key in hex format\r\n * @param privateKeyHex - The private key in hex format\r\n * @returns KeyPair object\r\n * @throws Error if keys are not valid hex strings\r\n */\r\nexport function importKeyPair(publicKeyHex: string, privateKeyHex: string): KeyPair {\r\n // Validate that keys are valid hex strings\r\n const hexPattern = /^[0-9a-f]+$/i;\r\n if (!hexPattern.test(publicKeyHex)) {\r\n throw new Error('Invalid public key: must be a hex string');\r\n }\r\n if (!hexPattern.test(privateKeyHex)) {\r\n throw new Error('Invalid private key: must be a hex string');\r\n }\r\n \r\n return {\r\n publicKey: publicKeyHex,\r\n privateKey: privateKeyHex,\r\n };\r\n}\r\n","import { createHash } from 'node:crypto';\nimport { signMessage, verifySignature } from '../identity/keypair';\n\n/**\n * Message types on the Agora network.\n * Every piece of data flowing between agents is wrapped in an envelope.\n */\nexport type MessageType =\n | 'announce' // Agent publishes capabilities/state\n | 'discover' // Agent requests peer discovery\n | 'request' // Agent requests a service\n | 'response' // Agent responds to a request\n | 'publish' // Agent publishes knowledge/state\n | 'subscribe' // Agent subscribes to a topic/domain\n | 'verify' // Agent verifies another agent's claim\n | 'ack' // Acknowledgement\n | 'error' // Error response\n | 'paper_discovery' // Agent publishes a discovered academic paper\n | 'peer_list_request' // Request peer list from relay\n | 'peer_list_response' // Relay responds with connected peers\n | 'peer_referral' // Agent recommends another agent\n | 'capability_announce' // Agent publishes capabilities to network\n | 'capability_query' // Agent queries for capabilities\n | 'capability_response' // Response with matching peers\n | 'commit' // Agent commits to a prediction (commit-reveal pattern)\n | 'reveal' // Agent reveals prediction and outcome\n | 'verification' // Agent verifies another agent's output\n | 'revocation' // Agent revokes a prior verification\n | 'reputation_query' // Agent queries for reputation data\n | 'reputation_response'; // Response to reputation query\n\n/**\n * The signed envelope that wraps every message on the network.\n * Content-addressed: the ID is the hash of the canonical payload.\n * Signed: every envelope carries a signature from the sender's private key.\n */\nexport interface Envelope<T = unknown> {\n /** Content-addressed ID: SHA-256 hash of canonical payload */\n id: string;\n /** Message type */\n type: MessageType;\n /** Sender peer ID (full ID) */\n from: string;\n /** Recipient peer IDs (full IDs) */\n to: string[];\n /** Unix timestamp (ms) when the message was created */\n timestamp: number;\n /** Optional: ID of the message this is responding to */\n inReplyTo?: string;\n /** The actual payload */\n payload: T;\n /** ed25519 signature over the canonical form (hex-encoded) */\n signature: string;\n}\n\n/**\n * Deterministic JSON serialization with recursively sorted keys.\n */\nfunction stableStringify(value: unknown): string {\n if (value === null || value === undefined) return JSON.stringify(value);\n if (typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) {\n return '[' + value.map(stableStringify).join(',') + ']';\n }\n const keys = Object.keys(value as Record<string, unknown>).sort();\n const pairs = keys.map(k => JSON.stringify(k) + ':' + stableStringify((value as Record<string, unknown>)[k]));\n return '{' + pairs.join(',') + '}';\n}\n\n/**\n * Canonical form of an envelope for signing/hashing.\n * Deterministic JSON serialization: recursively sorted keys, no whitespace.\n */\nexport function canonicalize(\n type: MessageType,\n from: string,\n to: string[],\n timestamp: number,\n payload: unknown,\n inReplyTo?: string,\n): string {\n const obj: Record<string, unknown> = { from, payload, timestamp, to, type };\n if (inReplyTo !== undefined) {\n obj.inReplyTo = inReplyTo;\n }\n return stableStringify(obj);\n}\n\nfunction normalizeRecipients(from: string, to?: string | string[]): string[] {\n const list = Array.isArray(to) ? to : (typeof to === 'string' ? [to] : [from]);\n const unique = new Set<string>();\n for (const recipient of list) {\n if (typeof recipient === 'string' && recipient.trim().length > 0) {\n unique.add(recipient);\n }\n }\n if (unique.size === 0) {\n unique.add(from);\n }\n return Array.from(unique);\n}\n\n/**\n * Compute the content-addressed ID for a message.\n */\nexport function computeId(canonical: string): string {\n return createHash('sha256').update(canonical).digest('hex');\n}\n\n/**\n * Create a signed envelope.\n * @param type - Message type\n * @param from - Sender's public key (hex)\n * @param privateKey - Sender's private key (hex) for signing\n * @param payload - The message payload\n * @param timestamp - Timestamp for the envelope (ms), defaults to Date.now()\n * @param inReplyTo - Optional ID of the message being replied to\n * @param to - Recipient peer ID(s)\n * @returns A signed Envelope\n */\nexport function createEnvelope<T>(\n type: MessageType,\n from: string,\n privateKey: string,\n payload: T,\n timestamp: number = Date.now(),\n inReplyTo?: string,\n to?: string | string[],\n): Envelope<T> {\n const recipients = normalizeRecipients(from, to);\n const canonical = canonicalize(type, from, recipients, timestamp, payload, inReplyTo);\n const id = computeId(canonical);\n const signature = signMessage(canonical, privateKey);\n\n return {\n id,\n type,\n from,\n to: recipients,\n timestamp,\n ...(inReplyTo !== undefined ? { inReplyTo } : {}),\n payload,\n signature,\n };\n}\n\n/**\n * Verify an envelope's integrity and authenticity.\n * Checks:\n * 1. Canonical form matches the ID (content-addressing)\n * 2. Signature is valid for the sender's public key\n * \n * @returns Object with `valid` boolean and optional `reason` for failure\n */\nexport function verifyEnvelope(envelope: Envelope): { valid: boolean; reason?: string } {\n const { id, type, from, to, timestamp, payload, signature, inReplyTo } = envelope;\n if (!from || !Array.isArray(to) || to.length === 0) {\n return { valid: false, reason: 'invalid_routing_fields' };\n }\n\n // Reconstruct canonical form.\n const canonical = canonicalize(type, from, to, timestamp, payload, inReplyTo);\n\n // Check content-addressed ID\n const expectedId = computeId(canonical);\n if (id !== expectedId) {\n return { valid: false, reason: 'id_mismatch' };\n }\n\n const sigValid = verifySignature(canonical, signature, from);\n if (!sigValid) {\n return { valid: false, reason: 'signature_invalid' };\n }\n\n return { valid: true };\n}\n","/**\r\n * store.ts — File-based message store for offline peers.\r\n * When the relay has storage enabled for certain public keys, messages\r\n * for offline recipients are persisted and delivered when they connect.\r\n */\r\n\r\nimport * as fs from 'node:fs';\r\nimport * as path from 'node:path';\r\n\r\nexport interface StoredMessage {\r\n from: string;\r\n envelope: object;\r\n}\r\n\r\nexport class MessageStore {\r\n private storageDir: string;\r\n\r\n constructor(storageDir: string) {\r\n this.storageDir = storageDir;\r\n fs.mkdirSync(storageDir, { recursive: true });\r\n }\r\n\r\n private recipientDir(publicKey: string): string {\r\n const safe = publicKey.replace(/[^a-zA-Z0-9_-]/g, '_');\r\n return path.join(this.storageDir, safe);\r\n }\r\n\r\n save(recipientKey: string, message: StoredMessage): void {\r\n const dir = this.recipientDir(recipientKey);\r\n fs.mkdirSync(dir, { recursive: true });\r\n const filename = `${Date.now()}-${crypto.randomUUID()}.json`;\r\n fs.writeFileSync(path.join(dir, filename), JSON.stringify(message));\r\n }\r\n\r\n load(recipientKey: string): StoredMessage[] {\r\n const dir = this.recipientDir(recipientKey);\r\n if (!fs.existsSync(dir)) return [];\r\n const files = fs.readdirSync(dir).sort();\r\n const messages: StoredMessage[] = [];\r\n for (const file of files) {\r\n if (!file.endsWith('.json')) continue;\r\n try {\r\n const data = fs.readFileSync(path.join(dir, file), 'utf8');\r\n messages.push(JSON.parse(data) as StoredMessage);\r\n } catch {\r\n // Skip files that cannot be read or parsed\r\n }\r\n }\r\n return messages;\r\n }\r\n\r\n clear(recipientKey: string): void {\r\n const dir = this.recipientDir(recipientKey);\r\n if (!fs.existsSync(dir)) return;\r\n const files = fs.readdirSync(dir);\r\n for (const file of files) {\r\n if (file.endsWith('.json')) {\r\n fs.unlinkSync(path.join(dir, file));\r\n }\r\n }\r\n }\r\n}\r\n","import { EventEmitter } from 'node:events';\r\nimport { WebSocketServer, WebSocket } from 'ws';\r\nimport { verifyEnvelope, createEnvelope, type Envelope } from '../message/envelope';\r\nimport type { PeerListRequestPayload, PeerListResponsePayload } from '../message/types/peer-discovery';\r\nimport { MessageStore } from './store';\r\n\r\ninterface SenderWindow {\r\n count: number;\r\n windowStart: number;\r\n}\r\n\r\nexport interface RelayRateLimitOptions {\r\n enabled?: boolean;\r\n maxMessages?: number;\r\n windowMs?: number;\r\n}\r\n\r\nexport interface RelayEnvelopeDedupOptions {\r\n enabled?: boolean;\r\n maxIds?: number;\r\n}\r\n\r\n/**\r\n * Represents a connected agent in the relay\r\n */\r\ninterface ConnectedAgent {\r\n /** Agent's public key */\r\n publicKey: string;\r\n /** Optional agent name */\r\n name?: string;\r\n /** WebSocket connection */\r\n socket: WebSocket;\r\n /** Last seen timestamp (ms) */\r\n lastSeen: number;\r\n /** Optional metadata */\r\n metadata?: {\r\n version?: string;\r\n capabilities?: string[];\r\n };\r\n}\r\n\r\n/**\r\n * Events emitted by RelayServer\r\n */\r\nexport interface RelayServerEvents {\r\n 'agent-registered': (publicKey: string) => void;\r\n 'agent-disconnected': (publicKey: string) => void;\r\n /** Emitted when a session disconnects (same as agent-disconnected for compatibility) */\r\n 'disconnection': (publicKey: string) => void;\r\n 'message-relayed': (from: string, to: string, envelope: Envelope) => void;\r\n 'error': (error: Error) => void;\r\n}\r\n\r\n/**\r\n * WebSocket relay server for routing messages between agents.\r\n * \r\n * Agents connect to the relay and register with their public key.\r\n * Messages are routed to recipients based on the 'to' field.\r\n * All envelopes are verified before being forwarded.\r\n */\r\nexport interface RelayServerOptions {\r\n /** Optional relay identity for peer_list_request handling */\r\n identity?: { publicKey: string; privateKey: string };\r\n /** Public keys that should have messages stored when offline */\r\n storagePeers?: string[];\r\n /** Directory for persisting messages for storage peers */\r\n storageDir?: string;\r\n /** Maximum number of concurrent registered peers (default: 100) */\r\n maxPeers?: number;\r\n /** Per-sender sliding-window message rate limiting */\r\n rateLimit?: RelayRateLimitOptions;\r\n /** Envelope ID deduplication options */\r\n envelopeDedup?: RelayEnvelopeDedupOptions;\r\n}\r\n\r\nexport class RelayServer extends EventEmitter {\r\n private wss: WebSocketServer | null = null;\r\n /** publicKey -> sessionId -> ConnectedAgent (multiple sessions per key) */\r\n private sessions = new Map<string, Map<string, ConnectedAgent>>();\r\n private identity?: { publicKey: string; privateKey: string };\r\n private storagePeers: string[] = [];\r\n private store: MessageStore | null = null;\r\n private maxPeers: number = 100;\r\n private readonly senderWindows: Map<string, SenderWindow> = new Map();\r\n private static readonly MAX_SENDER_ENTRIES = 500;\r\n private readonly processedEnvelopeIds: Set<string> = new Set();\r\n private rateLimitEnabled = true;\r\n private rateLimitMaxMessages = 10;\r\n private rateLimitWindowMs = 60_000;\r\n private envelopeDedupEnabled = true;\r\n private envelopeDedupMaxIds = 1000;\r\n\r\n constructor(options?: { publicKey: string; privateKey: string } | RelayServerOptions) {\r\n super();\r\n if (options) {\r\n if ('identity' in options && options.identity) {\r\n this.identity = options.identity;\r\n } else if ('publicKey' in options && 'privateKey' in options) {\r\n this.identity = { publicKey: options.publicKey, privateKey: options.privateKey };\r\n }\r\n const opts = options as RelayServerOptions;\r\n if (opts.storagePeers?.length && opts.storageDir) {\r\n this.storagePeers = opts.storagePeers;\r\n this.store = new MessageStore(opts.storageDir);\r\n }\r\n if (opts.maxPeers !== undefined) {\r\n this.maxPeers = opts.maxPeers;\r\n }\r\n if (opts.rateLimit) {\r\n if (opts.rateLimit.enabled !== undefined) {\r\n this.rateLimitEnabled = opts.rateLimit.enabled;\r\n }\r\n if (opts.rateLimit.maxMessages !== undefined && opts.rateLimit.maxMessages > 0) {\r\n this.rateLimitMaxMessages = opts.rateLimit.maxMessages;\r\n }\r\n if (opts.rateLimit.windowMs !== undefined && opts.rateLimit.windowMs > 0) {\r\n this.rateLimitWindowMs = opts.rateLimit.windowMs;\r\n }\r\n }\r\n if (opts.envelopeDedup) {\r\n if (opts.envelopeDedup.enabled !== undefined) {\r\n this.envelopeDedupEnabled = opts.envelopeDedup.enabled;\r\n }\r\n if (opts.envelopeDedup.maxIds !== undefined && opts.envelopeDedup.maxIds > 0) {\r\n this.envelopeDedupMaxIds = opts.envelopeDedup.maxIds;\r\n }\r\n }\r\n }\r\n }\r\n\r\n private isRateLimitedSender(senderPublicKey: string): boolean {\r\n if (!this.rateLimitEnabled) {\r\n return false;\r\n }\r\n\r\n const now = Date.now();\r\n const window = this.senderWindows.get(senderPublicKey);\r\n\r\n if (this.senderWindows.size >= RelayServer.MAX_SENDER_ENTRIES && !window) {\r\n this.evictOldestSenderWindow();\r\n }\r\n\r\n if (!window || (now - window.windowStart) > this.rateLimitWindowMs) {\r\n this.senderWindows.set(senderPublicKey, { count: 1, windowStart: now });\r\n return false;\r\n }\r\n\r\n window.count++;\r\n return window.count > this.rateLimitMaxMessages;\r\n }\r\n\r\n private evictOldestSenderWindow(): void {\r\n let oldestKey: string | null = null;\r\n let oldestTime = Infinity;\r\n\r\n for (const [key, window] of this.senderWindows.entries()) {\r\n if (window.windowStart < oldestTime) {\r\n oldestTime = window.windowStart;\r\n oldestKey = key;\r\n }\r\n }\r\n\r\n if (oldestKey !== null) {\r\n this.senderWindows.delete(oldestKey);\r\n }\r\n }\r\n\r\n private isDuplicateEnvelopeId(envelopeId: string): boolean {\r\n if (!this.envelopeDedupEnabled) {\r\n return false;\r\n }\r\n\r\n if (this.processedEnvelopeIds.has(envelopeId)) {\r\n return true;\r\n }\r\n\r\n this.processedEnvelopeIds.add(envelopeId);\r\n if (this.processedEnvelopeIds.size > this.envelopeDedupMaxIds) {\r\n const oldest = this.processedEnvelopeIds.values().next().value;\r\n if (oldest !== undefined) {\r\n this.processedEnvelopeIds.delete(oldest);\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Start the relay server\r\n * @param port - Port to listen on\r\n * @param host - Optional host (default: all interfaces)\r\n */\r\n start(port: number, host?: string): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n try {\r\n this.wss = new WebSocketServer({ port, host: host ?? '0.0.0.0' });\r\n let resolved = false;\r\n\r\n this.wss.on('error', (error) => {\r\n this.emit('error', error);\r\n if (!resolved) {\r\n resolved = true;\r\n reject(error);\r\n }\r\n });\r\n\r\n this.wss.on('listening', () => {\r\n if (!resolved) {\r\n resolved = true;\r\n resolve();\r\n }\r\n });\r\n\r\n this.wss.on('connection', (socket: WebSocket) => {\r\n this.handleConnection(socket);\r\n });\r\n } catch (error) {\r\n reject(error);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Stop the relay server\r\n */\r\n async stop(): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n if (!this.wss) {\r\n resolve();\r\n return;\r\n }\r\n\r\n // Close all agent connections (all sessions)\r\n for (const sessionMap of this.sessions.values()) {\r\n for (const agent of sessionMap.values()) {\r\n agent.socket.close();\r\n }\r\n }\r\n this.sessions.clear();\r\n\r\n this.wss.close((err) => {\r\n if (err) {\r\n reject(err);\r\n } else {\r\n this.wss = null;\r\n resolve();\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Get one connected agent per public key (first session). For backward compatibility.\r\n */\r\n getAgents(): Map<string, ConnectedAgent> {\r\n const out = new Map<string, ConnectedAgent>();\r\n for (const [key, sessionMap] of this.sessions) {\r\n const first = sessionMap.values().next().value;\r\n if (first) out.set(key, first);\r\n }\r\n return out;\r\n }\r\n\r\n /**\r\n * Handle incoming connection\r\n */\r\n private handleConnection(socket: WebSocket): void {\r\n let agentPublicKey: string | null = null;\r\n let sessionId: string | null = null;\r\n\r\n socket.on('message', (data: Buffer) => {\r\n try {\r\n const msg = JSON.parse(data.toString());\r\n\r\n // Handle registration\r\n if (msg.type === 'register' && !agentPublicKey) {\r\n if (!msg.publicKey || typeof msg.publicKey !== 'string') {\r\n this.sendError(socket, 'Invalid registration: missing or invalid publicKey');\r\n socket.close();\r\n return;\r\n }\r\n\r\n const publicKey = msg.publicKey;\r\n const name = msg.name;\r\n agentPublicKey = publicKey;\r\n sessionId = crypto.randomUUID();\r\n\r\n // Allow multiple sessions per publicKey; only enforce max unique peers\r\n if (!this.sessions.has(publicKey) && this.sessions.size >= this.maxPeers) {\r\n this.sendError(socket, `Relay is at capacity (max ${this.maxPeers} peers)`);\r\n socket.close();\r\n return;\r\n }\r\n\r\n const agent: ConnectedAgent = {\r\n publicKey,\r\n name,\r\n socket,\r\n lastSeen: Date.now(),\r\n };\r\n\r\n if (!this.sessions.has(publicKey)) {\r\n this.sessions.set(publicKey, new Map());\r\n }\r\n this.sessions.get(publicKey)!.set(sessionId, agent);\r\n const isFirstSession = this.sessions.get(publicKey)!.size === 1;\r\n\r\n this.emit('agent-registered', publicKey);\r\n\r\n // Build peers list: one entry per connected publicKey + storage peers\r\n const peers: Array<{ publicKey: string; name?: string }> = [];\r\n for (const [key, sessionMap] of this.sessions) {\r\n if (key === publicKey) continue;\r\n const firstAgent = sessionMap.values().next().value;\r\n peers.push({ publicKey: key, name: firstAgent?.name });\r\n }\r\n for (const storagePeer of this.storagePeers) {\r\n if (storagePeer !== publicKey && !this.sessions.has(storagePeer)) {\r\n peers.push({ publicKey: storagePeer, name: undefined });\r\n }\r\n }\r\n\r\n socket.send(JSON.stringify({\r\n type: 'registered',\r\n publicKey,\r\n sessionId,\r\n peers,\r\n }));\r\n\r\n // Notify other agents only when this is the first session for this peer\r\n if (isFirstSession) {\r\n this.broadcastPeerEvent('peer_online', publicKey, name);\r\n }\r\n\r\n // Deliver any stored messages for this peer\r\n if (this.store && this.storagePeers.includes(publicKey)) {\r\n const queued = this.store.load(publicKey);\r\n for (const stored of queued) {\r\n socket.send(JSON.stringify({\r\n type: 'message',\r\n from: stored.from,\r\n envelope: stored.envelope,\r\n }));\r\n }\r\n this.store.clear(publicKey);\r\n }\r\n return;\r\n }\r\n\r\n // Require registration before processing messages\r\n if (!agentPublicKey) {\r\n this.sendError(socket, 'Not registered: send registration message first');\r\n socket.close();\r\n return;\r\n }\r\n\r\n // Handle message relay\r\n if (msg.type === 'message') {\r\n if (!msg.to || typeof msg.to !== 'string') {\r\n this.sendError(socket, 'Invalid message: missing or invalid \"to\" field');\r\n return;\r\n }\r\n\r\n if (!msg.envelope || typeof msg.envelope !== 'object') {\r\n this.sendError(socket, 'Invalid message: missing or invalid \"envelope\" field');\r\n return;\r\n }\r\n\r\n const envelope = msg.envelope as Envelope;\r\n\r\n // Verify envelope signature\r\n const verification = verifyEnvelope(envelope);\r\n if (!verification.valid) {\r\n this.sendError(socket, `Invalid envelope: ${verification.reason || 'verification failed'}`);\r\n return;\r\n }\r\n\r\n // Verify sender matches registered agent\r\n const envelopeFrom = envelope.from;\r\n if (envelopeFrom !== agentPublicKey) {\r\n this.sendError(socket, 'Envelope sender does not match registered public key');\r\n return;\r\n }\r\n\r\n // Strict p2p routing: envelope.to must include the relay transport recipient.\r\n if (!Array.isArray(envelope.to) || envelope.to.length === 0 || !envelope.to.includes(msg.to)) {\r\n this.sendError(socket, 'Envelope recipients do not include requested relay recipient');\r\n return;\r\n }\r\n\r\n if (this.isRateLimitedSender(agentPublicKey)) {\r\n return;\r\n }\r\n\r\n if (this.isDuplicateEnvelopeId(envelope.id)) {\r\n return;\r\n }\r\n\r\n // Update lastSeen for any session of sender\r\n const senderSessionMap = this.sessions.get(agentPublicKey);\r\n if (senderSessionMap) {\r\n for (const a of senderSessionMap.values()) {\r\n a.lastSeen = Date.now();\r\n }\r\n }\r\n\r\n // Handle peer_list_request directed at relay\r\n if (envelope.type === 'peer_list_request' && this.identity && msg.to === this.identity.publicKey) {\r\n this.handlePeerListRequest(envelope as Envelope<PeerListRequestPayload>, socket, agentPublicKey);\r\n return;\r\n }\r\n\r\n // Find all recipient sessions\r\n const recipientSessionMap = this.sessions.get(msg.to);\r\n const openRecipients = recipientSessionMap\r\n ? Array.from(recipientSessionMap.values()).filter(a => a.socket.readyState === WebSocket.OPEN)\r\n : [];\r\n if (openRecipients.length === 0) {\r\n // If recipient is a storage peer, queue the message\r\n if (this.store && this.storagePeers.includes(msg.to)) {\r\n this.store.save(msg.to, {\r\n from: agentPublicKey,\r\n envelope,\r\n });\r\n this.emit('message-relayed', agentPublicKey, msg.to, envelope);\r\n } else {\r\n this.sendError(socket, 'Recipient not connected', 'unknown_recipient');\r\n }\r\n return;\r\n }\r\n\r\n // Forward envelope to all sessions of the recipient\r\n try {\r\n const relayMessage = {\r\n type: 'message',\r\n from: agentPublicKey,\r\n envelope,\r\n };\r\n const messageStr = JSON.stringify(relayMessage);\r\n for (const recipient of openRecipients) {\r\n recipient.socket.send(messageStr);\r\n }\r\n this.emit('message-relayed', agentPublicKey, msg.to, envelope);\r\n } catch (err) {\r\n this.sendError(socket, 'Failed to relay message');\r\n this.emit('error', err as Error);\r\n }\r\n return;\r\n }\r\n\r\n // Handle ping\r\n if (msg.type === 'ping') {\r\n socket.send(JSON.stringify({ type: 'pong' }));\r\n return;\r\n }\r\n\r\n // Unknown message type\r\n this.sendError(socket, `Unknown message type: ${msg.type}`);\r\n } catch (err) {\r\n // Invalid JSON or other parsing errors\r\n this.emit('error', new Error(`Message parsing failed: ${err instanceof Error ? err.message : String(err)}`));\r\n this.sendError(socket, 'Invalid message format');\r\n }\r\n });\r\n\r\n socket.on('close', () => {\r\n if (agentPublicKey && sessionId) {\r\n const sessionMap = this.sessions.get(agentPublicKey);\r\n if (sessionMap) {\r\n const agent = sessionMap.get(sessionId);\r\n const agentName = agent?.name;\r\n sessionMap.delete(sessionId);\r\n if (sessionMap.size === 0) {\r\n this.sessions.delete(agentPublicKey);\r\n this.emit('agent-disconnected', agentPublicKey);\r\n this.emit('disconnection', agentPublicKey);\r\n // Storage-enabled peers are always considered connected; skip peer_offline for them\r\n if (!this.storagePeers.includes(agentPublicKey)) {\r\n this.broadcastPeerEvent('peer_offline', agentPublicKey, agentName);\r\n }\r\n }\r\n }\r\n }\r\n });\r\n\r\n socket.on('error', (error) => {\r\n this.emit('error', error);\r\n });\r\n }\r\n\r\n /**\r\n * Send an error message to a client\r\n */\r\n private sendError(socket: WebSocket, message: string, code?: string): void {\r\n try {\r\n if (socket.readyState === WebSocket.OPEN) {\r\n const payload: { type: 'error'; message: string; code?: string } = { type: 'error', message };\r\n if (code) payload.code = code;\r\n socket.send(JSON.stringify(payload));\r\n }\r\n } catch (err) {\r\n // Log errors when sending error messages, but don't propagate to avoid cascading failures\r\n this.emit('error', new Error(`Failed to send error message: ${err instanceof Error ? err.message : String(err)}`));\r\n }\r\n }\r\n\r\n /**\r\n * Broadcast a peer event to all connected agents (all sessions except the one for publicKey)\r\n */\r\n private broadcastPeerEvent(eventType: 'peer_online' | 'peer_offline', publicKey: string, name?: string): void {\r\n const message = {\r\n type: eventType,\r\n publicKey,\r\n name,\r\n };\r\n const messageStr = JSON.stringify(message);\r\n\r\n for (const [key, sessionMap] of this.sessions) {\r\n if (key === publicKey) continue;\r\n for (const agent of sessionMap.values()) {\r\n if (agent.socket.readyState === WebSocket.OPEN) {\r\n try {\r\n agent.socket.send(messageStr);\r\n } catch (err) {\r\n this.emit('error', new Error(`Failed to send ${eventType} event: ${err instanceof Error ? err.message : String(err)}`));\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Handle peer list request from an agent\r\n */\r\n private handlePeerListRequest(envelope: Envelope<PeerListRequestPayload>, socket: WebSocket, requesterPublicKey: string): void {\r\n if (!this.identity) {\r\n this.sendError(socket, 'Relay does not support peer discovery (no identity configured)');\r\n return;\r\n }\r\n\r\n const { filters } = envelope.payload;\r\n const now = Date.now();\r\n\r\n // One entry per publicKey (first session for lastSeen/metadata)\r\n const peersList: ConnectedAgent[] = [];\r\n for (const [key, sessionMap] of this.sessions) {\r\n if (key === requesterPublicKey) continue;\r\n const first = sessionMap.values().next().value;\r\n if (first) peersList.push(first);\r\n }\r\n\r\n let peers = peersList;\r\n\r\n // Apply filters\r\n if (filters?.activeWithin) {\r\n peers = peers.filter(p => (now - p.lastSeen) < filters.activeWithin!);\r\n }\r\n\r\n if (filters?.limit && filters.limit > 0) {\r\n peers = peers.slice(0, filters.limit);\r\n }\r\n\r\n // Build response payload\r\n const response: PeerListResponsePayload = {\r\n peers: peers.map(p => ({\r\n publicKey: p.publicKey,\r\n metadata: p.name || p.metadata ? {\r\n name: p.name,\r\n version: p.metadata?.version,\r\n capabilities: p.metadata?.capabilities,\r\n } : undefined,\r\n lastSeen: p.lastSeen,\r\n })),\r\n totalPeers: this.sessions.size - (this.sessions.has(requesterPublicKey) ? 1 : 0),\r\n relayPublicKey: this.identity.publicKey,\r\n };\r\n\r\n // Create signed envelope\r\n const responseEnvelope = createEnvelope(\r\n 'peer_list_response',\r\n this.identity.publicKey,\r\n this.identity.privateKey,\r\n response,\r\n Date.now(),\r\n envelope.id, // Reply to the request\r\n [requesterPublicKey]\r\n );\r\n\r\n // Send response\r\n const relayMessage = {\r\n type: 'message',\r\n from: this.identity.publicKey,\r\n name: 'relay',\r\n envelope: responseEnvelope,\r\n };\r\n\r\n try {\r\n socket.send(JSON.stringify(relayMessage));\r\n } catch (err) {\r\n this.emit('error', new Error(`Failed to send peer list response: ${err instanceof Error ? err.message : String(err)}`));\r\n }\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
createEnvelope,
|
|
3
3
|
generateKeyPair,
|
|
4
4
|
verifyEnvelope
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MJGCRX6B.js";
|
|
6
6
|
|
|
7
7
|
// src/transport/peer-config.ts
|
|
8
8
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
@@ -1507,4 +1507,4 @@ export {
|
|
|
1507
1507
|
computeTrustScores,
|
|
1508
1508
|
computeAllTrustScores
|
|
1509
1509
|
};
|
|
1510
|
-
//# sourceMappingURL=chunk-
|
|
1510
|
+
//# sourceMappingURL=chunk-NHTC6OZW.js.map
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
RelayServer,
|
|
3
3
|
createEnvelope,
|
|
4
4
|
verifyEnvelope
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MJGCRX6B.js";
|
|
6
6
|
|
|
7
7
|
// src/relay/message-buffer.ts
|
|
8
8
|
var MAX_MESSAGES_PER_AGENT = 100;
|
|
@@ -160,13 +160,10 @@ function createRestRouter(relay, buffer, sessions, createEnv, verifyEnv, rateLim
|
|
|
160
160
|
router.use(apiRateLimit(rateLimitRpm));
|
|
161
161
|
relay.on("message-relayed", (from, to, envelope) => {
|
|
162
162
|
if (!sessions.has(to)) return;
|
|
163
|
-
const agentMap = relay.getAgents();
|
|
164
|
-
const senderAgent = agentMap.get(from);
|
|
165
163
|
const env = envelope;
|
|
166
164
|
const msg = {
|
|
167
165
|
id: env.id,
|
|
168
166
|
from,
|
|
169
|
-
fromName: senderAgent?.name,
|
|
170
167
|
type: env.type,
|
|
171
168
|
payload: env.payload,
|
|
172
169
|
timestamp: env.timestamp,
|
|
@@ -291,11 +288,9 @@ function createRestRouter(relay, buffer, sessions, createEnv, verifyEnv, rateLim
|
|
|
291
288
|
}
|
|
292
289
|
const restRecipient = sessions.get(to);
|
|
293
290
|
if (restRecipient) {
|
|
294
|
-
const senderAgent = wsAgents.get(senderPublicKey);
|
|
295
291
|
const msg = {
|
|
296
292
|
id: envelope.id,
|
|
297
293
|
from: senderPublicKey,
|
|
298
|
-
fromName: session.name ?? senderAgent?.name,
|
|
299
294
|
type: envelope.type,
|
|
300
295
|
payload: envelope.payload,
|
|
301
296
|
timestamp: envelope.timestamp,
|
|
@@ -453,4 +448,4 @@ export {
|
|
|
453
448
|
createRestRouter,
|
|
454
449
|
runRelay
|
|
455
450
|
};
|
|
456
|
-
//# sourceMappingURL=chunk-
|
|
451
|
+
//# sourceMappingURL=chunk-OVDMZHTX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/relay/message-buffer.ts","../src/relay/jwt-auth.ts","../src/relay/rest-api.ts","../src/relay/run-relay.ts"],"sourcesContent":["/**\r\n * message-buffer.ts — In-memory bounded message queue per agent.\r\n *\r\n * When messages are delivered to an agent via the relay, they are also\r\n * stored here so that HTTP polling clients can retrieve them via GET /v1/messages.\r\n */\r\n\r\nexport interface BufferedMessage {\r\n id: string;\r\n from: string;\r\n type: string;\r\n payload: unknown;\r\n timestamp: number;\r\n inReplyTo?: string;\r\n}\r\n\r\ninterface StoredMessage {\r\n message: BufferedMessage;\r\n receivedAt: number;\r\n}\r\n\r\nconst MAX_MESSAGES_PER_AGENT = 100;\r\n\r\n/**\r\n * MessageBuffer stores inbound messages per agent public key.\r\n * FIFO eviction when the buffer is full (max 100 messages).\r\n * Messages older than ttlMs (measured from when they were received) are pruned on access.\r\n */\r\nexport class MessageBuffer {\r\n private buffers: Map<string, StoredMessage[]> = new Map();\r\n private ttlMs: number;\r\n\r\n constructor(options?: { ttlMs?: number }) {\r\n this.ttlMs = options?.ttlMs ?? 86400000; // default 24h\r\n }\r\n\r\n /**\r\n * Add a message to an agent's buffer.\r\n * Evicts the oldest message if the buffer is full.\r\n */\r\n add(publicKey: string, message: BufferedMessage): void {\r\n let queue = this.buffers.get(publicKey);\r\n if (!queue) {\r\n queue = [];\r\n this.buffers.set(publicKey, queue);\r\n }\r\n queue.push({ message, receivedAt: Date.now() });\r\n if (queue.length > MAX_MESSAGES_PER_AGENT) {\r\n queue.shift(); // FIFO eviction\r\n }\r\n }\r\n\r\n /**\r\n * Retrieve messages for an agent, optionally filtering by `since` timestamp.\r\n * Returns messages with timestamp > since (exclusive). Prunes expired messages.\r\n */\r\n get(publicKey: string, since?: number): BufferedMessage[] {\r\n const now = Date.now();\r\n let queue = this.buffers.get(publicKey) ?? [];\r\n // Prune messages older than ttlMs (based on wall-clock receive time)\r\n queue = queue.filter((s) => now - s.receivedAt < this.ttlMs);\r\n this.buffers.set(publicKey, queue);\r\n const messages = queue.map((s) => s.message);\r\n if (since === undefined) {\r\n return [...messages];\r\n }\r\n return messages.filter((m) => m.timestamp > since);\r\n }\r\n\r\n /**\r\n * Clear all messages for an agent (after polling without `since`).\r\n */\r\n clear(publicKey: string): void {\r\n this.buffers.set(publicKey, []);\r\n }\r\n\r\n /**\r\n * Remove all state for a disconnected agent.\r\n */\r\n delete(publicKey: string): void {\r\n this.buffers.delete(publicKey);\r\n }\r\n}\r\n","/**\r\n * jwt-auth.ts — JWT token creation and validation middleware.\r\n *\r\n * Tokens are signed with AGORA_RELAY_JWT_SECRET (required env var).\r\n * Expiry defaults to 3600 seconds (1 hour), configurable via AGORA_JWT_EXPIRY_SECONDS.\r\n *\r\n * Token payload: { publicKey, name }\r\n */\r\n\r\nimport jwt from 'jsonwebtoken';\r\nimport { randomBytes } from 'node:crypto';\r\nimport type { Request, Response, NextFunction } from 'express';\r\n\r\nexport interface JwtPayload {\r\n publicKey: string;\r\n name?: string;\r\n}\r\n\r\n/**\r\n * Augment Express Request to carry decoded JWT payload.\r\n */\r\nexport interface AuthenticatedRequest extends Request {\r\n agent?: JwtPayload;\r\n}\r\n\r\n/**\r\n * Revocation set for invalidated tokens (populated by DELETE /v1/disconnect).\r\n * Stored as a Map of JWT `jti` → expiry timestamp (ms).\r\n * Entries are automatically removed once their JWT would have expired anyway,\r\n * preventing unbounded memory growth.\r\n */\r\nconst revokedJtis: Map<string, number> = new Map();\r\n\r\n/**\r\n * Remove revoked JTI entries whose token expiry has already passed.\r\n * These tokens can no longer be used regardless, so no need to keep them.\r\n */\r\nfunction pruneExpiredRevocations(): void {\r\n const now = Date.now();\r\n for (const [jti, expiry] of revokedJtis) {\r\n if (expiry <= now) {\r\n revokedJtis.delete(jti);\r\n }\r\n }\r\n}\r\n\r\nfunction getJwtSecret(): string {\r\n const secret = process.env.AGORA_RELAY_JWT_SECRET;\r\n if (!secret) {\r\n throw new Error(\r\n 'AGORA_RELAY_JWT_SECRET environment variable is required but not set'\r\n );\r\n }\r\n return secret;\r\n}\r\n\r\nfunction getExpirySeconds(): number {\r\n const raw = process.env.AGORA_JWT_EXPIRY_SECONDS;\r\n if (raw) {\r\n const parsed = parseInt(raw, 10);\r\n if (!isNaN(parsed) && parsed > 0) {\r\n return parsed;\r\n }\r\n }\r\n return 3600; // 1 hour default\r\n}\r\n\r\n/**\r\n * Create a signed JWT for a registered agent.\r\n * Returns the token string and its expiry timestamp (ms since epoch).\r\n */\r\nexport function createToken(payload: JwtPayload): {\r\n token: string;\r\n expiresAt: number;\r\n} {\r\n const secret = getJwtSecret();\r\n const expirySeconds = getExpirySeconds();\r\n const jti = `${Date.now()}-${randomBytes(16).toString('hex')}`;\r\n\r\n const token = jwt.sign(\r\n { publicKey: payload.publicKey, name: payload.name, jti },\r\n secret,\r\n { expiresIn: expirySeconds }\r\n );\r\n\r\n const expiresAt = Date.now() + expirySeconds * 1000;\r\n return { token, expiresAt };\r\n}\r\n\r\n/**\r\n * Revoke a token by its jti claim so it cannot be used again.\r\n * The revocation entry is stored with the token's expiry so it can be\r\n * pruned automatically once the token would no longer be valid anyway.\r\n */\r\nexport function revokeToken(token: string): void {\r\n try {\r\n const secret = getJwtSecret();\r\n const decoded = jwt.verify(token, secret) as jwt.JwtPayload & {\r\n jti?: string;\r\n exp?: number;\r\n };\r\n if (decoded.jti) {\r\n const expiry = decoded.exp ? decoded.exp * 1000 : Date.now();\r\n revokedJtis.set(decoded.jti, expiry);\r\n pruneExpiredRevocations();\r\n }\r\n } catch {\r\n // Token already invalid — nothing to revoke\r\n }\r\n}\r\n\r\n/**\r\n * Express middleware that validates the Authorization: Bearer <token> header.\r\n * Attaches decoded payload to `req.agent` on success.\r\n * Responds with 401 if missing/invalid/expired/revoked.\r\n */\r\nexport function requireAuth(\r\n req: AuthenticatedRequest,\r\n res: Response,\r\n next: NextFunction\r\n): void {\r\n const authHeader = req.headers.authorization;\r\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\r\n res.status(401).json({ error: 'Missing or malformed Authorization header' });\r\n return;\r\n }\r\n\r\n const token = authHeader.slice(7);\r\n try {\r\n const secret = getJwtSecret();\r\n const decoded = jwt.verify(token, secret) as jwt.JwtPayload & {\r\n publicKey: string;\r\n name?: string;\r\n jti?: string;\r\n };\r\n\r\n if (decoded.jti && revokedJtis.has(decoded.jti)) {\r\n res.status(401).json({ error: 'Token has been revoked' });\r\n return;\r\n }\r\n\r\n req.agent = { publicKey: decoded.publicKey, name: decoded.name };\r\n next();\r\n } catch (err) {\r\n if (err instanceof jwt.TokenExpiredError) {\r\n res.status(401).json({ error: 'Token expired' });\r\n } else {\r\n res.status(401).json({ error: 'Invalid token' });\r\n }\r\n }\r\n}\r\n","/**\r\n * rest-api.ts — Express router implementing the Agora relay REST API.\r\n *\r\n * Endpoints:\r\n * POST /v1/register — Register agent, obtain JWT session token\r\n * POST /v1/send — Send message to a peer (requires auth)\r\n * GET /v1/peers — List online peers (requires auth)\r\n * GET /v1/messages — Poll for new inbound messages (requires auth)\r\n * DELETE /v1/disconnect — Invalidate token and disconnect (requires auth)\r\n */\r\n\r\nimport { Router } from 'express';\r\nimport type { Request, Response } from 'express';\r\nimport { rateLimit } from 'express-rate-limit';\r\nimport {\r\n createToken,\r\n revokeToken,\r\n requireAuth,\r\n type AuthenticatedRequest,\r\n} from './jwt-auth';\r\nimport { MessageBuffer, type BufferedMessage } from './message-buffer';\r\n\r\nconst apiRateLimit = (rpm: number): ReturnType<typeof rateLimit> => rateLimit({\r\n windowMs: 60_000,\r\n limit: rpm,\r\n standardHeaders: 'draft-7',\r\n legacyHeaders: false,\r\n message: { error: 'Too many requests — try again later' },\r\n});\r\n\r\n/**\r\n * A session for a REST-connected agent.\r\n * privateKey is held only in memory and never logged or persisted.\r\n */\r\nexport interface RestSession {\r\n publicKey: string;\r\n privateKey: string;\r\n name?: string;\r\n metadata?: { version?: string; capabilities?: string[] };\r\n registeredAt: number;\r\n expiresAt: number;\r\n token: string;\r\n}\r\n\r\nfunction pruneExpiredSessions(\r\n sessions: Map<string, RestSession>,\r\n buffer: MessageBuffer\r\n): void {\r\n const now = Date.now();\r\n for (const [publicKey, session] of sessions) {\r\n if (session.expiresAt <= now) {\r\n sessions.delete(publicKey);\r\n buffer.delete(publicKey);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Minimal interface for the relay server that the REST API depends on.\r\n */\r\nexport interface RelayInterface {\r\n getAgents(): Map<\r\n string,\r\n {\r\n publicKey: string;\r\n name?: string;\r\n lastSeen: number;\r\n metadata?: { version?: string; capabilities?: string[] };\r\n socket: unknown;\r\n }\r\n >;\r\n on(\r\n event: 'message-relayed',\r\n handler: (from: string, to: string, envelope: unknown) => void\r\n ): void;\r\n}\r\n\r\n/**\r\n * Envelope creation function interface (matches createEnvelope from message/envelope).\r\n */\r\nexport type CreateEnvelopeFn = (\r\n type: string,\r\n from: string,\r\n to: string[],\r\n privateKey: string,\r\n payload: unknown,\r\n timestamp?: number,\r\n inReplyTo?: string\r\n) => {\r\n id: string;\r\n type: string;\r\n from: string;\r\n to: string[];\r\n timestamp: number;\r\n payload: unknown;\r\n signature: string;\r\n inReplyTo?: string;\r\n};\r\n\r\n/**\r\n * Envelope verification function interface.\r\n */\r\nexport type VerifyEnvelopeFn = (envelope: unknown) => {\r\n valid: boolean;\r\n reason?: string;\r\n};\r\n\r\n/**\r\n * Create the REST API router.\r\n */\r\nexport function createRestRouter(\r\n relay: RelayInterface,\r\n buffer: MessageBuffer,\r\n sessions: Map<string, RestSession>,\r\n createEnv: CreateEnvelopeFn,\r\n verifyEnv: VerifyEnvelopeFn,\r\n rateLimitRpm = 60\r\n): Router {\r\n const router = Router();\r\n router.use(apiRateLimit(rateLimitRpm));\r\n\r\n relay.on('message-relayed', (from, to, envelope) => {\r\n if (!sessions.has(to)) return;\r\n const env = envelope as {\r\n id: string;\r\n type: string;\r\n payload: unknown;\r\n timestamp: number;\r\n inReplyTo?: string;\r\n };\r\n const msg: BufferedMessage = {\r\n id: env.id,\r\n from,\r\n type: env.type,\r\n payload: env.payload,\r\n timestamp: env.timestamp,\r\n inReplyTo: env.inReplyTo,\r\n };\r\n buffer.add(to, msg);\r\n });\r\n\r\n router.post('/v1/register', async (req: Request, res: Response) => {\r\n const { publicKey, privateKey, name, metadata } = req.body as {\r\n publicKey?: string;\r\n privateKey?: string;\r\n name?: string;\r\n metadata?: { version?: string; capabilities?: string[] };\r\n };\r\n\r\n if (!publicKey || typeof publicKey !== 'string') {\r\n res.status(400).json({ error: 'publicKey is required' });\r\n return;\r\n }\r\n if (!privateKey || typeof privateKey !== 'string') {\r\n res.status(400).json({ error: 'privateKey is required' });\r\n return;\r\n }\r\n\r\n const testEnvelope = createEnv(\r\n 'announce',\r\n publicKey,\r\n [publicKey],\r\n privateKey,\r\n { challenge: 'register' },\r\n Date.now()\r\n );\r\n const verification = verifyEnv(testEnvelope);\r\n if (!verification.valid) {\r\n res\r\n .status(400)\r\n .json({ error: 'Key pair verification failed: ' + verification.reason });\r\n return;\r\n }\r\n\r\n const { token, expiresAt } = createToken({ publicKey, name });\r\n pruneExpiredSessions(sessions, buffer);\r\n\r\n const session: RestSession = {\r\n publicKey,\r\n privateKey,\r\n name,\r\n metadata,\r\n registeredAt: Date.now(),\r\n expiresAt,\r\n token,\r\n };\r\n sessions.set(publicKey, session);\r\n\r\n const wsAgents = relay.getAgents();\r\n const peers: Array<{ publicKey: string; name?: string; lastSeen: number }> = [];\r\n for (const agent of wsAgents.values()) {\r\n if (agent.publicKey !== publicKey) {\r\n peers.push({\r\n publicKey: agent.publicKey,\r\n name: agent.name,\r\n lastSeen: agent.lastSeen,\r\n });\r\n }\r\n }\r\n for (const s of sessions.values()) {\r\n if (s.publicKey !== publicKey && !wsAgents.has(s.publicKey)) {\r\n peers.push({\r\n publicKey: s.publicKey,\r\n name: s.name,\r\n lastSeen: s.registeredAt,\r\n });\r\n }\r\n }\r\n\r\n res.json({ token, expiresAt, peers });\r\n });\r\n\r\n router.post(\r\n '/v1/send',\r\n requireAuth,\r\n async (req: AuthenticatedRequest, res: Response) => {\r\n const { to, type, payload, inReplyTo } = req.body as {\r\n to?: string;\r\n type?: string;\r\n payload?: unknown;\r\n inReplyTo?: string;\r\n };\r\n\r\n if (!to || typeof to !== 'string') {\r\n res.status(400).json({ error: 'to is required' });\r\n return;\r\n }\r\n if (!type || typeof type !== 'string') {\r\n res.status(400).json({ error: 'type is required' });\r\n return;\r\n }\r\n if (payload === undefined) {\r\n res.status(400).json({ error: 'payload is required' });\r\n return;\r\n }\r\n\r\n const senderPublicKey = req.agent!.publicKey;\r\n const session = sessions.get(senderPublicKey);\r\n if (!session) {\r\n res.status(401).json({ error: 'Session not found — please re-register' });\r\n return;\r\n }\r\n\r\n const envelope = createEnv(\r\n type,\r\n senderPublicKey,\r\n [to],\r\n session.privateKey,\r\n payload,\r\n Date.now(),\r\n inReplyTo\r\n );\r\n\r\n const wsAgents = relay.getAgents();\r\n const wsRecipient = wsAgents.get(to);\r\n if (wsRecipient && wsRecipient.socket) {\r\n const ws = wsRecipient.socket as { readyState: number; send(data: string): void };\r\n const OPEN = 1;\r\n if (ws.readyState !== OPEN) {\r\n res.status(503).json({ error: 'Recipient connection is not open' });\r\n return;\r\n }\r\n try {\r\n const relayMsg = JSON.stringify({\r\n type: 'message',\r\n from: senderPublicKey,\r\n name: session.name,\r\n envelope,\r\n });\r\n ws.send(relayMsg);\r\n res.json({ ok: true, envelopeId: envelope.id });\r\n return;\r\n } catch (err) {\r\n res.status(500).json({\r\n error:\r\n 'Failed to deliver message: ' +\r\n (err instanceof Error ? err.message : String(err)),\r\n });\r\n return;\r\n }\r\n }\r\n\r\n const restRecipient = sessions.get(to);\r\n if (restRecipient) {\r\n const msg: BufferedMessage = {\r\n id: envelope.id,\r\n from: senderPublicKey,\r\n type: envelope.type,\r\n payload: envelope.payload,\r\n timestamp: envelope.timestamp,\r\n inReplyTo: envelope.inReplyTo,\r\n };\r\n buffer.add(to, msg);\r\n res.json({ ok: true, envelopeId: envelope.id });\r\n return;\r\n }\r\n\r\n res.status(404).json({ error: 'Recipient not connected' });\r\n }\r\n );\r\n\r\n router.get(\r\n '/v1/peers',\r\n requireAuth,\r\n (req: AuthenticatedRequest, res: Response) => {\r\n const callerPublicKey = req.agent!.publicKey;\r\n const wsAgents = relay.getAgents();\r\n const peerList: Array<{\r\n publicKey: string;\r\n name?: string;\r\n lastSeen: number;\r\n metadata?: { version?: string; capabilities?: string[] };\r\n }> = [];\r\n\r\n for (const agent of wsAgents.values()) {\r\n if (agent.publicKey !== callerPublicKey) {\r\n peerList.push({\r\n publicKey: agent.publicKey,\r\n name: agent.name,\r\n lastSeen: agent.lastSeen,\r\n metadata: agent.metadata,\r\n });\r\n }\r\n }\r\n\r\n for (const s of sessions.values()) {\r\n if (s.publicKey !== callerPublicKey && !wsAgents.has(s.publicKey)) {\r\n peerList.push({\r\n publicKey: s.publicKey,\r\n name: s.name,\r\n lastSeen: s.registeredAt,\r\n metadata: s.metadata,\r\n });\r\n }\r\n }\r\n\r\n res.json({ peers: peerList });\r\n }\r\n );\r\n\r\n router.get(\r\n '/v1/messages',\r\n requireAuth,\r\n (req: AuthenticatedRequest, res: Response) => {\r\n const publicKey = req.agent!.publicKey;\r\n const sinceRaw = req.query.since as string | undefined;\r\n const limitRaw = req.query.limit as string | undefined;\r\n\r\n const since = sinceRaw ? parseInt(sinceRaw, 10) : undefined;\r\n const limit = Math.min(limitRaw ? parseInt(limitRaw, 10) : 50, 100);\r\n\r\n let messages = buffer.get(publicKey, since);\r\n const hasMore = messages.length > limit;\r\n if (hasMore) {\r\n messages = messages.slice(0, limit);\r\n }\r\n\r\n if (since === undefined) {\r\n buffer.clear(publicKey);\r\n }\r\n\r\n res.json({ messages, hasMore });\r\n }\r\n );\r\n\r\n router.delete(\r\n '/v1/disconnect',\r\n requireAuth,\r\n (req: AuthenticatedRequest, res: Response) => {\r\n const publicKey = req.agent!.publicKey;\r\n const authHeader = req.headers.authorization!;\r\n const token = authHeader.slice(7);\r\n\r\n revokeToken(token);\r\n sessions.delete(publicKey);\r\n buffer.delete(publicKey);\r\n\r\n res.json({ ok: true });\r\n }\r\n );\r\n\r\n return router;\r\n}\r\n","/**\r\n * run-relay.ts — Start Agora relay: WebSocket server and optional REST API.\r\n *\r\n * When REST is enabled, starts:\r\n * 1. WebSocket relay (RelayServer) on wsPort\r\n * 2. REST API server (Express) on restPort\r\n *\r\n * Environment variables:\r\n * RELAY_PORT — WebSocket relay port (default: 3002); alias: PORT\r\n * REST_PORT — REST API port (default: 3001)\r\n * JWT_SECRET — Secret for JWT session tokens (alias: AGORA_RELAY_JWT_SECRET)\r\n * AGORA_JWT_EXPIRY_SECONDS — JWT expiry in seconds (default: 3600)\r\n * MAX_PEERS — Maximum concurrent registered peers (default: 100)\r\n * MESSAGE_TTL_MS — Message buffer TTL in ms (default: 86400000 = 24h)\r\n * RATE_LIMIT_RPM — REST API requests per minute per IP (default: 60)\r\n * ALLOWED_ORIGINS — CORS origins, comma-separated or * (default: *)\r\n */\r\n\r\nimport http from 'node:http';\r\nimport express from 'express';\r\nimport cors from 'cors';\r\nimport { RelayServer, type RelayServerOptions } from './server';\r\nimport {\r\n createEnvelope,\r\n verifyEnvelope,\r\n type Envelope,\r\n type MessageType,\r\n} from '../message/envelope';\r\nimport { createRestRouter, type CreateEnvelopeFn } from './rest-api';\r\nimport { MessageBuffer } from './message-buffer';\r\nimport type { RestSession } from './rest-api';\r\n\r\n/** Wrapper so REST API can pass string type; createEnvelope expects MessageType */\r\nconst createEnvelopeForRest: CreateEnvelopeFn = (\r\n type,\r\n from,\r\n to,\r\n privateKey,\r\n payload,\r\n timestamp,\r\n inReplyTo\r\n) =>\r\n createEnvelope(\r\n type as MessageType,\r\n from,\r\n privateKey,\r\n payload,\r\n timestamp ?? Date.now(),\r\n inReplyTo,\r\n to\r\n );\r\n\r\nexport interface RunRelayOptions {\r\n /** WebSocket port (default from RELAY_PORT or PORT env, or 3002) */\r\n wsPort?: number;\r\n /** REST API port (default from REST_PORT env, or 3001). Ignored if enableRest is false. */\r\n restPort?: number;\r\n /** Enable REST API (requires JWT_SECRET or AGORA_RELAY_JWT_SECRET). Default: true if secret is set. */\r\n enableRest?: boolean;\r\n /** Relay server options (identity, storagePeers, storageDir) */\r\n relayOptions?: RelayServerOptions;\r\n}\r\n\r\n/**\r\n * Start WebSocket relay and optionally REST API.\r\n * Returns { relay, httpServer } where httpServer is set only when REST is enabled.\r\n */\r\nexport async function runRelay(options: RunRelayOptions = {}): Promise<{\r\n relay: RelayServer;\r\n httpServer?: http.Server;\r\n}> {\r\n const wsPort = options.wsPort ?? parseInt(\r\n process.env.RELAY_PORT ?? process.env.PORT ?? '3002', 10\r\n );\r\n const jwtSecret = process.env.JWT_SECRET ?? process.env.AGORA_RELAY_JWT_SECRET;\r\n const enableRest =\r\n options.enableRest ??\r\n (typeof jwtSecret === 'string' && jwtSecret.length > 0);\r\n\r\n const maxPeers = parseInt(process.env.MAX_PEERS ?? '100', 10);\r\n const relayOptions: RelayServerOptions = { ...options.relayOptions, maxPeers };\r\n\r\n const relay = new RelayServer(relayOptions);\r\n await relay.start(wsPort);\r\n\r\n if (!enableRest) {\r\n return { relay };\r\n }\r\n\r\n if (!jwtSecret) {\r\n await relay.stop();\r\n throw new Error(\r\n 'JWT_SECRET (or AGORA_RELAY_JWT_SECRET) environment variable is required when REST API is enabled'\r\n );\r\n }\r\n\r\n // Expose jwtSecret via env so jwt-auth.ts can read it (it reads AGORA_RELAY_JWT_SECRET)\r\n if (!process.env.AGORA_RELAY_JWT_SECRET) {\r\n process.env.AGORA_RELAY_JWT_SECRET = jwtSecret;\r\n }\r\n\r\n const restPort = options.restPort ?? parseInt(process.env.REST_PORT ?? '3001', 10);\r\n const messageTtlMs = parseInt(process.env.MESSAGE_TTL_MS ?? '86400000', 10);\r\n const messageBuffer = new MessageBuffer({ ttlMs: messageTtlMs });\r\n const restSessions = new Map<string, RestSession>();\r\n\r\n const allowedOrigins = process.env.ALLOWED_ORIGINS ?? '*';\r\n const corsOrigins = allowedOrigins === '*'\r\n ? '*'\r\n : allowedOrigins.split(',').map((o) => o.trim()).filter((o) => o.length > 0);\r\n\r\n const app = express();\r\n app.use(cors({\r\n origin: corsOrigins,\r\n methods: ['GET', 'POST', 'DELETE'],\r\n allowedHeaders: ['Content-Type', 'Authorization'],\r\n }));\r\n app.use(express.json());\r\n\r\n const verifyForRest = (envelope: unknown): { valid: boolean; reason?: string } =>\r\n verifyEnvelope(envelope as Envelope);\r\n\r\n const rateLimitRpm = parseInt(process.env.RATE_LIMIT_RPM ?? '60', 10);\r\n const router = createRestRouter(\r\n relay as Parameters<typeof createRestRouter>[0],\r\n messageBuffer,\r\n restSessions,\r\n createEnvelopeForRest,\r\n verifyForRest,\r\n rateLimitRpm\r\n );\r\n app.use(router);\r\n\r\n app.use((_req, res) => {\r\n res.status(404).json({ error: 'Not found' });\r\n });\r\n\r\n const httpServer = http.createServer(app);\r\n await new Promise<void>((resolve, reject) => {\r\n httpServer.listen(restPort, () => resolve());\r\n httpServer.on('error', reject);\r\n });\r\n\r\n return { relay, httpServer };\r\n}\r\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -25,12 +25,12 @@ import {
|
|
|
25
25
|
sendToPeer,
|
|
26
26
|
sendViaRelay,
|
|
27
27
|
verifyReveal
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-NHTC6OZW.js";
|
|
29
29
|
import {
|
|
30
30
|
RelayServer,
|
|
31
31
|
createEnvelope,
|
|
32
32
|
verifyEnvelope
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-MJGCRX6B.js";
|
|
34
34
|
|
|
35
35
|
// src/cli.ts
|
|
36
36
|
import { parseArgs } from "util";
|
package/dist/index.d.ts
CHANGED
|
@@ -991,7 +991,6 @@ declare class RelayClient extends EventEmitter {
|
|
|
991
991
|
interface BufferedMessage {
|
|
992
992
|
id: string;
|
|
993
993
|
from: string;
|
|
994
|
-
fromName?: string;
|
|
995
994
|
type: string;
|
|
996
995
|
payload: unknown;
|
|
997
996
|
timestamp: number;
|
|
@@ -1035,7 +1034,6 @@ declare class MessageBuffer {
|
|
|
1035
1034
|
*/
|
|
1036
1035
|
interface StoredMessage {
|
|
1037
1036
|
from: string;
|
|
1038
|
-
name?: string;
|
|
1039
1037
|
envelope: object;
|
|
1040
1038
|
}
|
|
1041
1039
|
declare class MessageStore {
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
validateVerificationRecord,
|
|
47
47
|
verifyReveal,
|
|
48
48
|
verifyVerificationSignature
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-NHTC6OZW.js";
|
|
50
50
|
import {
|
|
51
51
|
MessageBuffer,
|
|
52
52
|
createRestRouter,
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
requireAuth,
|
|
55
55
|
revokeToken,
|
|
56
56
|
runRelay
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-OVDMZHTX.js";
|
|
58
58
|
import {
|
|
59
59
|
MessageStore,
|
|
60
60
|
RelayServer,
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
signMessage,
|
|
68
68
|
verifyEnvelope,
|
|
69
69
|
verifySignature
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-MJGCRX6B.js";
|
|
71
71
|
|
|
72
72
|
// src/identity/seen-keys.ts
|
|
73
73
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/identity/keypair.ts","../src/message/envelope.ts","../src/relay/store.ts","../src/relay/server.ts"],"sourcesContent":["import { sign, verify, generateKeyPairSync } from 'node:crypto';\r\n\r\n/**\r\n * Represents an ed25519 key pair for agent identity\r\n */\r\nexport interface KeyPair {\r\n publicKey: string; // hex-encoded\r\n privateKey: string; // hex-encoded\r\n}\r\n\r\n/**\r\n * Generates a new ed25519 key pair\r\n * @returns KeyPair with hex-encoded public and private keys\r\n */\r\nexport function generateKeyPair(): KeyPair {\r\n const { publicKey, privateKey } = generateKeyPairSync('ed25519');\r\n \r\n return {\r\n publicKey: publicKey.export({ type: 'spki', format: 'der' }).toString('hex'),\r\n privateKey: privateKey.export({ type: 'pkcs8', format: 'der' }).toString('hex'),\r\n };\r\n}\r\n\r\n/**\r\n * Signs a message with the private key\r\n * @param message - The message to sign (string or Buffer)\r\n * @param privateKeyHex - The private key in hex format\r\n * @returns Signature as hex string\r\n */\r\nexport function signMessage(message: string | Buffer, privateKeyHex: string): string {\r\n const messageBuffer = typeof message === 'string' ? Buffer.from(message) : message;\r\n const privateKey = Buffer.from(privateKeyHex, 'hex');\r\n \r\n const signature = sign(null, messageBuffer, {\r\n key: privateKey,\r\n format: 'der',\r\n type: 'pkcs8',\r\n });\r\n \r\n return signature.toString('hex');\r\n}\r\n\r\n/**\r\n * Verifies a signature with the public key\r\n * @param message - The original message (string or Buffer)\r\n * @param signatureHex - The signature in hex format\r\n * @param publicKeyHex - The public key in hex format\r\n * @returns true if signature is valid, false otherwise\r\n */\r\nexport function verifySignature(\r\n message: string | Buffer,\r\n signatureHex: string,\r\n publicKeyHex: string\r\n): boolean {\r\n const messageBuffer = typeof message === 'string' ? Buffer.from(message) : message;\r\n const signature = Buffer.from(signatureHex, 'hex');\r\n const publicKey = Buffer.from(publicKeyHex, 'hex');\r\n \r\n try {\r\n return verify(null, messageBuffer, {\r\n key: publicKey,\r\n format: 'der',\r\n type: 'spki',\r\n }, signature);\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Exports a key pair to a JSON-serializable format\r\n * @param keyPair - The key pair to export\r\n * @returns KeyPair object with hex-encoded keys\r\n */\r\nexport function exportKeyPair(keyPair: KeyPair): KeyPair {\r\n return {\r\n publicKey: keyPair.publicKey,\r\n privateKey: keyPair.privateKey,\r\n };\r\n}\r\n\r\n/**\r\n * Imports a key pair from hex strings\r\n * @param publicKeyHex - The public key in hex format\r\n * @param privateKeyHex - The private key in hex format\r\n * @returns KeyPair object\r\n * @throws Error if keys are not valid hex strings\r\n */\r\nexport function importKeyPair(publicKeyHex: string, privateKeyHex: string): KeyPair {\r\n // Validate that keys are valid hex strings\r\n const hexPattern = /^[0-9a-f]+$/i;\r\n if (!hexPattern.test(publicKeyHex)) {\r\n throw new Error('Invalid public key: must be a hex string');\r\n }\r\n if (!hexPattern.test(privateKeyHex)) {\r\n throw new Error('Invalid private key: must be a hex string');\r\n }\r\n \r\n return {\r\n publicKey: publicKeyHex,\r\n privateKey: privateKeyHex,\r\n };\r\n}\r\n","import { createHash } from 'node:crypto';\nimport { signMessage, verifySignature } from '../identity/keypair';\n\n/**\n * Message types on the Agora network.\n * Every piece of data flowing between agents is wrapped in an envelope.\n */\nexport type MessageType =\n | 'announce' // Agent publishes capabilities/state\n | 'discover' // Agent requests peer discovery\n | 'request' // Agent requests a service\n | 'response' // Agent responds to a request\n | 'publish' // Agent publishes knowledge/state\n | 'subscribe' // Agent subscribes to a topic/domain\n | 'verify' // Agent verifies another agent's claim\n | 'ack' // Acknowledgement\n | 'error' // Error response\n | 'paper_discovery' // Agent publishes a discovered academic paper\n | 'peer_list_request' // Request peer list from relay\n | 'peer_list_response' // Relay responds with connected peers\n | 'peer_referral' // Agent recommends another agent\n | 'capability_announce' // Agent publishes capabilities to network\n | 'capability_query' // Agent queries for capabilities\n | 'capability_response' // Response with matching peers\n | 'commit' // Agent commits to a prediction (commit-reveal pattern)\n | 'reveal' // Agent reveals prediction and outcome\n | 'verification' // Agent verifies another agent's output\n | 'revocation' // Agent revokes a prior verification\n | 'reputation_query' // Agent queries for reputation data\n | 'reputation_response'; // Response to reputation query\n\n/**\n * The signed envelope that wraps every message on the network.\n * Content-addressed: the ID is the hash of the canonical payload.\n * Signed: every envelope carries a signature from the sender's private key.\n */\nexport interface Envelope<T = unknown> {\n /** Content-addressed ID: SHA-256 hash of canonical payload */\n id: string;\n /** Message type */\n type: MessageType;\n /** Sender peer ID (full ID) */\n from: string;\n /** Recipient peer IDs (full IDs) */\n to: string[];\n /** Unix timestamp (ms) when the message was created */\n timestamp: number;\n /** Optional: ID of the message this is responding to */\n inReplyTo?: string;\n /** The actual payload */\n payload: T;\n /** ed25519 signature over the canonical form (hex-encoded) */\n signature: string;\n}\n\n/**\n * Deterministic JSON serialization with recursively sorted keys.\n */\nfunction stableStringify(value: unknown): string {\n if (value === null || value === undefined) return JSON.stringify(value);\n if (typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) {\n return '[' + value.map(stableStringify).join(',') + ']';\n }\n const keys = Object.keys(value as Record<string, unknown>).sort();\n const pairs = keys.map(k => JSON.stringify(k) + ':' + stableStringify((value as Record<string, unknown>)[k]));\n return '{' + pairs.join(',') + '}';\n}\n\n/**\n * Canonical form of an envelope for signing/hashing.\n * Deterministic JSON serialization: recursively sorted keys, no whitespace.\n */\nexport function canonicalize(\n type: MessageType,\n from: string,\n to: string[],\n timestamp: number,\n payload: unknown,\n inReplyTo?: string,\n): string {\n const obj: Record<string, unknown> = { from, payload, timestamp, to, type };\n if (inReplyTo !== undefined) {\n obj.inReplyTo = inReplyTo;\n }\n return stableStringify(obj);\n}\n\nfunction normalizeRecipients(from: string, to?: string | string[]): string[] {\n const list = Array.isArray(to) ? to : (typeof to === 'string' ? [to] : [from]);\n const unique = new Set<string>();\n for (const recipient of list) {\n if (typeof recipient === 'string' && recipient.trim().length > 0) {\n unique.add(recipient);\n }\n }\n if (unique.size === 0) {\n unique.add(from);\n }\n return Array.from(unique);\n}\n\n/**\n * Compute the content-addressed ID for a message.\n */\nexport function computeId(canonical: string): string {\n return createHash('sha256').update(canonical).digest('hex');\n}\n\n/**\n * Create a signed envelope.\n * @param type - Message type\n * @param from - Sender's public key (hex)\n * @param privateKey - Sender's private key (hex) for signing\n * @param payload - The message payload\n * @param timestamp - Timestamp for the envelope (ms), defaults to Date.now()\n * @param inReplyTo - Optional ID of the message being replied to\n * @param to - Recipient peer ID(s)\n * @returns A signed Envelope\n */\nexport function createEnvelope<T>(\n type: MessageType,\n from: string,\n privateKey: string,\n payload: T,\n timestamp: number = Date.now(),\n inReplyTo?: string,\n to?: string | string[],\n): Envelope<T> {\n const recipients = normalizeRecipients(from, to);\n const canonical = canonicalize(type, from, recipients, timestamp, payload, inReplyTo);\n const id = computeId(canonical);\n const signature = signMessage(canonical, privateKey);\n\n return {\n id,\n type,\n from,\n to: recipients,\n timestamp,\n ...(inReplyTo !== undefined ? { inReplyTo } : {}),\n payload,\n signature,\n };\n}\n\n/**\n * Verify an envelope's integrity and authenticity.\n * Checks:\n * 1. Canonical form matches the ID (content-addressing)\n * 2. Signature is valid for the sender's public key\n * \n * @returns Object with `valid` boolean and optional `reason` for failure\n */\nexport function verifyEnvelope(envelope: Envelope): { valid: boolean; reason?: string } {\n const { id, type, from, to, timestamp, payload, signature, inReplyTo } = envelope;\n if (!from || !Array.isArray(to) || to.length === 0) {\n return { valid: false, reason: 'invalid_routing_fields' };\n }\n\n // Reconstruct canonical form.\n const canonical = canonicalize(type, from, to, timestamp, payload, inReplyTo);\n\n // Check content-addressed ID\n const expectedId = computeId(canonical);\n if (id !== expectedId) {\n return { valid: false, reason: 'id_mismatch' };\n }\n\n const sigValid = verifySignature(canonical, signature, from);\n if (!sigValid) {\n return { valid: false, reason: 'signature_invalid' };\n }\n\n return { valid: true };\n}\n","/**\r\n * store.ts — File-based message store for offline peers.\r\n * When the relay has storage enabled for certain public keys, messages\r\n * for offline recipients are persisted and delivered when they connect.\r\n */\r\n\r\nimport * as fs from 'node:fs';\r\nimport * as path from 'node:path';\r\n\r\nexport interface StoredMessage {\r\n from: string;\r\n name?: string;\r\n envelope: object;\r\n}\r\n\r\nexport class MessageStore {\r\n private storageDir: string;\r\n\r\n constructor(storageDir: string) {\r\n this.storageDir = storageDir;\r\n fs.mkdirSync(storageDir, { recursive: true });\r\n }\r\n\r\n private recipientDir(publicKey: string): string {\r\n const safe = publicKey.replace(/[^a-zA-Z0-9_-]/g, '_');\r\n return path.join(this.storageDir, safe);\r\n }\r\n\r\n save(recipientKey: string, message: StoredMessage): void {\r\n const dir = this.recipientDir(recipientKey);\r\n fs.mkdirSync(dir, { recursive: true });\r\n const filename = `${Date.now()}-${crypto.randomUUID()}.json`;\r\n fs.writeFileSync(path.join(dir, filename), JSON.stringify(message));\r\n }\r\n\r\n load(recipientKey: string): StoredMessage[] {\r\n const dir = this.recipientDir(recipientKey);\r\n if (!fs.existsSync(dir)) return [];\r\n const files = fs.readdirSync(dir).sort();\r\n const messages: StoredMessage[] = [];\r\n for (const file of files) {\r\n if (!file.endsWith('.json')) continue;\r\n try {\r\n const data = fs.readFileSync(path.join(dir, file), 'utf8');\r\n messages.push(JSON.parse(data) as StoredMessage);\r\n } catch {\r\n // Skip files that cannot be read or parsed\r\n }\r\n }\r\n return messages;\r\n }\r\n\r\n clear(recipientKey: string): void {\r\n const dir = this.recipientDir(recipientKey);\r\n if (!fs.existsSync(dir)) return;\r\n const files = fs.readdirSync(dir);\r\n for (const file of files) {\r\n if (file.endsWith('.json')) {\r\n fs.unlinkSync(path.join(dir, file));\r\n }\r\n }\r\n }\r\n}\r\n","import { EventEmitter } from 'node:events';\r\nimport { WebSocketServer, WebSocket } from 'ws';\r\nimport { verifyEnvelope, createEnvelope, type Envelope } from '../message/envelope';\r\nimport type { PeerListRequestPayload, PeerListResponsePayload } from '../message/types/peer-discovery';\r\nimport { MessageStore } from './store';\r\n\r\ninterface SenderWindow {\r\n count: number;\r\n windowStart: number;\r\n}\r\n\r\nexport interface RelayRateLimitOptions {\r\n enabled?: boolean;\r\n maxMessages?: number;\r\n windowMs?: number;\r\n}\r\n\r\nexport interface RelayEnvelopeDedupOptions {\r\n enabled?: boolean;\r\n maxIds?: number;\r\n}\r\n\r\n/**\r\n * Represents a connected agent in the relay\r\n */\r\ninterface ConnectedAgent {\r\n /** Agent's public key */\r\n publicKey: string;\r\n /** Optional agent name */\r\n name?: string;\r\n /** WebSocket connection */\r\n socket: WebSocket;\r\n /** Last seen timestamp (ms) */\r\n lastSeen: number;\r\n /** Optional metadata */\r\n metadata?: {\r\n version?: string;\r\n capabilities?: string[];\r\n };\r\n}\r\n\r\n/**\r\n * Events emitted by RelayServer\r\n */\r\nexport interface RelayServerEvents {\r\n 'agent-registered': (publicKey: string) => void;\r\n 'agent-disconnected': (publicKey: string) => void;\r\n /** Emitted when a session disconnects (same as agent-disconnected for compatibility) */\r\n 'disconnection': (publicKey: string) => void;\r\n 'message-relayed': (from: string, to: string, envelope: Envelope) => void;\r\n 'error': (error: Error) => void;\r\n}\r\n\r\n/**\r\n * WebSocket relay server for routing messages between agents.\r\n * \r\n * Agents connect to the relay and register with their public key.\r\n * Messages are routed to recipients based on the 'to' field.\r\n * All envelopes are verified before being forwarded.\r\n */\r\nexport interface RelayServerOptions {\r\n /** Optional relay identity for peer_list_request handling */\r\n identity?: { publicKey: string; privateKey: string };\r\n /** Public keys that should have messages stored when offline */\r\n storagePeers?: string[];\r\n /** Directory for persisting messages for storage peers */\r\n storageDir?: string;\r\n /** Maximum number of concurrent registered peers (default: 100) */\r\n maxPeers?: number;\r\n /** Per-sender sliding-window message rate limiting */\r\n rateLimit?: RelayRateLimitOptions;\r\n /** Envelope ID deduplication options */\r\n envelopeDedup?: RelayEnvelopeDedupOptions;\r\n}\r\n\r\nexport class RelayServer extends EventEmitter {\r\n private wss: WebSocketServer | null = null;\r\n /** publicKey -> sessionId -> ConnectedAgent (multiple sessions per key) */\r\n private sessions = new Map<string, Map<string, ConnectedAgent>>();\r\n private identity?: { publicKey: string; privateKey: string };\r\n private storagePeers: string[] = [];\r\n private store: MessageStore | null = null;\r\n private maxPeers: number = 100;\r\n private readonly senderWindows: Map<string, SenderWindow> = new Map();\r\n private static readonly MAX_SENDER_ENTRIES = 500;\r\n private readonly processedEnvelopeIds: Set<string> = new Set();\r\n private rateLimitEnabled = true;\r\n private rateLimitMaxMessages = 10;\r\n private rateLimitWindowMs = 60_000;\r\n private envelopeDedupEnabled = true;\r\n private envelopeDedupMaxIds = 1000;\r\n\r\n constructor(options?: { publicKey: string; privateKey: string } | RelayServerOptions) {\r\n super();\r\n if (options) {\r\n if ('identity' in options && options.identity) {\r\n this.identity = options.identity;\r\n } else if ('publicKey' in options && 'privateKey' in options) {\r\n this.identity = { publicKey: options.publicKey, privateKey: options.privateKey };\r\n }\r\n const opts = options as RelayServerOptions;\r\n if (opts.storagePeers?.length && opts.storageDir) {\r\n this.storagePeers = opts.storagePeers;\r\n this.store = new MessageStore(opts.storageDir);\r\n }\r\n if (opts.maxPeers !== undefined) {\r\n this.maxPeers = opts.maxPeers;\r\n }\r\n if (opts.rateLimit) {\r\n if (opts.rateLimit.enabled !== undefined) {\r\n this.rateLimitEnabled = opts.rateLimit.enabled;\r\n }\r\n if (opts.rateLimit.maxMessages !== undefined && opts.rateLimit.maxMessages > 0) {\r\n this.rateLimitMaxMessages = opts.rateLimit.maxMessages;\r\n }\r\n if (opts.rateLimit.windowMs !== undefined && opts.rateLimit.windowMs > 0) {\r\n this.rateLimitWindowMs = opts.rateLimit.windowMs;\r\n }\r\n }\r\n if (opts.envelopeDedup) {\r\n if (opts.envelopeDedup.enabled !== undefined) {\r\n this.envelopeDedupEnabled = opts.envelopeDedup.enabled;\r\n }\r\n if (opts.envelopeDedup.maxIds !== undefined && opts.envelopeDedup.maxIds > 0) {\r\n this.envelopeDedupMaxIds = opts.envelopeDedup.maxIds;\r\n }\r\n }\r\n }\r\n }\r\n\r\n private isRateLimitedSender(senderPublicKey: string): boolean {\r\n if (!this.rateLimitEnabled) {\r\n return false;\r\n }\r\n\r\n const now = Date.now();\r\n const window = this.senderWindows.get(senderPublicKey);\r\n\r\n if (this.senderWindows.size >= RelayServer.MAX_SENDER_ENTRIES && !window) {\r\n this.evictOldestSenderWindow();\r\n }\r\n\r\n if (!window || (now - window.windowStart) > this.rateLimitWindowMs) {\r\n this.senderWindows.set(senderPublicKey, { count: 1, windowStart: now });\r\n return false;\r\n }\r\n\r\n window.count++;\r\n return window.count > this.rateLimitMaxMessages;\r\n }\r\n\r\n private evictOldestSenderWindow(): void {\r\n let oldestKey: string | null = null;\r\n let oldestTime = Infinity;\r\n\r\n for (const [key, window] of this.senderWindows.entries()) {\r\n if (window.windowStart < oldestTime) {\r\n oldestTime = window.windowStart;\r\n oldestKey = key;\r\n }\r\n }\r\n\r\n if (oldestKey !== null) {\r\n this.senderWindows.delete(oldestKey);\r\n }\r\n }\r\n\r\n private isDuplicateEnvelopeId(envelopeId: string): boolean {\r\n if (!this.envelopeDedupEnabled) {\r\n return false;\r\n }\r\n\r\n if (this.processedEnvelopeIds.has(envelopeId)) {\r\n return true;\r\n }\r\n\r\n this.processedEnvelopeIds.add(envelopeId);\r\n if (this.processedEnvelopeIds.size > this.envelopeDedupMaxIds) {\r\n const oldest = this.processedEnvelopeIds.values().next().value;\r\n if (oldest !== undefined) {\r\n this.processedEnvelopeIds.delete(oldest);\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Start the relay server\r\n * @param port - Port to listen on\r\n * @param host - Optional host (default: all interfaces)\r\n */\r\n start(port: number, host?: string): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n try {\r\n this.wss = new WebSocketServer({ port, host: host ?? '0.0.0.0' });\r\n let resolved = false;\r\n\r\n this.wss.on('error', (error) => {\r\n this.emit('error', error);\r\n if (!resolved) {\r\n resolved = true;\r\n reject(error);\r\n }\r\n });\r\n\r\n this.wss.on('listening', () => {\r\n if (!resolved) {\r\n resolved = true;\r\n resolve();\r\n }\r\n });\r\n\r\n this.wss.on('connection', (socket: WebSocket) => {\r\n this.handleConnection(socket);\r\n });\r\n } catch (error) {\r\n reject(error);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Stop the relay server\r\n */\r\n async stop(): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n if (!this.wss) {\r\n resolve();\r\n return;\r\n }\r\n\r\n // Close all agent connections (all sessions)\r\n for (const sessionMap of this.sessions.values()) {\r\n for (const agent of sessionMap.values()) {\r\n agent.socket.close();\r\n }\r\n }\r\n this.sessions.clear();\r\n\r\n this.wss.close((err) => {\r\n if (err) {\r\n reject(err);\r\n } else {\r\n this.wss = null;\r\n resolve();\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Get one connected agent per public key (first session). For backward compatibility.\r\n */\r\n getAgents(): Map<string, ConnectedAgent> {\r\n const out = new Map<string, ConnectedAgent>();\r\n for (const [key, sessionMap] of this.sessions) {\r\n const first = sessionMap.values().next().value;\r\n if (first) out.set(key, first);\r\n }\r\n return out;\r\n }\r\n\r\n /**\r\n * Handle incoming connection\r\n */\r\n private handleConnection(socket: WebSocket): void {\r\n let agentPublicKey: string | null = null;\r\n let sessionId: string | null = null;\r\n\r\n socket.on('message', (data: Buffer) => {\r\n try {\r\n const msg = JSON.parse(data.toString());\r\n\r\n // Handle registration\r\n if (msg.type === 'register' && !agentPublicKey) {\r\n if (!msg.publicKey || typeof msg.publicKey !== 'string') {\r\n this.sendError(socket, 'Invalid registration: missing or invalid publicKey');\r\n socket.close();\r\n return;\r\n }\r\n\r\n const publicKey = msg.publicKey;\r\n const name = msg.name;\r\n agentPublicKey = publicKey;\r\n sessionId = crypto.randomUUID();\r\n\r\n // Allow multiple sessions per publicKey; only enforce max unique peers\r\n if (!this.sessions.has(publicKey) && this.sessions.size >= this.maxPeers) {\r\n this.sendError(socket, `Relay is at capacity (max ${this.maxPeers} peers)`);\r\n socket.close();\r\n return;\r\n }\r\n\r\n const agent: ConnectedAgent = {\r\n publicKey,\r\n name,\r\n socket,\r\n lastSeen: Date.now(),\r\n };\r\n\r\n if (!this.sessions.has(publicKey)) {\r\n this.sessions.set(publicKey, new Map());\r\n }\r\n this.sessions.get(publicKey)!.set(sessionId, agent);\r\n const isFirstSession = this.sessions.get(publicKey)!.size === 1;\r\n\r\n this.emit('agent-registered', publicKey);\r\n\r\n // Build peers list: one entry per connected publicKey + storage peers\r\n const peers: Array<{ publicKey: string; name?: string }> = [];\r\n for (const [key, sessionMap] of this.sessions) {\r\n if (key === publicKey) continue;\r\n const firstAgent = sessionMap.values().next().value;\r\n peers.push({ publicKey: key, name: firstAgent?.name });\r\n }\r\n for (const storagePeer of this.storagePeers) {\r\n if (storagePeer !== publicKey && !this.sessions.has(storagePeer)) {\r\n peers.push({ publicKey: storagePeer, name: undefined });\r\n }\r\n }\r\n\r\n socket.send(JSON.stringify({\r\n type: 'registered',\r\n publicKey,\r\n sessionId,\r\n peers,\r\n }));\r\n\r\n // Notify other agents only when this is the first session for this peer\r\n if (isFirstSession) {\r\n this.broadcastPeerEvent('peer_online', publicKey, name);\r\n }\r\n\r\n // Deliver any stored messages for this peer\r\n if (this.store && this.storagePeers.includes(publicKey)) {\r\n const queued = this.store.load(publicKey);\r\n for (const stored of queued) {\r\n socket.send(JSON.stringify({\r\n type: 'message',\r\n from: stored.from,\r\n name: stored.name,\r\n envelope: stored.envelope,\r\n }));\r\n }\r\n this.store.clear(publicKey);\r\n }\r\n return;\r\n }\r\n\r\n // Require registration before processing messages\r\n if (!agentPublicKey) {\r\n this.sendError(socket, 'Not registered: send registration message first');\r\n socket.close();\r\n return;\r\n }\r\n\r\n // Handle message relay\r\n if (msg.type === 'message') {\r\n if (!msg.to || typeof msg.to !== 'string') {\r\n this.sendError(socket, 'Invalid message: missing or invalid \"to\" field');\r\n return;\r\n }\r\n\r\n if (!msg.envelope || typeof msg.envelope !== 'object') {\r\n this.sendError(socket, 'Invalid message: missing or invalid \"envelope\" field');\r\n return;\r\n }\r\n\r\n const envelope = msg.envelope as Envelope;\r\n\r\n // Verify envelope signature\r\n const verification = verifyEnvelope(envelope);\r\n if (!verification.valid) {\r\n this.sendError(socket, `Invalid envelope: ${verification.reason || 'verification failed'}`);\r\n return;\r\n }\r\n\r\n // Verify sender matches registered agent\r\n const envelopeFrom = envelope.from;\r\n if (envelopeFrom !== agentPublicKey) {\r\n this.sendError(socket, 'Envelope sender does not match registered public key');\r\n return;\r\n }\r\n\r\n // Strict p2p routing: envelope.to must include the relay transport recipient.\r\n if (!Array.isArray(envelope.to) || envelope.to.length === 0 || !envelope.to.includes(msg.to)) {\r\n this.sendError(socket, 'Envelope recipients do not include requested relay recipient');\r\n return;\r\n }\r\n\r\n if (this.isRateLimitedSender(agentPublicKey)) {\r\n return;\r\n }\r\n\r\n if (this.isDuplicateEnvelopeId(envelope.id)) {\r\n return;\r\n }\r\n\r\n // Update lastSeen for any session of sender\r\n const senderSessionMap = this.sessions.get(agentPublicKey);\r\n if (senderSessionMap) {\r\n for (const a of senderSessionMap.values()) {\r\n a.lastSeen = Date.now();\r\n }\r\n }\r\n\r\n // Handle peer_list_request directed at relay\r\n if (envelope.type === 'peer_list_request' && this.identity && msg.to === this.identity.publicKey) {\r\n this.handlePeerListRequest(envelope as Envelope<PeerListRequestPayload>, socket, agentPublicKey);\r\n return;\r\n }\r\n\r\n // Find all recipient sessions\r\n const recipientSessionMap = this.sessions.get(msg.to);\r\n const openRecipients = recipientSessionMap\r\n ? Array.from(recipientSessionMap.values()).filter(a => a.socket.readyState === WebSocket.OPEN)\r\n : [];\r\n if (openRecipients.length === 0) {\r\n // If recipient is a storage peer, queue the message\r\n if (this.store && this.storagePeers.includes(msg.to)) {\r\n const senderSessionMap = this.sessions.get(agentPublicKey);\r\n const senderAgent = senderSessionMap?.values().next().value;\r\n this.store.save(msg.to, {\r\n from: agentPublicKey,\r\n name: senderAgent?.name,\r\n envelope,\r\n });\r\n this.emit('message-relayed', agentPublicKey, msg.to, envelope);\r\n } else {\r\n this.sendError(socket, 'Recipient not connected', 'unknown_recipient');\r\n }\r\n return;\r\n }\r\n\r\n // Forward envelope to all sessions of the recipient\r\n try {\r\n const senderSessionMap = this.sessions.get(agentPublicKey);\r\n const senderAgent = senderSessionMap?.values().next().value;\r\n const relayMessage = {\r\n type: 'message',\r\n from: agentPublicKey,\r\n name: senderAgent?.name,\r\n envelope,\r\n };\r\n const messageStr = JSON.stringify(relayMessage);\r\n for (const recipient of openRecipients) {\r\n recipient.socket.send(messageStr);\r\n }\r\n this.emit('message-relayed', agentPublicKey, msg.to, envelope);\r\n } catch (err) {\r\n this.sendError(socket, 'Failed to relay message');\r\n this.emit('error', err as Error);\r\n }\r\n return;\r\n }\r\n\r\n // Handle ping\r\n if (msg.type === 'ping') {\r\n socket.send(JSON.stringify({ type: 'pong' }));\r\n return;\r\n }\r\n\r\n // Unknown message type\r\n this.sendError(socket, `Unknown message type: ${msg.type}`);\r\n } catch (err) {\r\n // Invalid JSON or other parsing errors\r\n this.emit('error', new Error(`Message parsing failed: ${err instanceof Error ? err.message : String(err)}`));\r\n this.sendError(socket, 'Invalid message format');\r\n }\r\n });\r\n\r\n socket.on('close', () => {\r\n if (agentPublicKey && sessionId) {\r\n const sessionMap = this.sessions.get(agentPublicKey);\r\n if (sessionMap) {\r\n const agent = sessionMap.get(sessionId);\r\n const agentName = agent?.name;\r\n sessionMap.delete(sessionId);\r\n if (sessionMap.size === 0) {\r\n this.sessions.delete(agentPublicKey);\r\n this.emit('agent-disconnected', agentPublicKey);\r\n this.emit('disconnection', agentPublicKey);\r\n // Storage-enabled peers are always considered connected; skip peer_offline for them\r\n if (!this.storagePeers.includes(agentPublicKey)) {\r\n this.broadcastPeerEvent('peer_offline', agentPublicKey, agentName);\r\n }\r\n }\r\n }\r\n }\r\n });\r\n\r\n socket.on('error', (error) => {\r\n this.emit('error', error);\r\n });\r\n }\r\n\r\n /**\r\n * Send an error message to a client\r\n */\r\n private sendError(socket: WebSocket, message: string, code?: string): void {\r\n try {\r\n if (socket.readyState === WebSocket.OPEN) {\r\n const payload: { type: 'error'; message: string; code?: string } = { type: 'error', message };\r\n if (code) payload.code = code;\r\n socket.send(JSON.stringify(payload));\r\n }\r\n } catch (err) {\r\n // Log errors when sending error messages, but don't propagate to avoid cascading failures\r\n this.emit('error', new Error(`Failed to send error message: ${err instanceof Error ? err.message : String(err)}`));\r\n }\r\n }\r\n\r\n /**\r\n * Broadcast a peer event to all connected agents (all sessions except the one for publicKey)\r\n */\r\n private broadcastPeerEvent(eventType: 'peer_online' | 'peer_offline', publicKey: string, name?: string): void {\r\n const message = {\r\n type: eventType,\r\n publicKey,\r\n name,\r\n };\r\n const messageStr = JSON.stringify(message);\r\n\r\n for (const [key, sessionMap] of this.sessions) {\r\n if (key === publicKey) continue;\r\n for (const agent of sessionMap.values()) {\r\n if (agent.socket.readyState === WebSocket.OPEN) {\r\n try {\r\n agent.socket.send(messageStr);\r\n } catch (err) {\r\n this.emit('error', new Error(`Failed to send ${eventType} event: ${err instanceof Error ? err.message : String(err)}`));\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Handle peer list request from an agent\r\n */\r\n private handlePeerListRequest(envelope: Envelope<PeerListRequestPayload>, socket: WebSocket, requesterPublicKey: string): void {\r\n if (!this.identity) {\r\n this.sendError(socket, 'Relay does not support peer discovery (no identity configured)');\r\n return;\r\n }\r\n\r\n const { filters } = envelope.payload;\r\n const now = Date.now();\r\n\r\n // One entry per publicKey (first session for lastSeen/metadata)\r\n const peersList: ConnectedAgent[] = [];\r\n for (const [key, sessionMap] of this.sessions) {\r\n if (key === requesterPublicKey) continue;\r\n const first = sessionMap.values().next().value;\r\n if (first) peersList.push(first);\r\n }\r\n\r\n let peers = peersList;\r\n\r\n // Apply filters\r\n if (filters?.activeWithin) {\r\n peers = peers.filter(p => (now - p.lastSeen) < filters.activeWithin!);\r\n }\r\n\r\n if (filters?.limit && filters.limit > 0) {\r\n peers = peers.slice(0, filters.limit);\r\n }\r\n\r\n // Build response payload\r\n const response: PeerListResponsePayload = {\r\n peers: peers.map(p => ({\r\n publicKey: p.publicKey,\r\n metadata: p.name || p.metadata ? {\r\n name: p.name,\r\n version: p.metadata?.version,\r\n capabilities: p.metadata?.capabilities,\r\n } : undefined,\r\n lastSeen: p.lastSeen,\r\n })),\r\n totalPeers: this.sessions.size - (this.sessions.has(requesterPublicKey) ? 1 : 0),\r\n relayPublicKey: this.identity.publicKey,\r\n };\r\n\r\n // Create signed envelope\r\n const responseEnvelope = createEnvelope(\r\n 'peer_list_response',\r\n this.identity.publicKey,\r\n this.identity.privateKey,\r\n response,\r\n Date.now(),\r\n envelope.id, // Reply to the request\r\n [requesterPublicKey]\r\n );\r\n\r\n // Send response\r\n const relayMessage = {\r\n type: 'message',\r\n from: this.identity.publicKey,\r\n name: 'relay',\r\n envelope: responseEnvelope,\r\n };\r\n\r\n try {\r\n socket.send(JSON.stringify(relayMessage));\r\n } catch (err) {\r\n this.emit('error', new Error(`Failed to send peer list response: ${err instanceof Error ? err.message : String(err)}`));\r\n }\r\n }\r\n}\r\n"],"mappings":"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","names":["senderSessionMap"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/relay/message-buffer.ts","../src/relay/jwt-auth.ts","../src/relay/rest-api.ts","../src/relay/run-relay.ts"],"sourcesContent":["/**\r\n * message-buffer.ts — In-memory bounded message queue per agent.\r\n *\r\n * When messages are delivered to an agent via the relay, they are also\r\n * stored here so that HTTP polling clients can retrieve them via GET /v1/messages.\r\n */\r\n\r\nexport interface BufferedMessage {\r\n id: string;\r\n from: string;\r\n fromName?: string;\r\n type: string;\r\n payload: unknown;\r\n timestamp: number;\r\n inReplyTo?: string;\r\n}\r\n\r\ninterface StoredMessage {\r\n message: BufferedMessage;\r\n receivedAt: number;\r\n}\r\n\r\nconst MAX_MESSAGES_PER_AGENT = 100;\r\n\r\n/**\r\n * MessageBuffer stores inbound messages per agent public key.\r\n * FIFO eviction when the buffer is full (max 100 messages).\r\n * Messages older than ttlMs (measured from when they were received) are pruned on access.\r\n */\r\nexport class MessageBuffer {\r\n private buffers: Map<string, StoredMessage[]> = new Map();\r\n private ttlMs: number;\r\n\r\n constructor(options?: { ttlMs?: number }) {\r\n this.ttlMs = options?.ttlMs ?? 86400000; // default 24h\r\n }\r\n\r\n /**\r\n * Add a message to an agent's buffer.\r\n * Evicts the oldest message if the buffer is full.\r\n */\r\n add(publicKey: string, message: BufferedMessage): void {\r\n let queue = this.buffers.get(publicKey);\r\n if (!queue) {\r\n queue = [];\r\n this.buffers.set(publicKey, queue);\r\n }\r\n queue.push({ message, receivedAt: Date.now() });\r\n if (queue.length > MAX_MESSAGES_PER_AGENT) {\r\n queue.shift(); // FIFO eviction\r\n }\r\n }\r\n\r\n /**\r\n * Retrieve messages for an agent, optionally filtering by `since` timestamp.\r\n * Returns messages with timestamp > since (exclusive). Prunes expired messages.\r\n */\r\n get(publicKey: string, since?: number): BufferedMessage[] {\r\n const now = Date.now();\r\n let queue = this.buffers.get(publicKey) ?? [];\r\n // Prune messages older than ttlMs (based on wall-clock receive time)\r\n queue = queue.filter((s) => now - s.receivedAt < this.ttlMs);\r\n this.buffers.set(publicKey, queue);\r\n const messages = queue.map((s) => s.message);\r\n if (since === undefined) {\r\n return [...messages];\r\n }\r\n return messages.filter((m) => m.timestamp > since);\r\n }\r\n\r\n /**\r\n * Clear all messages for an agent (after polling without `since`).\r\n */\r\n clear(publicKey: string): void {\r\n this.buffers.set(publicKey, []);\r\n }\r\n\r\n /**\r\n * Remove all state for a disconnected agent.\r\n */\r\n delete(publicKey: string): void {\r\n this.buffers.delete(publicKey);\r\n }\r\n}\r\n","/**\r\n * jwt-auth.ts — JWT token creation and validation middleware.\r\n *\r\n * Tokens are signed with AGORA_RELAY_JWT_SECRET (required env var).\r\n * Expiry defaults to 3600 seconds (1 hour), configurable via AGORA_JWT_EXPIRY_SECONDS.\r\n *\r\n * Token payload: { publicKey, name }\r\n */\r\n\r\nimport jwt from 'jsonwebtoken';\r\nimport { randomBytes } from 'node:crypto';\r\nimport type { Request, Response, NextFunction } from 'express';\r\n\r\nexport interface JwtPayload {\r\n publicKey: string;\r\n name?: string;\r\n}\r\n\r\n/**\r\n * Augment Express Request to carry decoded JWT payload.\r\n */\r\nexport interface AuthenticatedRequest extends Request {\r\n agent?: JwtPayload;\r\n}\r\n\r\n/**\r\n * Revocation set for invalidated tokens (populated by DELETE /v1/disconnect).\r\n * Stored as a Map of JWT `jti` → expiry timestamp (ms).\r\n * Entries are automatically removed once their JWT would have expired anyway,\r\n * preventing unbounded memory growth.\r\n */\r\nconst revokedJtis: Map<string, number> = new Map();\r\n\r\n/**\r\n * Remove revoked JTI entries whose token expiry has already passed.\r\n * These tokens can no longer be used regardless, so no need to keep them.\r\n */\r\nfunction pruneExpiredRevocations(): void {\r\n const now = Date.now();\r\n for (const [jti, expiry] of revokedJtis) {\r\n if (expiry <= now) {\r\n revokedJtis.delete(jti);\r\n }\r\n }\r\n}\r\n\r\nfunction getJwtSecret(): string {\r\n const secret = process.env.AGORA_RELAY_JWT_SECRET;\r\n if (!secret) {\r\n throw new Error(\r\n 'AGORA_RELAY_JWT_SECRET environment variable is required but not set'\r\n );\r\n }\r\n return secret;\r\n}\r\n\r\nfunction getExpirySeconds(): number {\r\n const raw = process.env.AGORA_JWT_EXPIRY_SECONDS;\r\n if (raw) {\r\n const parsed = parseInt(raw, 10);\r\n if (!isNaN(parsed) && parsed > 0) {\r\n return parsed;\r\n }\r\n }\r\n return 3600; // 1 hour default\r\n}\r\n\r\n/**\r\n * Create a signed JWT for a registered agent.\r\n * Returns the token string and its expiry timestamp (ms since epoch).\r\n */\r\nexport function createToken(payload: JwtPayload): {\r\n token: string;\r\n expiresAt: number;\r\n} {\r\n const secret = getJwtSecret();\r\n const expirySeconds = getExpirySeconds();\r\n const jti = `${Date.now()}-${randomBytes(16).toString('hex')}`;\r\n\r\n const token = jwt.sign(\r\n { publicKey: payload.publicKey, name: payload.name, jti },\r\n secret,\r\n { expiresIn: expirySeconds }\r\n );\r\n\r\n const expiresAt = Date.now() + expirySeconds * 1000;\r\n return { token, expiresAt };\r\n}\r\n\r\n/**\r\n * Revoke a token by its jti claim so it cannot be used again.\r\n * The revocation entry is stored with the token's expiry so it can be\r\n * pruned automatically once the token would no longer be valid anyway.\r\n */\r\nexport function revokeToken(token: string): void {\r\n try {\r\n const secret = getJwtSecret();\r\n const decoded = jwt.verify(token, secret) as jwt.JwtPayload & {\r\n jti?: string;\r\n exp?: number;\r\n };\r\n if (decoded.jti) {\r\n const expiry = decoded.exp ? decoded.exp * 1000 : Date.now();\r\n revokedJtis.set(decoded.jti, expiry);\r\n pruneExpiredRevocations();\r\n }\r\n } catch {\r\n // Token already invalid — nothing to revoke\r\n }\r\n}\r\n\r\n/**\r\n * Express middleware that validates the Authorization: Bearer <token> header.\r\n * Attaches decoded payload to `req.agent` on success.\r\n * Responds with 401 if missing/invalid/expired/revoked.\r\n */\r\nexport function requireAuth(\r\n req: AuthenticatedRequest,\r\n res: Response,\r\n next: NextFunction\r\n): void {\r\n const authHeader = req.headers.authorization;\r\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\r\n res.status(401).json({ error: 'Missing or malformed Authorization header' });\r\n return;\r\n }\r\n\r\n const token = authHeader.slice(7);\r\n try {\r\n const secret = getJwtSecret();\r\n const decoded = jwt.verify(token, secret) as jwt.JwtPayload & {\r\n publicKey: string;\r\n name?: string;\r\n jti?: string;\r\n };\r\n\r\n if (decoded.jti && revokedJtis.has(decoded.jti)) {\r\n res.status(401).json({ error: 'Token has been revoked' });\r\n return;\r\n }\r\n\r\n req.agent = { publicKey: decoded.publicKey, name: decoded.name };\r\n next();\r\n } catch (err) {\r\n if (err instanceof jwt.TokenExpiredError) {\r\n res.status(401).json({ error: 'Token expired' });\r\n } else {\r\n res.status(401).json({ error: 'Invalid token' });\r\n }\r\n }\r\n}\r\n","/**\r\n * rest-api.ts — Express router implementing the Agora relay REST API.\r\n *\r\n * Endpoints:\r\n * POST /v1/register — Register agent, obtain JWT session token\r\n * POST /v1/send — Send message to a peer (requires auth)\r\n * GET /v1/peers — List online peers (requires auth)\r\n * GET /v1/messages — Poll for new inbound messages (requires auth)\r\n * DELETE /v1/disconnect — Invalidate token and disconnect (requires auth)\r\n */\r\n\r\nimport { Router } from 'express';\r\nimport type { Request, Response } from 'express';\r\nimport { rateLimit } from 'express-rate-limit';\r\nimport {\r\n createToken,\r\n revokeToken,\r\n requireAuth,\r\n type AuthenticatedRequest,\r\n} from './jwt-auth';\r\nimport { MessageBuffer, type BufferedMessage } from './message-buffer';\r\n\r\nconst apiRateLimit = (rpm: number): ReturnType<typeof rateLimit> => rateLimit({\r\n windowMs: 60_000,\r\n limit: rpm,\r\n standardHeaders: 'draft-7',\r\n legacyHeaders: false,\r\n message: { error: 'Too many requests — try again later' },\r\n});\r\n\r\n/**\r\n * A session for a REST-connected agent.\r\n * privateKey is held only in memory and never logged or persisted.\r\n */\r\nexport interface RestSession {\r\n publicKey: string;\r\n privateKey: string;\r\n name?: string;\r\n metadata?: { version?: string; capabilities?: string[] };\r\n registeredAt: number;\r\n expiresAt: number;\r\n token: string;\r\n}\r\n\r\nfunction pruneExpiredSessions(\r\n sessions: Map<string, RestSession>,\r\n buffer: MessageBuffer\r\n): void {\r\n const now = Date.now();\r\n for (const [publicKey, session] of sessions) {\r\n if (session.expiresAt <= now) {\r\n sessions.delete(publicKey);\r\n buffer.delete(publicKey);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Minimal interface for the relay server that the REST API depends on.\r\n */\r\nexport interface RelayInterface {\r\n getAgents(): Map<\r\n string,\r\n {\r\n publicKey: string;\r\n name?: string;\r\n lastSeen: number;\r\n metadata?: { version?: string; capabilities?: string[] };\r\n socket: unknown;\r\n }\r\n >;\r\n on(\r\n event: 'message-relayed',\r\n handler: (from: string, to: string, envelope: unknown) => void\r\n ): void;\r\n}\r\n\r\n/**\r\n * Envelope creation function interface (matches createEnvelope from message/envelope).\r\n */\r\nexport type CreateEnvelopeFn = (\r\n type: string,\r\n from: string,\r\n to: string[],\r\n privateKey: string,\r\n payload: unknown,\r\n timestamp?: number,\r\n inReplyTo?: string\r\n) => {\r\n id: string;\r\n type: string;\r\n from: string;\r\n to: string[];\r\n timestamp: number;\r\n payload: unknown;\r\n signature: string;\r\n inReplyTo?: string;\r\n};\r\n\r\n/**\r\n * Envelope verification function interface.\r\n */\r\nexport type VerifyEnvelopeFn = (envelope: unknown) => {\r\n valid: boolean;\r\n reason?: string;\r\n};\r\n\r\n/**\r\n * Create the REST API router.\r\n */\r\nexport function createRestRouter(\r\n relay: RelayInterface,\r\n buffer: MessageBuffer,\r\n sessions: Map<string, RestSession>,\r\n createEnv: CreateEnvelopeFn,\r\n verifyEnv: VerifyEnvelopeFn,\r\n rateLimitRpm = 60\r\n): Router {\r\n const router = Router();\r\n router.use(apiRateLimit(rateLimitRpm));\r\n\r\n relay.on('message-relayed', (from, to, envelope) => {\r\n if (!sessions.has(to)) return;\r\n const agentMap = relay.getAgents();\r\n const senderAgent = agentMap.get(from);\r\n const env = envelope as {\r\n id: string;\r\n type: string;\r\n payload: unknown;\r\n timestamp: number;\r\n inReplyTo?: string;\r\n };\r\n const msg: BufferedMessage = {\r\n id: env.id,\r\n from,\r\n fromName: senderAgent?.name,\r\n type: env.type,\r\n payload: env.payload,\r\n timestamp: env.timestamp,\r\n inReplyTo: env.inReplyTo,\r\n };\r\n buffer.add(to, msg);\r\n });\r\n\r\n router.post('/v1/register', async (req: Request, res: Response) => {\r\n const { publicKey, privateKey, name, metadata } = req.body as {\r\n publicKey?: string;\r\n privateKey?: string;\r\n name?: string;\r\n metadata?: { version?: string; capabilities?: string[] };\r\n };\r\n\r\n if (!publicKey || typeof publicKey !== 'string') {\r\n res.status(400).json({ error: 'publicKey is required' });\r\n return;\r\n }\r\n if (!privateKey || typeof privateKey !== 'string') {\r\n res.status(400).json({ error: 'privateKey is required' });\r\n return;\r\n }\r\n\r\n const testEnvelope = createEnv(\r\n 'announce',\r\n publicKey,\r\n [publicKey],\r\n privateKey,\r\n { challenge: 'register' },\r\n Date.now()\r\n );\r\n const verification = verifyEnv(testEnvelope);\r\n if (!verification.valid) {\r\n res\r\n .status(400)\r\n .json({ error: 'Key pair verification failed: ' + verification.reason });\r\n return;\r\n }\r\n\r\n const { token, expiresAt } = createToken({ publicKey, name });\r\n pruneExpiredSessions(sessions, buffer);\r\n\r\n const session: RestSession = {\r\n publicKey,\r\n privateKey,\r\n name,\r\n metadata,\r\n registeredAt: Date.now(),\r\n expiresAt,\r\n token,\r\n };\r\n sessions.set(publicKey, session);\r\n\r\n const wsAgents = relay.getAgents();\r\n const peers: Array<{ publicKey: string; name?: string; lastSeen: number }> = [];\r\n for (const agent of wsAgents.values()) {\r\n if (agent.publicKey !== publicKey) {\r\n peers.push({\r\n publicKey: agent.publicKey,\r\n name: agent.name,\r\n lastSeen: agent.lastSeen,\r\n });\r\n }\r\n }\r\n for (const s of sessions.values()) {\r\n if (s.publicKey !== publicKey && !wsAgents.has(s.publicKey)) {\r\n peers.push({\r\n publicKey: s.publicKey,\r\n name: s.name,\r\n lastSeen: s.registeredAt,\r\n });\r\n }\r\n }\r\n\r\n res.json({ token, expiresAt, peers });\r\n });\r\n\r\n router.post(\r\n '/v1/send',\r\n requireAuth,\r\n async (req: AuthenticatedRequest, res: Response) => {\r\n const { to, type, payload, inReplyTo } = req.body as {\r\n to?: string;\r\n type?: string;\r\n payload?: unknown;\r\n inReplyTo?: string;\r\n };\r\n\r\n if (!to || typeof to !== 'string') {\r\n res.status(400).json({ error: 'to is required' });\r\n return;\r\n }\r\n if (!type || typeof type !== 'string') {\r\n res.status(400).json({ error: 'type is required' });\r\n return;\r\n }\r\n if (payload === undefined) {\r\n res.status(400).json({ error: 'payload is required' });\r\n return;\r\n }\r\n\r\n const senderPublicKey = req.agent!.publicKey;\r\n const session = sessions.get(senderPublicKey);\r\n if (!session) {\r\n res.status(401).json({ error: 'Session not found — please re-register' });\r\n return;\r\n }\r\n\r\n const envelope = createEnv(\r\n type,\r\n senderPublicKey,\r\n [to],\r\n session.privateKey,\r\n payload,\r\n Date.now(),\r\n inReplyTo\r\n );\r\n\r\n const wsAgents = relay.getAgents();\r\n const wsRecipient = wsAgents.get(to);\r\n if (wsRecipient && wsRecipient.socket) {\r\n const ws = wsRecipient.socket as { readyState: number; send(data: string): void };\r\n const OPEN = 1;\r\n if (ws.readyState !== OPEN) {\r\n res.status(503).json({ error: 'Recipient connection is not open' });\r\n return;\r\n }\r\n try {\r\n const relayMsg = JSON.stringify({\r\n type: 'message',\r\n from: senderPublicKey,\r\n name: session.name,\r\n envelope,\r\n });\r\n ws.send(relayMsg);\r\n res.json({ ok: true, envelopeId: envelope.id });\r\n return;\r\n } catch (err) {\r\n res.status(500).json({\r\n error:\r\n 'Failed to deliver message: ' +\r\n (err instanceof Error ? err.message : String(err)),\r\n });\r\n return;\r\n }\r\n }\r\n\r\n const restRecipient = sessions.get(to);\r\n if (restRecipient) {\r\n const senderAgent = wsAgents.get(senderPublicKey);\r\n const msg: BufferedMessage = {\r\n id: envelope.id,\r\n from: senderPublicKey,\r\n fromName: session.name ?? senderAgent?.name,\r\n type: envelope.type,\r\n payload: envelope.payload,\r\n timestamp: envelope.timestamp,\r\n inReplyTo: envelope.inReplyTo,\r\n };\r\n buffer.add(to, msg);\r\n res.json({ ok: true, envelopeId: envelope.id });\r\n return;\r\n }\r\n\r\n res.status(404).json({ error: 'Recipient not connected' });\r\n }\r\n );\r\n\r\n router.get(\r\n '/v1/peers',\r\n requireAuth,\r\n (req: AuthenticatedRequest, res: Response) => {\r\n const callerPublicKey = req.agent!.publicKey;\r\n const wsAgents = relay.getAgents();\r\n const peerList: Array<{\r\n publicKey: string;\r\n name?: string;\r\n lastSeen: number;\r\n metadata?: { version?: string; capabilities?: string[] };\r\n }> = [];\r\n\r\n for (const agent of wsAgents.values()) {\r\n if (agent.publicKey !== callerPublicKey) {\r\n peerList.push({\r\n publicKey: agent.publicKey,\r\n name: agent.name,\r\n lastSeen: agent.lastSeen,\r\n metadata: agent.metadata,\r\n });\r\n }\r\n }\r\n\r\n for (const s of sessions.values()) {\r\n if (s.publicKey !== callerPublicKey && !wsAgents.has(s.publicKey)) {\r\n peerList.push({\r\n publicKey: s.publicKey,\r\n name: s.name,\r\n lastSeen: s.registeredAt,\r\n metadata: s.metadata,\r\n });\r\n }\r\n }\r\n\r\n res.json({ peers: peerList });\r\n }\r\n );\r\n\r\n router.get(\r\n '/v1/messages',\r\n requireAuth,\r\n (req: AuthenticatedRequest, res: Response) => {\r\n const publicKey = req.agent!.publicKey;\r\n const sinceRaw = req.query.since as string | undefined;\r\n const limitRaw = req.query.limit as string | undefined;\r\n\r\n const since = sinceRaw ? parseInt(sinceRaw, 10) : undefined;\r\n const limit = Math.min(limitRaw ? parseInt(limitRaw, 10) : 50, 100);\r\n\r\n let messages = buffer.get(publicKey, since);\r\n const hasMore = messages.length > limit;\r\n if (hasMore) {\r\n messages = messages.slice(0, limit);\r\n }\r\n\r\n if (since === undefined) {\r\n buffer.clear(publicKey);\r\n }\r\n\r\n res.json({ messages, hasMore });\r\n }\r\n );\r\n\r\n router.delete(\r\n '/v1/disconnect',\r\n requireAuth,\r\n (req: AuthenticatedRequest, res: Response) => {\r\n const publicKey = req.agent!.publicKey;\r\n const authHeader = req.headers.authorization!;\r\n const token = authHeader.slice(7);\r\n\r\n revokeToken(token);\r\n sessions.delete(publicKey);\r\n buffer.delete(publicKey);\r\n\r\n res.json({ ok: true });\r\n }\r\n );\r\n\r\n return router;\r\n}\r\n","/**\r\n * run-relay.ts — Start Agora relay: WebSocket server and optional REST API.\r\n *\r\n * When REST is enabled, starts:\r\n * 1. WebSocket relay (RelayServer) on wsPort\r\n * 2. REST API server (Express) on restPort\r\n *\r\n * Environment variables:\r\n * RELAY_PORT — WebSocket relay port (default: 3002); alias: PORT\r\n * REST_PORT — REST API port (default: 3001)\r\n * JWT_SECRET — Secret for JWT session tokens (alias: AGORA_RELAY_JWT_SECRET)\r\n * AGORA_JWT_EXPIRY_SECONDS — JWT expiry in seconds (default: 3600)\r\n * MAX_PEERS — Maximum concurrent registered peers (default: 100)\r\n * MESSAGE_TTL_MS — Message buffer TTL in ms (default: 86400000 = 24h)\r\n * RATE_LIMIT_RPM — REST API requests per minute per IP (default: 60)\r\n * ALLOWED_ORIGINS — CORS origins, comma-separated or * (default: *)\r\n */\r\n\r\nimport http from 'node:http';\r\nimport express from 'express';\r\nimport cors from 'cors';\r\nimport { RelayServer, type RelayServerOptions } from './server';\r\nimport {\r\n createEnvelope,\r\n verifyEnvelope,\r\n type Envelope,\r\n type MessageType,\r\n} from '../message/envelope';\r\nimport { createRestRouter, type CreateEnvelopeFn } from './rest-api';\r\nimport { MessageBuffer } from './message-buffer';\r\nimport type { RestSession } from './rest-api';\r\n\r\n/** Wrapper so REST API can pass string type; createEnvelope expects MessageType */\r\nconst createEnvelopeForRest: CreateEnvelopeFn = (\r\n type,\r\n from,\r\n to,\r\n privateKey,\r\n payload,\r\n timestamp,\r\n inReplyTo\r\n) =>\r\n createEnvelope(\r\n type as MessageType,\r\n from,\r\n privateKey,\r\n payload,\r\n timestamp ?? Date.now(),\r\n inReplyTo,\r\n to\r\n );\r\n\r\nexport interface RunRelayOptions {\r\n /** WebSocket port (default from RELAY_PORT or PORT env, or 3002) */\r\n wsPort?: number;\r\n /** REST API port (default from REST_PORT env, or 3001). Ignored if enableRest is false. */\r\n restPort?: number;\r\n /** Enable REST API (requires JWT_SECRET or AGORA_RELAY_JWT_SECRET). Default: true if secret is set. */\r\n enableRest?: boolean;\r\n /** Relay server options (identity, storagePeers, storageDir) */\r\n relayOptions?: RelayServerOptions;\r\n}\r\n\r\n/**\r\n * Start WebSocket relay and optionally REST API.\r\n * Returns { relay, httpServer } where httpServer is set only when REST is enabled.\r\n */\r\nexport async function runRelay(options: RunRelayOptions = {}): Promise<{\r\n relay: RelayServer;\r\n httpServer?: http.Server;\r\n}> {\r\n const wsPort = options.wsPort ?? parseInt(\r\n process.env.RELAY_PORT ?? process.env.PORT ?? '3002', 10\r\n );\r\n const jwtSecret = process.env.JWT_SECRET ?? process.env.AGORA_RELAY_JWT_SECRET;\r\n const enableRest =\r\n options.enableRest ??\r\n (typeof jwtSecret === 'string' && jwtSecret.length > 0);\r\n\r\n const maxPeers = parseInt(process.env.MAX_PEERS ?? '100', 10);\r\n const relayOptions: RelayServerOptions = { ...options.relayOptions, maxPeers };\r\n\r\n const relay = new RelayServer(relayOptions);\r\n await relay.start(wsPort);\r\n\r\n if (!enableRest) {\r\n return { relay };\r\n }\r\n\r\n if (!jwtSecret) {\r\n await relay.stop();\r\n throw new Error(\r\n 'JWT_SECRET (or AGORA_RELAY_JWT_SECRET) environment variable is required when REST API is enabled'\r\n );\r\n }\r\n\r\n // Expose jwtSecret via env so jwt-auth.ts can read it (it reads AGORA_RELAY_JWT_SECRET)\r\n if (!process.env.AGORA_RELAY_JWT_SECRET) {\r\n process.env.AGORA_RELAY_JWT_SECRET = jwtSecret;\r\n }\r\n\r\n const restPort = options.restPort ?? parseInt(process.env.REST_PORT ?? '3001', 10);\r\n const messageTtlMs = parseInt(process.env.MESSAGE_TTL_MS ?? '86400000', 10);\r\n const messageBuffer = new MessageBuffer({ ttlMs: messageTtlMs });\r\n const restSessions = new Map<string, RestSession>();\r\n\r\n const allowedOrigins = process.env.ALLOWED_ORIGINS ?? '*';\r\n const corsOrigins = allowedOrigins === '*'\r\n ? '*'\r\n : allowedOrigins.split(',').map((o) => o.trim()).filter((o) => o.length > 0);\r\n\r\n const app = express();\r\n app.use(cors({\r\n origin: corsOrigins,\r\n methods: ['GET', 'POST', 'DELETE'],\r\n allowedHeaders: ['Content-Type', 'Authorization'],\r\n }));\r\n app.use(express.json());\r\n\r\n const verifyForRest = (envelope: unknown): { valid: boolean; reason?: string } =>\r\n verifyEnvelope(envelope as Envelope);\r\n\r\n const rateLimitRpm = parseInt(process.env.RATE_LIMIT_RPM ?? '60', 10);\r\n const router = createRestRouter(\r\n relay as Parameters<typeof createRestRouter>[0],\r\n messageBuffer,\r\n restSessions,\r\n createEnvelopeForRest,\r\n verifyForRest,\r\n rateLimitRpm\r\n );\r\n app.use(router);\r\n\r\n app.use((_req, res) => {\r\n res.status(404).json({ error: 'Not found' });\r\n });\r\n\r\n const httpServer = http.createServer(app);\r\n await new Promise<void>((resolve, reject) => {\r\n httpServer.listen(restPort, () => resolve());\r\n httpServer.on('error', reject);\r\n });\r\n\r\n return { relay, httpServer };\r\n}\r\n"],"mappings":"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","names":[]}
|
|
File without changes
|