mirror of
https://github.com/xtr-dev/rondevu-demo.git
synced 2025-12-13 03:53:22 +00:00
- Move onicecandidate handler setup before setLocalDescription - Directly use API for sending candidates instead of signaler - Use signaler only for receiving remote candidates
193 lines
5.6 KiB
JavaScript
193 lines
5.6 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Test script to connect to @bas's chat service and send a "hello" message
|
|
*
|
|
* IMPORTANT: This script requires the 'wrtc' package which must be installed separately.
|
|
* See TEST_README.md for detailed installation instructions.
|
|
*
|
|
* Quick start:
|
|
* npm install wrtc
|
|
* npm test
|
|
*
|
|
* Requirements:
|
|
* - Node.js 19+ (or Node.js 18 with --experimental-global-webcrypto)
|
|
* - wrtc package (requires native compilation)
|
|
* - Build tools (python, make, g++)
|
|
*/
|
|
|
|
import { Rondevu, RondevuSignaler, NodeCryptoAdapter } from '@xtr-dev/rondevu-client'
|
|
|
|
// Import wrtc
|
|
let wrtc
|
|
try {
|
|
const wrtcModule = await import('wrtc')
|
|
wrtc = wrtcModule.default || wrtcModule
|
|
} catch (error) {
|
|
console.error('❌ Error: wrtc package not found or failed to load')
|
|
console.error('\nThe wrtc package is required for WebRTC support in Node.js.')
|
|
console.error('Install it with:')
|
|
console.error('\n npm install wrtc')
|
|
console.error('\nNote: wrtc requires native compilation and may take a few minutes to install.')
|
|
console.error('\nError details:', error.message)
|
|
process.exit(1)
|
|
}
|
|
|
|
const { RTCPeerConnection } = wrtc
|
|
|
|
// Configuration
|
|
const API_URL = 'https://api.ronde.vu'
|
|
const TARGET_USER = 'bas'
|
|
const SERVICE_FQN = `chat:2.0.0@${TARGET_USER}`
|
|
const MESSAGE = 'hello'
|
|
|
|
// TURN server configuration (IPv4 TURN only - matches demo default)
|
|
const RTC_CONFIG = {
|
|
iceServers: [
|
|
{ urls: 'stun:57.129.61.67:3478' },
|
|
{
|
|
urls: [
|
|
'turn:57.129.61.67:3478?transport=tcp',
|
|
'turn:57.129.61.67:3478?transport=udp',
|
|
],
|
|
username: 'webrtcuser',
|
|
credential: 'supersecretpassword'
|
|
}
|
|
]
|
|
}
|
|
|
|
async function main() {
|
|
console.log('🚀 Rondevu Test Script')
|
|
console.log('='.repeat(50))
|
|
|
|
try {
|
|
// 1. Initialize Rondevu with Node crypto adapter
|
|
console.log('1. Initializing Rondevu client...')
|
|
const rondevu = new Rondevu({
|
|
apiUrl: API_URL,
|
|
username: `test-${Date.now()}`, // Anonymous test user
|
|
cryptoAdapter: new NodeCryptoAdapter()
|
|
})
|
|
|
|
await rondevu.initialize()
|
|
console.log(` ✓ Initialized as: ${rondevu.getUsername()}`)
|
|
console.log(` ✓ Public key: ${rondevu.getPublicKey()?.substring(0, 20)}...`)
|
|
|
|
// 2. Discover service
|
|
console.log(`\n2. Looking for service: ${SERVICE_FQN}`)
|
|
const serviceData = await rondevu.getService(SERVICE_FQN)
|
|
console.log(` ✓ Found service from @${serviceData.username}`)
|
|
console.log(` ✓ Offer ID: ${serviceData.offerId}`)
|
|
|
|
// 3. Create peer connection
|
|
console.log('\n3. Creating WebRTC peer connection...')
|
|
const pc = new RTCPeerConnection(RTC_CONFIG)
|
|
|
|
// 4. Create data channel
|
|
console.log('4. Creating data channel...')
|
|
const dc = pc.createDataChannel('chat')
|
|
|
|
// Set up data channel handlers
|
|
dc.onopen = () => {
|
|
console.log(' ✓ Data channel opened!')
|
|
console.log(`\n📤 Sending message: "${MESSAGE}"`)
|
|
dc.send(MESSAGE)
|
|
|
|
// Close after sending
|
|
setTimeout(() => {
|
|
console.log('\n✅ Test completed successfully!')
|
|
dc.close()
|
|
pc.close()
|
|
process.exit(0)
|
|
}, 1000)
|
|
}
|
|
|
|
dc.onmessage = (event) => {
|
|
console.log(`📥 Received: ${event.data}`)
|
|
}
|
|
|
|
dc.onerror = (error) => {
|
|
console.error('❌ Data channel error:', error)
|
|
process.exit(1)
|
|
}
|
|
|
|
// 5. Set up ICE candidate exchange FIRST
|
|
console.log('5. Setting up ICE candidate exchange...')
|
|
|
|
// Send our ICE candidates
|
|
pc.onicecandidate = async (event) => {
|
|
if (event.candidate) {
|
|
console.log(' 📤 Sending ICE candidate')
|
|
try {
|
|
await rondevu.getAPIPublic().addOfferIceCandidates(
|
|
serviceData.serviceFqn,
|
|
serviceData.offerId,
|
|
[event.candidate.toJSON()]
|
|
)
|
|
} catch (err) {
|
|
console.error(' ❌ Failed to send ICE candidate:', err.message)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start polling for remote ICE candidates
|
|
const signaler = new RondevuSignaler(rondevu, SERVICE_FQN, TARGET_USER)
|
|
signaler.offerId = serviceData.offerId
|
|
signaler.serviceFqn = serviceData.serviceFqn
|
|
|
|
signaler.addListener((candidate) => {
|
|
console.log(' 📥 Received ICE candidate')
|
|
pc.addIceCandidate(candidate)
|
|
})
|
|
|
|
// 6. Set remote offer
|
|
console.log('6. Setting remote offer...')
|
|
await pc.setRemoteDescription({ type: 'offer', sdp: serviceData.sdp })
|
|
console.log(' ✓ Remote offer set')
|
|
|
|
// 7. Create and set local answer (this triggers ICE gathering)
|
|
console.log('7. Creating answer...')
|
|
const answer = await pc.createAnswer()
|
|
await pc.setLocalDescription(answer)
|
|
console.log(' ✓ Local answer set')
|
|
|
|
// 8. Send answer to server
|
|
console.log('8. Sending answer to signaling server...')
|
|
await rondevu.postOfferAnswer(
|
|
serviceData.serviceFqn,
|
|
serviceData.offerId,
|
|
answer.sdp
|
|
)
|
|
console.log(' ✓ Answer sent')
|
|
|
|
// Monitor connection state
|
|
pc.onconnectionstatechange = () => {
|
|
console.log(` Connection state: ${pc.connectionState}`)
|
|
if (pc.connectionState === 'failed') {
|
|
console.error('❌ Connection failed')
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
pc.oniceconnectionstatechange = () => {
|
|
console.log(` ICE state: ${pc.iceConnectionState}`)
|
|
}
|
|
|
|
console.log('\n⏳ Waiting for connection...')
|
|
|
|
// Timeout after 30 seconds
|
|
setTimeout(() => {
|
|
if (pc.connectionState !== 'connected') {
|
|
console.error('❌ Connection timeout')
|
|
process.exit(1)
|
|
}
|
|
}, 30000)
|
|
|
|
} catch (error) {
|
|
console.error('\n❌ Error:', error.message)
|
|
console.error(error)
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
main()
|