WebRTC DataChannel implementation for Node.js with STUN, TURN, NAT traversal, and encryption. Pure Node.js, no native dependencies.
npm install node-rtc-connectionA Node.js WebRTC implementation with full ICE/STUN/TURN support for real peer-to-peer networking.
- ✅ Real Network Transport: Uses actual UDP/TCP sockets for true peer-to-peer connections
- ✅ ICE Support: Full Interactive Connectivity Establishment with candidate gathering
- ✅ STUN Support: NAT traversal with server reflexive candidates
- ✅ TURN Support: Relay candidates for restrictive network environments
- ✅ Data Channels: Reliable and ordered data channels for P2P communication
- ✅ DTLS/SCTP: Secure transport with DTLS encryption and SCTP for data channels
- ✅ Standards Compliant: Follows WebRTC and ICE specifications
``bash`
npm install node-rtc-connection
`javascript
const { RTCPeerConnection } = require('node-rtc-connection');
// Create two peer connections
const pc1 = new RTCPeerConnection({ iceServers: [] });
const pc2 = new RTCPeerConnection({ iceServers: [] });
// Set up data channel on peer 1
const channel = pc1.createDataChannel('chat');
channel.on('open', () => {
console.log('Channel opened!');
channel.send('Hello from Peer 1!');
});
channel.on('message', (event) => {
console.log('Received:', event.data);
});
// Peer 2 receives data channel
pc2.on('datachannel', (event) => {
const channel = event.channel;
channel.on('message', (event) => {
console.log('Received:', event.data);
channel.send('Hello from Peer 2!');
});
});
// Exchange ICE candidates
pc1.on('icecandidate', (e) => {
if (e.candidate) pc2.addIceCandidate(e.candidate);
});
pc2.on('icecandidate', (e) => {
if (e.candidate) pc1.addIceCandidate(e.candidate);
});
// Signaling (offer/answer exchange)
async function connect() {
const offer = await pc1.createOffer();
await pc1.setLocalDescription(offer);
await pc2.setRemoteDescription(offer);
const answer = await pc2.createAnswer();
await pc2.setLocalDescription(answer);
await pc1.setRemoteDescription(answer);
}
connect();
`
`javascript
const { RTCPeerConnection } = require('node-rtc-connection');
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
};
const pc = new RTCPeerConnection(config);
// Listen for gathered ICE candidates
pc.on('icecandidate', (event) => {
if (event.candidate) {
console.log('ICE Candidate:', event.candidate.candidate);
// Send to remote peer via your signaling channel
}
});
// Create offer and start ICE gathering
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
`
`javascript
const { RTCPeerConnection } = require('node-rtc-connection');
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:turn.example.com:3478',
username: 'your-username',
credential: 'your-password'
}
]
};
const pc = new RTCPeerConnection(config);
pc.on('icecandidate', (event) => {
if (event.candidate) {
const candidate = event.candidate.candidate;
// Check candidate type
if (candidate.includes('typ relay')) {
console.log('TURN relay candidate:', candidate);
} else if (candidate.includes('typ srflx')) {
console.log('STUN reflexive candidate:', candidate);
} else if (candidate.includes('typ host')) {
console.log('Host candidate:', candidate);
}
}
});
`
`javascript
const config = {
// Array of ICE servers (STUN/TURN)
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
},
{
urls: [
'turn:turn.example.com:3478?transport=udp',
'turn:turn.example.com:3478?transport=tcp'
],
username: 'user',
credential: 'pass'
}
],
// ICE transport policy
iceTransportPolicy: 'all', // 'all' or 'relay'
// Bundle policy
bundlePolicy: 'balanced', // 'balanced', 'max-bundle', or 'max-compat'
// RTCP mux policy
rtcpMuxPolicy: 'require', // 'negotiate' or 'require'
// ICE candidate pool size
iceCandidatePoolSize: 0
};
const pc = new RTCPeerConnection(config);
`
The library supports query string parameters in ICE server URLs for advanced configuration:
`javascript`
const config = {
iceServers: [
// Transport selection
{
urls: 'turn:turn.example.com:3478?transport=udp',
username: 'user',
credential: 'pass'
},
// Multiple parameters
{
urls: 'turn:turn.example.com:3478?transport=tcp&ttl=86400',
username: 'user',
credential: 'pass'
},
// Multiple URLs with different transports
{
urls: [
'turn:turn.cloudflare.com:3478?transport=udp',
'turn:turn.cloudflare.com:3478?transport=tcp',
'turns:turn.cloudflare.com:5349?transport=tcp'
],
username: 'cloudflare_user',
credential: 'cloudflare_pass'
}
]
};
Supported Query Parameters:
- transport=udp|tcp - Select transport protocol (UDP or TCP)ttl=
- - Set allocation lifetime for TURN (default: 600)
- Custom parameters - Can be added for vendor-specific features
URL Format Examples:
- stun:host:port - Basic STUN serverturn:host:port?transport=udp
- - TURN with UDP transportturn:host:port?transport=tcp&custom=value
- - Multiple parametersturns:host:port?transport=tcp
- - Secure TURN over TLS
`javascript
// Create data channel with options
const channel = pc.createDataChannel('myChannel', {
ordered: true, // Guarantee message order
maxRetransmits: 3, // Max retransmissions (if not ordered)
maxPacketLifeTime: 3000, // Max packet lifetime in ms
protocol: 'custom', // Sub-protocol
negotiated: false, // Manual negotiation
id: 0 // Channel ID (if negotiated)
});
// Events
channel.on('open', () => {
console.log('Channel opened');
});
channel.on('close', () => {
console.log('Channel closed');
});
channel.on('error', (error) => {
console.error('Channel error:', error);
});
channel.on('message', (event) => {
console.log('Message received:', event.data);
});
// Send data
channel.send('Hello World');
channel.send(Buffer.from([1, 2, 3, 4])); // Binary data
// Close channel
channel.close();
`
`javascript
const pc = new RTCPeerConnection(config);
// ICE candidate discovered
pc.on('icecandidate', (event) => {
// event.candidate contains the ICE candidate
});
// ICE gathering state changed
pc.on('icegatheringstatechange', () => {
console.log('Gathering state:', pc.iceGatheringState);
// 'new', 'gathering', or 'complete'
});
// ICE connection state changed
pc.on('iceconnectionstatechange', () => {
console.log('ICE state:', pc.iceConnectionState);
// 'new', 'checking', 'connected', 'completed', 'failed', 'disconnected', 'closed'
});
// Connection state changed
pc.on('connectionstatechange', () => {
console.log('Connection state:', pc.connectionState);
// 'new', 'connecting', 'connected', 'disconnected', 'failed', 'closed'
});
// Signaling state changed
pc.on('signalingstatechange', () => {
console.log('Signaling state:', pc.signalingState);
// 'stable', 'have-local-offer', 'have-remote-offer', 'have-local-pranswer', 'have-remote-pranswer', 'closed'
});
// Data channel received (for answerer)
pc.on('datachannel', (event) => {
const channel = event.channel;
console.log('Received data channel:', channel.label);
});
// Negotiation needed
pc.on('negotiationneeded', () => {
console.log('Negotiation needed');
});
`
`javascript
const { RTCPeerConnection } = require('node-rtc-connection');
async function createPeerConnection() {
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
};
// Create peer connections
const offerer = new RTCPeerConnection(config);
const answerer = new RTCPeerConnection(config);
// Exchange ICE candidates
offerer.on('icecandidate', (e) => {
if (e.candidate) answerer.addIceCandidate(e.candidate);
});
answerer.on('icecandidate', (e) => {
if (e.candidate) offerer.addIceCandidate(e.candidate);
});
// Set up data channel on offerer
const channel = offerer.createDataChannel('chat');
channel.on('open', () => {
console.log('Offerer: Channel opened');
channel.send('Hello from offerer!');
});
channel.on('message', (event) => {
console.log('Offerer received:', event.data);
});
// Answerer receives data channel
answerer.on('datachannel', (event) => {
const channel = event.channel;
channel.on('open', () => {
console.log('Answerer: Channel opened');
});
channel.on('message', (event) => {
console.log('Answerer received:', event.data);
channel.send('Hello from answerer!');
});
});
// Perform signaling
const offer = await offerer.createOffer();
await offerer.setLocalDescription(offer);
await answerer.setRemoteDescription(offer);
const answer = await answerer.createAnswer();
await answerer.setLocalDescription(answer);
await offerer.setRemoteDescription(answer);
// Wait for connection
await new Promise(resolve => setTimeout(resolve, 2000));
// Clean up
channel.close();
offerer.close();
answerer.close();
}
createPeerConnection().catch(console.error);
`
The package includes several example files demonstrating different features:
- examples/real-networking-demo.js - Basic peer-to-peer connection without STUN/TURN
- examples/stun-demo.js - STUN server usage for NAT traversal
- examples/turn-demo.js - TURN relay with full peer-to-peer communication
- examples/peer-connection-demo.js - Simple peer connection setup
- examples/simple-datachannel.js - Basic data channel usage
Run examples:
`bash`
node examples/real-networking-demo.js
node examples/stun-demo.js
node examples/turn-demo.js
The examples use a peer.config.json file for centralized configuration:
`json`
{
"iceServers": [
{ "urls": "stun:stun.l.google.com:19302" }
],
"localDemo": {
"iceServers": []
},
"stunOnly": {
"iceServers": [
{ "urls": "stun:stun.l.google.com:19302" }
]
},
"turnConfig": {
"iceServers": [
{ "urls": "stun:stun.example.com:3478" },
{
"urls": "turn:turn.example.com:3478",
"username": "user",
"credential": "pass"
}
]
}
}
#### Constructor
`javascript`
new RTCPeerConnection(configuration?)
#### Methods
- createOffer(options?) - Create SDP offercreateAnswer(options?)
- - Create SDP answersetLocalDescription(description)
- - Set local SDPsetRemoteDescription(description)
- - Set remote SDPaddIceCandidate(candidate)
- - Add remote ICE candidatecreateDataChannel(label, options?)
- - Create data channelclose()
- - Close the connection
#### Properties
- localDescription - Local SDP descriptionremoteDescription
- - Remote SDP descriptionsignalingState
- - Current signaling stateiceGatheringState
- - ICE gathering stateiceConnectionState
- - ICE connection stateconnectionState
- - Overall connection state
#### Methods
- send(data) - Send data (string or Buffer)close()
- - Close the channel
#### Properties
- label - Channel labelordered
- - Whether messages are orderedmaxRetransmits
- - Maximum retransmissionsmaxPacketLifeTime
- - Maximum packet lifetimeprotocol
- - Sub-protocolnegotiated
- - Whether manually negotiatedid
- - Channel IDreadyState
- - Current state ('connecting', 'open', 'closing', 'closed')bufferedAmount
- - Bytes queued to send
- Node.js 14 or higher
- UDP/TCP network access for ICE connectivity
For production use, it's recommended to run your own TURN server using coturn:
`bashInstall coturn
apt-get install coturn
BSD-3-Clause
Contributions are welcome! Please feel free to submit issues or pull requests.
This implementation is inspired by and follows the WebRTC standards and specifications, with particular reference to Chromium's WebRTC implementation.