@ruvector/edge-net 0.4.2 → 0.4.3

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.
@@ -0,0 +1,686 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * WebRTC P2P Data Channel Test
4
+ *
5
+ * Tests real WebRTC peer-to-peer communication with:
6
+ * - Two separate Node.js processes
7
+ * - Firebase signaling for offer/answer/ICE exchange
8
+ * - WebRTC data channel message exchange
9
+ *
10
+ * Usage:
11
+ * node tests/webrtc-peer-test.js
12
+ * node tests/webrtc-peer-test.js --peer1 # Run peer 1 only
13
+ * node tests/webrtc-peer-test.js --peer2 # Run peer 2 only
14
+ *
15
+ * @module @ruvector/edge-net/tests/webrtc-peer-test
16
+ */
17
+
18
+ import { spawn, fork } from 'child_process';
19
+ import { fileURLToPath } from 'url';
20
+ import path from 'path';
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = path.dirname(__filename);
24
+ const pkgDir = path.join(__dirname, '..');
25
+
26
+ // Test configuration
27
+ const TEST_CONFIG = {
28
+ testRoom: `webrtc-test-${Date.now()}`,
29
+ timeout: 60000, // 60 second timeout
30
+ messageCount: 5,
31
+ };
32
+
33
+ // ============================================
34
+ // SINGLE PEER TEST RUNNER
35
+ // ============================================
36
+
37
+ async function runPeer(peerNum, room) {
38
+ console.log(`\n${'='.repeat(60)}`);
39
+ console.log(` PEER ${peerNum} STARTING`);
40
+ console.log(`${'='.repeat(60)}\n`);
41
+
42
+ // Dynamic imports
43
+ const { WebRTCPeerConnection, WebRTCPeerManager, WEBRTC_CONFIG } = await import('../webrtc.js');
44
+ const { FirebaseSignaling, getFirebaseConfig } = await import('../firebase-signaling.js');
45
+ const { randomBytes } = await import('crypto');
46
+
47
+ // Generate unique peer ID
48
+ const peerId = `test-peer-${peerNum}-${randomBytes(4).toString('hex')}`;
49
+ const isInitiator = peerNum === 1;
50
+
51
+ console.log(`[Peer ${peerNum}] ID: ${peerId}`);
52
+ console.log(`[Peer ${peerNum}] Room: ${room}`);
53
+ console.log(`[Peer ${peerNum}] Role: ${isInitiator ? 'INITIATOR' : 'RESPONDER'}`);
54
+
55
+ // Create local identity
56
+ const localIdentity = {
57
+ piKey: peerId,
58
+ publicKey: randomBytes(32).toString('hex'),
59
+ siteId: `test-site-${peerNum}`,
60
+ };
61
+
62
+ // Initialize Firebase signaling
63
+ const firebase = new FirebaseSignaling({
64
+ peerId,
65
+ room,
66
+ firebaseConfig: getFirebaseConfig(),
67
+ verifySignatures: false, // Skip WASM verification for test
68
+ });
69
+
70
+ // Initialize WebRTC peer manager
71
+ const webrtc = new WebRTCPeerManager(localIdentity, {
72
+ fallbackToSimulation: false,
73
+ fallbackToDHT: false,
74
+ });
75
+
76
+ // Track state
77
+ let connectedPeerId = null;
78
+ let dataChannelReady = false;
79
+ let messagesSent = 0;
80
+ let messagesReceived = 0;
81
+ const receivedMessages = [];
82
+
83
+ // Setup WebRTC external signaling via Firebase
84
+ webrtc.setExternalSignaling(async (type, toPeerId, data) => {
85
+ console.log(`[Peer ${peerNum}] Signaling -> ${type} to ${toPeerId.slice(0, 8)}...`);
86
+ switch (type) {
87
+ case 'offer':
88
+ await firebase.sendOffer(toPeerId, data);
89
+ break;
90
+ case 'answer':
91
+ await firebase.sendAnswer(toPeerId, data);
92
+ break;
93
+ case 'ice-candidate':
94
+ await firebase.sendIceCandidate(toPeerId, data);
95
+ break;
96
+ }
97
+ });
98
+
99
+ // Handle peer discovery from Firebase
100
+ firebase.on('peer-discovered', async ({ peerId: discoveredPeerId }) => {
101
+ console.log(`[Peer ${peerNum}] Discovered peer: ${discoveredPeerId.slice(0, 16)}...`);
102
+
103
+ // Only initiator starts connection (peer with higher ID to avoid race)
104
+ if (isInitiator && peerId > discoveredPeerId) {
105
+ console.log(`[Peer ${peerNum}] Initiating WebRTC connection...`);
106
+ try {
107
+ await webrtc.connectToPeer(discoveredPeerId);
108
+ } catch (err) {
109
+ console.error(`[Peer ${peerNum}] Connect failed:`, err.message);
110
+ }
111
+ }
112
+ });
113
+
114
+ // Handle incoming offers
115
+ firebase.on('offer', async ({ from, offer }) => {
116
+ console.log(`[Peer ${peerNum}] Received OFFER from ${from.slice(0, 8)}...`);
117
+ try {
118
+ await webrtc.handleOffer({ from, offer });
119
+ } catch (err) {
120
+ console.error(`[Peer ${peerNum}] Handle offer failed:`, err.message);
121
+ }
122
+ });
123
+
124
+ // Handle incoming answers
125
+ firebase.on('answer', async ({ from, answer }) => {
126
+ console.log(`[Peer ${peerNum}] Received ANSWER from ${from.slice(0, 8)}...`);
127
+ try {
128
+ await webrtc.handleAnswer({ from, answer });
129
+ } catch (err) {
130
+ console.error(`[Peer ${peerNum}] Handle answer failed:`, err.message);
131
+ }
132
+ });
133
+
134
+ // Handle ICE candidates
135
+ firebase.on('ice-candidate', async ({ from, candidate }) => {
136
+ console.log(`[Peer ${peerNum}] Received ICE candidate from ${from.slice(0, 8)}...`);
137
+ try {
138
+ await webrtc.handleIceCandidate({ from, candidate });
139
+ } catch (err) {
140
+ console.error(`[Peer ${peerNum}] Handle ICE failed:`, err.message);
141
+ }
142
+ });
143
+
144
+ // Handle WebRTC peer connected
145
+ webrtc.on('peer-connected', (connPeerId) => {
146
+ console.log(`\n[Peer ${peerNum}] DATA CHANNEL OPEN with ${connPeerId.slice(0, 8)}...`);
147
+ connectedPeerId = connPeerId;
148
+ dataChannelReady = true;
149
+
150
+ // Start sending test messages
151
+ sendTestMessages();
152
+ });
153
+
154
+ // Handle incoming messages
155
+ webrtc.on('message', ({ from, message }) => {
156
+ messagesReceived++;
157
+ receivedMessages.push({ from, message, receivedAt: Date.now() });
158
+ console.log(`[Peer ${peerNum}] Received message #${messagesReceived}:`,
159
+ typeof message === 'object' ? JSON.stringify(message).slice(0, 50) : message.slice(0, 50));
160
+
161
+ // Send echo response
162
+ if (message.type !== 'echo' && message.type !== 'heartbeat' && message.type !== 'heartbeat-ack') {
163
+ try {
164
+ webrtc.sendToPeer(from, {
165
+ type: 'echo',
166
+ originalMessage: message,
167
+ echoFrom: peerId,
168
+ timestamp: Date.now(),
169
+ });
170
+ } catch (err) {
171
+ // Channel might be closing
172
+ }
173
+ }
174
+ });
175
+
176
+ // Send test messages
177
+ async function sendTestMessages() {
178
+ console.log(`\n[Peer ${peerNum}] Starting test message exchange...`);
179
+
180
+ for (let i = 0; i < TEST_CONFIG.messageCount; i++) {
181
+ await new Promise(resolve => setTimeout(resolve, 500));
182
+
183
+ if (!dataChannelReady) break;
184
+
185
+ const testMessage = {
186
+ type: 'test',
187
+ from: peerId,
188
+ sequence: i + 1,
189
+ timestamp: Date.now(),
190
+ payload: `Hello from Peer ${peerNum}, message ${i + 1}`,
191
+ };
192
+
193
+ try {
194
+ webrtc.sendToPeer(connectedPeerId, testMessage);
195
+ messagesSent++;
196
+ console.log(`[Peer ${peerNum}] Sent message #${messagesSent}`);
197
+ } catch (err) {
198
+ console.error(`[Peer ${peerNum}] Send failed:`, err.message);
199
+ }
200
+ }
201
+ }
202
+
203
+ // Connect to Firebase
204
+ console.log(`\n[Peer ${peerNum}] Connecting to Firebase...`);
205
+ const connected = await firebase.connect();
206
+
207
+ if (!connected) {
208
+ console.error(`[Peer ${peerNum}] Firebase connection failed!`);
209
+ process.exit(1);
210
+ }
211
+
212
+ console.log(`[Peer ${peerNum}] Firebase connected, waiting for peers...`);
213
+
214
+ // Timeout handler
215
+ const timeout = setTimeout(async () => {
216
+ console.log(`\n[Peer ${peerNum}] TEST TIMEOUT - Summary:`);
217
+ console.log(` - Data channel established: ${dataChannelReady}`);
218
+ console.log(` - Messages sent: ${messagesSent}`);
219
+ console.log(` - Messages received: ${messagesReceived}`);
220
+
221
+ await cleanup();
222
+ process.exit(dataChannelReady && messagesReceived > 0 ? 0 : 1);
223
+ }, TEST_CONFIG.timeout);
224
+
225
+ // Cleanup function
226
+ async function cleanup() {
227
+ clearTimeout(timeout);
228
+ webrtc.close();
229
+ await firebase.disconnect();
230
+ }
231
+
232
+ // Success check
233
+ const checkSuccess = setInterval(async () => {
234
+ if (messagesSent >= TEST_CONFIG.messageCount && messagesReceived >= TEST_CONFIG.messageCount) {
235
+ clearInterval(checkSuccess);
236
+ console.log(`\n[Peer ${peerNum}] TEST PASSED!`);
237
+ console.log(` - Messages sent: ${messagesSent}`);
238
+ console.log(` - Messages received: ${messagesReceived}`);
239
+
240
+ await cleanup();
241
+ process.exit(0);
242
+ }
243
+ }, 1000);
244
+
245
+ // Handle graceful shutdown
246
+ process.on('SIGINT', async () => {
247
+ console.log(`\n[Peer ${peerNum}] Shutting down...`);
248
+ await cleanup();
249
+ process.exit(0);
250
+ });
251
+ }
252
+
253
+ // ============================================
254
+ // MAIN ORCHESTRATOR
255
+ // ============================================
256
+
257
+ async function runDualPeerTest() {
258
+ console.log('\n' + '='.repeat(60));
259
+ console.log(' WebRTC P2P Data Channel Test');
260
+ console.log(' Testing real peer-to-peer communication');
261
+ console.log('='.repeat(60) + '\n');
262
+
263
+ const room = TEST_CONFIG.testRoom;
264
+ console.log(`Test room: ${room}`);
265
+ console.log(`Timeout: ${TEST_CONFIG.timeout / 1000}s`);
266
+ console.log(`Messages per peer: ${TEST_CONFIG.messageCount}\n`);
267
+
268
+ // Spawn two peer processes
269
+ const testFile = fileURLToPath(import.meta.url);
270
+
271
+ const peer1 = fork(testFile, ['--peer', '1', '--room', room], {
272
+ stdio: 'inherit',
273
+ env: { ...process.env, FORCE_COLOR: '1' }
274
+ });
275
+
276
+ // Delay peer2 to ensure peer1 registers first
277
+ await new Promise(resolve => setTimeout(resolve, 2000));
278
+
279
+ const peer2 = fork(testFile, ['--peer', '2', '--room', room], {
280
+ stdio: 'inherit',
281
+ env: { ...process.env, FORCE_COLOR: '1' }
282
+ });
283
+
284
+ let peer1Exit = null;
285
+ let peer2Exit = null;
286
+
287
+ peer1.on('exit', (code) => {
288
+ peer1Exit = code;
289
+ console.log(`\nPeer 1 exited with code ${code}`);
290
+ checkCompletion();
291
+ });
292
+
293
+ peer2.on('exit', (code) => {
294
+ peer2Exit = code;
295
+ console.log(`\nPeer 2 exited with code ${code}`);
296
+ checkCompletion();
297
+ });
298
+
299
+ function checkCompletion() {
300
+ if (peer1Exit !== null && peer2Exit !== null) {
301
+ const success = peer1Exit === 0 && peer2Exit === 0;
302
+ console.log('\n' + '='.repeat(60));
303
+ console.log(` TEST ${success ? 'PASSED' : 'FAILED'}`);
304
+ console.log('='.repeat(60) + '\n');
305
+ process.exit(success ? 0 : 1);
306
+ }
307
+ }
308
+
309
+ // Overall timeout
310
+ setTimeout(() => {
311
+ console.log('\nOverall test timeout - killing processes');
312
+ peer1.kill();
313
+ peer2.kill();
314
+ process.exit(1);
315
+ }, TEST_CONFIG.timeout + 10000);
316
+ }
317
+
318
+ // ============================================
319
+ // INLINE PEER TEST (NO SUBPROCESS)
320
+ // ============================================
321
+
322
+ async function runInlineTest() {
323
+ console.log('\n' + '='.repeat(60));
324
+ console.log(' WebRTC P2P Inline Test');
325
+ console.log(' Two peers in same process');
326
+ console.log('='.repeat(60) + '\n');
327
+
328
+ const { WebRTCPeerConnection, WEBRTC_CONFIG } = await import('../webrtc.js');
329
+ const { randomBytes } = await import('crypto');
330
+
331
+ // Load wrtc for Node.js
332
+ let wrtc;
333
+ try {
334
+ wrtc = await import('wrtc');
335
+ console.log('wrtc module loaded successfully');
336
+ } catch (err) {
337
+ console.error('Failed to load wrtc:', err.message);
338
+ process.exit(1);
339
+ }
340
+
341
+ const { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate } = wrtc;
342
+
343
+ // Create two peer connections
344
+ const peer1Id = `peer-1-${randomBytes(4).toString('hex')}`;
345
+ const peer2Id = `peer-2-${randomBytes(4).toString('hex')}`;
346
+
347
+ const identity1 = { piKey: peer1Id, siteId: 'test-1' };
348
+ const identity2 = { piKey: peer2Id, siteId: 'test-2' };
349
+
350
+ console.log(`\nPeer 1 ID: ${peer1Id}`);
351
+ console.log(`Peer 2 ID: ${peer2Id}`);
352
+
353
+ // Create WebRTC peer connections
354
+ const peerConn1 = new WebRTCPeerConnection(peer2Id, identity1, true);
355
+ const peerConn2 = new WebRTCPeerConnection(peer1Id, identity2, false);
356
+
357
+ // Track messages
358
+ let peer1Messages = [];
359
+ let peer2Messages = [];
360
+ let peer1ChannelOpen = false;
361
+ let peer2ChannelOpen = false;
362
+
363
+ // Setup message handlers
364
+ peerConn1.on('message', ({ message }) => {
365
+ console.log(`[Peer 1] Received:`, typeof message === 'string' ? message : JSON.stringify(message));
366
+ peer1Messages.push(message);
367
+ });
368
+
369
+ peerConn2.on('message', ({ message }) => {
370
+ console.log(`[Peer 2] Received:`, typeof message === 'string' ? message : JSON.stringify(message));
371
+ peer2Messages.push(message);
372
+ });
373
+
374
+ peerConn1.on('channel-open', () => {
375
+ console.log('[Peer 1] Data channel OPEN');
376
+ peer1ChannelOpen = true;
377
+ });
378
+
379
+ peerConn2.on('channel-open', () => {
380
+ console.log('[Peer 2] Data channel OPEN');
381
+ peer2ChannelOpen = true;
382
+ });
383
+
384
+ // Initialize both peers
385
+ console.log('\nInitializing peer connections...');
386
+ await peerConn1.initialize();
387
+ await peerConn2.initialize();
388
+
389
+ // Exchange ICE candidates directly
390
+ const peer1Candidates = [];
391
+ const peer2Candidates = [];
392
+
393
+ peerConn1.on('ice-candidate', ({ candidate }) => {
394
+ console.log('[Peer 1] Generated ICE candidate');
395
+ peer1Candidates.push(candidate);
396
+ // Forward to peer2 (simulating signaling)
397
+ peerConn2.addIceCandidate(candidate).catch(e => console.log('ICE add failed:', e.message));
398
+ });
399
+
400
+ peerConn2.on('ice-candidate', ({ candidate }) => {
401
+ console.log('[Peer 2] Generated ICE candidate');
402
+ peer2Candidates.push(candidate);
403
+ // Forward to peer1 (simulating signaling)
404
+ peerConn1.addIceCandidate(candidate).catch(e => console.log('ICE add failed:', e.message));
405
+ });
406
+
407
+ // Create offer from peer1
408
+ console.log('\n[Peer 1] Creating offer...');
409
+ const offer = await peerConn1.createOffer();
410
+ console.log('[Peer 1] Offer created');
411
+
412
+ // Peer2 handles offer and creates answer
413
+ console.log('[Peer 2] Handling offer...');
414
+ const answer = await peerConn2.handleOffer(offer);
415
+ console.log('[Peer 2] Answer created');
416
+
417
+ // Peer1 handles answer
418
+ console.log('[Peer 1] Handling answer...');
419
+ await peerConn1.handleAnswer(answer);
420
+ console.log('[Peer 1] Answer processed');
421
+
422
+ // Wait for connection
423
+ console.log('\nWaiting for data channel...');
424
+
425
+ const channelTimeout = 30000;
426
+ const startTime = Date.now();
427
+
428
+ while (!peer1ChannelOpen || !peer2ChannelOpen) {
429
+ if (Date.now() - startTime > channelTimeout) {
430
+ console.error('\nTimeout waiting for data channel!');
431
+ console.log(`Peer 1 channel open: ${peer1ChannelOpen}`);
432
+ console.log(`Peer 2 channel open: ${peer2ChannelOpen}`);
433
+ console.log(`Peer 1 state: ${peerConn1.state}`);
434
+ console.log(`Peer 2 state: ${peerConn2.state}`);
435
+ peerConn1.close();
436
+ peerConn2.close();
437
+ process.exit(1);
438
+ }
439
+ await new Promise(resolve => setTimeout(resolve, 100));
440
+ }
441
+
442
+ console.log('\nData channels established!');
443
+
444
+ // Send test messages
445
+ console.log('\nExchanging test messages...');
446
+
447
+ for (let i = 0; i < 3; i++) {
448
+ peerConn1.send({ type: 'test', from: 'peer1', seq: i });
449
+ peerConn2.send({ type: 'test', from: 'peer2', seq: i });
450
+ await new Promise(resolve => setTimeout(resolve, 200));
451
+ }
452
+
453
+ // Wait for messages
454
+ await new Promise(resolve => setTimeout(resolve, 1000));
455
+
456
+ // Report results
457
+ console.log('\n' + '='.repeat(60));
458
+ console.log(' TEST RESULTS');
459
+ console.log('='.repeat(60));
460
+ console.log(`Peer 1 received: ${peer1Messages.length} messages`);
461
+ console.log(`Peer 2 received: ${peer2Messages.length} messages`);
462
+
463
+ const success = peer1Messages.length >= 3 && peer2Messages.length >= 3;
464
+ console.log(`\nTEST ${success ? 'PASSED' : 'FAILED'}`);
465
+
466
+ // Cleanup
467
+ peerConn1.close();
468
+ peerConn2.close();
469
+
470
+ process.exit(success ? 0 : 1);
471
+ }
472
+
473
+ // ============================================
474
+ // FIREBASE INTEGRATION TEST
475
+ // ============================================
476
+
477
+ async function runFirebaseTest() {
478
+ console.log('\n' + '='.repeat(60));
479
+ console.log(' WebRTC + Firebase Signaling Test');
480
+ console.log('='.repeat(60) + '\n');
481
+
482
+ const { WebRTCPeerManager } = await import('../webrtc.js');
483
+ const { FirebaseSignaling, getFirebaseConfig } = await import('../firebase-signaling.js');
484
+ const { randomBytes } = await import('crypto');
485
+
486
+ const room = `firebase-test-${Date.now()}`;
487
+ console.log(`Test room: ${room}\n`);
488
+
489
+ // Create two peers
490
+ const peer1Id = `fp1-${randomBytes(4).toString('hex')}`;
491
+ const peer2Id = `fp2-${randomBytes(4).toString('hex')}`;
492
+
493
+ const identity1 = { piKey: peer1Id, siteId: 'firebase-test-1' };
494
+ const identity2 = { piKey: peer2Id, siteId: 'firebase-test-2' };
495
+
496
+ // Create Firebase signaling for each peer
497
+ const fb1 = new FirebaseSignaling({ peerId: peer1Id, room, verifySignatures: false });
498
+ const fb2 = new FirebaseSignaling({ peerId: peer2Id, room, verifySignatures: false });
499
+
500
+ // Create WebRTC managers
501
+ const webrtc1 = new WebRTCPeerManager(identity1, { fallbackToSimulation: false });
502
+ const webrtc2 = new WebRTCPeerManager(identity2, { fallbackToSimulation: false });
503
+
504
+ // Wire up external signaling
505
+ webrtc1.setExternalSignaling(async (type, to, data) => {
506
+ console.log(`[P1] Signaling -> ${type}`);
507
+ if (type === 'offer') await fb1.sendOffer(to, data);
508
+ else if (type === 'answer') await fb1.sendAnswer(to, data);
509
+ else if (type === 'ice-candidate') await fb1.sendIceCandidate(to, data);
510
+ });
511
+
512
+ webrtc2.setExternalSignaling(async (type, to, data) => {
513
+ console.log(`[P2] Signaling -> ${type}`);
514
+ if (type === 'offer') await fb2.sendOffer(to, data);
515
+ else if (type === 'answer') await fb2.sendAnswer(to, data);
516
+ else if (type === 'ice-candidate') await fb2.sendIceCandidate(to, data);
517
+ });
518
+
519
+ // Track state
520
+ let connected = false;
521
+ let messages1 = [];
522
+ let messages2 = [];
523
+
524
+ // Wire Firebase events to WebRTC
525
+ fb1.on('peer-discovered', async ({ peerId }) => {
526
+ console.log(`[P1] Discovered: ${peerId.slice(0, 8)}`);
527
+ if (peer1Id > peerId) {
528
+ await webrtc1.connectToPeer(peerId);
529
+ }
530
+ });
531
+
532
+ fb2.on('peer-discovered', async ({ peerId }) => {
533
+ console.log(`[P2] Discovered: ${peerId.slice(0, 8)}`);
534
+ if (peer2Id > peerId) {
535
+ await webrtc2.connectToPeer(peerId);
536
+ }
537
+ });
538
+
539
+ fb1.on('offer', async ({ from, offer }) => {
540
+ console.log(`[P1] Received offer`);
541
+ await webrtc1.handleOffer({ from, offer });
542
+ });
543
+
544
+ fb2.on('offer', async ({ from, offer }) => {
545
+ console.log(`[P2] Received offer`);
546
+ await webrtc2.handleOffer({ from, offer });
547
+ });
548
+
549
+ fb1.on('answer', async ({ from, answer }) => {
550
+ console.log(`[P1] Received answer`);
551
+ await webrtc1.handleAnswer({ from, answer });
552
+ });
553
+
554
+ fb2.on('answer', async ({ from, answer }) => {
555
+ console.log(`[P2] Received answer`);
556
+ await webrtc2.handleAnswer({ from, answer });
557
+ });
558
+
559
+ fb1.on('ice-candidate', async ({ from, candidate }) => {
560
+ await webrtc1.handleIceCandidate({ from, candidate });
561
+ });
562
+
563
+ fb2.on('ice-candidate', async ({ from, candidate }) => {
564
+ await webrtc2.handleIceCandidate({ from, candidate });
565
+ });
566
+
567
+ webrtc1.on('peer-connected', (peerId) => {
568
+ console.log(`[P1] Connected to ${peerId.slice(0, 8)}`);
569
+ connected = true;
570
+ });
571
+
572
+ webrtc2.on('peer-connected', (peerId) => {
573
+ console.log(`[P2] Connected to ${peerId.slice(0, 8)}`);
574
+ connected = true;
575
+ });
576
+
577
+ webrtc1.on('message', ({ message }) => {
578
+ console.log(`[P1] Message:`, JSON.stringify(message).slice(0, 50));
579
+ messages1.push(message);
580
+ });
581
+
582
+ webrtc2.on('message', ({ message }) => {
583
+ console.log(`[P2] Message:`, JSON.stringify(message).slice(0, 50));
584
+ messages2.push(message);
585
+ });
586
+
587
+ // Connect to Firebase
588
+ console.log('Connecting to Firebase...');
589
+ const conn1 = await fb1.connect();
590
+ const conn2 = await fb2.connect();
591
+
592
+ if (!conn1 || !conn2) {
593
+ console.error('Firebase connection failed');
594
+ process.exit(1);
595
+ }
596
+
597
+ console.log('Both peers connected to Firebase\n');
598
+
599
+ // Wait for WebRTC connection
600
+ const timeout = 45000;
601
+ const start = Date.now();
602
+
603
+ while (!connected && Date.now() - start < timeout) {
604
+ await new Promise(r => setTimeout(r, 500));
605
+ }
606
+
607
+ if (!connected) {
608
+ console.error('\nWebRTC connection timeout');
609
+ await fb1.disconnect();
610
+ await fb2.disconnect();
611
+ process.exit(1);
612
+ }
613
+
614
+ // Exchange messages
615
+ console.log('\nExchanging messages...');
616
+ await new Promise(r => setTimeout(r, 1000));
617
+
618
+ webrtc1.sendToPeer(peer2Id, { type: 'hello', from: 'peer1' });
619
+ webrtc2.sendToPeer(peer1Id, { type: 'hello', from: 'peer2' });
620
+
621
+ await new Promise(r => setTimeout(r, 2000));
622
+
623
+ // Results
624
+ console.log('\n' + '='.repeat(60));
625
+ console.log(' RESULTS');
626
+ console.log('='.repeat(60));
627
+ console.log(`P1 messages: ${messages1.length}`);
628
+ console.log(`P2 messages: ${messages2.length}`);
629
+
630
+ const success = messages1.length > 0 && messages2.length > 0;
631
+ console.log(`\nTEST ${success ? 'PASSED' : 'FAILED'}`);
632
+
633
+ // Cleanup
634
+ webrtc1.close();
635
+ webrtc2.close();
636
+ await fb1.disconnect();
637
+ await fb2.disconnect();
638
+
639
+ process.exit(success ? 0 : 1);
640
+ }
641
+
642
+ // ============================================
643
+ // ENTRY POINT
644
+ // ============================================
645
+
646
+ const args = process.argv.slice(2);
647
+
648
+ if (args.includes('--peer')) {
649
+ const peerIndex = args.indexOf('--peer');
650
+ const peerNum = parseInt(args[peerIndex + 1], 10);
651
+ const roomIndex = args.indexOf('--room');
652
+ const room = roomIndex !== -1 ? args[roomIndex + 1] : TEST_CONFIG.testRoom;
653
+
654
+ runPeer(peerNum, room).catch(err => {
655
+ console.error('Peer error:', err);
656
+ process.exit(1);
657
+ });
658
+ } else if (args.includes('--inline')) {
659
+ runInlineTest().catch(err => {
660
+ console.error('Inline test error:', err);
661
+ process.exit(1);
662
+ });
663
+ } else if (args.includes('--firebase')) {
664
+ runFirebaseTest().catch(err => {
665
+ console.error('Firebase test error:', err);
666
+ process.exit(1);
667
+ });
668
+ } else if (args.includes('--dual')) {
669
+ runDualPeerTest().catch(err => {
670
+ console.error('Dual peer test error:', err);
671
+ process.exit(1);
672
+ });
673
+ } else {
674
+ // Default: run inline test (simplest, no subprocesses)
675
+ console.log('Usage:');
676
+ console.log(' node tests/webrtc-peer-test.js --inline # Same-process test (recommended)');
677
+ console.log(' node tests/webrtc-peer-test.js --firebase # Firebase signaling test');
678
+ console.log(' node tests/webrtc-peer-test.js --dual # Two-process test');
679
+ console.log('');
680
+ console.log('Running inline test by default...\n');
681
+
682
+ runInlineTest().catch(err => {
683
+ console.error('Test error:', err);
684
+ process.exit(1);
685
+ });
686
+ }