/** * WebRTC Named Channels Test * * Tests multiple DataChannels with different names to the same peer. * * Usage (two terminals): * * Terminal 1 (initiator): * ``` * PHYHUB_DIRECT=true \ * DEVICE_ID= \ * ACCESS_KEY= \ * PEER_TWIN_ID= \ * ROLE=initiator \ * bunx ts-node src/test/webrtc-channel-names-test.ts * ``` * * Terminal 2 (responder): * ``` * PHYHUB_DIRECT=true \ * DEVICE_ID= \ * ACCESS_KEY= \ * PEER_TWIN_ID= \ * ROLE=responder \ * bunx ts-node src/test/webrtc-channel-names-test.ts * ``` */ import { PhyHubClient } from '../index'; import { PhygridDataChannel } from '../services/webrtc/types'; interface TestResult { channel: string; sent: number; received: number; passed: boolean; } const CHANNEL_NAMES = [undefined, 'control', 'data', 'telemetry'] as const; const MESSAGES_PER_CHANNEL = 3; async function runInitiator(peerTwinId: string): Promise { console.log('\n=== Running as INITIATOR ===\n'); const client = await PhyHubClient.connect(); console.log('Connected to PhyHub!\n'); const results: TestResult[] = []; const channels: Map = new Map(); const receivedCounts: Map = new Map(); // Test each channel name for (const channelName of CHANNEL_NAMES) { const displayName = channelName ?? 'default'; console.log(`\n--- Creating channel: "${displayName}" ---`); try { const channel = await client.getDataChannel(peerTwinId, channelName); console.log(`Channel "${displayName}" created. Label: ${channel.getChannelName()}`); // Verify channel name const expectedName = channelName ?? 'default'; if (channel.getChannelName() !== expectedName) { console.error(`ERROR: Expected channel name "${expectedName}", got "${channel.getChannelName()}"`); } channels.set(displayName, channel); receivedCounts.set(displayName, 0); // Setup message handler channel.onMessage((data) => { console.log(`[${displayName}] RECEIVED:`, data); receivedCounts.set(displayName, (receivedCounts.get(displayName) || 0) + 1); }); } catch (error) { console.error(`Failed to create channel "${displayName}":`, error); results.push({ channel: displayName, sent: 0, received: 0, passed: false, }); } } // Wait for responder to set up channels console.log('\nWaiting 3 seconds for responder to set up...'); await sleep(3000); // Send messages on each channel console.log('\n--- Sending messages ---\n'); for (const [displayName, channel] of channels) { if (!channel.isOpen()) { console.log(`Channel "${displayName}" not open, skipping...`); continue; } for (let i = 1; i <= MESSAGES_PER_CHANNEL; i++) { const msg = { channel: displayName, messageNum: i, from: 'initiator', timestamp: Date.now(), }; console.log(`[${displayName}] SENDING:`, msg); channel.send(msg); await sleep(200); } } // Wait for responses console.log('\nWaiting 5 seconds for responses...'); await sleep(5000); // Compile results console.log('\n--- Test Results ---\n'); for (const [displayName] of channels) { const received = receivedCounts.get(displayName) || 0; const passed = received >= MESSAGES_PER_CHANNEL; results.push({ channel: displayName, sent: MESSAGES_PER_CHANNEL, received, passed, }); } // Print results console.log('Channel'.padEnd(15) + 'Sent'.padEnd(8) + 'Received'.padEnd(10) + 'Status'); console.log('-'.repeat(45)); for (const result of results) { const status = result.passed ? '✅ PASS' : '❌ FAIL'; console.log( result.channel.padEnd(15) + result.sent.toString().padEnd(8) + result.received.toString().padEnd(10) + status, ); } // Summary const allPassed = results.every((r) => r.passed); console.log('\n' + '='.repeat(45)); console.log(allPassed ? '✅ ALL TESTS PASSED' : '❌ SOME TESTS FAILED'); // Close all channels for (const channel of channels.values()) { channel.close(); } } async function runResponder(peerTwinId: string): Promise { console.log('\n=== Running as RESPONDER ===\n'); const client = await PhyHubClient.connect(); console.log('Connected to PhyHub!\n'); const channels: Map = new Map(); const receivedCounts: Map = new Map(); let channelsReceived = 0; return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Timeout waiting for channels (60s)')); }, 60000); // Accept each channel name for (const channelName of CHANNEL_NAMES) { const displayName = channelName ?? 'default'; console.log(`Waiting for channel: "${displayName}"`); client.onDataChannel( (channel) => { console.log(`\n[${displayName}] Channel received! Label: ${channel.getChannelName()}`); channels.set(displayName, channel); receivedCounts.set(displayName, 0); channelsReceived++; channel.onMessage((data) => { console.log(`[${displayName}] RECEIVED:`, data); receivedCounts.set(displayName, (receivedCounts.get(displayName) || 0) + 1); // Echo back const response = { channel: displayName, type: 'echo', originalMessage: data.messageNum, from: 'responder', timestamp: Date.now(), }; console.log(`[${displayName}] SENDING:`, response); channel.send(response); }); channel.onClose(() => { console.log(`[${displayName}] Channel closed`); // Check if all channels closed const openChannels = Array.from(channels.values()).filter((c) => c.isOpen()); if (openChannels.length === 0 && channelsReceived === CHANNEL_NAMES.length) { clearTimeout(timeout); // Print summary console.log('\n--- Responder Summary ---\n'); console.log('Channel'.padEnd(15) + 'Messages Received'); console.log('-'.repeat(35)); for (const [name, count] of receivedCounts) { console.log(name.padEnd(15) + count.toString()); } resolve(); } }); }, { channelName }, ); } }); } function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } async function main(): Promise { console.log('='.repeat(60)); console.log('WebRTC Named Channels Test'); console.log('='.repeat(60)); if (process.env.PHYHUB_DIRECT !== 'true') { console.error('ERROR: Set PHYHUB_DIRECT=true'); process.exit(1); } const peerTwinId = process.env.PEER_TWIN_ID; const role = process.env.ROLE?.toLowerCase(); if (!peerTwinId) { console.error('ERROR: Set PEER_TWIN_ID'); process.exit(1); } if (role !== 'initiator' && role !== 'responder') { console.error('ERROR: Set ROLE to initiator or responder'); process.exit(1); } console.log(`\nRole: ${role}`); console.log(`Peer Twin ID: ${peerTwinId}`); console.log(`Channels to test: ${CHANNEL_NAMES.map((n) => n ?? 'default').join(', ')}`); try { if (role === 'initiator') { await runInitiator(peerTwinId); } else { await runResponder(peerTwinId); } console.log('\n[SUCCESS] Test completed!'); process.exit(0); } catch (error) { console.error('\n[FAILED]', error); process.exit(1); } } if (require.main === module) { main(); }