diff --git a/README.md b/README.md index 638a9cd..e573a03 100644 --- a/README.md +++ b/README.md @@ -74,10 +74,57 @@ npm run start:node -- --port 8083 --bootstrap localhost:8080 ### Command Line Options ```bash ---port # Port to listen on (default: random) ---id # Node ID (default: auto-generated UUID) ---bootstrap # Bootstrap node address (host:port) ---position # Initial ring position (default: 0) +--port # Port to listen on (default: random) +--id # Node ID (default: auto-generated UUID) +--bootstrap # Bootstrap node address (host:port) +--position # Initial ring position (default: 0) +--ice-servers # ICE servers configuration (JSON array) +--config # Load configuration from JSON file +--help # Show help message +``` + +### WebRTC ICE Servers Configuration + +The Ring Network uses WebRTC for peer-to-peer connections. You can configure custom ICE servers (STUN/TURN) for better connectivity: + +#### Using Command Line +```bash +# With custom STUN servers +node node.js --ice-servers '[{"urls":"stun:your-stun-server.com:19302"}]' + +# With TURN server for NAT traversal +node node.js --ice-servers '[{"urls":"stun:stun.l.google.com:19302"},{"urls":"turn:your-turn-server.com:3478","username":"user","credential":"pass"}]' +``` + +#### Using Configuration Files +```bash +# Load from config file +node node.js --config config/ice-servers-with-turn.json + +# For Oracle nodes +node oracle.js --config config/ice-servers-public-turn.json +``` + +#### Available Configuration Examples +- `config/ice-servers-default.json` - Default Google STUN servers +- `config/ice-servers-with-turn.json` - Template with TURN server +- `config/ice-servers-public-turn.json` - Public TURN servers for testing + +#### ICE Server Configuration Format +```json +{ + "iceServers": [ + { + "urls": "stun:stun.l.google.com:19302" + }, + { + "urls": "turn:turnserver.example.com:3478", + "username": "your_username", + "credential": "your_password" + } + ] +} +``` --help # Show help message ``` diff --git a/config/ice-servers-default.json b/config/ice-servers-default.json new file mode 100644 index 0000000..4ed756f --- /dev/null +++ b/config/ice-servers-default.json @@ -0,0 +1,10 @@ +{ + "iceServers": [ + { + "urls": "stun:stun.l.google.com:19302" + }, + { + "urls": "stun:stun1.l.google.com:19302" + } + ] +} diff --git a/config/ice-servers-public-turn.json b/config/ice-servers-public-turn.json new file mode 100644 index 0000000..4f78689 --- /dev/null +++ b/config/ice-servers-public-turn.json @@ -0,0 +1,21 @@ +{ + "iceServers": [ + { + "urls": [ + "stun:stun.l.google.com:19302", + "stun:stun1.l.google.com:19302", + "stun:stun2.l.google.com:19302" + ] + }, + { + "urls": "turn:openrelay.metered.ca:80", + "username": "openrelayproject", + "credential": "openrelayproject" + }, + { + "urls": "turn:openrelay.metered.ca:443", + "username": "openrelayproject", + "credential": "openrelayproject" + } + ] +} diff --git a/config/ice-servers-with-turn.json b/config/ice-servers-with-turn.json new file mode 100644 index 0000000..6190772 --- /dev/null +++ b/config/ice-servers-with-turn.json @@ -0,0 +1,15 @@ +{ + "iceServers": [ + { + "urls": "stun:stun.l.google.com:19302" + }, + { + "urls": "stun:stun1.l.google.com:19302" + }, + { + "urls": "turn:turnserver.example.com:3478", + "username": "your_username", + "credential": "your_password" + } + ] +} diff --git a/node.js b/node.js index ea96c38..7baf1c2 100644 --- a/node.js +++ b/node.js @@ -2,6 +2,7 @@ import { RingNode } from './src/ring-node.js'; import chalk from 'chalk'; +import { readFileSync } from 'fs'; const args = process.argv.slice(2); const options = {}; @@ -21,6 +22,26 @@ for (let i = 0; i < args.length; i++) { case '--position': options.ringPosition = parseInt(args[++i]); break; + case '--ice-servers': + try { + options.iceServers = JSON.parse(args[++i]); + } catch (error) { + console.error(chalk.red('Error parsing ICE servers JSON:'), error.message); + process.exit(1); + } + break; + case '--config': + try { + const configPath = args[++i]; + const configData = JSON.parse(readFileSync(configPath, 'utf8')); + if (configData.iceServers) { + options.iceServers = configData.iceServers; + } + } catch (error) { + console.error(chalk.red('Error loading config file:'), error.message); + process.exit(1); + } + break; case '--help': console.log(` ${chalk.blue('Ring Network Node')} @@ -28,16 +49,27 @@ ${chalk.blue('Ring Network Node')} Usage: node node.js [options] Options: - --port Port to listen on (default: random) - --id Node ID (default: auto-generated) - --bootstrap Bootstrap node address (host:port) - --position Initial ring position (default: 0) - --help Show this help message + --port Port to listen on (default: random) + --id Node ID (default: auto-generated) + --bootstrap Bootstrap node address (host:port) + --position Initial ring position (default: 0) + --ice-servers ICE servers configuration (JSON array) + --config Load configuration from JSON file + --help Show this help message Examples: node node.js --port 8080 node node.js --port 8081 --bootstrap localhost:8080 node node.js --id mynode --port 8082 --bootstrap localhost:8080 + node node.js --config config/ice-servers-with-turn.json --port 8080 + +ICE Servers Example: + --ice-servers '[{"urls":"stun:stun.l.google.com:19302"},{"urls":"turn:turn.example.com:3478","username":"user","credential":"pass"}]' + +Config File Example: + config/ice-servers-default.json - Default STUN servers + config/ice-servers-with-turn.json - With TURN server + config/ice-servers-public-turn.json - Public TURN servers `); process.exit(0); break; diff --git a/oracle.js b/oracle.js index bf48a35..f9ae9f2 100644 --- a/oracle.js +++ b/oracle.js @@ -2,6 +2,7 @@ import { OracleNode } from './src/oracle-node.js'; import chalk from 'chalk'; +import { readFileSync } from 'fs'; const args = process.argv.slice(2); const options = {}; @@ -21,6 +22,26 @@ for (let i = 0; i < args.length; i++) { case '--position': options.ringPosition = parseInt(args[++i]); break; + case '--ice-servers': + try { + options.iceServers = JSON.parse(args[++i]); + } catch (error) { + console.error(chalk.red('Error parsing ICE servers JSON:'), error.message); + process.exit(1); + } + break; + case '--config': + try { + const configPath = args[++i]; + const configData = JSON.parse(readFileSync(configPath, 'utf8')); + if (configData.iceServers) { + options.iceServers = configData.iceServers; + } + } catch (error) { + console.error(chalk.red('Error loading config file:'), error.message); + process.exit(1); + } + break; case '--help': console.log(` ${chalk.yellow('Ring Network Oracle Node')} @@ -28,16 +49,27 @@ ${chalk.yellow('Ring Network Oracle Node')} Usage: node oracle.js [options] Options: - --port Port to listen on (default: random) - --id Node ID (default: auto-generated) - --bootstrap Bootstrap node address (host:port) - --position Initial ring position (default: 0) - --help Show this help message + --port Port to listen on (default: random) + --id Node ID (default: auto-generated) + --bootstrap Bootstrap node address (host:port) + --position Initial ring position (default: 0) + --ice-servers ICE servers configuration (JSON array) + --config Load configuration from JSON file + --help Show this help message Examples: node oracle.js --port 8080 node oracle.js --port 8081 --bootstrap localhost:8080 node oracle.js --id oracle1 --port 8082 --bootstrap localhost:8080 + node oracle.js --config config/ice-servers-with-turn.json --port 8080 + +ICE Servers Example: + --ice-servers '[{"urls":"stun:stun.l.google.com:19302"},{"urls":"turn:turn.example.com:3478","username":"user","credential":"pass"}]' + +Config File Example: + config/ice-servers-default.json - Default STUN servers + config/ice-servers-with-turn.json - With TURN server + config/ice-servers-public-turn.json - Public TURN servers Oracle Services: - network-analysis: Analyze network topology and health diff --git a/src/ring-node.js b/src/ring-node.js index 917d80c..394b5cd 100644 --- a/src/ring-node.js +++ b/src/ring-node.js @@ -24,7 +24,9 @@ export class RingNode extends EventEmitter { } }; - this.webrtc = new WebRTCManager(this.id); + this.webrtc = new WebRTCManager(this.id, { + iceServers: options.iceServers + }); this.discoveryServer = null; this.knownNodes = new Map(); this.messageHistory = new Set(); diff --git a/src/webrtc-manager.js b/src/webrtc-manager.js index 654a37e..f18e5a8 100644 --- a/src/webrtc-manager.js +++ b/src/webrtc-manager.js @@ -4,11 +4,26 @@ import { v4 as uuidv4 } from 'uuid'; import wrtc from '@koush/wrtc'; export class WebRTCManager extends EventEmitter { - constructor(nodeId) { + constructor(nodeId, options = {}) { super(); this.nodeId = nodeId; this.connections = new Map(); this.pendingConnections = new Map(); + + // Configure ICE servers with defaults + this.iceServers = options.iceServers || [ + { urls: 'stun:stun.l.google.com:19302' }, + { urls: 'stun:stun1.l.google.com:19302' } + ]; + + // Validate ICE servers configuration + try { + WebRTCManager.validateIceServers(this.iceServers); + console.log(`🌐 Using ${this.iceServers.length} ICE server(s) for WebRTC connections`); + } catch (error) { + console.error('Invalid ICE servers configuration:', error.message); + throw error; + } } async createConnection(peerId, isInitiator = false) { @@ -23,10 +38,7 @@ export class WebRTCManager extends EventEmitter { trickle: false, wrtc: wrtc, config: { - iceServers: [ - { urls: 'stun:stun.l.google.com:19302' }, - { urls: 'stun:stun1.l.google.com:19302' } - ] + iceServers: this.iceServers } }); @@ -159,4 +171,32 @@ export class WebRTCManager extends EventEmitter { } this.connections.clear(); } + + static validateIceServers(iceServers) { + if (!Array.isArray(iceServers)) { + throw new Error('ICE servers must be an array'); + } + + for (const server of iceServers) { + if (!server.urls) { + throw new Error('Each ICE server must have a "urls" property'); + } + + const urls = Array.isArray(server.urls) ? server.urls : [server.urls]; + for (const url of urls) { + if (!url.startsWith('stun:') && !url.startsWith('turn:') && !url.startsWith('turns:')) { + throw new Error(`Invalid ICE server URL: ${url}. Must start with stun:, turn:, or turns:`); + } + } + + // If it's a TURN server, it should have credentials + if (urls.some(url => url.startsWith('turn:') || url.startsWith('turns:'))) { + if (!server.username || !server.credential) { + console.warn(`Warning: TURN server ${server.urls} missing username/credential`); + } + } + } + + return true; + } }