@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.
- package/deploy/.env.example +97 -0
- package/deploy/DEPLOY.md +481 -0
- package/deploy/Dockerfile +99 -0
- package/deploy/docker-compose.yml +162 -0
- package/deploy/genesis-prod.js +1536 -0
- package/deploy/health-check.js +187 -0
- package/deploy/prometheus.yml +38 -0
- package/firebase-signaling.js +41 -2
- package/package.json +8 -1
- package/real-workers.js +9 -4
- package/scheduler.js +8 -4
- package/tests/distributed-workers-test.js +1609 -0
- package/tests/p2p-migration-test.js +1102 -0
- package/tests/webrtc-peer-test.js +686 -0
- package/webrtc.js +693 -40
|
@@ -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
|
+
}
|