node-rtc-connection 1.0.5 → 1.0.7
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 +8 -8
- package/dist/index.cjs +125 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +125 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/NativePeerConnectionFactory.js +125 -17
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# node-rtc-connection - WebRTC DataChannels for Node.js
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/node-rtc-connection)
|
|
4
4
|
[](https://github.com/nmhung1210/nodertc/actions)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ A production-ready WebRTC DataChannel implementation for Node.js with **real net
|
|
|
8
8
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
11
|
-
|
|
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.
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
@@ -28,7 +28,7 @@ NodeRTC provides WebRTC-style peer-to-peer data connections using Node.js built-
|
|
|
28
28
|
## Installation
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
npm install
|
|
31
|
+
npm install node-rtc-connection
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## Quick Start
|
|
@@ -36,7 +36,7 @@ npm install nodertc
|
|
|
36
36
|
### Basic Usage
|
|
37
37
|
|
|
38
38
|
```javascript
|
|
39
|
-
const { createPeerConnection } = require('
|
|
39
|
+
const { createPeerConnection } = require('node-rtc-connection');
|
|
40
40
|
|
|
41
41
|
// Create peer connection
|
|
42
42
|
const pc = createPeerConnection();
|
|
@@ -68,7 +68,7 @@ await pc.setLocalDescription(offer);
|
|
|
68
68
|
### With STUN and TURN (Recommended)
|
|
69
69
|
|
|
70
70
|
```javascript
|
|
71
|
-
const { createPeerConnection } = require('
|
|
71
|
+
const { createPeerConnection } = require('node-rtc-connection');
|
|
72
72
|
|
|
73
73
|
// Configuration with STUN and TURN
|
|
74
74
|
const config = {
|
|
@@ -295,7 +295,7 @@ new RTCPeerConnection(configuration, nativePeerConnectionFactory)
|
|
|
295
295
|
|
|
296
296
|
## Testing
|
|
297
297
|
|
|
298
|
-
|
|
298
|
+
node-rtc-connection includes comprehensive unit tests covering all components:
|
|
299
299
|
|
|
300
300
|
```bash
|
|
301
301
|
# Run all unit tests (fast, ~500ms)
|
|
@@ -421,7 +421,7 @@ Ported from Chromium's WebRTC implementation:
|
|
|
421
421
|
|
|
422
422
|
## Links
|
|
423
423
|
|
|
424
|
-
- [NPM Package](https://www.npmjs.com/package/
|
|
424
|
+
- [NPM Package](https://www.npmjs.com/package/node-rtc-connection)
|
|
425
425
|
- [GitHub Repository](https://github.com/nmhung1210/nodertc)
|
|
426
426
|
- [Issues](https://github.com/nmhung1210/nodertc/issues)
|
|
427
427
|
- [Contributing Guide](CONTRIBUTING.md)
|
package/dist/index.cjs
CHANGED
|
@@ -2400,6 +2400,9 @@ function requireNativePeerConnectionFactory () {
|
|
|
2400
2400
|
this._secureConnection = null;
|
|
2401
2401
|
this._udpTransport = null;
|
|
2402
2402
|
this._iceCandidates = [];
|
|
2403
|
+
this._remoteCandidates = [];
|
|
2404
|
+
this._selectedLocalCandidate = null;
|
|
2405
|
+
this._selectedRemoteCandidate = null;
|
|
2403
2406
|
|
|
2404
2407
|
console.log('[NativePeerConnection] Created with STUN/ICE/Encryption support');
|
|
2405
2408
|
console.log(` - Encryption: ${this._useEncryption ? 'enabled' : 'disabled (requires valid certs)'}`);
|
|
@@ -2545,22 +2548,106 @@ function requireNativePeerConnectionFactory () {
|
|
|
2545
2548
|
}
|
|
2546
2549
|
|
|
2547
2550
|
if (!candidate || !candidate.candidate) {
|
|
2551
|
+
// null candidate signals end of candidates - try to select best pair
|
|
2552
|
+
if (this._remoteCandidates.length > 0 && !this._selectedRemoteCandidate) {
|
|
2553
|
+
this._selectBestCandidatePair();
|
|
2554
|
+
}
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
// Parse ICE candidate
|
|
2559
|
+
const parsed = this._parseIceCandidate(candidate.candidate);
|
|
2560
|
+
if (!parsed) {
|
|
2548
2561
|
return;
|
|
2549
2562
|
}
|
|
2550
2563
|
|
|
2551
|
-
//
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2564
|
+
// Store the remote candidate
|
|
2565
|
+
this._remoteCandidates.push(parsed);
|
|
2566
|
+
console.log(`[NativePeerConnection] Added remote ICE candidate (${parsed.type}): ${parsed.ip}:${parsed.port}`);
|
|
2567
|
+
|
|
2568
|
+
// For backward compatibility, set remote address immediately if not set
|
|
2569
|
+
if (!this._remoteAddress) {
|
|
2570
|
+
this._remoteAddress = parsed.ip;
|
|
2571
|
+
this._remotePort = parsed.port;
|
|
2556
2572
|
console.log(`[NativePeerConnection] Remote address: ${this._remoteAddress}:${this._remotePort}`);
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
// If we haven't selected a pair yet, try to select now
|
|
2576
|
+
if (!this._selectedRemoteCandidate && this._iceCandidates.length > 0) {
|
|
2577
|
+
this._selectBestCandidatePair();
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// If we selected a pair and are offerer, connect
|
|
2581
|
+
if (this._selectedRemoteCandidate && this._isOfferer &&
|
|
2582
|
+
this._signalingState === 0 && !this._socket) {
|
|
2583
|
+
await this._connectToPeer();
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
/**
|
|
2588
|
+
* Parse ICE candidate string
|
|
2589
|
+
* @private
|
|
2590
|
+
*/
|
|
2591
|
+
_parseIceCandidate(candidateStr) {
|
|
2592
|
+
const parts = candidateStr.split(' ');
|
|
2593
|
+
if (parts.length < 6) {
|
|
2594
|
+
return null;
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
return {
|
|
2598
|
+
foundation: parts[0].replace('candidate:', ''),
|
|
2599
|
+
component: parts[1],
|
|
2600
|
+
protocol: parts[2],
|
|
2601
|
+
priority: parseInt(parts[3], 10),
|
|
2602
|
+
ip: parts[4],
|
|
2603
|
+
port: parseInt(parts[5], 10),
|
|
2604
|
+
type: parts[7], // typ host/srflx/relay
|
|
2605
|
+
relatedAddress: parts[9] || null,
|
|
2606
|
+
relatedPort: parts[11] ? parseInt(parts[11], 10) : null
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
/**
|
|
2611
|
+
* Select best candidate pair for connection
|
|
2612
|
+
* Prioritizes: relay > srflx > host
|
|
2613
|
+
* @private
|
|
2614
|
+
*/
|
|
2615
|
+
_selectBestCandidatePair() {
|
|
2616
|
+
if (this._remoteCandidates.length === 0 || this._iceCandidates.length === 0) {
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
// Priority order: relay > srflx > host
|
|
2621
|
+
const typePriority = { 'relay': 3, 'srflx': 2, 'host': 1 };
|
|
2622
|
+
|
|
2623
|
+
// Find best local candidate
|
|
2624
|
+
let bestLocal = this._iceCandidates[0];
|
|
2625
|
+
for (const candidate of this._iceCandidates) {
|
|
2626
|
+
if (typePriority[candidate.type] > typePriority[bestLocal.type]) {
|
|
2627
|
+
bestLocal = candidate;
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
// Find best remote candidate
|
|
2632
|
+
let bestRemote = this._remoteCandidates[0];
|
|
2633
|
+
for (const candidate of this._remoteCandidates) {
|
|
2634
|
+
if (typePriority[candidate.type] > typePriority[bestRemote.type]) {
|
|
2635
|
+
bestRemote = candidate;
|
|
2562
2636
|
}
|
|
2563
2637
|
}
|
|
2638
|
+
|
|
2639
|
+
this._selectedLocalCandidate = bestLocal;
|
|
2640
|
+
this._selectedRemoteCandidate = bestRemote;
|
|
2641
|
+
|
|
2642
|
+
// Update addresses for connection
|
|
2643
|
+
this._localAddress = bestLocal.ip;
|
|
2644
|
+
this._localPort = bestLocal.port;
|
|
2645
|
+
this._remoteAddress = bestRemote.ip;
|
|
2646
|
+
this._remotePort = bestRemote.port;
|
|
2647
|
+
|
|
2648
|
+
console.log(`[NativePeerConnection] Selected candidate pair:`);
|
|
2649
|
+
console.log(` Local: ${bestLocal.type} ${this._localAddress}:${this._localPort}`);
|
|
2650
|
+
console.log(` Remote: ${bestRemote.type} ${this._remoteAddress}:${this._remotePort}`);
|
|
2564
2651
|
}
|
|
2565
2652
|
|
|
2566
2653
|
/**
|
|
@@ -2696,14 +2783,24 @@ function requireNativePeerConnectionFactory () {
|
|
|
2696
2783
|
return;
|
|
2697
2784
|
}
|
|
2698
2785
|
|
|
2699
|
-
//
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2786
|
+
// Check if we're using relay candidates - if so, skip tie-breaking
|
|
2787
|
+
const usingRelay = this._selectedLocalCandidate?.type === 'relay' ||
|
|
2788
|
+
this._selectedRemoteCandidate?.type === 'relay';
|
|
2789
|
+
|
|
2790
|
+
if (!usingRelay) {
|
|
2791
|
+
// Tie-breaking: only connect if our port is higher than remote port
|
|
2792
|
+
// This ensures only one peer connects, avoiding the race condition
|
|
2793
|
+
// Note: This only works for direct connections (host/srflx)
|
|
2794
|
+
if (this._localPort < this._remotePort) {
|
|
2795
|
+
console.log(`[NativePeerConnection] Not connecting (local port ${this._localPort} < remote port ${this._remotePort}), waiting for incoming`);
|
|
2796
|
+
return;
|
|
2797
|
+
}
|
|
2704
2798
|
}
|
|
2705
2799
|
|
|
2706
2800
|
console.log(`[NativePeerConnection] Connecting to ${this._remoteAddress}:${this._remotePort}`);
|
|
2801
|
+
if (usingRelay) {
|
|
2802
|
+
console.log(`[NativePeerConnection] Using TURN relay connection`);
|
|
2803
|
+
}
|
|
2707
2804
|
|
|
2708
2805
|
this._socket = new net.Socket();
|
|
2709
2806
|
|
|
@@ -2996,7 +3093,12 @@ a=max-message-size:262144
|
|
|
2996
3093
|
|
|
2997
3094
|
// Emit each candidate
|
|
2998
3095
|
for (const candidate of candidates) {
|
|
2999
|
-
|
|
3096
|
+
// Parse and store the candidate
|
|
3097
|
+
const parsed = this._parseIceCandidate(candidate.candidate);
|
|
3098
|
+
if (parsed) {
|
|
3099
|
+
this._iceCandidates.push(parsed);
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3000
3102
|
this.emit('icecandidate', {
|
|
3001
3103
|
candidate: candidate.candidate,
|
|
3002
3104
|
sdpMid: candidate.sdpMid,
|
|
@@ -3010,8 +3112,14 @@ a=max-message-size:262144
|
|
|
3010
3112
|
|
|
3011
3113
|
// Fallback: emit only local host candidate
|
|
3012
3114
|
if (this._localAddress && this._localPort) {
|
|
3115
|
+
const candidateStr = `candidate:1 1 tcp 2130706431 ${this._localAddress} ${this._localPort} typ host`;
|
|
3116
|
+
const parsed = this._parseIceCandidate(candidateStr);
|
|
3117
|
+
if (parsed) {
|
|
3118
|
+
this._iceCandidates.push(parsed);
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3013
3121
|
const candidate = {
|
|
3014
|
-
candidate:
|
|
3122
|
+
candidate: candidateStr,
|
|
3015
3123
|
sdpMid: 'data',
|
|
3016
3124
|
sdpMLineIndex: 0
|
|
3017
3125
|
};
|