node-rtc-connection 1.0.11 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +355 -289
- package/dist/index.cjs +4318 -3095
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +4318 -3095
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/datachannel/RTCDataChannel.js +354 -0
- package/src/dtls/RTCCertificate.js +310 -0
- package/src/dtls/RTCDtlsTransport.js +247 -0
- package/src/foundation/ByteBufferQueue.js +235 -0
- package/src/foundation/RTCError.js +226 -0
- package/src/ice/RTCIceCandidate.js +301 -0
- package/src/ice/RTCIceTransport.js +956 -0
- package/src/index.d.ts +316 -145
- package/src/index.js +78 -45
- package/src/network/network-transport.js +478 -0
- package/src/peerconnection/RTCPeerConnection.js +847 -0
- package/src/sctp/RTCSctpTransport.js +253 -0
- package/src/sdp/RTCSessionDescription.js +102 -0
- package/src/sdp/sdp-utils.js +224 -0
- package/src/stun/stun-client.js +643 -0
- package/src/ICEGatherer.js +0 -341
- package/src/NativePeerConnectionFactory.js +0 -1044
- package/src/RTCDataChannel.js +0 -346
- package/src/RTCDataChannelEvent.js +0 -50
- package/src/RTCError.js +0 -66
- package/src/RTCIceCandidate.js +0 -184
- package/src/RTCPeerConnection.js +0 -505
- package/src/RTCPeerConnectionIceEvent.js +0 -58
- package/src/RTCSessionDescription.js +0 -62
- package/src/STUNClient.js +0 -222
- package/src/SecureConnection.js +0 -298
- package/src/TURNClient.js +0 -561
- package/src/UDPTransport.js +0 -236
package/README.md
CHANGED
|
@@ -1,29 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# NodeRTC
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://github.com/nmhung1210/nodertc/actions)
|
|
5
|
-
[](https://opensource.org/licenses/MIT)
|
|
6
|
-
|
|
7
|
-
A production-ready WebRTC DataChannel implementation for Node.js with **real networking**, **STUN/TURN support**, **MESSAGE-INTEGRITY authentication**, and **optional encryption**. Built with pure Node.js - no native dependencies required.
|
|
8
|
-
|
|
9
|
-
## Overview
|
|
10
|
-
|
|
11
|
-
node-rtc-connection provides WebRTC-style peer-to-peer data connections using Node.js built-in modules. It supports NAT traversal via STUN/TURN servers with full RFC 5766 MESSAGE-INTEGRITY authentication, optional TLS encryption, and works across the internet for most network configurations.
|
|
3
|
+
A Node.js WebRTC implementation with full ICE/STUN/TURN support for real peer-to-peer networking.
|
|
12
4
|
|
|
13
5
|
## Features
|
|
14
6
|
|
|
15
|
-
- ✅ **
|
|
16
|
-
- ✅ **
|
|
17
|
-
- ✅ **STUN Support
|
|
18
|
-
- ✅ **TURN Support
|
|
19
|
-
- ✅ **
|
|
20
|
-
- ✅ **
|
|
21
|
-
- ✅ **
|
|
22
|
-
- ✅ **Real Networking** - TCP/UDP with actual peer-to-peer communication
|
|
23
|
-
- ✅ **Event-based API** - Built on Node.js EventEmitter
|
|
24
|
-
- ✅ **Zero Dependencies** - Pure Node.js, no native modules
|
|
25
|
-
- ✅ **Docker TURN Server** - Included for testing and development
|
|
26
|
-
- ❌ **No Media Support** - DataChannel only (no audio/video)
|
|
7
|
+
- ✅ **Real Network Transport**: Uses actual UDP/TCP sockets for true peer-to-peer connections
|
|
8
|
+
- ✅ **ICE Support**: Full Interactive Connectivity Establishment with candidate gathering
|
|
9
|
+
- ✅ **STUN Support**: NAT traversal with server reflexive candidates
|
|
10
|
+
- ✅ **TURN Support**: Relay candidates for restrictive network environments
|
|
11
|
+
- ✅ **Data Channels**: Reliable and ordered data channels for P2P communication
|
|
12
|
+
- ✅ **DTLS/SCTP**: Secure transport with DTLS encryption and SCTP for data channels
|
|
13
|
+
- ✅ **Standards Compliant**: Follows WebRTC and ICE specifications
|
|
27
14
|
|
|
28
15
|
## Installation
|
|
29
16
|
|
|
@@ -33,395 +20,474 @@ npm install node-rtc-connection
|
|
|
33
20
|
|
|
34
21
|
## Quick Start
|
|
35
22
|
|
|
36
|
-
### Basic
|
|
23
|
+
### Basic Local Connection (No STUN/TURN)
|
|
37
24
|
|
|
38
25
|
```javascript
|
|
39
|
-
const {
|
|
26
|
+
const { RTCPeerConnection } = require('node-rtc-connection');
|
|
40
27
|
|
|
41
|
-
// Create peer
|
|
42
|
-
const
|
|
28
|
+
// Create two peer connections
|
|
29
|
+
const pc1 = new RTCPeerConnection({ iceServers: [] });
|
|
30
|
+
const pc2 = new RTCPeerConnection({ iceServers: [] });
|
|
43
31
|
|
|
44
|
-
//
|
|
45
|
-
const channel =
|
|
32
|
+
// Set up data channel on peer 1
|
|
33
|
+
const channel = pc1.createDataChannel('chat');
|
|
46
34
|
|
|
47
35
|
channel.on('open', () => {
|
|
48
|
-
console.log('
|
|
49
|
-
channel.send('Hello
|
|
36
|
+
console.log('Channel opened!');
|
|
37
|
+
channel.send('Hello from Peer 1!');
|
|
50
38
|
});
|
|
51
39
|
|
|
52
40
|
channel.on('message', (event) => {
|
|
53
|
-
console.log('Received:', event.data
|
|
41
|
+
console.log('Received:', event.data);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Peer 2 receives data channel
|
|
45
|
+
pc2.on('datachannel', (event) => {
|
|
46
|
+
const channel = event.channel;
|
|
47
|
+
|
|
48
|
+
channel.on('message', (event) => {
|
|
49
|
+
console.log('Received:', event.data);
|
|
50
|
+
channel.send('Hello from Peer 2!');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Exchange ICE candidates
|
|
55
|
+
pc1.on('icecandidate', (e) => {
|
|
56
|
+
if (e.candidate) pc2.addIceCandidate(e.candidate);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
pc2.on('icecandidate', (e) => {
|
|
60
|
+
if (e.candidate) pc1.addIceCandidate(e.candidate);
|
|
54
61
|
});
|
|
55
62
|
|
|
56
|
-
//
|
|
63
|
+
// Signaling (offer/answer exchange)
|
|
64
|
+
async function connect() {
|
|
65
|
+
const offer = await pc1.createOffer();
|
|
66
|
+
await pc1.setLocalDescription(offer);
|
|
67
|
+
|
|
68
|
+
await pc2.setRemoteDescription(offer);
|
|
69
|
+
const answer = await pc2.createAnswer();
|
|
70
|
+
await pc2.setLocalDescription(answer);
|
|
71
|
+
|
|
72
|
+
await pc1.setRemoteDescription(answer);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
connect();
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### With STUN Server (NAT Traversal)
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
const { RTCPeerConnection } = require('node-rtc-connection');
|
|
82
|
+
|
|
83
|
+
const config = {
|
|
84
|
+
iceServers: [
|
|
85
|
+
{ urls: 'stun:stun.l.google.com:19302' }
|
|
86
|
+
]
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const pc = new RTCPeerConnection(config);
|
|
90
|
+
|
|
91
|
+
// Listen for gathered ICE candidates
|
|
57
92
|
pc.on('icecandidate', (event) => {
|
|
58
93
|
if (event.candidate) {
|
|
59
|
-
|
|
94
|
+
console.log('ICE Candidate:', event.candidate.candidate);
|
|
95
|
+
// Send to remote peer via your signaling channel
|
|
60
96
|
}
|
|
61
97
|
});
|
|
62
98
|
|
|
63
|
-
// Create and
|
|
99
|
+
// Create offer and start ICE gathering
|
|
64
100
|
const offer = await pc.createOffer();
|
|
65
101
|
await pc.setLocalDescription(offer);
|
|
66
102
|
```
|
|
67
103
|
|
|
68
|
-
### With
|
|
104
|
+
### With TURN Server (Relay Support)
|
|
69
105
|
|
|
70
106
|
```javascript
|
|
71
|
-
const {
|
|
107
|
+
const { RTCPeerConnection } = require('node-rtc-connection');
|
|
72
108
|
|
|
73
|
-
// Configuration with STUN and TURN
|
|
74
109
|
const config = {
|
|
75
110
|
iceServers: [
|
|
76
111
|
{ urls: 'stun:stun.l.google.com:19302' },
|
|
77
112
|
{
|
|
78
113
|
urls: 'turn:turn.example.com:3478',
|
|
79
|
-
username: 'username',
|
|
80
|
-
credential: 'password'
|
|
114
|
+
username: 'your-username',
|
|
115
|
+
credential: 'your-password'
|
|
81
116
|
}
|
|
82
|
-
]
|
|
83
|
-
encryption: false, // Optional TLS encryption
|
|
84
|
-
transport: 'tcp' // Use TCP (or 'udp')
|
|
117
|
+
]
|
|
85
118
|
};
|
|
86
119
|
|
|
87
|
-
|
|
88
|
-
const pc = createPeerConnection(config);
|
|
120
|
+
const pc = new RTCPeerConnection(config);
|
|
89
121
|
|
|
90
|
-
// Rest is the same as basic usage...
|
|
91
|
-
|
|
92
|
-
channel.on('message', (event) => {
|
|
93
|
-
console.log('Received:', event.data.toString());
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// ICE candidates (includes STUN reflexive candidates)
|
|
97
122
|
pc.on('icecandidate', (event) => {
|
|
98
123
|
if (event.candidate) {
|
|
99
|
-
|
|
100
|
-
|
|
124
|
+
const candidate = event.candidate.candidate;
|
|
125
|
+
|
|
126
|
+
// Check candidate type
|
|
127
|
+
if (candidate.includes('typ relay')) {
|
|
128
|
+
console.log('TURN relay candidate:', candidate);
|
|
129
|
+
} else if (candidate.includes('typ srflx')) {
|
|
130
|
+
console.log('STUN reflexive candidate:', candidate);
|
|
131
|
+
} else if (candidate.includes('typ host')) {
|
|
132
|
+
console.log('Host candidate:', candidate);
|
|
133
|
+
}
|
|
101
134
|
}
|
|
102
135
|
});
|
|
103
136
|
```
|
|
104
137
|
|
|
105
|
-
|
|
138
|
+
## Configuration Options
|
|
106
139
|
|
|
107
|
-
|
|
140
|
+
```javascript
|
|
141
|
+
const config = {
|
|
142
|
+
// Array of ICE servers (STUN/TURN)
|
|
143
|
+
iceServers: [
|
|
144
|
+
{
|
|
145
|
+
urls: 'stun:stun.l.google.com:19302'
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
urls: [
|
|
149
|
+
'turn:turn.example.com:3478?transport=udp',
|
|
150
|
+
'turn:turn.example.com:3478?transport=tcp'
|
|
151
|
+
],
|
|
152
|
+
username: 'user',
|
|
153
|
+
credential: 'pass'
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
|
|
157
|
+
// ICE transport policy
|
|
158
|
+
iceTransportPolicy: 'all', // 'all' or 'relay'
|
|
159
|
+
|
|
160
|
+
// Bundle policy
|
|
161
|
+
bundlePolicy: 'balanced', // 'balanced', 'max-bundle', or 'max-compat'
|
|
162
|
+
|
|
163
|
+
// RTCP mux policy
|
|
164
|
+
rtcpMuxPolicy: 'require', // 'negotiate' or 'require'
|
|
165
|
+
|
|
166
|
+
// ICE candidate pool size
|
|
167
|
+
iceCandidatePoolSize: 0
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const pc = new RTCPeerConnection(config);
|
|
171
|
+
```
|
|
108
172
|
|
|
109
|
-
###
|
|
173
|
+
### Query String Parameters in Server URLs
|
|
110
174
|
|
|
111
|
-
|
|
112
|
-
const { createPeerConnection } = require('./src');
|
|
175
|
+
The library supports query string parameters in ICE server URLs for advanced configuration:
|
|
113
176
|
|
|
114
|
-
|
|
115
|
-
const
|
|
177
|
+
```javascript
|
|
178
|
+
const config = {
|
|
116
179
|
iceServers: [
|
|
117
|
-
|
|
180
|
+
// Transport selection
|
|
181
|
+
{
|
|
182
|
+
urls: 'turn:turn.example.com:3478?transport=udp',
|
|
183
|
+
username: 'user',
|
|
184
|
+
credential: 'pass'
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
// Multiple parameters
|
|
188
|
+
{
|
|
189
|
+
urls: 'turn:turn.example.com:3478?transport=tcp&ttl=86400',
|
|
190
|
+
username: 'user',
|
|
191
|
+
credential: 'pass'
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
// Multiple URLs with different transports
|
|
195
|
+
{
|
|
196
|
+
urls: [
|
|
197
|
+
'turn:turn.cloudflare.com:3478?transport=udp',
|
|
198
|
+
'turn:turn.cloudflare.com:3478?transport=tcp',
|
|
199
|
+
'turns:turn.cloudflare.com:5349?transport=tcp'
|
|
200
|
+
],
|
|
201
|
+
username: 'cloudflare_user',
|
|
202
|
+
credential: 'cloudflare_pass'
|
|
203
|
+
}
|
|
118
204
|
]
|
|
119
|
-
}
|
|
205
|
+
};
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Supported Query Parameters:**
|
|
209
|
+
- `transport=udp|tcp` - Select transport protocol (UDP or TCP)
|
|
210
|
+
- `ttl=<seconds>` - Set allocation lifetime for TURN (default: 600)
|
|
211
|
+
- Custom parameters - Can be added for vendor-specific features
|
|
212
|
+
|
|
213
|
+
**URL Format Examples:**
|
|
214
|
+
- `stun:host:port` - Basic STUN server
|
|
215
|
+
- `turn:host:port?transport=udp` - TURN with UDP transport
|
|
216
|
+
- `turn:host:port?transport=tcp&custom=value` - Multiple parameters
|
|
217
|
+
- `turns:host:port?transport=tcp` - Secure TURN over TLS
|
|
120
218
|
|
|
121
|
-
|
|
219
|
+
## Data Channel API
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
// Create data channel with options
|
|
122
223
|
const channel = pc.createDataChannel('myChannel', {
|
|
123
|
-
ordered: true
|
|
224
|
+
ordered: true, // Guarantee message order
|
|
225
|
+
maxRetransmits: 3, // Max retransmissions (if not ordered)
|
|
226
|
+
maxPacketLifeTime: 3000, // Max packet lifetime in ms
|
|
227
|
+
protocol: 'custom', // Sub-protocol
|
|
228
|
+
negotiated: false, // Manual negotiation
|
|
229
|
+
id: 0 // Channel ID (if negotiated)
|
|
124
230
|
});
|
|
125
231
|
|
|
126
|
-
//
|
|
232
|
+
// Events
|
|
127
233
|
channel.on('open', () => {
|
|
128
|
-
console.log('
|
|
129
|
-
channel.send('Hello, peer!');
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
channel.on('message', (event) => {
|
|
133
|
-
console.log('Received:', event.data);
|
|
234
|
+
console.log('Channel opened');
|
|
134
235
|
});
|
|
135
236
|
|
|
136
237
|
channel.on('close', () => {
|
|
137
|
-
console.log('
|
|
238
|
+
console.log('Channel closed');
|
|
138
239
|
});
|
|
139
240
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (event.candidate) {
|
|
143
|
-
// Send candidate to remote peer via signaling
|
|
144
|
-
console.log('ICE candidate:', event.candidate);
|
|
145
|
-
}
|
|
241
|
+
channel.on('error', (error) => {
|
|
242
|
+
console.error('Channel error:', error);
|
|
146
243
|
});
|
|
147
244
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const remoteChannel = event.channel;
|
|
151
|
-
console.log('Remote channel opened:', remoteChannel.label);
|
|
245
|
+
channel.on('message', (event) => {
|
|
246
|
+
console.log('Message received:', event.data);
|
|
152
247
|
});
|
|
153
248
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
});
|
|
249
|
+
// Send data
|
|
250
|
+
channel.send('Hello World');
|
|
251
|
+
channel.send(Buffer.from([1, 2, 3, 4])); // Binary data
|
|
252
|
+
|
|
253
|
+
// Close channel
|
|
254
|
+
channel.close();
|
|
161
255
|
```
|
|
162
256
|
|
|
163
|
-
|
|
257
|
+
## RTCPeerConnection Events
|
|
164
258
|
|
|
165
259
|
```javascript
|
|
166
|
-
const
|
|
260
|
+
const pc = new RTCPeerConnection(config);
|
|
167
261
|
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
262
|
+
// ICE candidate discovered
|
|
263
|
+
pc.on('icecandidate', (event) => {
|
|
264
|
+
// event.candidate contains the ICE candidate
|
|
171
265
|
});
|
|
172
266
|
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
267
|
+
// ICE gathering state changed
|
|
268
|
+
pc.on('icegatheringstatechange', () => {
|
|
269
|
+
console.log('Gathering state:', pc.iceGatheringState);
|
|
270
|
+
// 'new', 'gathering', or 'complete'
|
|
176
271
|
});
|
|
177
272
|
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
273
|
+
// ICE connection state changed
|
|
274
|
+
pc.on('iceconnectionstatechange', () => {
|
|
275
|
+
console.log('ICE state:', pc.iceConnectionState);
|
|
276
|
+
// 'new', 'checking', 'connected', 'completed', 'failed', 'disconnected', 'closed'
|
|
183
277
|
});
|
|
184
278
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
279
|
+
// Connection state changed
|
|
280
|
+
pc.on('connectionstatechange', () => {
|
|
281
|
+
console.log('Connection state:', pc.connectionState);
|
|
282
|
+
// 'new', 'connecting', 'connected', 'disconnected', 'failed', 'closed'
|
|
189
283
|
});
|
|
190
284
|
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
console.log('Peer 2 received:', event.data);
|
|
196
|
-
channel.send('Hello from Peer 2!');
|
|
197
|
-
});
|
|
285
|
+
// Signaling state changed
|
|
286
|
+
pc.on('signalingstatechange', () => {
|
|
287
|
+
console.log('Signaling state:', pc.signalingState);
|
|
288
|
+
// 'stable', 'have-local-offer', 'have-remote-offer', 'have-local-pranswer', 'have-remote-pranswer', 'closed'
|
|
198
289
|
});
|
|
199
290
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
console.log('Channel opened');
|
|
205
|
-
channel.send('Hello from Peer 1!');
|
|
291
|
+
// Data channel received (for answerer)
|
|
292
|
+
pc.on('datachannel', (event) => {
|
|
293
|
+
const channel = event.channel;
|
|
294
|
+
console.log('Received data channel:', channel.label);
|
|
206
295
|
});
|
|
207
296
|
|
|
208
|
-
|
|
209
|
-
|
|
297
|
+
// Negotiation needed
|
|
298
|
+
pc.on('negotiationneeded', () => {
|
|
299
|
+
console.log('Negotiation needed');
|
|
210
300
|
});
|
|
211
|
-
|
|
212
|
-
// Start signaling
|
|
213
|
-
async function connect() {
|
|
214
|
-
// Create offer
|
|
215
|
-
const offer = await pc1.createOffer();
|
|
216
|
-
await pc1.setLocalDescription(offer);
|
|
217
|
-
|
|
218
|
-
// Set remote description on peer 2
|
|
219
|
-
await pc2.setRemoteDescription(offer);
|
|
220
|
-
|
|
221
|
-
// Create answer
|
|
222
|
-
const answer = await pc2.createAnswer();
|
|
223
|
-
await pc2.setLocalDescription(answer);
|
|
224
|
-
|
|
225
|
-
// Set remote description on peer 1
|
|
226
|
-
await pc1.setRemoteDescription(answer);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
connect();
|
|
230
301
|
```
|
|
231
302
|
|
|
232
|
-
##
|
|
233
|
-
|
|
234
|
-
### RTCPeerConnection
|
|
303
|
+
## Complete Example: Two-Peer Communication
|
|
235
304
|
|
|
236
|
-
#### Constructor
|
|
237
305
|
```javascript
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
- `createDataChannel(label, options)` - Create a data channel
|
|
256
|
-
- `getConfiguration()` - Get current configuration
|
|
257
|
-
- `setConfiguration(configuration)` - Update configuration
|
|
258
|
-
- `close()` - Close the peer connection
|
|
259
|
-
- `getStats()` - Get connection statistics
|
|
260
|
-
|
|
261
|
-
#### Events
|
|
262
|
-
- `signalingstatechange` - Signaling state changed
|
|
263
|
-
- `iceconnectionstatechange` - ICE connection state changed
|
|
264
|
-
- `icegatheringstatechange` - ICE gathering state changed
|
|
265
|
-
- `connectionstatechange` - Connection state changed
|
|
266
|
-
- `icecandidate` - New ICE candidate available
|
|
267
|
-
- `datachannel` - Remote data channel opened
|
|
268
|
-
- `negotiationneeded` - Negotiation needed
|
|
269
|
-
|
|
270
|
-
### RTCDataChannel
|
|
271
|
-
|
|
272
|
-
#### Properties
|
|
273
|
-
- `label` - Channel label
|
|
274
|
-
- `ordered` - Whether messages are ordered
|
|
275
|
-
- `maxPacketLifeTime` - Maximum packet lifetime
|
|
276
|
-
- `maxRetransmits` - Maximum retransmits
|
|
277
|
-
- `protocol` - Subprotocol name
|
|
278
|
-
- `negotiated` - Whether channel was negotiated
|
|
279
|
-
- `id` - Channel ID
|
|
280
|
-
- `readyState` - Current state: 'connecting', 'open', 'closing', 'closed'
|
|
281
|
-
- `bufferedAmount` - Bytes queued to send
|
|
282
|
-
- `bufferedAmountLowThreshold` - Threshold for bufferedamountlow event
|
|
283
|
-
- `binaryType` - Binary data format: 'arraybuffer' or 'blob'
|
|
284
|
-
|
|
285
|
-
#### Methods
|
|
286
|
-
- `send(data)` - Send data (string, ArrayBuffer, or ArrayBufferView)
|
|
287
|
-
- `close()` - Close the channel
|
|
306
|
+
const { RTCPeerConnection } = require('node-rtc-connection');
|
|
307
|
+
|
|
308
|
+
async function createPeerConnection() {
|
|
309
|
+
const config = {
|
|
310
|
+
iceServers: [
|
|
311
|
+
{ urls: 'stun:stun.l.google.com:19302' }
|
|
312
|
+
]
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Create peer connections
|
|
316
|
+
const offerer = new RTCPeerConnection(config);
|
|
317
|
+
const answerer = new RTCPeerConnection(config);
|
|
318
|
+
|
|
319
|
+
// Exchange ICE candidates
|
|
320
|
+
offerer.on('icecandidate', (e) => {
|
|
321
|
+
if (e.candidate) answerer.addIceCandidate(e.candidate);
|
|
322
|
+
});
|
|
288
323
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
- `message` - Message received
|
|
293
|
-
- `error` - Error occurred
|
|
294
|
-
- `bufferedamountlow` - Buffered amount dropped below threshold
|
|
324
|
+
answerer.on('icecandidate', (e) => {
|
|
325
|
+
if (e.candidate) offerer.addIceCandidate(e.candidate);
|
|
326
|
+
});
|
|
295
327
|
|
|
296
|
-
|
|
328
|
+
// Set up data channel on offerer
|
|
329
|
+
const channel = offerer.createDataChannel('chat');
|
|
297
330
|
|
|
298
|
-
|
|
331
|
+
channel.on('open', () => {
|
|
332
|
+
console.log('Offerer: Channel opened');
|
|
333
|
+
channel.send('Hello from offerer!');
|
|
334
|
+
});
|
|
299
335
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
336
|
+
channel.on('message', (event) => {
|
|
337
|
+
console.log('Offerer received:', event.data);
|
|
338
|
+
});
|
|
303
339
|
|
|
304
|
-
|
|
305
|
-
|
|
340
|
+
// Answerer receives data channel
|
|
341
|
+
answerer.on('datachannel', (event) => {
|
|
342
|
+
const channel = event.channel;
|
|
306
343
|
|
|
307
|
-
|
|
308
|
-
|
|
344
|
+
channel.on('open', () => {
|
|
345
|
+
console.log('Answerer: Channel opened');
|
|
346
|
+
});
|
|
309
347
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
348
|
+
channel.on('message', (event) => {
|
|
349
|
+
console.log('Answerer received:', event.data);
|
|
350
|
+
channel.send('Hello from answerer!');
|
|
351
|
+
});
|
|
352
|
+
});
|
|
313
353
|
|
|
314
|
-
|
|
354
|
+
// Perform signaling
|
|
355
|
+
const offer = await offerer.createOffer();
|
|
356
|
+
await offerer.setLocalDescription(offer);
|
|
315
357
|
|
|
316
|
-
|
|
358
|
+
await answerer.setRemoteDescription(offer);
|
|
359
|
+
const answer = await answerer.createAnswer();
|
|
360
|
+
await answerer.setLocalDescription(answer);
|
|
317
361
|
|
|
318
|
-
|
|
362
|
+
await offerer.setRemoteDescription(answer);
|
|
319
363
|
|
|
320
|
-
|
|
364
|
+
// Wait for connection
|
|
365
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
321
366
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
- ✅ **Connection lifecycle** management
|
|
367
|
+
// Clean up
|
|
368
|
+
channel.close();
|
|
369
|
+
offerer.close();
|
|
370
|
+
answerer.close();
|
|
371
|
+
}
|
|
328
372
|
|
|
329
|
-
|
|
373
|
+
createPeerConnection().catch(console.error);
|
|
374
|
+
```
|
|
330
375
|
|
|
331
|
-
|
|
376
|
+
## Example Files
|
|
332
377
|
|
|
333
|
-
|
|
334
|
-
2. **TCP Server**: Each peer creates a TCP server listening on a random port
|
|
335
|
-
3. **Connection**: The answerer connects to the offerer's TCP server
|
|
336
|
-
4. **Data Channel Protocol**: Messages are framed with length + channel label + data
|
|
337
|
-
5. **Bidirectional Communication**: Both peers can send and receive on the established socket
|
|
378
|
+
The package includes several example files demonstrating different features:
|
|
338
379
|
|
|
339
|
-
|
|
380
|
+
- **`examples/real-networking-demo.js`** - Basic peer-to-peer connection without STUN/TURN
|
|
381
|
+
- **`examples/stun-demo.js`** - STUN server usage for NAT traversal
|
|
382
|
+
- **`examples/turn-demo.js`** - TURN relay with full peer-to-peer communication
|
|
383
|
+
- **`examples/peer-connection-demo.js`** - Simple peer connection setup
|
|
384
|
+
- **`examples/simple-datachannel.js`** - Basic data channel usage
|
|
340
385
|
|
|
386
|
+
Run examples:
|
|
341
387
|
```bash
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
✓ PC2: Data channel opened!
|
|
346
|
-
|
|
347
|
-
PC1: Sending first message...
|
|
348
|
-
📨 PC2 received: Hello from Peer 1!
|
|
349
|
-
PC2: Sending reply...
|
|
350
|
-
📨 PC1 received: Hello from Peer 2! Nice to meet you.
|
|
388
|
+
node examples/real-networking-demo.js
|
|
389
|
+
node examples/stun-demo.js
|
|
390
|
+
node examples/turn-demo.js
|
|
351
391
|
```
|
|
352
392
|
|
|
353
|
-
##
|
|
393
|
+
## Configuration File
|
|
354
394
|
|
|
355
|
-
|
|
395
|
+
The examples use a `peer.config.json` file for centralized configuration:
|
|
356
396
|
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
397
|
+
```json
|
|
398
|
+
{
|
|
399
|
+
"iceServers": [
|
|
400
|
+
{ "urls": "stun:stun.l.google.com:19302" }
|
|
401
|
+
],
|
|
402
|
+
"localDemo": {
|
|
403
|
+
"iceServers": []
|
|
404
|
+
},
|
|
405
|
+
"stunOnly": {
|
|
406
|
+
"iceServers": [
|
|
407
|
+
{ "urls": "stun:stun.l.google.com:19302" }
|
|
408
|
+
]
|
|
409
|
+
},
|
|
410
|
+
"turnConfig": {
|
|
411
|
+
"iceServers": [
|
|
412
|
+
{ "urls": "stun:stun.example.com:3478" },
|
|
413
|
+
{
|
|
414
|
+
"urls": "turn:turn.example.com:3478",
|
|
415
|
+
"username": "user",
|
|
416
|
+
"credential": "pass"
|
|
417
|
+
}
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
}
|
|
361
421
|
```
|
|
362
422
|
|
|
363
|
-
|
|
423
|
+
## API Reference
|
|
364
424
|
|
|
365
|
-
|
|
366
|
-
# Run all tests
|
|
367
|
-
npm test
|
|
425
|
+
### RTCPeerConnection
|
|
368
426
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
npm run test:integration # Integration tests
|
|
427
|
+
#### Constructor
|
|
428
|
+
```javascript
|
|
429
|
+
new RTCPeerConnection(configuration?)
|
|
373
430
|
```
|
|
374
431
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
432
|
+
#### Methods
|
|
433
|
+
- `createOffer(options?)` - Create SDP offer
|
|
434
|
+
- `createAnswer(options?)` - Create SDP answer
|
|
435
|
+
- `setLocalDescription(description)` - Set local SDP
|
|
436
|
+
- `setRemoteDescription(description)` - Set remote SDP
|
|
437
|
+
- `addIceCandidate(candidate)` - Add remote ICE candidate
|
|
438
|
+
- `createDataChannel(label, options?)` - Create data channel
|
|
439
|
+
- `close()` - Close the connection
|
|
380
440
|
|
|
381
|
-
|
|
441
|
+
#### Properties
|
|
442
|
+
- `localDescription` - Local SDP description
|
|
443
|
+
- `remoteDescription` - Remote SDP description
|
|
444
|
+
- `signalingState` - Current signaling state
|
|
445
|
+
- `iceGatheringState` - ICE gathering state
|
|
446
|
+
- `iceConnectionState` - ICE connection state
|
|
447
|
+
- `connectionState` - Overall connection state
|
|
382
448
|
|
|
383
|
-
###
|
|
449
|
+
### RTCDataChannel
|
|
384
450
|
|
|
385
|
-
|
|
451
|
+
#### Methods
|
|
452
|
+
- `send(data)` - Send data (string or Buffer)
|
|
453
|
+
- `close()` - Close the channel
|
|
386
454
|
|
|
387
|
-
|
|
388
|
-
-
|
|
455
|
+
#### Properties
|
|
456
|
+
- `label` - Channel label
|
|
457
|
+
- `ordered` - Whether messages are ordered
|
|
458
|
+
- `maxRetransmits` - Maximum retransmissions
|
|
459
|
+
- `maxPacketLifeTime` - Maximum packet lifetime
|
|
460
|
+
- `protocol` - Sub-protocol
|
|
461
|
+
- `negotiated` - Whether manually negotiated
|
|
462
|
+
- `id` - Channel ID
|
|
463
|
+
- `readyState` - Current state ('connecting', 'open', 'closing', 'closed')
|
|
464
|
+
- `bufferedAmount` - Bytes queued to send
|
|
389
465
|
|
|
390
|
-
|
|
466
|
+
## Requirements
|
|
391
467
|
|
|
392
|
-
|
|
468
|
+
- Node.js 14 or higher
|
|
469
|
+
- UDP/TCP network access for ICE connectivity
|
|
393
470
|
|
|
394
|
-
|
|
395
|
-
- ❌ MediaStream / MediaStreamTrack
|
|
396
|
-
- ❌ getUserMedia
|
|
397
|
-
- ❌ RTCRtpSender / RTCRtpReceiver
|
|
398
|
-
- ❌ RTCRtpTransceiver
|
|
399
|
-
- ❌ Audio/Video codecs
|
|
400
|
-
- ❌ RTCDTMFSender
|
|
401
|
-
- ❌ Media constraints
|
|
471
|
+
## Setting Up Your Own TURN Server
|
|
402
472
|
|
|
403
|
-
|
|
404
|
-
- Event handling uses Node.js EventEmitter instead of DOM events
|
|
405
|
-
- No dependency on browser APIs
|
|
406
|
-
- Synchronous where possible (async only for native operations)
|
|
473
|
+
For production use, it's recommended to run your own TURN server using [coturn](https://github.com/coturn/coturn):
|
|
407
474
|
|
|
408
|
-
|
|
475
|
+
```bash
|
|
476
|
+
# Install coturn
|
|
477
|
+
apt-get install coturn
|
|
409
478
|
|
|
410
|
-
|
|
479
|
+
# Basic configuration
|
|
480
|
+
turnserver -v -L 0.0.0.0 -a -u user:password -r realm
|
|
481
|
+
```
|
|
411
482
|
|
|
412
483
|
## License
|
|
413
484
|
|
|
414
|
-
|
|
485
|
+
BSD-3-Clause
|
|
415
486
|
|
|
416
|
-
##
|
|
487
|
+
## Contributing
|
|
417
488
|
|
|
418
|
-
|
|
419
|
-
- Original source: `third_party/blink/renderer/modules/peerconnection/`
|
|
420
|
-
- Copyright (C) 2012 Google Inc.
|
|
489
|
+
Contributions are welcome! Please feel free to submit issues or pull requests.
|
|
421
490
|
|
|
422
|
-
##
|
|
491
|
+
## Acknowledgments
|
|
423
492
|
|
|
424
|
-
|
|
425
|
-
- [GitHub Repository](https://github.com/nmhung1210/nodertc)
|
|
426
|
-
- [Issues](https://github.com/nmhung1210/nodertc/issues)
|
|
427
|
-
- [Contributing Guide](CONTRIBUTING.md)
|
|
493
|
+
This implementation is inspired by and follows the WebRTC standards and specifications, with particular reference to Chromium's WebRTC implementation.
|