diff --git a/NODE_HOST_GUIDE.md b/NODE_HOST_GUIDE.md deleted file mode 100644 index 68d9386..0000000 --- a/NODE_HOST_GUIDE.md +++ /dev/null @@ -1,591 +0,0 @@ -# Hosting WebRTC Services with Node.js - -This guide shows you how to create a WebRTC service host in Node.js that web clients can discover and connect to using Rondevu. - -## Table of Contents - -- [Overview](#overview) -- [Prerequisites](#prerequisites) -- [Node.js Host (Offerer)](#nodejs-host-offerer) -- [Browser Client (Answerer)](#browser-client-answerer) -- [Message Protocol](#message-protocol) -- [WebRTC Patterns](#webrtc-patterns) -- [TURN Server Configuration](#turn-server-configuration) -- [Troubleshooting](#troubleshooting) - -## Overview - -In this pattern: -- **Node.js host** runs a service (e.g., chat bot, data processor) and publishes offers on Rondevu -- **Browser clients** discover the service and connect via WebRTC -- **Direct P2P communication** happens over WebRTC data channels (no server relay after connection) - -``` -┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ -│ Node.js Host │────1───▶│ Rondevu │◀───2────│ Browser Client │ -│ (Offerer) │ │ Server │ │ (Answerer) │ -│ │ │ │ │ │ -│ Publishes offer │ │ Signaling │ │ Gets offer │ -│ Creates channel │ │ │ │ Receives channel│ -└────────┬────────┘ └──────────────┘ └────────┬────────┘ - │ │ - │ 3. WebRTC P2P Connection │ - └─────────────────────────────────────────────────────┘ - (Data channel messages) -``` - -## Prerequisites - -### Node.js Requirements - -- Node.js 19+ (recommended), OR -- Node.js 18 with `--experimental-global-webcrypto` flag - -### Install Dependencies - -```bash -npm install @xtr-dev/rondevu-client wrtc -``` - -**Important:** `wrtc` requires native compilation and build tools: - -**Ubuntu/Debian:** -```bash -sudo apt-get install python3 make g++ -npm install wrtc -``` - -**macOS:** -```bash -# Xcode Command Line Tools required -xcode-select --install -npm install wrtc -``` - -**Windows:** -```bash -# Visual Studio Build Tools required -npm install --global windows-build-tools -npm install wrtc -``` - -Installation may take several minutes as wrtc compiles native WebRTC libraries. - -## Node.js Host (Offerer) - -Here's a complete example of a Node.js service host that creates a chat bot: - -```javascript -#!/usr/bin/env node -import { Rondevu, NodeCryptoAdapter } from '@xtr-dev/rondevu-client' -import wrtcModule from 'wrtc' - -const { RTCPeerConnection } = wrtcModule - -// Configuration -const API_URL = 'https://api.ronde.vu' -const USERNAME = 'chatbot' // Your service username -const SERVICE = 'chat:2.0.0' // Service name (username will be auto-appended) - -async function main() { - console.log('🤖 Starting Chat Bot Service') - console.log('='.repeat(50)) - - // 1. Connect to Rondevu with Node crypto adapter and ICE server preset - console.log('1. Connecting to Rondevu...') - const rondevu = await Rondevu.connect({ - apiUrl: API_URL, - username: USERNAME, - cryptoAdapter: new NodeCryptoAdapter(), - iceServers: 'ipv4-turn' // Use preset: 'ipv4-turn', 'hostname-turns', 'google-stun', or 'relay-only' - }) - - console.log(` ✓ Connected as: ${rondevu.getUsername()}`) - console.log(` ✓ Public key: ${rondevu.getPublicKey()?.substring(0, 20)}...`) - - // 2. Publish service with automatic offer management - console.log('2. Publishing service with automatic offer management...') - - await rondevu.publishService({ - service: SERVICE, - maxOffers: 5, // Maintain up to 5 concurrent offers - offerFactory: async (rtcConfig) => { - console.log('\n3. Creating new WebRTC offer...') - const pc = new RTCPeerConnection(rtcConfig) - - // IMPORTANT: Offerer creates the data channel - const dc = pc.createDataChannel('chat', { - ordered: true, - maxRetransmits: 3 - }) - - // Set up data channel handlers - dc.onopen = () => { - console.log(' ✓ Data channel opened with new peer!') - - // Send welcome message - dc.send(JSON.stringify({ - type: 'identify', - from: USERNAME, - publicKey: rondevu.getPublicKey() - })) - } - - dc.onmessage = (event) => { - try { - const msg = JSON.parse(event.data) - console.log(`📥 Message from peer:`, msg) - - if (msg.type === 'identify') { - // Peer identified themselves - console.log(` Peer: @${msg.from}`) - - // Send acknowledgment - dc.send(JSON.stringify({ - type: 'identify_ack', - from: USERNAME, - publicKey: rondevu.getPublicKey() - })) - } else if (msg.type === 'message') { - // Received chat message - echo it back - console.log(` 💬 @${msg.from || 'peer'}: ${msg.text}`) - - dc.send(JSON.stringify({ - type: 'message', - from: USERNAME, - text: `Echo: ${msg.text}` - })) - } - } catch (err) { - console.error('Failed to parse message:', err) - } - } - - dc.onclose = () => { - console.log(' ❌ Data channel closed') - } - - dc.onerror = (error) => { - console.error(' ❌ Data channel error:', error) - } - - // Monitor connection state - pc.onconnectionstatechange = () => { - console.log(` Connection state: ${pc.connectionState}`) - if (pc.connectionState === 'connected') { - console.log(` ✅ Connected to peer!`) - } else if (pc.connectionState === 'failed' || pc.connectionState === 'closed') { - console.log(` ❌ Connection ${pc.connectionState}`) - } - } - - pc.oniceconnectionstatechange = () => { - console.log(` ICE state: ${pc.iceConnectionState}`) - } - - // Create offer - const offer = await pc.createOffer() - await pc.setLocalDescription(offer) - console.log(' ✓ Offer created and local description set') - - return { pc, dc, offer } - }, - ttl: 300000 // 5 minutes per offer - }) - - console.log(` ✓ Service published: ${SERVICE}@${USERNAME}`) - - // 3. Start automatic offer pool management - console.log('3. Starting automatic offer pool management...') - await rondevu.startFilling() - console.log(` ✓ Maintaining up to 5 concurrent offers`) - console.log(` ✓ Polling for answers and ICE candidates`) - console.log(`\n✅ Service is live! Clients can connect to: ${SERVICE}@${USERNAME}`) - - // 4. Handle graceful shutdown - process.on('SIGINT', () => { - console.log('\n\n🛑 Shutting down...') - rondevu.stopFilling() - process.exit(0) - }) -} - -main().catch(err => { - console.error('Fatal error:', err) - process.exit(1) -}) -``` - -### Running the Host - -```bash -# Make executable -chmod +x host-service.js - -# Run -node host-service.js - -# Or with Node 18: -node --experimental-global-webcrypto host-service.js -``` - -## Browser Client (Answerer) - -Here's how to connect from a browser (or see the [demo app](https://ronde.vu) for a full UI): - -```javascript -import { Rondevu } from '@xtr-dev/rondevu-client' - -// Configuration -const API_URL = 'https://api.ronde.vu' -const SERVICE_FQN = 'chat:2.0.0@chatbot' // Full service name with username - -async function connectToService() { - console.log('🌐 Connecting to chat bot...') - - // 1. Connect to Rondevu with ICE server preset - const rondevu = await Rondevu.connect({ - apiUrl: API_URL, - iceServers: 'ipv4-turn' // Use same preset as host - // No username = auto-generated anonymous username - }) - - console.log(`✓ Connected as: ${rondevu.getUsername()}`) - - // 2. Connect to service (automatic WebRTC setup) - console.log(`Looking for service: ${SERVICE_FQN}`) - - const connection = await rondevu.connectToService({ - serviceFqn: SERVICE_FQN, - onConnection: ({ dc, peerUsername }) => { - console.log(`✅ Connected to @${peerUsername}!`) - - // Set up message handler - dc.addEventListener('message', (event) => { - try { - const msg = JSON.parse(event.data) - console.log('📥 Message:', msg) - - if (msg.type === 'identify') { - console.log(` Peer identified as: @${msg.from}`) - } else if (msg.type === 'identify_ack') { - console.log(' ✅ Connection acknowledged!') - - // Send a test message - dc.send(JSON.stringify({ - type: 'message', - text: 'Hello from browser!' - })) - } else if (msg.type === 'message') { - console.log(` 💬 @${msg.from}: ${msg.text}`) - } - } catch (err) { - console.error('Parse error:', err) - } - }) - - // Send identify message - dc.send(JSON.stringify({ - type: 'identify', - from: rondevu.getUsername(), - publicKey: rondevu.getPublicKey() - })) - } - }) - - console.log('✅ Connection established!') - - // Monitor connection state - connection.pc.onconnectionstatechange = () => { - console.log(`Connection state: ${connection.pc.connectionState}`) - if (connection.pc.connectionState === 'failed' || connection.pc.connectionState === 'closed') { - console.log('❌ Connection ended') - } - } -} - -// Run it -connectToService().catch(err => { - console.error('Error:', err) -}) -``` - -## Message Protocol - -The examples above use a simple JSON-based protocol: - -### Message Types - -#### 1. Identify -Sent when a peer first connects to introduce themselves. - -```javascript -{ - type: 'identify', - from: 'username', - publicKey: 'base64-encoded-public-key' // For verification -} -``` - -#### 2. Identify Acknowledgment -Response to identify message. - -```javascript -{ - type: 'identify_ack', - from: 'username', - publicKey: 'base64-encoded-public-key' -} -``` - -#### 3. Chat Message -Actual message content. - -```javascript -{ - type: 'message', - from: 'username', // Optional - text: 'message text' -} -``` - -### Custom Protocols - -You can implement any protocol you want over the data channel: - -```javascript -// Binary protocol -dc.binaryType = 'arraybuffer' -dc.send(new Uint8Array([1, 2, 3, 4])) - -// Custom JSON protocol -dc.send(JSON.stringify({ - type: 'file-transfer', - filename: 'document.pdf', - size: 1024000, - chunks: 100 -})) -``` - -## WebRTC Patterns - -### Critical Pattern: Data Channel Creation - -**IMPORTANT:** In WebRTC, only the **offerer** creates data channels. The **answerer** receives them. - -```javascript -// ✅ CORRECT - Offerer (Node.js host) -const pc = new RTCPeerConnection() -const dc = pc.createDataChannel('chat') // Offerer creates -const offer = await pc.createOffer() -// ... - -// ✅ CORRECT - Answerer (Browser client) -const pc = new RTCPeerConnection() -pc.ondatachannel = (event) => { // Answerer receives via event - const dc = event.channel - // ... -} -await pc.setRemoteDescription(offer) -// ... - -// ❌ WRONG - Answerer creating channel -const pc = new RTCPeerConnection() -const dc = pc.createDataChannel('chat') // DON'T DO THIS! -// This creates a SEPARATE channel that won't communicate -``` - -Creating channels on both sides results in two separate, non-communicating channels. Always follow the offerer/answerer pattern. - -### ICE Candidate Timing - -Set up ICE handlers **before** setting local description to catch all candidates: - -```javascript -// ✅ CORRECT ORDER -pc.onicecandidate = (event) => { - // Send candidate to server -} - -await pc.setLocalDescription(offer) // This triggers ICE gathering - -// ❌ WRONG ORDER -await pc.setLocalDescription(offer) // Starts gathering immediately - -pc.onicecandidate = (event) => { - // Might miss early candidates! -} -``` - -### Answer Before ICE (Answerer) - -Answerers should send their answer **before** ICE gathering to authorize candidate posting: - -```javascript -// ✅ CORRECT - Answer first, then gather ICE -await pc.setRemoteDescription(offer) -const answer = await pc.createAnswer() - -// Send answer to authorize ICE posting -await rondevu.postOfferAnswer(serviceFqn, offerId, answer.sdp) - -// Now set local description (starts ICE gathering) -await pc.setLocalDescription(answer) - -// ICE candidates can now be posted (authorized) -``` - -## TURN Server Configuration - -For production deployments, you'll need TURN servers for NAT traversal: - -```javascript -const RTC_CONFIG = { - iceServers: [ - // STUN for public IP discovery - { urls: 'stun:stun.l.google.com:19302' }, - - // TURN relay for NAT traversal - { - urls: [ - 'turn:your-turn-server.com:3478?transport=tcp', - 'turn:your-turn-server.com:3478?transport=udp', - ], - username: 'your-username', - credential: 'your-password' - } - ] -} -``` - -### Testing TURN Connectivity - -Use `turnutils_uclient` to verify TURN server: - -```bash -# Install coturn utilities -sudo apt-get install coturn-utils - -# Test TURN server -turnutils_uclient -u username -w password your-turn-server.com 3478 -y -``` - -### Force TURN (Testing) - -To test if TURN is working, force relay mode: - -```javascript -const RTC_CONFIG = { - iceServers: [/* ... */], - iceTransportPolicy: 'relay' // Forces TURN, bypasses direct connections -} -``` - -**Remove** `iceTransportPolicy: 'relay'` for production to allow direct connections when possible. - -## Troubleshooting - -### Connection Stuck in "connecting" - -**Possible causes:** -1. TURN server not working -2. Both peers behind same NAT (hairpinning issue) -3. Firewall blocking UDP ports - -**Solutions:** -```javascript -// Enable relay-only mode to test TURN -const RTC_CONFIG = { - iceServers: [/* ... */], - iceTransportPolicy: 'relay' -} - -// Check TURN server -turnutils_uclient -u user -w pass server.com 3478 -y - -// Verify both peers are on different networks -``` - -### No Candidates Gathered - -**Possible causes:** -1. ICE handler set up too late -2. STUN/TURN servers unreachable -3. Firewall blocking - -**Solutions:** -```javascript -// Set handler BEFORE setLocalDescription -pc.onicecandidate = (event) => { /* ... */ } -await pc.setLocalDescription(offer) - -// Test STUN connectivity -ping stun.l.google.com -``` - -### Messages Not Received - -**Possible causes:** -1. Data channel created on both sides -2. Channel not opened yet -3. Wrong channel name - -**Solutions:** -```javascript -// Only offerer creates channel -// Offerer: -const dc = pc.createDataChannel('chat') - -// Answerer: -pc.ondatachannel = (event) => { - const dc = event.channel // Receive it -} - -// Wait for channel to open -dc.onopen = () => { - dc.send('message') // Now safe to send -} -``` - -### wrtc Installation Fails - -**Ubuntu/Debian:** -```bash -sudo apt-get update -sudo apt-get install -y python3 make g++ pkg-config libssl-dev -npm install wrtc -``` - -**macOS:** -```bash -xcode-select --install -npm install wrtc -``` - -**Windows:** -```bash -npm install --global windows-build-tools -npm install wrtc -``` - -## Complete Working Example - -See `/demo/test-connect.js` for a complete working example that connects to the chat demo at `chat:2.0.0@bas`. - -To run: -```bash -cd demo -npm install wrtc -npm test -``` - -## Additional Resources - -- [Rondevu Client API](../client/README.md) -- [WebRTC MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API) -- [wrtc GitHub](https://github.com/node-webrtc/node-webrtc) -- [TURN Server Setup (coturn)](https://github.com/coturn/coturn) - -## License - -MIT