diff --git a/package.json b/package.json index f2c2c6b..26919a9 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", - "deploy": "npm run build && npx wrangler pages deploy dist --project-name=rondevu-demo" + "deploy": "npm run build && npx wrangler pages deploy dist --project-name=rondevu-demo", + "test": "node test-connect.js" }, "dependencies": { "@xtr-dev/rondevu-client": "file:../client", @@ -15,7 +16,8 @@ "qrcode": "^1.5.4", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-hot-toast": "^2.6.0" + "react-hot-toast": "^2.6.0", + "wrtc": "^0.4.7" }, "devDependencies": { "@types/react": "^18.2.0", diff --git a/test-connect.js b/test-connect.js new file mode 100644 index 0000000..e3f6703 --- /dev/null +++ b/test-connect.js @@ -0,0 +1,161 @@ +#!/usr/bin/env node +/** + * Test script to connect to @bas's chat service and send a "hello" message + * + * Usage: node test-connect.js + * + * Requires Node.js 19+ or Node.js 18 with --experimental-global-webcrypto + */ + +import { Rondevu, RondevuSignaler, NodeCryptoAdapter } from '@xtr-dev/rondevu-client' +import wrtc from 'wrtc' + +const { RTCPeerConnection } = wrtc + +// Configuration +const API_URL = 'https://rondevu.xtrdev.workers.dev' +const TARGET_USER = 'bas' +const SERVICE_FQN = `chat:1.0.0@${TARGET_USER}` +const MESSAGE = 'hello' + +// TURN server configuration +const RTC_CONFIG = { + iceServers: [ + { urls: 'stun:stun.share.fish:3478' }, + { + urls: [ + 'turns:turn.share.fish:5349?transport=tcp', + 'turns:turn.share.fish:5349?transport=udp', + 'turn:turn.share.fish:3478?transport=tcp', + 'turn:turn.share.fish: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 remote offer + console.log('5. Setting remote offer...') + await pc.setRemoteDescription({ type: 'offer', sdp: serviceData.sdp }) + console.log(' āœ“ Remote offer set') + + // 6. Create and set local answer + console.log('6. Creating answer...') + const answer = await pc.createAnswer() + await pc.setLocalDescription(answer) + console.log(' āœ“ Local answer set') + + // 7. Send answer to server + console.log('7. Sending answer to signaling server...') + await rondevu.postOfferAnswer( + serviceData.serviceFqn, + serviceData.offerId, + answer.sdp + ) + console.log(' āœ“ Answer sent') + + // 8. Handle ICE candidates + console.log('8. Setting up ICE candidate exchange...') + const signaler = new RondevuSignaler(rondevu, SERVICE_FQN, TARGET_USER) + + // Send our ICE candidates + pc.onicecandidate = async (event) => { + if (event.candidate) { + console.log(' šŸ“¤ Sending ICE candidate') + await signaler.addIceCandidate(event.candidate) + } + } + + // Receive remote ICE candidates + signaler.addListener((candidate) => { + console.log(' šŸ“„ Received ICE candidate') + pc.addIceCandidate(candidate) + }) + + // 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()