@ruvector/edge-net 0.1.6 → 0.2.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/ledger.js +663 -0
- package/monitor.js +675 -0
- package/onnx-worker.js +482 -0
- package/package.json +41 -5
- package/qdag.js +582 -0
- package/real-agents.js +252 -39
- package/scheduler.js +764 -0
- package/signaling.js +732 -0
package/signaling.js
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ruvector/edge-net WebRTC Signaling Server
|
|
3
|
+
*
|
|
4
|
+
* Real signaling server for WebRTC peer connections
|
|
5
|
+
* Enables true P2P connections between nodes
|
|
6
|
+
*
|
|
7
|
+
* @module @ruvector/edge-net/signaling
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { EventEmitter } from 'events';
|
|
11
|
+
import { createServer } from 'http';
|
|
12
|
+
import { randomBytes, createHash } from 'crypto';
|
|
13
|
+
|
|
14
|
+
// ============================================
|
|
15
|
+
// SIGNALING SERVER
|
|
16
|
+
// ============================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* WebRTC Signaling Server
|
|
20
|
+
* Routes offers, answers, and ICE candidates between peers
|
|
21
|
+
*/
|
|
22
|
+
export class SignalingServer extends EventEmitter {
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
super();
|
|
25
|
+
this.port = options.port || 8765;
|
|
26
|
+
this.server = null;
|
|
27
|
+
this.wss = null;
|
|
28
|
+
|
|
29
|
+
this.peers = new Map(); // peerId -> { ws, info, rooms }
|
|
30
|
+
this.rooms = new Map(); // roomId -> Set<peerId>
|
|
31
|
+
this.pendingOffers = new Map(); // offerId -> { from, to, offer }
|
|
32
|
+
|
|
33
|
+
this.stats = {
|
|
34
|
+
connections: 0,
|
|
35
|
+
messages: 0,
|
|
36
|
+
offers: 0,
|
|
37
|
+
answers: 0,
|
|
38
|
+
iceCandidates: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Start the signaling server
|
|
44
|
+
*/
|
|
45
|
+
async start() {
|
|
46
|
+
return new Promise(async (resolve, reject) => {
|
|
47
|
+
try {
|
|
48
|
+
// Create HTTP server
|
|
49
|
+
this.server = createServer((req, res) => {
|
|
50
|
+
if (req.url === '/health') {
|
|
51
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
52
|
+
res.end(JSON.stringify({ status: 'ok', peers: this.peers.size }));
|
|
53
|
+
} else if (req.url === '/stats') {
|
|
54
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
55
|
+
res.end(JSON.stringify(this.getStats()));
|
|
56
|
+
} else {
|
|
57
|
+
res.writeHead(404);
|
|
58
|
+
res.end('Not found');
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Create WebSocket server
|
|
63
|
+
const { WebSocketServer } = await import('ws');
|
|
64
|
+
this.wss = new WebSocketServer({ server: this.server });
|
|
65
|
+
|
|
66
|
+
this.wss.on('connection', (ws, req) => {
|
|
67
|
+
this.handleConnection(ws, req);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.server.listen(this.port, () => {
|
|
71
|
+
console.log(`[Signaling] Server running on port ${this.port}`);
|
|
72
|
+
this.emit('ready', { port: this.port });
|
|
73
|
+
resolve(this);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
} catch (error) {
|
|
77
|
+
reject(error);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Handle new WebSocket connection
|
|
84
|
+
*/
|
|
85
|
+
handleConnection(ws, req) {
|
|
86
|
+
const peerId = `peer-${randomBytes(8).toString('hex')}`;
|
|
87
|
+
|
|
88
|
+
const peerInfo = {
|
|
89
|
+
id: peerId,
|
|
90
|
+
ws,
|
|
91
|
+
info: {},
|
|
92
|
+
rooms: new Set(),
|
|
93
|
+
connectedAt: Date.now(),
|
|
94
|
+
lastSeen: Date.now(),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
this.peers.set(peerId, peerInfo);
|
|
98
|
+
this.stats.connections++;
|
|
99
|
+
|
|
100
|
+
// Send welcome message
|
|
101
|
+
this.sendTo(peerId, {
|
|
102
|
+
type: 'welcome',
|
|
103
|
+
peerId,
|
|
104
|
+
serverTime: Date.now(),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
ws.on('message', (data) => {
|
|
108
|
+
try {
|
|
109
|
+
const message = JSON.parse(data.toString());
|
|
110
|
+
this.handleMessage(peerId, message);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('[Signaling] Invalid message:', error.message);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
ws.on('close', () => {
|
|
117
|
+
this.handleDisconnect(peerId);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
ws.on('error', (error) => {
|
|
121
|
+
console.error(`[Signaling] Peer ${peerId} error:`, error.message);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
this.emit('peer-connected', { peerId });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Handle incoming message from peer
|
|
129
|
+
*/
|
|
130
|
+
handleMessage(peerId, message) {
|
|
131
|
+
const peer = this.peers.get(peerId);
|
|
132
|
+
if (!peer) return;
|
|
133
|
+
|
|
134
|
+
peer.lastSeen = Date.now();
|
|
135
|
+
this.stats.messages++;
|
|
136
|
+
|
|
137
|
+
switch (message.type) {
|
|
138
|
+
case 'register':
|
|
139
|
+
this.handleRegister(peerId, message);
|
|
140
|
+
break;
|
|
141
|
+
|
|
142
|
+
case 'join-room':
|
|
143
|
+
this.handleJoinRoom(peerId, message);
|
|
144
|
+
break;
|
|
145
|
+
|
|
146
|
+
case 'leave-room':
|
|
147
|
+
this.handleLeaveRoom(peerId, message);
|
|
148
|
+
break;
|
|
149
|
+
|
|
150
|
+
case 'offer':
|
|
151
|
+
this.handleOffer(peerId, message);
|
|
152
|
+
break;
|
|
153
|
+
|
|
154
|
+
case 'answer':
|
|
155
|
+
this.handleAnswer(peerId, message);
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
case 'ice-candidate':
|
|
159
|
+
this.handleIceCandidate(peerId, message);
|
|
160
|
+
break;
|
|
161
|
+
|
|
162
|
+
case 'discover':
|
|
163
|
+
this.handleDiscover(peerId, message);
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
case 'broadcast':
|
|
167
|
+
this.handleBroadcast(peerId, message);
|
|
168
|
+
break;
|
|
169
|
+
|
|
170
|
+
case 'ping':
|
|
171
|
+
this.sendTo(peerId, { type: 'pong', timestamp: Date.now() });
|
|
172
|
+
break;
|
|
173
|
+
|
|
174
|
+
default:
|
|
175
|
+
console.log(`[Signaling] Unknown message type: ${message.type}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Handle peer registration
|
|
181
|
+
*/
|
|
182
|
+
handleRegister(peerId, message) {
|
|
183
|
+
const peer = this.peers.get(peerId);
|
|
184
|
+
if (!peer) return;
|
|
185
|
+
|
|
186
|
+
peer.info = {
|
|
187
|
+
nodeId: message.nodeId,
|
|
188
|
+
capabilities: message.capabilities || [],
|
|
189
|
+
publicKey: message.publicKey,
|
|
190
|
+
region: message.region,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
this.sendTo(peerId, {
|
|
194
|
+
type: 'registered',
|
|
195
|
+
peerId,
|
|
196
|
+
info: peer.info,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
this.emit('peer-registered', { peerId, info: peer.info });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Handle room join
|
|
204
|
+
*/
|
|
205
|
+
handleJoinRoom(peerId, message) {
|
|
206
|
+
const roomId = message.roomId || 'default';
|
|
207
|
+
const peer = this.peers.get(peerId);
|
|
208
|
+
if (!peer) return;
|
|
209
|
+
|
|
210
|
+
// Create room if doesn't exist
|
|
211
|
+
if (!this.rooms.has(roomId)) {
|
|
212
|
+
this.rooms.set(roomId, new Set());
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const room = this.rooms.get(roomId);
|
|
216
|
+
room.add(peerId);
|
|
217
|
+
peer.rooms.add(roomId);
|
|
218
|
+
|
|
219
|
+
// Get existing peers in room
|
|
220
|
+
const existingPeers = Array.from(room)
|
|
221
|
+
.filter(id => id !== peerId)
|
|
222
|
+
.map(id => {
|
|
223
|
+
const p = this.peers.get(id);
|
|
224
|
+
return { peerId: id, info: p?.info };
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Notify joining peer of existing peers
|
|
228
|
+
this.sendTo(peerId, {
|
|
229
|
+
type: 'room-joined',
|
|
230
|
+
roomId,
|
|
231
|
+
peers: existingPeers,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Notify existing peers of new peer
|
|
235
|
+
for (const otherPeerId of room) {
|
|
236
|
+
if (otherPeerId !== peerId) {
|
|
237
|
+
this.sendTo(otherPeerId, {
|
|
238
|
+
type: 'peer-joined',
|
|
239
|
+
roomId,
|
|
240
|
+
peerId,
|
|
241
|
+
info: peer.info,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.emit('room-join', { roomId, peerId });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Handle room leave
|
|
251
|
+
*/
|
|
252
|
+
handleLeaveRoom(peerId, message) {
|
|
253
|
+
const roomId = message.roomId;
|
|
254
|
+
const peer = this.peers.get(peerId);
|
|
255
|
+
if (!peer) return;
|
|
256
|
+
|
|
257
|
+
const room = this.rooms.get(roomId);
|
|
258
|
+
if (!room) return;
|
|
259
|
+
|
|
260
|
+
room.delete(peerId);
|
|
261
|
+
peer.rooms.delete(roomId);
|
|
262
|
+
|
|
263
|
+
// Notify other peers
|
|
264
|
+
for (const otherPeerId of room) {
|
|
265
|
+
this.sendTo(otherPeerId, {
|
|
266
|
+
type: 'peer-left',
|
|
267
|
+
roomId,
|
|
268
|
+
peerId,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Clean up empty room
|
|
273
|
+
if (room.size === 0) {
|
|
274
|
+
this.rooms.delete(roomId);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Handle WebRTC offer
|
|
280
|
+
*/
|
|
281
|
+
handleOffer(peerId, message) {
|
|
282
|
+
this.stats.offers++;
|
|
283
|
+
|
|
284
|
+
const targetPeerId = message.to;
|
|
285
|
+
const target = this.peers.get(targetPeerId);
|
|
286
|
+
|
|
287
|
+
if (!target) {
|
|
288
|
+
this.sendTo(peerId, {
|
|
289
|
+
type: 'error',
|
|
290
|
+
error: 'Peer not found',
|
|
291
|
+
targetPeerId,
|
|
292
|
+
});
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Forward offer to target
|
|
297
|
+
this.sendTo(targetPeerId, {
|
|
298
|
+
type: 'offer',
|
|
299
|
+
from: peerId,
|
|
300
|
+
offer: message.offer,
|
|
301
|
+
connectionId: message.connectionId,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
this.emit('offer', { from: peerId, to: targetPeerId });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Handle WebRTC answer
|
|
309
|
+
*/
|
|
310
|
+
handleAnswer(peerId, message) {
|
|
311
|
+
this.stats.answers++;
|
|
312
|
+
|
|
313
|
+
const targetPeerId = message.to;
|
|
314
|
+
const target = this.peers.get(targetPeerId);
|
|
315
|
+
|
|
316
|
+
if (!target) return;
|
|
317
|
+
|
|
318
|
+
// Forward answer to target
|
|
319
|
+
this.sendTo(targetPeerId, {
|
|
320
|
+
type: 'answer',
|
|
321
|
+
from: peerId,
|
|
322
|
+
answer: message.answer,
|
|
323
|
+
connectionId: message.connectionId,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
this.emit('answer', { from: peerId, to: targetPeerId });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Handle ICE candidate
|
|
331
|
+
*/
|
|
332
|
+
handleIceCandidate(peerId, message) {
|
|
333
|
+
this.stats.iceCandidates++;
|
|
334
|
+
|
|
335
|
+
const targetPeerId = message.to;
|
|
336
|
+
const target = this.peers.get(targetPeerId);
|
|
337
|
+
|
|
338
|
+
if (!target) return;
|
|
339
|
+
|
|
340
|
+
// Forward ICE candidate to target
|
|
341
|
+
this.sendTo(targetPeerId, {
|
|
342
|
+
type: 'ice-candidate',
|
|
343
|
+
from: peerId,
|
|
344
|
+
candidate: message.candidate,
|
|
345
|
+
connectionId: message.connectionId,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Handle peer discovery request
|
|
351
|
+
*/
|
|
352
|
+
handleDiscover(peerId, message) {
|
|
353
|
+
const capabilities = message.capabilities || [];
|
|
354
|
+
const limit = message.limit || 10;
|
|
355
|
+
|
|
356
|
+
const matches = [];
|
|
357
|
+
|
|
358
|
+
for (const [id, peer] of this.peers) {
|
|
359
|
+
if (id === peerId) continue;
|
|
360
|
+
|
|
361
|
+
// Check capability match
|
|
362
|
+
if (capabilities.length > 0) {
|
|
363
|
+
const peerCaps = peer.info.capabilities || [];
|
|
364
|
+
const hasMatch = capabilities.some(cap => peerCaps.includes(cap));
|
|
365
|
+
if (!hasMatch) continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
matches.push({
|
|
369
|
+
peerId: id,
|
|
370
|
+
info: peer.info,
|
|
371
|
+
lastSeen: peer.lastSeen,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
if (matches.length >= limit) break;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
this.sendTo(peerId, {
|
|
378
|
+
type: 'discover-result',
|
|
379
|
+
peers: matches,
|
|
380
|
+
total: this.peers.size - 1,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Handle broadcast to room
|
|
386
|
+
*/
|
|
387
|
+
handleBroadcast(peerId, message) {
|
|
388
|
+
const roomId = message.roomId;
|
|
389
|
+
const room = this.rooms.get(roomId);
|
|
390
|
+
|
|
391
|
+
if (!room) return;
|
|
392
|
+
|
|
393
|
+
for (const otherPeerId of room) {
|
|
394
|
+
if (otherPeerId !== peerId) {
|
|
395
|
+
this.sendTo(otherPeerId, {
|
|
396
|
+
type: 'broadcast',
|
|
397
|
+
from: peerId,
|
|
398
|
+
roomId,
|
|
399
|
+
data: message.data,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Handle peer disconnect
|
|
407
|
+
*/
|
|
408
|
+
handleDisconnect(peerId) {
|
|
409
|
+
const peer = this.peers.get(peerId);
|
|
410
|
+
if (!peer) return;
|
|
411
|
+
|
|
412
|
+
// Leave all rooms
|
|
413
|
+
for (const roomId of peer.rooms) {
|
|
414
|
+
const room = this.rooms.get(roomId);
|
|
415
|
+
if (room) {
|
|
416
|
+
room.delete(peerId);
|
|
417
|
+
|
|
418
|
+
// Notify other peers
|
|
419
|
+
for (const otherPeerId of room) {
|
|
420
|
+
this.sendTo(otherPeerId, {
|
|
421
|
+
type: 'peer-left',
|
|
422
|
+
roomId,
|
|
423
|
+
peerId,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Clean up empty room
|
|
428
|
+
if (room.size === 0) {
|
|
429
|
+
this.rooms.delete(roomId);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
this.peers.delete(peerId);
|
|
435
|
+
this.emit('peer-disconnected', { peerId });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Send message to peer
|
|
440
|
+
*/
|
|
441
|
+
sendTo(peerId, message) {
|
|
442
|
+
const peer = this.peers.get(peerId);
|
|
443
|
+
if (peer && peer.ws.readyState === 1) {
|
|
444
|
+
peer.ws.send(JSON.stringify(message));
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Get server stats
|
|
452
|
+
*/
|
|
453
|
+
getStats() {
|
|
454
|
+
return {
|
|
455
|
+
peers: this.peers.size,
|
|
456
|
+
rooms: this.rooms.size,
|
|
457
|
+
...this.stats,
|
|
458
|
+
uptime: Date.now() - (this.startTime || Date.now()),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Stop the server
|
|
464
|
+
*/
|
|
465
|
+
async stop() {
|
|
466
|
+
return new Promise((resolve) => {
|
|
467
|
+
// Close all peer connections
|
|
468
|
+
for (const [peerId, peer] of this.peers) {
|
|
469
|
+
peer.ws.close();
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
this.peers.clear();
|
|
473
|
+
this.rooms.clear();
|
|
474
|
+
|
|
475
|
+
if (this.wss) {
|
|
476
|
+
this.wss.close();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (this.server) {
|
|
480
|
+
this.server.close(() => {
|
|
481
|
+
console.log('[Signaling] Server stopped');
|
|
482
|
+
resolve();
|
|
483
|
+
});
|
|
484
|
+
} else {
|
|
485
|
+
resolve();
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// ============================================
|
|
492
|
+
// SIGNALING CLIENT
|
|
493
|
+
// ============================================
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* WebRTC Signaling Client
|
|
497
|
+
* Connects to signaling server for peer discovery and connection setup
|
|
498
|
+
*/
|
|
499
|
+
export class SignalingClient extends EventEmitter {
|
|
500
|
+
constructor(options = {}) {
|
|
501
|
+
super();
|
|
502
|
+
this.serverUrl = options.serverUrl || 'ws://localhost:8765';
|
|
503
|
+
this.nodeId = options.nodeId || `node-${randomBytes(8).toString('hex')}`;
|
|
504
|
+
this.capabilities = options.capabilities || [];
|
|
505
|
+
|
|
506
|
+
this.ws = null;
|
|
507
|
+
this.peerId = null;
|
|
508
|
+
this.connected = false;
|
|
509
|
+
this.rooms = new Set();
|
|
510
|
+
|
|
511
|
+
this.pendingConnections = new Map();
|
|
512
|
+
this.peerConnections = new Map();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Connect to signaling server
|
|
517
|
+
*/
|
|
518
|
+
async connect() {
|
|
519
|
+
return new Promise(async (resolve, reject) => {
|
|
520
|
+
try {
|
|
521
|
+
let WebSocket;
|
|
522
|
+
if (typeof globalThis.WebSocket !== 'undefined') {
|
|
523
|
+
WebSocket = globalThis.WebSocket;
|
|
524
|
+
} else {
|
|
525
|
+
const ws = await import('ws');
|
|
526
|
+
WebSocket = ws.default || ws.WebSocket;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
this.ws = new WebSocket(this.serverUrl);
|
|
530
|
+
|
|
531
|
+
const timeout = setTimeout(() => {
|
|
532
|
+
reject(new Error('Connection timeout'));
|
|
533
|
+
}, 10000);
|
|
534
|
+
|
|
535
|
+
this.ws.onopen = () => {
|
|
536
|
+
clearTimeout(timeout);
|
|
537
|
+
this.connected = true;
|
|
538
|
+
this.emit('connected');
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
this.ws.onmessage = (event) => {
|
|
542
|
+
const message = JSON.parse(event.data);
|
|
543
|
+
this.handleMessage(message);
|
|
544
|
+
|
|
545
|
+
if (message.type === 'registered') {
|
|
546
|
+
resolve(this);
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
this.ws.onclose = () => {
|
|
551
|
+
this.connected = false;
|
|
552
|
+
this.emit('disconnected');
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
this.ws.onerror = (error) => {
|
|
556
|
+
clearTimeout(timeout);
|
|
557
|
+
reject(error);
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
} catch (error) {
|
|
561
|
+
reject(error);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Handle incoming message
|
|
568
|
+
*/
|
|
569
|
+
handleMessage(message) {
|
|
570
|
+
switch (message.type) {
|
|
571
|
+
case 'welcome':
|
|
572
|
+
this.peerId = message.peerId;
|
|
573
|
+
// Register with capabilities
|
|
574
|
+
this.send({
|
|
575
|
+
type: 'register',
|
|
576
|
+
nodeId: this.nodeId,
|
|
577
|
+
capabilities: this.capabilities,
|
|
578
|
+
});
|
|
579
|
+
break;
|
|
580
|
+
|
|
581
|
+
case 'registered':
|
|
582
|
+
this.emit('registered', message);
|
|
583
|
+
break;
|
|
584
|
+
|
|
585
|
+
case 'room-joined':
|
|
586
|
+
this.rooms.add(message.roomId);
|
|
587
|
+
this.emit('room-joined', message);
|
|
588
|
+
break;
|
|
589
|
+
|
|
590
|
+
case 'peer-joined':
|
|
591
|
+
this.emit('peer-joined', message);
|
|
592
|
+
break;
|
|
593
|
+
|
|
594
|
+
case 'peer-left':
|
|
595
|
+
this.emit('peer-left', message);
|
|
596
|
+
break;
|
|
597
|
+
|
|
598
|
+
case 'offer':
|
|
599
|
+
this.emit('offer', message);
|
|
600
|
+
break;
|
|
601
|
+
|
|
602
|
+
case 'answer':
|
|
603
|
+
this.emit('answer', message);
|
|
604
|
+
break;
|
|
605
|
+
|
|
606
|
+
case 'ice-candidate':
|
|
607
|
+
this.emit('ice-candidate', message);
|
|
608
|
+
break;
|
|
609
|
+
|
|
610
|
+
case 'discover-result':
|
|
611
|
+
this.emit('discover-result', message);
|
|
612
|
+
break;
|
|
613
|
+
|
|
614
|
+
case 'broadcast':
|
|
615
|
+
this.emit('broadcast', message);
|
|
616
|
+
break;
|
|
617
|
+
|
|
618
|
+
case 'pong':
|
|
619
|
+
this.emit('pong', message);
|
|
620
|
+
break;
|
|
621
|
+
|
|
622
|
+
default:
|
|
623
|
+
this.emit('message', message);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Send message to server
|
|
629
|
+
*/
|
|
630
|
+
send(message) {
|
|
631
|
+
if (this.connected && this.ws?.readyState === 1) {
|
|
632
|
+
this.ws.send(JSON.stringify(message));
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Join a room
|
|
640
|
+
*/
|
|
641
|
+
joinRoom(roomId) {
|
|
642
|
+
return this.send({ type: 'join-room', roomId });
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Leave a room
|
|
647
|
+
*/
|
|
648
|
+
leaveRoom(roomId) {
|
|
649
|
+
this.rooms.delete(roomId);
|
|
650
|
+
return this.send({ type: 'leave-room', roomId });
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Send WebRTC offer to peer
|
|
655
|
+
*/
|
|
656
|
+
sendOffer(targetPeerId, offer, connectionId) {
|
|
657
|
+
return this.send({
|
|
658
|
+
type: 'offer',
|
|
659
|
+
to: targetPeerId,
|
|
660
|
+
offer,
|
|
661
|
+
connectionId,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Send WebRTC answer to peer
|
|
667
|
+
*/
|
|
668
|
+
sendAnswer(targetPeerId, answer, connectionId) {
|
|
669
|
+
return this.send({
|
|
670
|
+
type: 'answer',
|
|
671
|
+
to: targetPeerId,
|
|
672
|
+
answer,
|
|
673
|
+
connectionId,
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Send ICE candidate to peer
|
|
679
|
+
*/
|
|
680
|
+
sendIceCandidate(targetPeerId, candidate, connectionId) {
|
|
681
|
+
return this.send({
|
|
682
|
+
type: 'ice-candidate',
|
|
683
|
+
to: targetPeerId,
|
|
684
|
+
candidate,
|
|
685
|
+
connectionId,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Discover peers with capabilities
|
|
691
|
+
*/
|
|
692
|
+
discover(capabilities = [], limit = 10) {
|
|
693
|
+
return this.send({
|
|
694
|
+
type: 'discover',
|
|
695
|
+
capabilities,
|
|
696
|
+
limit,
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Broadcast to room
|
|
702
|
+
*/
|
|
703
|
+
broadcast(roomId, data) {
|
|
704
|
+
return this.send({
|
|
705
|
+
type: 'broadcast',
|
|
706
|
+
roomId,
|
|
707
|
+
data,
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Ping server
|
|
713
|
+
*/
|
|
714
|
+
ping() {
|
|
715
|
+
return this.send({ type: 'ping' });
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Close connection
|
|
720
|
+
*/
|
|
721
|
+
close() {
|
|
722
|
+
if (this.ws) {
|
|
723
|
+
this.ws.close();
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// ============================================
|
|
729
|
+
// EXPORTS
|
|
730
|
+
// ============================================
|
|
731
|
+
|
|
732
|
+
export default SignalingServer;
|