pyre-cli 1.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -11
- package/dist/index.js +1325 -33
- package/package.json +2 -2
- package/src/formatters/index.ts +1 -1
- package/src/formatters/output.ts +47 -19
- package/src/formatters/render.ts +207 -16
- package/src/formatters/types.ts +4 -0
- package/src/history.ts +85 -47
- package/src/index.ts +200 -20
- package/src/live/export.ts +5 -2
- package/src/live/input.ts +296 -164
- package/src/live/render.ts +32 -28
- package/src/live/state.ts +41 -21
- package/src/monitors/collectors.ts +220 -17
- package/src/monitors/index.ts +2 -2
- package/src/monitors/types.ts +42 -1
- package/src/p2p/client.ts +271 -0
- package/src/p2p/index.ts +3 -0
- package/src/p2p/protocol.ts +112 -0
- package/src/p2p/server.ts +390 -0
- package/src/p2p/types.ts +97 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import net from 'node:net';
|
|
2
|
+
import tls from 'node:tls';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { collectAll } from '../monitors/index.js';
|
|
7
|
+
import { encodeAuthOk, encodeAuthFail, encodeData, encodePing, encodePong, encodeDisconnect, hashPassword, computeHMAC, verifyHMAC, signMessage, decodeMessage, generateNonce } from './protocol.js';
|
|
8
|
+
import type { P2PServerOptions, P2PConnectionInfo } from './types.js';
|
|
9
|
+
|
|
10
|
+
interface Peer {
|
|
11
|
+
socket: net.Socket;
|
|
12
|
+
authenticated: boolean;
|
|
13
|
+
intervalHandle: NodeJS.Timeout | null;
|
|
14
|
+
pingHandle: NodeJS.Timeout | null;
|
|
15
|
+
readBuffer: Buffer;
|
|
16
|
+
readExpectingHeader: boolean;
|
|
17
|
+
readMessageLength: number;
|
|
18
|
+
ip: string;
|
|
19
|
+
pendingNonce: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const HEADER_SIZE = 4;
|
|
23
|
+
const DEFAULT_RATE_LIMIT_MAX = 5;
|
|
24
|
+
const DEFAULT_RATE_LIMIT_WINDOW = 60000;
|
|
25
|
+
const HMAC_KEY_LENGTH = 32;
|
|
26
|
+
|
|
27
|
+
function readMessageFromPeer(peer: Peer): Buffer | null {
|
|
28
|
+
while (peer.readBuffer.length > 0) {
|
|
29
|
+
if (peer.readExpectingHeader) {
|
|
30
|
+
if (peer.readBuffer.length >= HEADER_SIZE) {
|
|
31
|
+
peer.readMessageLength = peer.readBuffer.readUInt32BE(0);
|
|
32
|
+
peer.readBuffer = peer.readBuffer.subarray(HEADER_SIZE);
|
|
33
|
+
peer.readExpectingHeader = false;
|
|
34
|
+
} else {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!peer.readExpectingHeader) {
|
|
40
|
+
if (peer.readBuffer.length >= peer.readMessageLength) {
|
|
41
|
+
const body = peer.readBuffer.subarray(0, peer.readMessageLength);
|
|
42
|
+
peer.readBuffer = peer.readBuffer.subarray(peer.readMessageLength);
|
|
43
|
+
peer.readExpectingHeader = true;
|
|
44
|
+
return body;
|
|
45
|
+
} else {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isIPAllowed(ip: string, allowedIPs: string[], deniedIPs: string[]): boolean {
|
|
54
|
+
for (const denied of deniedIPs) {
|
|
55
|
+
if (ip === denied || ip.startsWith(denied)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (allowedIPs.length === 0) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
for (const allowed of allowedIPs) {
|
|
63
|
+
if (ip === allowed || ip.startsWith(allowed)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface RateLimitEntry {
|
|
71
|
+
attempts: number;
|
|
72
|
+
firstAttempt: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function deriveHMACKey(password: string): string {
|
|
76
|
+
return crypto.createHash('sha256').update(password).digest('hex').slice(0, HMAC_KEY_LENGTH);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class P2PServer {
|
|
80
|
+
private server: net.Server | tls.Server | null = null;
|
|
81
|
+
private peers: Map<net.Socket, Peer> = new Map();
|
|
82
|
+
private options: P2PServerOptions;
|
|
83
|
+
private running = false;
|
|
84
|
+
private rateLimitMap: Map<string, RateLimitEntry> = new Map();
|
|
85
|
+
private auditStream: fs.WriteStream | null = null;
|
|
86
|
+
private hmacKey: string;
|
|
87
|
+
|
|
88
|
+
constructor(options: P2PServerOptions) {
|
|
89
|
+
this.options = options;
|
|
90
|
+
this.hmacKey = deriveHMACKey(options.password);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get info(): P2PConnectionInfo {
|
|
94
|
+
return {
|
|
95
|
+
host: this.options.host,
|
|
96
|
+
port: this.options.port,
|
|
97
|
+
peersConnected: this.peers.size,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async start(): Promise<void> {
|
|
102
|
+
if (this.options.auditLog) {
|
|
103
|
+
try {
|
|
104
|
+
fs.mkdirSync(this.options.auditLog, { recursive: true });
|
|
105
|
+
const logFile = `${this.options.auditLog}/p2p-audit.log`;
|
|
106
|
+
this.auditStream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
107
|
+
} catch {
|
|
108
|
+
console.log(chalk.yellow('Warning: could not open audit log'));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
const useTLS = !!(this.options.tlsCert && this.options.tlsKey);
|
|
114
|
+
|
|
115
|
+
if (useTLS) {
|
|
116
|
+
const tlsOptions: tls.SecureContextOptions = {
|
|
117
|
+
key: fs.readFileSync(this.options.tlsKey!),
|
|
118
|
+
cert: fs.readFileSync(this.options.tlsCert!),
|
|
119
|
+
};
|
|
120
|
+
if (this.options.tlsCA) {
|
|
121
|
+
tlsOptions.ca = fs.readFileSync(this.options.tlsCA!);
|
|
122
|
+
}
|
|
123
|
+
this.server = tls.createServer(tlsOptions, (socket) => this.onConnection(socket));
|
|
124
|
+
} else {
|
|
125
|
+
this.server = net.createServer((socket) => this.onConnection(socket));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.server.on('error', (err: Error) => reject(err));
|
|
129
|
+
this.server.listen(this.options.port, this.options.host, () => {
|
|
130
|
+
this.running = true;
|
|
131
|
+
const addr = this.server?.address();
|
|
132
|
+
const bound = addr ? (typeof addr === 'object' ? `${addr.address}:${addr.port}` : String(addr)) : 'unknown';
|
|
133
|
+
const protocol = useTLS ? 'TLS' : 'TCP';
|
|
134
|
+
console.log(chalk.green(`P2P server listening on ${bound} (${protocol})`));
|
|
135
|
+
console.log(chalk.dim(` Password: ${this.options.password}`));
|
|
136
|
+
console.log(chalk.dim(` Interval: ${this.options.intervalMs}ms`));
|
|
137
|
+
const allowedIPs = this.options.allowedIPs ?? [];
|
|
138
|
+
const deniedIPs = this.options.deniedIPs ?? [];
|
|
139
|
+
if (allowedIPs.length > 0) console.log(chalk.dim(` Allowed IPs: ${allowedIPs.join(', ')}`));
|
|
140
|
+
if (deniedIPs.length > 0) console.log(chalk.dim(` Denied IPs: ${deniedIPs.join(', ')}`));
|
|
141
|
+
resolve();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
stop(): void {
|
|
147
|
+
this.running = false;
|
|
148
|
+
for (const peer of this.peers.values()) {
|
|
149
|
+
this.disconnectPeer(peer, 'Server shutting down');
|
|
150
|
+
}
|
|
151
|
+
this.peers.clear();
|
|
152
|
+
this.rateLimitMap.clear();
|
|
153
|
+
if (this.server) {
|
|
154
|
+
this.server.close();
|
|
155
|
+
this.server = null;
|
|
156
|
+
}
|
|
157
|
+
if (this.auditStream) {
|
|
158
|
+
this.auditStream.end();
|
|
159
|
+
this.auditStream = null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private audit(ip: string, event: string, detail: string): void {
|
|
164
|
+
if (!this.auditStream) return;
|
|
165
|
+
const ts = new Date().toISOString();
|
|
166
|
+
this.auditStream.write(`${ts} ${ip} ${event} ${detail}\n`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private getPeerIP(socket: net.Socket): string {
|
|
170
|
+
const addr = socket.remoteAddress;
|
|
171
|
+
if (!addr) return 'unknown';
|
|
172
|
+
if (addr.startsWith('::ffff:')) {
|
|
173
|
+
return addr.slice(7);
|
|
174
|
+
}
|
|
175
|
+
return addr;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private checkRateLimit(ip: string): boolean {
|
|
179
|
+
const maxAttempts = this.options.rateLimitMaxAttempts ?? DEFAULT_RATE_LIMIT_MAX;
|
|
180
|
+
const windowMs = this.options.rateLimitWindowMs ?? DEFAULT_RATE_LIMIT_WINDOW;
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
|
|
183
|
+
const entry = this.rateLimitMap.get(ip);
|
|
184
|
+
if (!entry) {
|
|
185
|
+
this.rateLimitMap.set(ip, { attempts: 1, firstAttempt: now });
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (now - entry.firstAttempt > windowMs) {
|
|
190
|
+
this.rateLimitMap.set(ip, { attempts: 1, firstAttempt: now });
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
entry.attempts++;
|
|
195
|
+
if (entry.attempts > maxAttempts) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private cleanupRateLimit(ip: string): void {
|
|
202
|
+
this.rateLimitMap.delete(ip);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private onConnection(socket: net.Socket): void {
|
|
206
|
+
const ip = this.getPeerIP(socket);
|
|
207
|
+
|
|
208
|
+
const allowedIPs = this.options.allowedIPs ?? [];
|
|
209
|
+
const deniedIPs = this.options.deniedIPs ?? [];
|
|
210
|
+
|
|
211
|
+
if (!isIPAllowed(ip, allowedIPs, deniedIPs)) {
|
|
212
|
+
this.audit(ip, 'BLOCKED', 'IP not allowed');
|
|
213
|
+
console.log(chalk.yellow(`Blocked IP: ${ip}`));
|
|
214
|
+
socket.destroy();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!this.checkRateLimit(ip)) {
|
|
219
|
+
this.audit(ip, 'RATE_LIMITED', 'Too many auth attempts');
|
|
220
|
+
console.log(chalk.yellow(`Rate limit exceeded for IP: ${ip}`));
|
|
221
|
+
socket.destroy();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const peer: Peer = {
|
|
226
|
+
socket,
|
|
227
|
+
authenticated: false,
|
|
228
|
+
intervalHandle: null,
|
|
229
|
+
pingHandle: null,
|
|
230
|
+
readBuffer: Buffer.alloc(0),
|
|
231
|
+
readExpectingHeader: true,
|
|
232
|
+
readMessageLength: 0,
|
|
233
|
+
ip,
|
|
234
|
+
pendingNonce: null,
|
|
235
|
+
};
|
|
236
|
+
this.peers.set(socket, peer);
|
|
237
|
+
|
|
238
|
+
const remoteAddr = socket.remoteAddress
|
|
239
|
+
? `${socket.remoteAddress}:${socket.remotePort}`
|
|
240
|
+
: 'unknown';
|
|
241
|
+
console.log(chalk.cyan(`Peer connected: ${remoteAddr}`));
|
|
242
|
+
this.audit(ip, 'CONNECT', remoteAddr);
|
|
243
|
+
|
|
244
|
+
socket.on('data', (data) => this.onData(peer, data));
|
|
245
|
+
socket.on('close', () => this.onPeerClose(peer));
|
|
246
|
+
socket.on('error', (err) => this.onPeerError(peer, err));
|
|
247
|
+
|
|
248
|
+
socket.setTimeout(30000);
|
|
249
|
+
socket.on('timeout', () => {
|
|
250
|
+
if (!peer.authenticated) {
|
|
251
|
+
this.disconnectPeer(peer, 'Authentication timeout');
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const nonce = generateNonce();
|
|
256
|
+
this.sendSigned(peer, signMessage('challenge', { nonce }, this.hmacKey));
|
|
257
|
+
peer.pendingNonce = nonce;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private onData(peer: Peer, data: Buffer): void {
|
|
261
|
+
peer.readBuffer = Buffer.concat([peer.readBuffer, data]);
|
|
262
|
+
|
|
263
|
+
if (!peer.authenticated) {
|
|
264
|
+
const body = readMessageFromPeer(peer);
|
|
265
|
+
if (body) {
|
|
266
|
+
this.handleAuthMessage(peer, body);
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private handleAuthMessage(peer: Peer, body: Buffer): void {
|
|
273
|
+
try {
|
|
274
|
+
const raw = body.toString('utf-8');
|
|
275
|
+
const msg = decodeMessage(body);
|
|
276
|
+
if (msg.kind !== 'auth') {
|
|
277
|
+
peer.socket.write(signMessage('auth-fail', { ok: false, reason: 'Expected auth message first' }, this.hmacKey));
|
|
278
|
+
this.disconnectPeer(peer, 'Expected auth message');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const authPayload = msg.payload as { passwordHash?: string; nonce?: string };
|
|
283
|
+
if (!authPayload.passwordHash || !authPayload.nonce) {
|
|
284
|
+
peer.socket.write(signMessage('auth-fail', { ok: false, reason: 'Invalid auth payload' }, this.hmacKey));
|
|
285
|
+
this.disconnectPeer(peer, 'Invalid auth payload');
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (peer.pendingNonce && authPayload.nonce !== peer.pendingNonce) {
|
|
290
|
+
this.audit(peer.ip, 'AUTH_FAIL', 'Nonce mismatch');
|
|
291
|
+
peer.socket.write(signMessage('auth-fail', { ok: false, reason: 'Invalid nonce' }, this.hmacKey));
|
|
292
|
+
this.disconnectPeer(peer, 'Invalid nonce');
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const expectedHash = hashPassword(this.options.password, authPayload.nonce);
|
|
297
|
+
if (authPayload.passwordHash !== expectedHash) {
|
|
298
|
+
this.audit(peer.ip, 'AUTH_FAIL', 'Invalid password hash');
|
|
299
|
+
peer.socket.write(signMessage('auth-fail', { ok: false, reason: 'Invalid password' }, this.hmacKey));
|
|
300
|
+
this.disconnectPeer(peer, 'Authentication failed');
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
peer.authenticated = true;
|
|
305
|
+
this.cleanupRateLimit(peer.ip);
|
|
306
|
+
peer.pendingNonce = null;
|
|
307
|
+
peer.socket.write(signMessage('auth-ok', { ok: true }, this.hmacKey));
|
|
308
|
+
console.log(chalk.green(`Peer authenticated: ${peer.socket.remoteAddress}`));
|
|
309
|
+
this.audit(peer.ip, 'AUTH_OK', 'Challenge-response successful');
|
|
310
|
+
this.startDataStream(peer);
|
|
311
|
+
this.startPing(peer);
|
|
312
|
+
} catch {
|
|
313
|
+
peer.socket.write(signMessage('auth-fail', { ok: false, reason: 'Invalid message format' }, this.hmacKey));
|
|
314
|
+
this.disconnectPeer(peer, 'Invalid message format');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private sendSigned(peer: Peer, message: Buffer): void {
|
|
319
|
+
if (!peer.socket.destroyed) {
|
|
320
|
+
peer.socket.write(message);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private startDataStream(peer: Peer): void {
|
|
325
|
+
const send = async () => {
|
|
326
|
+
if (!peer.authenticated || peer.socket.destroyed) return;
|
|
327
|
+
try {
|
|
328
|
+
const data = await collectAll({ detailed: this.options.detailed });
|
|
329
|
+
this.sendSigned(peer, signMessage('data', { stats: data }, this.hmacKey));
|
|
330
|
+
} catch {
|
|
331
|
+
// skip failed tick
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
send();
|
|
336
|
+
peer.intervalHandle = setInterval(send, this.options.intervalMs);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private startPing(peer: Peer): void {
|
|
340
|
+
peer.pingHandle = setInterval(() => {
|
|
341
|
+
if (peer.socket.destroyed) return;
|
|
342
|
+
this.sendSigned(peer, signMessage('ping', { ts: Date.now() }, this.hmacKey));
|
|
343
|
+
}, 5000);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private onPeerClose(peer: Peer): void {
|
|
347
|
+
this.cleanupPeer(peer);
|
|
348
|
+
console.log(chalk.yellow(`Peer disconnected: ${peer.socket.remoteAddress}`));
|
|
349
|
+
this.audit(peer.ip, 'DISCONNECT', 'Peer closed connection');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private onPeerError(peer: Peer, err: Error): void {
|
|
353
|
+
const code = (err as any).code;
|
|
354
|
+
if (code === 'ECONNRESET') {
|
|
355
|
+
console.log(chalk.yellow(`Peer connection reset: ${peer.socket.remoteAddress}`));
|
|
356
|
+
this.audit(peer.ip, 'ERROR', 'Connection reset');
|
|
357
|
+
} else {
|
|
358
|
+
console.log(chalk.red(`Peer error: ${err.message}`));
|
|
359
|
+
this.audit(peer.ip, 'ERROR', err.message);
|
|
360
|
+
}
|
|
361
|
+
this.cleanupPeer(peer);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private disconnectPeer(peer: Peer, reason: string): void {
|
|
365
|
+
try {
|
|
366
|
+
this.sendSigned(peer, signMessage('disconnect', { reason }, this.hmacKey));
|
|
367
|
+
} catch {
|
|
368
|
+
// socket may already be closed
|
|
369
|
+
}
|
|
370
|
+
peer.socket.destroy();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private cleanupPeer(peer: Peer): void {
|
|
374
|
+
if (peer.intervalHandle) {
|
|
375
|
+
clearInterval(peer.intervalHandle);
|
|
376
|
+
peer.intervalHandle = null;
|
|
377
|
+
}
|
|
378
|
+
if (peer.pingHandle) {
|
|
379
|
+
clearInterval(peer.pingHandle);
|
|
380
|
+
peer.pingHandle = null;
|
|
381
|
+
}
|
|
382
|
+
this.peers.delete(peer.socket);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export async function startP2PServer(options: P2PServerOptions): Promise<P2PServer> {
|
|
387
|
+
const server = new P2PServer(options);
|
|
388
|
+
await server.start();
|
|
389
|
+
return server;
|
|
390
|
+
}
|
package/src/p2p/types.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P2P live data transfer protocol types.
|
|
3
|
+
*
|
|
4
|
+
* Defines the message shapes exchanged between a pyre server
|
|
5
|
+
* (host) and a pyre client (peer) over a TCP connection.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type P2PMessageKind = 'auth' | 'auth-ok' | 'auth-fail' | 'data' | 'ping' | 'pong' | 'disconnect' | 'challenge';
|
|
9
|
+
|
|
10
|
+
export interface P2PMessage {
|
|
11
|
+
kind: P2PMessageKind;
|
|
12
|
+
payload: unknown;
|
|
13
|
+
hmac?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ChallengeMessage {
|
|
17
|
+
nonce: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AuthMessage {
|
|
21
|
+
passwordHash: string;
|
|
22
|
+
nonce: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AuthOkMessage {
|
|
26
|
+
ok: true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AuthFailMessage {
|
|
30
|
+
ok: false;
|
|
31
|
+
reason: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DataMessage {
|
|
35
|
+
stats: import('../monitors/types.js').StatsData;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PingMessage {
|
|
39
|
+
ts: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PongMessage {
|
|
43
|
+
ts: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DisconnectMessage {
|
|
47
|
+
reason: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type P2PPayload =
|
|
51
|
+
| ChallengeMessage
|
|
52
|
+
| AuthMessage
|
|
53
|
+
| AuthOkMessage
|
|
54
|
+
| AuthFailMessage
|
|
55
|
+
| DataMessage
|
|
56
|
+
| PingMessage
|
|
57
|
+
| PongMessage
|
|
58
|
+
| DisconnectMessage;
|
|
59
|
+
|
|
60
|
+
export interface P2PServerOptions {
|
|
61
|
+
host: string;
|
|
62
|
+
port: number;
|
|
63
|
+
password: string;
|
|
64
|
+
intervalMs: number;
|
|
65
|
+
detailed: boolean;
|
|
66
|
+
tlsCert?: string;
|
|
67
|
+
tlsKey?: string;
|
|
68
|
+
tlsCA?: string;
|
|
69
|
+
rateLimitMaxAttempts?: number;
|
|
70
|
+
rateLimitWindowMs?: number;
|
|
71
|
+
allowedIPs?: string[];
|
|
72
|
+
deniedIPs?: string[];
|
|
73
|
+
auditLog?: string;
|
|
74
|
+
hmacKey?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface P2PClientOptions {
|
|
78
|
+
host: string;
|
|
79
|
+
port: number;
|
|
80
|
+
password: string;
|
|
81
|
+
tls?: boolean;
|
|
82
|
+
tlsCA?: string;
|
|
83
|
+
tlsInsecure?: boolean;
|
|
84
|
+
auditLog?: string;
|
|
85
|
+
hmacKey?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface P2PConnectionInfo {
|
|
89
|
+
host: string;
|
|
90
|
+
port: number;
|
|
91
|
+
peersConnected: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface RateLimitEntry {
|
|
95
|
+
attempts: number;
|
|
96
|
+
firstAttempt: number;
|
|
97
|
+
}
|