node-rtc-connection 1.0.12 → 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/src/ICEGatherer.js
DELETED
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ICE Candidate Gatherer
|
|
3
|
-
* Discovers local, reflexive (STUN), and relay (TURN) candidates
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const dgram = require('dgram');
|
|
8
|
-
const STUNClient = require('./STUNClient');
|
|
9
|
-
const TURNClient = require('./TURNClient');
|
|
10
|
-
|
|
11
|
-
class ICEGatherer {
|
|
12
|
-
constructor(options = {}) {
|
|
13
|
-
this.stunServers = options.stunServers || [
|
|
14
|
-
'stun.l.google.com:19302',
|
|
15
|
-
'stun1.l.google.com:19302',
|
|
16
|
-
'stun2.l.google.com:19302'
|
|
17
|
-
];
|
|
18
|
-
this.turnServers = options.turnServers || [];
|
|
19
|
-
this.gatherTimeout = options.gatherTimeout || 5000;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Gather all ICE candidates
|
|
24
|
-
* @param {number} localPort - Local port being used
|
|
25
|
-
* @returns {Promise<Array>} Array of ICE candidates
|
|
26
|
-
*/
|
|
27
|
-
async gatherCandidates(localPort) {
|
|
28
|
-
const candidates = [];
|
|
29
|
-
|
|
30
|
-
// 1. Gather host candidates (local interfaces)
|
|
31
|
-
const hostCandidates = this._getHostCandidates(localPort);
|
|
32
|
-
candidates.push(...hostCandidates);
|
|
33
|
-
|
|
34
|
-
// 2. Gather server reflexive candidates (via STUN)
|
|
35
|
-
try {
|
|
36
|
-
const srflxCandidates = await this._getServerReflexiveCandidates(localPort);
|
|
37
|
-
candidates.push(...srflxCandidates);
|
|
38
|
-
} catch (err) {
|
|
39
|
-
console.warn('Failed to gather STUN candidates:', err.message);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// 3. Gather relay candidates (via TURN)
|
|
43
|
-
try {
|
|
44
|
-
const relayCandidates = await this._getRelayCandidates(localPort);
|
|
45
|
-
candidates.push(...relayCandidates);
|
|
46
|
-
} catch (err) {
|
|
47
|
-
console.warn('Failed to gather TURN candidates:', err.message);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 4. Sort by priority (host > srflx > relay)
|
|
51
|
-
candidates.sort((a, b) => b.priority - a.priority);
|
|
52
|
-
|
|
53
|
-
return candidates;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get host candidates from local network interfaces
|
|
58
|
-
* @private
|
|
59
|
-
*/
|
|
60
|
-
_getHostCandidates(port) {
|
|
61
|
-
const candidates = [];
|
|
62
|
-
const interfaces = os.networkInterfaces();
|
|
63
|
-
let foundation = 1;
|
|
64
|
-
|
|
65
|
-
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
66
|
-
for (const addr of addrs) {
|
|
67
|
-
// Skip internal/loopback addresses
|
|
68
|
-
if (addr.internal) {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Support both IPv4 and IPv6
|
|
73
|
-
if (addr.family !== 'IPv4' && addr.family !== 'IPv6') {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Calculate priority (IPv4 slightly higher than IPv6)
|
|
78
|
-
const typePreference = addr.family === 'IPv4' ? 65535 : 65534;
|
|
79
|
-
const priority = this._calculatePriority('host', typePreference, foundation);
|
|
80
|
-
|
|
81
|
-
candidates.push({
|
|
82
|
-
candidate: `candidate:${foundation} 1 udp ${priority} ${addr.address} ${port} typ host`,
|
|
83
|
-
sdpMLineIndex: 0,
|
|
84
|
-
sdpMid: 'data',
|
|
85
|
-
foundation: String(foundation),
|
|
86
|
-
component: 1,
|
|
87
|
-
protocol: 'udp',
|
|
88
|
-
priority,
|
|
89
|
-
ip: addr.address,
|
|
90
|
-
port,
|
|
91
|
-
type: 'host',
|
|
92
|
-
tcpType: null,
|
|
93
|
-
relatedAddress: null,
|
|
94
|
-
relatedPort: null
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
foundation++;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return candidates;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Get server reflexive candidates via STUN
|
|
106
|
-
* @private
|
|
107
|
-
*/
|
|
108
|
-
async _getServerReflexiveCandidates(localPort) {
|
|
109
|
-
const candidates = [];
|
|
110
|
-
const stunPromises = [];
|
|
111
|
-
|
|
112
|
-
// Try multiple STUN servers in parallel
|
|
113
|
-
for (const stunServer of this.stunServers) {
|
|
114
|
-
const promise = this._querySTUNServer(stunServer, localPort)
|
|
115
|
-
.catch(err => null); // Ignore individual failures
|
|
116
|
-
stunPromises.push(promise);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Wait for first successful response
|
|
120
|
-
const results = await Promise.race([
|
|
121
|
-
Promise.any(stunPromises.filter(p => p)),
|
|
122
|
-
new Promise(resolve => setTimeout(() => resolve(null), this.gatherTimeout))
|
|
123
|
-
]);
|
|
124
|
-
|
|
125
|
-
if (results) {
|
|
126
|
-
const foundation = 100;
|
|
127
|
-
const priority = this._calculatePriority('srflx', 65535, foundation);
|
|
128
|
-
|
|
129
|
-
candidates.push({
|
|
130
|
-
candidate: `candidate:${foundation} 1 udp ${priority} ${results.ip} ${results.port} typ srflx raddr ${results.localIp} rport ${localPort}`,
|
|
131
|
-
sdpMLineIndex: 0,
|
|
132
|
-
sdpMid: 'data',
|
|
133
|
-
foundation: String(foundation),
|
|
134
|
-
component: 1,
|
|
135
|
-
protocol: 'udp',
|
|
136
|
-
priority,
|
|
137
|
-
ip: results.ip,
|
|
138
|
-
port: results.port,
|
|
139
|
-
type: 'srflx',
|
|
140
|
-
tcpType: null,
|
|
141
|
-
relatedAddress: results.localIp,
|
|
142
|
-
relatedPort: localPort
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return candidates;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Query a STUN server
|
|
151
|
-
* @private
|
|
152
|
-
*/
|
|
153
|
-
async _querySTUNServer(stunServer, localPort) {
|
|
154
|
-
const client = new STUNClient();
|
|
155
|
-
try {
|
|
156
|
-
const result = await client.getReflexiveAddress(stunServer);
|
|
157
|
-
|
|
158
|
-
// Get local IP that would be used to reach STUN server
|
|
159
|
-
const localIp = this._getLocalIPForRemote();
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
...result,
|
|
163
|
-
localIp
|
|
164
|
-
};
|
|
165
|
-
} finally {
|
|
166
|
-
client.close();
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Get local IP address that would be used for external connections
|
|
172
|
-
* @private
|
|
173
|
-
*/
|
|
174
|
-
_getLocalIPForRemote() {
|
|
175
|
-
const interfaces = os.networkInterfaces();
|
|
176
|
-
|
|
177
|
-
// Prefer non-internal IPv4 addresses first
|
|
178
|
-
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
179
|
-
for (const addr of addrs) {
|
|
180
|
-
if (!addr.internal && addr.family === 'IPv4') {
|
|
181
|
-
return addr.address;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Fall back to IPv6 if no IPv4 available
|
|
187
|
-
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
188
|
-
for (const addr of addrs) {
|
|
189
|
-
if (!addr.internal && addr.family === 'IPv6') {
|
|
190
|
-
return addr.address;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return '0.0.0.0';
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Get relay candidates via TURN
|
|
200
|
-
* @private
|
|
201
|
-
*/
|
|
202
|
-
async _getRelayCandidates(localPort) {
|
|
203
|
-
const candidates = [];
|
|
204
|
-
|
|
205
|
-
if (this.turnServers.length === 0) {
|
|
206
|
-
console.log('[ICEGatherer] No TURN servers configured');
|
|
207
|
-
return candidates;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
console.log(`[ICEGatherer] Querying ${this.turnServers.length} TURN server(s) for relay candidates...`);
|
|
211
|
-
|
|
212
|
-
const turnPromises = [];
|
|
213
|
-
|
|
214
|
-
// Try TURN servers
|
|
215
|
-
for (const turnConfig of this.turnServers) {
|
|
216
|
-
const promise = this._queryTURNServer(turnConfig, localPort)
|
|
217
|
-
.catch(err => {
|
|
218
|
-
console.warn(`[ICEGatherer] TURN query failed: ${err.message}`);
|
|
219
|
-
return null;
|
|
220
|
-
});
|
|
221
|
-
turnPromises.push(promise);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Wait for first successful response or timeout
|
|
225
|
-
const results = await Promise.race([
|
|
226
|
-
...turnPromises,
|
|
227
|
-
new Promise(resolve => setTimeout(() => resolve(null), this.gatherTimeout))
|
|
228
|
-
]);
|
|
229
|
-
|
|
230
|
-
if (results) {
|
|
231
|
-
const foundation = 200;
|
|
232
|
-
const priority = this._calculatePriority('relay', 65535, foundation);
|
|
233
|
-
|
|
234
|
-
const localIp = this._getLocalIPForRemote();
|
|
235
|
-
|
|
236
|
-
console.log(`[ICEGatherer] Got TURN relay: ${results.relayedAddress}:${results.relayedPort}`);
|
|
237
|
-
|
|
238
|
-
candidates.push({
|
|
239
|
-
candidate: `candidate:${foundation} 1 udp ${priority} ${results.relayedAddress} ${results.relayedPort} typ relay raddr ${localIp} rport ${localPort}`,
|
|
240
|
-
sdpMLineIndex: 0,
|
|
241
|
-
sdpMid: 'data',
|
|
242
|
-
foundation: String(foundation),
|
|
243
|
-
component: 1,
|
|
244
|
-
protocol: 'udp',
|
|
245
|
-
priority,
|
|
246
|
-
ip: results.relayedAddress,
|
|
247
|
-
port: results.relayedPort,
|
|
248
|
-
type: 'relay',
|
|
249
|
-
tcpType: null,
|
|
250
|
-
relatedAddress: localIp,
|
|
251
|
-
relatedPort: localPort
|
|
252
|
-
});
|
|
253
|
-
} else {
|
|
254
|
-
console.log('[ICEGatherer] No relay candidates obtained from TURN servers');
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return candidates;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Query a TURN server
|
|
262
|
-
* @private
|
|
263
|
-
*/
|
|
264
|
-
async _queryTURNServer(turnConfig, localPort) {
|
|
265
|
-
const client = new TURNClient({
|
|
266
|
-
server: turnConfig.urls || turnConfig.url,
|
|
267
|
-
username: turnConfig.username,
|
|
268
|
-
password: turnConfig.credential,
|
|
269
|
-
transport: 'udp'
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
try {
|
|
273
|
-
const result = await client.allocate();
|
|
274
|
-
return result;
|
|
275
|
-
} finally {
|
|
276
|
-
client.close();
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Calculate ICE candidate priority (RFC 5245)
|
|
282
|
-
* @private
|
|
283
|
-
*/
|
|
284
|
-
_calculatePriority(type, localPref, foundation) {
|
|
285
|
-
const typePreference = {
|
|
286
|
-
'host': 126,
|
|
287
|
-
'srflx': 100,
|
|
288
|
-
'prflx': 110,
|
|
289
|
-
'relay': 0
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const typePref = typePreference[type] || 0;
|
|
293
|
-
const componentId = 1; // RTP component
|
|
294
|
-
|
|
295
|
-
// Priority = (2^24)*(type preference) + (2^8)*(local preference) + (256 - component ID)
|
|
296
|
-
return (typePref << 24) + (localPref << 8) + (256 - componentId);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Parse ICE candidate string
|
|
301
|
-
* @param {string} candidateStr - ICE candidate string
|
|
302
|
-
* @returns {Object} Parsed candidate object
|
|
303
|
-
*/
|
|
304
|
-
static parseCandidate(candidateStr) {
|
|
305
|
-
// Remove "candidate:" prefix if present
|
|
306
|
-
const str = candidateStr.replace(/^candidate:/, '');
|
|
307
|
-
const parts = str.split(' ');
|
|
308
|
-
|
|
309
|
-
if (parts.length < 8) {
|
|
310
|
-
throw new Error('Invalid candidate string');
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const candidate = {
|
|
314
|
-
foundation: parts[0],
|
|
315
|
-
component: parseInt(parts[1], 10),
|
|
316
|
-
protocol: parts[2].toLowerCase(),
|
|
317
|
-
priority: parseInt(parts[3], 10),
|
|
318
|
-
ip: parts[4],
|
|
319
|
-
port: parseInt(parts[5], 10),
|
|
320
|
-
type: parts[7]
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
// Parse optional fields (raddr, rport, etc.)
|
|
324
|
-
for (let i = 8; i < parts.length; i += 2) {
|
|
325
|
-
const key = parts[i];
|
|
326
|
-
const value = parts[i + 1];
|
|
327
|
-
|
|
328
|
-
if (key === 'raddr') {
|
|
329
|
-
candidate.relatedAddress = value;
|
|
330
|
-
} else if (key === 'rport') {
|
|
331
|
-
candidate.relatedPort = parseInt(value, 10);
|
|
332
|
-
} else if (key === 'tcptype') {
|
|
333
|
-
candidate.tcpType = value;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return candidate;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
module.exports = ICEGatherer;
|