node-rtc-connection 1.0.12 → 1.0.15

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.
@@ -1,1044 +0,0 @@
1
- const EventEmitter = require('events');
2
- const net = require('net');
3
- const dgram = require('dgram');
4
- const crypto = require('crypto');
5
- const os = require('os');
6
- const STUNClient = require('./STUNClient');
7
- const ICEGatherer = require('./ICEGatherer');
8
- const { SecureConnection } = require('./SecureConnection');
9
- const UDPTransport = require('./UDPTransport');
10
-
11
- /**
12
- * NativePeerConnectionFactory creates native peer connection instances.
13
- * Real implementation using Node.js net package for peer-to-peer communication.
14
- */
15
- class NativePeerConnectionFactory {
16
- constructor() {
17
- this._initialized = false;
18
- this._peerConnections = new Set();
19
- }
20
-
21
- /**
22
- * Initialize the factory
23
- */
24
- initialize() {
25
- if (this._initialized) {
26
- return;
27
- }
28
-
29
- console.log('[NativePeerConnectionFactory] Initializing with Node.js net package');
30
- this._initialized = true;
31
- }
32
-
33
- /**
34
- * Create a native peer connection
35
- * @param {Object} configuration - RTCConfiguration
36
- * @returns {NativePeerConnection}
37
- */
38
- createPeerConnection(configuration) {
39
- if (!this._initialized) {
40
- this.initialize();
41
- }
42
-
43
- const nativePC = new NativePeerConnection(configuration);
44
- this._peerConnections.add(nativePC);
45
-
46
- nativePC.on('close', () => {
47
- this._peerConnections.delete(nativePC);
48
- });
49
-
50
- return nativePC;
51
- }
52
-
53
- /**
54
- * Dispose of the factory and all peer connections
55
- */
56
- dispose() {
57
- for (const pc of this._peerConnections) {
58
- pc.close();
59
- }
60
- this._peerConnections.clear();
61
- this._initialized = false;
62
- }
63
- }
64
-
65
- /**
66
- * NativePeerConnection - Real implementation using Node.js net package
67
- * Implements peer-to-peer connection using TCP for data channels
68
- */
69
- class NativePeerConnection extends EventEmitter {
70
- constructor(configuration) {
71
- super();
72
- this._configuration = configuration || {};
73
- this._signalingState = 0; // stable
74
- this._iceConnectionState = 0; // new
75
- this._iceGatheringState = 0; // new
76
- this._connectionState = 0; // new
77
- this._localDescription = null;
78
- this._remoteDescription = null;
79
- this._dataChannels = new Map();
80
- this._closed = false;
81
-
82
- // Networking components
83
- this._server = null;
84
- this._socket = null;
85
- this._localAddress = null;
86
- this._localPort = null;
87
- this._remoteAddress = null;
88
- this._remotePort = null;
89
- this._isOfferer = false;
90
- this._iceUsername = this._generateRandomString(8);
91
- this._icePassword = this._generateRandomString(24);
92
- this._fingerprint = this._generateFingerprint();
93
-
94
- // New features
95
- this._useEncryption = this._configuration.encryption === true; // Disabled by default
96
- this._useUDP = this._configuration.transport === 'udp';
97
- this._iceGatherer = new ICEGatherer({
98
- stunServers: this._extractSTUNServers(this._configuration),
99
- turnServers: this._extractTURNServers(this._configuration)
100
- });
101
- this._secureConnection = null;
102
- this._udpTransport = null;
103
- this._iceCandidates = [];
104
- this._remoteCandidates = [];
105
- this._selectedLocalCandidate = null;
106
- this._selectedRemoteCandidate = null;
107
-
108
- console.log('[NativePeerConnection] Created with STUN/ICE/Encryption support');
109
- console.log(` - Encryption: ${this._useEncryption ? 'enabled' : 'disabled (requires valid certs)'}`);
110
- console.log(` - Transport: ${this._useUDP ? 'UDP' : 'TCP'}`);
111
- }
112
-
113
- /**
114
- * Extract STUN servers from configuration
115
- * @private
116
- */
117
- _extractSTUNServers(config) {
118
- const stunServers = [];
119
-
120
- if (config.iceServers) {
121
- for (const server of config.iceServers) {
122
- if (server.urls) {
123
- const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
124
- for (const url of urls) {
125
- if (url.startsWith('stun:')) {
126
- stunServers.push(url.replace('stun:', ''));
127
- }
128
- }
129
- }
130
- }
131
- }
132
-
133
- return stunServers.length > 0 ? stunServers : undefined;
134
- }
135
-
136
- /**
137
- * Extract TURN servers from configuration
138
- * @private
139
- */
140
- _extractTURNServers(config) {
141
- const turnServers = [];
142
-
143
- if (config.iceServers) {
144
- for (const server of config.iceServers) {
145
- if (server.urls) {
146
- const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
147
- for (const url of urls) {
148
- if (url.startsWith('turn:')) {
149
- turnServers.push({
150
- urls: url,
151
- username: server.username,
152
- credential: server.credential
153
- });
154
- }
155
- }
156
- }
157
- }
158
- }
159
-
160
- if (turnServers.length > 0) {
161
- console.log(`[NativePeerConnection] Configured ${turnServers.length} TURN server(s)`);
162
- }
163
-
164
- return turnServers;
165
- }
166
-
167
- /**
168
- * Create an offer
169
- * @param {Object} options
170
- * @returns {Promise<{type: string, sdp: string}>}
171
- */
172
- async createOffer(options) {
173
- if (this._closed) {
174
- throw new Error('PeerConnection is closed');
175
- }
176
-
177
- this._isOfferer = true;
178
-
179
- // Create TCP server to listen for incoming connections
180
- await this._createServer();
181
-
182
- // Generate real SDP with actual network information
183
- const sdp = this._generateSDP('offer');
184
- return { type: 'offer', sdp };
185
- }
186
-
187
- /**
188
- * Create an answer
189
- * @param {Object} options
190
- * @returns {Promise<{type: string, sdp: string}>}
191
- */
192
- async createAnswer(options) {
193
- if (this._closed) {
194
- throw new Error('PeerConnection is closed');
195
- }
196
-
197
- if (!this._remoteDescription) {
198
- throw new Error('No remote description set');
199
- }
200
-
201
- this._isOfferer = false;
202
-
203
- // Create TCP server to listen for incoming connections (if needed)
204
- if (!this._server) {
205
- await this._createServer();
206
- }
207
-
208
- // Generate real SDP with actual network information
209
- const sdp = this._generateSDP('answer');
210
- return { type: 'answer', sdp };
211
- }
212
-
213
- /**
214
- * Set local description
215
- * @param {Object} description
216
- * @returns {Promise<void>}
217
- */
218
- async setLocalDescription(description) {
219
- if (this._closed) {
220
- throw new Error('PeerConnection is closed');
221
- }
222
-
223
- this._localDescription = description;
224
-
225
- // Update signaling state
226
- if (description.type === 'offer') {
227
- this._signalingState = 1; // have-local-offer
228
- this.emit('signalingstatechange', this._signalingState);
229
- } else if (description.type === 'answer') {
230
- this._signalingState = 0; // stable
231
- this.emit('signalingstatechange', this._signalingState);
232
-
233
- // If we're answerer and have remote description, connect
234
- if (!this._isOfferer && this._remoteDescription) {
235
- await this._connectToPeer();
236
- }
237
- }
238
-
239
- // Start real ICE gathering
240
- this._startIceGathering();
241
- }
242
-
243
- /**
244
- * Set remote description
245
- * @param {Object} description
246
- * @returns {Promise<void>}
247
- */
248
- async setRemoteDescription(description) {
249
- if (this._closed) {
250
- throw new Error('PeerConnection is closed');
251
- }
252
-
253
- // Parse remote SDP to extract connection information
254
- this._parseSDP(description.sdp);
255
- this._remoteDescription = description;
256
-
257
- // Update signaling state
258
- if (description.type === 'offer') {
259
- this._signalingState = 2; // have-remote-offer
260
- this.emit('signalingstatechange', this._signalingState);
261
- } else if (description.type === 'answer') {
262
- this._signalingState = 0; // stable
263
- this.emit('signalingstatechange', this._signalingState);
264
-
265
- // If we're offerer and have local description, connect
266
- if (this._isOfferer && this._localDescription) {
267
- await this._connectToPeer();
268
- }
269
- }
270
- }
271
-
272
- /**
273
- * Add ICE candidate
274
- * @param {Object} candidate
275
- * @returns {Promise<void>}
276
- */
277
- async addIceCandidate(candidate) {
278
- if (this._closed) {
279
- throw new Error('PeerConnection is closed');
280
- }
281
-
282
- if (!candidate || !candidate.candidate) {
283
- // null candidate signals end of candidates - try to select best pair
284
- if (this._remoteCandidates.length > 0 && !this._selectedRemoteCandidate) {
285
- this._selectBestCandidatePair();
286
- }
287
- return;
288
- }
289
-
290
- // Parse ICE candidate
291
- const parsed = this._parseIceCandidate(candidate.candidate);
292
- if (!parsed) {
293
- return;
294
- }
295
-
296
- // Store the remote candidate
297
- this._remoteCandidates.push(parsed);
298
- console.log(`[NativePeerConnection] Added remote ICE candidate (${parsed.type}): ${parsed.ip}:${parsed.port}`);
299
-
300
- // For backward compatibility, set remote address immediately if not set
301
- if (!this._remoteAddress) {
302
- this._remoteAddress = parsed.ip;
303
- this._remotePort = parsed.port;
304
- console.log(`[NativePeerConnection] Remote address: ${this._remoteAddress}:${this._remotePort}`);
305
- }
306
-
307
- // If we haven't selected a pair yet, try to select now
308
- if (!this._selectedRemoteCandidate && this._iceCandidates.length > 0) {
309
- this._selectBestCandidatePair();
310
- }
311
-
312
- // If we selected a pair and are offerer, connect
313
- if (this._selectedRemoteCandidate && this._isOfferer &&
314
- this._signalingState === 0 && !this._socket) {
315
- await this._connectToPeer();
316
- }
317
- }
318
-
319
- /**
320
- * Parse ICE candidate string
321
- * @private
322
- */
323
- _parseIceCandidate(candidateStr) {
324
- const parts = candidateStr.split(' ');
325
- if (parts.length < 6) {
326
- return null;
327
- }
328
-
329
- return {
330
- foundation: parts[0].replace('candidate:', ''),
331
- component: parts[1],
332
- protocol: parts[2],
333
- priority: parseInt(parts[3], 10),
334
- ip: parts[4],
335
- port: parseInt(parts[5], 10),
336
- type: parts[7], // typ host/srflx/relay
337
- relatedAddress: parts[9] || null,
338
- relatedPort: parts[11] ? parseInt(parts[11], 10) : null
339
- };
340
- }
341
-
342
- /**
343
- * Select best candidate pair for connection
344
- * Prioritizes: relay > srflx > host
345
- * @private
346
- */
347
- _selectBestCandidatePair() {
348
- if (this._remoteCandidates.length === 0 || this._iceCandidates.length === 0) {
349
- return;
350
- }
351
-
352
- // Priority order: relay > srflx > host
353
- const typePriority = { 'relay': 3, 'srflx': 2, 'host': 1 };
354
-
355
- // Find best local candidate
356
- let bestLocal = this._iceCandidates[0];
357
- for (const candidate of this._iceCandidates) {
358
- if (typePriority[candidate.type] > typePriority[bestLocal.type]) {
359
- bestLocal = candidate;
360
- }
361
- }
362
-
363
- // Find best remote candidate
364
- let bestRemote = this._remoteCandidates[0];
365
- for (const candidate of this._remoteCandidates) {
366
- if (typePriority[candidate.type] > typePriority[bestRemote.type]) {
367
- bestRemote = candidate;
368
- }
369
- }
370
-
371
- this._selectedLocalCandidate = bestLocal;
372
- this._selectedRemoteCandidate = bestRemote;
373
-
374
- // Update addresses for connection
375
- this._localAddress = bestLocal.ip;
376
- this._localPort = bestLocal.port;
377
- this._remoteAddress = bestRemote.ip;
378
- this._remotePort = bestRemote.port;
379
-
380
- console.log(`[NativePeerConnection] Selected candidate pair:`);
381
- console.log(` Local: ${bestLocal.type} ${this._localAddress}:${this._localPort}`);
382
- console.log(` Remote: ${bestRemote.type} ${this._remoteAddress}:${this._remotePort}`);
383
- }
384
-
385
- /**
386
- * Create a data channel
387
- * @param {string} label
388
- * @param {Object} options
389
- * @returns {NativeDataChannel}
390
- */
391
- createDataChannel(label, options) {
392
- if (this._closed) {
393
- throw new Error('PeerConnection is closed');
394
- }
395
-
396
- const channel = new NativeDataChannel(label, options, this);
397
- this._dataChannels.set(label, channel);
398
-
399
- // If socket is already connected, open the channel
400
- if (this._socket && this._socket.writable) {
401
- setTimeout(() => channel._setConnected(this._socket), 0);
402
- }
403
-
404
- // Emit negotiation needed
405
- setTimeout(() => {
406
- this.emit('negotiationneeded');
407
- }, 0);
408
-
409
- return channel;
410
- }
411
-
412
- /**
413
- * Set configuration
414
- * @param {Object} configuration
415
- */
416
- setConfiguration(configuration) {
417
- this._configuration = configuration;
418
- }
419
-
420
- /**
421
- * Get stats
422
- * @returns {Promise<Object>}
423
- */
424
- async getStats() {
425
- return {
426
- type: 'peer-connection',
427
- timestamp: Date.now(),
428
- // Mock stats
429
- };
430
- }
431
-
432
- /**
433
- * Close the peer connection
434
- */
435
- close() {
436
- if (this._closed) {
437
- return;
438
- }
439
-
440
- this._closed = true;
441
- this._signalingState = 5; // closed
442
-
443
- // Close all data channels
444
- for (const [label, channel] of this._dataChannels) {
445
- channel.close();
446
- }
447
- this._dataChannels.clear();
448
-
449
- // Close secure connection
450
- if (this._secureConnection) {
451
- this._secureConnection.close();
452
- this._secureConnection = null;
453
- }
454
-
455
- // Close UDP transport
456
- if (this._udpTransport) {
457
- this._udpTransport.close();
458
- this._udpTransport = null;
459
- }
460
-
461
- // Close socket
462
- if (this._socket) {
463
- this._socket.destroy();
464
- this._socket = null;
465
- }
466
-
467
- // Close server
468
- if (this._server) {
469
- this._server.close();
470
- this._server = null;
471
- }
472
-
473
- this.emit('signalingstatechange', this._signalingState);
474
- this.emit('close');
475
- }
476
-
477
- /**
478
- * Create TCP server to listen for incoming connections
479
- * @private
480
- */
481
- async _createServer() {
482
- return new Promise((resolve, reject) => {
483
- this._server = net.createServer((socket) => {
484
- console.log('[NativePeerConnection] Incoming connection accepted');
485
- this._handleIncomingConnection(socket);
486
- });
487
-
488
- this._server.listen(0, '0.0.0.0', () => {
489
- const address = this._server.address();
490
- this._localPort = address.port;
491
- this._localAddress = this._getLocalIPAddress();
492
- console.log(`[NativePeerConnection] Server listening on ${this._localAddress}:${this._localPort}`);
493
- resolve();
494
- });
495
-
496
- this._server.on('error', (err) => {
497
- console.error('[NativePeerConnection] Server error:', err);
498
- reject(err);
499
- });
500
- });
501
- }
502
-
503
- /**
504
- * Connect to remote peer
505
- * @private
506
- */
507
- async _connectToPeer() {
508
- if (!this._remoteAddress || !this._remotePort) {
509
- console.log('[NativePeerConnection] Cannot connect: missing remote address');
510
- return;
511
- }
512
-
513
- if (this._socket) {
514
- console.log('[NativePeerConnection] Already connected');
515
- return;
516
- }
517
-
518
- // Check if we're using relay candidates - if so, skip tie-breaking
519
- const usingRelay = this._selectedLocalCandidate?.type === 'relay' ||
520
- this._selectedRemoteCandidate?.type === 'relay';
521
-
522
- if (!usingRelay) {
523
- // Tie-breaking: only connect if our port is higher than remote port
524
- // This ensures only one peer connects, avoiding the race condition
525
- // Note: This only works for direct connections (host/srflx)
526
- if (this._localPort < this._remotePort) {
527
- console.log(`[NativePeerConnection] Not connecting (local port ${this._localPort} < remote port ${this._remotePort}), waiting for incoming`);
528
- return;
529
- }
530
- }
531
-
532
- console.log(`[NativePeerConnection] Connecting to ${this._remoteAddress}:${this._remotePort}`);
533
- if (usingRelay) {
534
- console.log(`[NativePeerConnection] Using TURN relay connection`);
535
- }
536
-
537
- this._socket = new net.Socket();
538
-
539
- this._socket.connect(this._remotePort, this._remoteAddress, async () => {
540
- console.log('[NativePeerConnection] Connected to peer');
541
-
542
- // Apply TLS encryption if enabled
543
- if (this._useEncryption) {
544
- await this._wrapWithEncryption(false);
545
- }
546
-
547
- this._iceConnectionState = 2; // connected
548
- this.emit('iceconnectionstatechange', this._iceConnectionState);
549
-
550
- // Announce existing data channels to peer and open them
551
- setImmediate(() => {
552
- for (const [label, channel] of this._dataChannels) {
553
- this._sendChannelAnnouncement(label);
554
- channel._setConnected(this._getActiveSocket());
555
- }
556
- });
557
- });
558
-
559
- this._setupSocketHandlers(this._socket);
560
- }
561
-
562
- /**
563
- * Handle incoming connection from peer
564
- * @private
565
- */
566
- async _handleIncomingConnection(socket) {
567
- if (this._socket) {
568
- console.log('[NativePeerConnection] Connection already exists, closing new one');
569
- socket.destroy();
570
- return;
571
- }
572
-
573
- console.log('[NativePeerConnection] Accepted connection from peer');
574
-
575
- // Setup handlers BEFORE storing socket to avoid missing early data
576
- this._setupSocketHandlers(socket);
577
-
578
- this._socket = socket;
579
-
580
- // Apply TLS encryption if enabled
581
- if (this._useEncryption) {
582
- await this._wrapWithEncryption(true);
583
- }
584
-
585
- this._iceConnectionState = 2; // connected
586
- this.emit('iceconnectionstatechange', this._iceConnectionState);
587
-
588
- // Announce existing data channels to peer
589
- setImmediate(() => {
590
- for (const [label, channel] of this._dataChannels) {
591
- this._sendChannelAnnouncement(label);
592
- channel._setConnected(this._getActiveSocket());
593
- }
594
- });
595
- }
596
-
597
- /**
598
- * Wrap socket with TLS encryption
599
- * @private
600
- */
601
- async _wrapWithEncryption(isServer) {
602
- try {
603
- console.log(`[NativePeerConnection] Enabling TLS encryption (${isServer ? 'server' : 'client'})`);
604
-
605
- this._secureConnection = new SecureConnection(this._socket, { isServer });
606
- await this._secureConnection.wrap();
607
-
608
- // Update fingerprint with real certificate
609
- this._fingerprint = this._secureConnection.getFingerprint();
610
-
611
- console.log('[NativePeerConnection] TLS encryption enabled');
612
- } catch (err) {
613
- console.error('[NativePeerConnection] Failed to enable encryption:', err);
614
- throw err;
615
- }
616
- }
617
-
618
- /**
619
- * Get the active socket (secure or plain)
620
- * @private
621
- */
622
- _getActiveSocket() {
623
- return this._secureConnection?.secureSocket || this._socket;
624
- }
625
-
626
- /**
627
- * Setup socket event handlers
628
- * @private
629
- */
630
- _setupSocketHandlers(socket) {
631
- socket.on('data', (data) => {
632
- this._handleIncomingData(data);
633
- });
634
-
635
- socket.on('close', () => {
636
- console.log('[NativePeerConnection] Connection closed');
637
- this._iceConnectionState = 6; // closed
638
- this.emit('iceconnectionstatechange', this._iceConnectionState);
639
-
640
- // Close all data channels
641
- for (const [label, channel] of this._dataChannels) {
642
- channel._handleDisconnect();
643
- }
644
- });
645
-
646
- socket.on('error', (err) => {
647
- console.error('[NativePeerConnection] Socket error:', err);
648
- this._iceConnectionState = 4; // failed
649
- this.emit('iceconnectionstatechange', this._iceConnectionState);
650
- });
651
- }
652
-
653
- /**
654
- * Send channel announcement to peer (empty message to trigger remote channel creation)
655
- * @private
656
- */
657
- _sendChannelAnnouncement(label) {
658
- if (!this._socket) {
659
- console.log(`[NativePeerConnection] Cannot announce ${label}: no socket`);
660
- return;
661
- }
662
-
663
- const labelBuffer = Buffer.from(label, 'utf8');
664
- const labelLength = labelBuffer.length;
665
- const emptyData = Buffer.alloc(0);
666
-
667
- // Message format: <length:4><label-length:2><label><data>
668
- // length is the number of bytes after the length field
669
- const totalLength = 2 + labelLength + emptyData.length;
670
- const buffer = Buffer.allocUnsafe(4 + totalLength);
671
-
672
- buffer.writeUInt32BE(totalLength, 0);
673
- buffer.writeUInt16BE(labelLength, 4);
674
- labelBuffer.copy(buffer, 6);
675
-
676
- this._socket.write(buffer);
677
- console.log(`[NativePeerConnection] Announced channel: ${label}`);
678
- }
679
-
680
- /**
681
- * Handle incoming data from socket
682
- * @private
683
- */
684
- _handleIncomingData(data) {
685
- try {
686
- // Parse message format: <length:4><channel-label-length:2><channel-label><data>
687
- let offset = 0;
688
-
689
- while (offset < data.length) {
690
- if (offset + 6 > data.length) break;
691
-
692
- const totalLength = data.readUInt32BE(offset);
693
- const labelLength = data.readUInt16BE(offset + 4);
694
-
695
- if (offset + 6 + labelLength + (totalLength - 2 - labelLength) > data.length) break;
696
-
697
- const label = data.toString('utf8', offset + 6, offset + 6 + labelLength);
698
- const messageData = data.slice(offset + 6 + labelLength, offset + 6 + totalLength - 2);
699
-
700
- // Find or create the data channel
701
- let channel = this._dataChannels.get(label);
702
- if (!channel) {
703
- // Create remote data channel
704
- console.log(`[NativePeerConnection] Creating remote channel: ${label}`);
705
- channel = new NativeDataChannel(label, {}, this);
706
- this._dataChannels.set(label, channel);
707
-
708
- // Emit datachannel event first (before setting connected)
709
- this.emit('datachannel', channel);
710
-
711
- // Then set it as connected after a small delay to allow event handlers to be set up
712
- setImmediate(() => {
713
- channel._setConnected(this._socket);
714
- });
715
- }
716
-
717
- // Deliver the message (if it has data - announcements are empty)
718
- if (messageData.length > 0) {
719
- channel._handleIncomingMessage(messageData);
720
- }
721
-
722
- offset += 6 + totalLength - 2;
723
- }
724
- } catch (err) {
725
- console.error('[NativePeerConnection] Error parsing incoming data:', err);
726
- }
727
- }
728
-
729
- /**
730
- * Generate real SDP with actual network information
731
- * @private
732
- */
733
- _generateSDP(type) {
734
- const sessionId = Date.now();
735
- const address = this._localAddress || '0.0.0.0';
736
- const port = this._localPort || 9;
737
-
738
- return `v=0
739
- o=- ${sessionId} 2 IN IP4 ${address}
740
- s=-
741
- t=0 0
742
- a=group:BUNDLE data
743
- a=msid-semantic: WMS
744
- m=application ${port} TCP/DTLS/SCTP webrtc-datachannel
745
- c=IN IP4 ${address}
746
- a=ice-ufrag:${this._iceUsername}
747
- a=ice-pwd:${this._icePassword}
748
- a=ice-options:trickle
749
- a=fingerprint:sha-256 ${this._fingerprint}
750
- a=setup:actpass
751
- a=mid:data
752
- a=sctp-port:5000
753
- a=max-message-size:262144
754
- `;
755
- }
756
-
757
- /**
758
- * Parse SDP to extract connection information
759
- * @private
760
- */
761
- _parseSDP(sdp) {
762
- const lines = sdp.split('\n');
763
-
764
- for (const line of lines) {
765
- // Parse connection line: c=IN IP4 <address>
766
- if (line.startsWith('c=IN IP4 ')) {
767
- const address = line.substring(9).trim();
768
- if (address !== '0.0.0.0') {
769
- this._remoteAddress = address;
770
- }
771
- }
772
-
773
- // Parse media line: m=application <port> ...
774
- if (line.startsWith('m=application ')) {
775
- const parts = line.split(' ');
776
- if (parts.length >= 2) {
777
- const port = parseInt(parts[1], 10);
778
- if (port > 0 && port !== 9) {
779
- this._remotePort = port;
780
- }
781
- }
782
- }
783
- }
784
-
785
- console.log(`[NativePeerConnection] Parsed SDP - Remote: ${this._remoteAddress}:${this._remotePort}`);
786
- }
787
-
788
- /**
789
- * Get local IP address
790
- * @private
791
- */
792
- _getLocalIPAddress() {
793
- const interfaces = os.networkInterfaces();
794
-
795
- // Try to find a non-internal IPv4 address first
796
- for (const name of Object.keys(interfaces)) {
797
- for (const iface of interfaces[name]) {
798
- if (iface.family === 'IPv4' && !iface.internal) {
799
- return iface.address;
800
- }
801
- }
802
- }
803
-
804
- // Fall back to IPv6 if no IPv4 available
805
- for (const name of Object.keys(interfaces)) {
806
- for (const iface of interfaces[name]) {
807
- if (iface.family === 'IPv6' && !iface.internal) {
808
- return iface.address;
809
- }
810
- }
811
- }
812
-
813
- // Fallback to localhost
814
- return '127.0.0.1';
815
- }
816
-
817
- /**
818
- * Start ICE gathering with real network addresses
819
- * @private
820
- */
821
- /**
822
- * Start ICE candidate gathering with STUN support
823
- * @private
824
- */
825
- async _startIceGathering() {
826
- this._iceGatheringState = 1; // gathering
827
- this.emit('icegatheringstatechange', this._iceGatheringState);
828
-
829
- try {
830
- // Use real ICE gatherer to get host and srflx candidates
831
- const candidates = await this._iceGatherer.gatherCandidates(this._localPort);
832
-
833
- console.log(`[NativePeerConnection] Gathered ${candidates.length} ICE candidates`);
834
-
835
- // Emit each candidate
836
- for (const candidate of candidates) {
837
- // Parse and store the candidate
838
- const parsed = this._parseIceCandidate(candidate.candidate);
839
- if (parsed) {
840
- this._iceCandidates.push(parsed);
841
- }
842
-
843
- this.emit('icecandidate', {
844
- candidate: candidate.candidate,
845
- sdpMid: candidate.sdpMid,
846
- sdpMLineIndex: candidate.sdpMLineIndex
847
- });
848
-
849
- console.log(`[NativePeerConnection] ICE candidate (${candidate.type}): ${candidate.ip}:${candidate.port}`);
850
- }
851
- } catch (err) {
852
- console.warn('[NativePeerConnection] ICE gathering error:', err.message);
853
-
854
- // Fallback: emit only local host candidate
855
- if (this._localAddress && this._localPort) {
856
- const candidateStr = `candidate:1 1 tcp 2130706431 ${this._localAddress} ${this._localPort} typ host`;
857
- const parsed = this._parseIceCandidate(candidateStr);
858
- if (parsed) {
859
- this._iceCandidates.push(parsed);
860
- }
861
-
862
- const candidate = {
863
- candidate: candidateStr,
864
- sdpMid: 'data',
865
- sdpMLineIndex: 0
866
- };
867
- this.emit('icecandidate', candidate);
868
- console.log(`[NativePeerConnection] Fallback ICE candidate: ${this._localAddress}:${this._localPort}`);
869
- }
870
- } finally {
871
- // Complete gathering
872
- setTimeout(() => {
873
- this._iceGatheringState = 2; // complete
874
- this.emit('icegatheringstatechange', this._iceGatheringState);
875
- this.emit('icecandidate', null); // End of candidates
876
- console.log('[NativePeerConnection] ICE gathering complete');
877
- }, 100);
878
- }
879
- }
880
-
881
- /**
882
- * Generate random string
883
- * @private
884
- */
885
- _generateRandomString(length) {
886
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
887
- let result = '';
888
- for (let i = 0; i < length; i++) {
889
- result += chars.charAt(Math.floor(Math.random() * chars.length));
890
- }
891
- return result;
892
- }
893
-
894
- /**
895
- * Generate fingerprint
896
- * @private
897
- */
898
- _generateFingerprint() {
899
- const bytes = [];
900
- for (let i = 0; i < 32; i++) {
901
- bytes.push(Math.floor(Math.random() * 256).toString(16).padStart(2, '0').toUpperCase());
902
- }
903
- return bytes.join(':');
904
- }
905
- }
906
-
907
- /**
908
- * NativeDataChannel - Real implementation using TCP socket
909
- */
910
- class NativeDataChannel extends EventEmitter {
911
- constructor(label, options = {}, peerConnection) {
912
- super();
913
- this.label = label;
914
- this.ordered = options.ordered !== undefined ? options.ordered : true;
915
- this.maxPacketLifeTime = options.maxPacketLifeTime || -1;
916
- this.maxRetransmits = options.maxRetransmits || -1;
917
- this.protocol = options.protocol || '';
918
- this.negotiated = options.negotiated || false;
919
- this.id = options.id !== undefined ? options.id : -1;
920
-
921
- this._state = 0; // connecting
922
- this._bufferedAmount = 0;
923
- this._closed = false;
924
- this._socket = null;
925
- this._peerConnection = peerConnection;
926
-
927
- console.log(`[NativeDataChannel] Created: ${label}`);
928
- }
929
-
930
- /**
931
- * Set socket connection for this channel
932
- * @private
933
- */
934
- _setConnected(socket) {
935
- if (this._closed) {
936
- return;
937
- }
938
-
939
- this._socket = socket;
940
- this._state = 1; // open
941
- this.emit('statechange', this._state);
942
- console.log(`[NativeDataChannel] ${this.label} - Channel opened`);
943
- }
944
-
945
- /**
946
- * Handle incoming message for this channel
947
- * @private
948
- */
949
- _handleIncomingMessage(data) {
950
- if (this._state !== 1) {
951
- return;
952
- }
953
-
954
- // Check if data is binary or text
955
- let binary = true;
956
- try {
957
- // Try to detect if it's valid UTF-8 text
958
- const text = data.toString('utf8');
959
- if (text.length > 0 && /^[\x20-\x7E\s]*$/.test(text)) {
960
- binary = false;
961
- }
962
- } catch (e) {
963
- binary = true;
964
- }
965
-
966
- this.emit('message', { data, binary });
967
- }
968
-
969
- /**
970
- * Handle socket disconnect
971
- * @private
972
- */
973
- _handleDisconnect() {
974
- if (this._closed) {
975
- return;
976
- }
977
-
978
- this._socket = null;
979
- this._state = 3; // closed
980
- this.emit('statechange', this._state);
981
- }
982
-
983
- /**
984
- * Send data over the channel
985
- * @param {Object} dataBuffer - { data: Buffer, binary: boolean }
986
- */
987
- send(dataBuffer) {
988
- if (this._state !== 1) {
989
- throw new Error('DataChannel is not open');
990
- }
991
-
992
- if (!this._socket || !this._socket.writable) {
993
- throw new Error('Socket is not writable');
994
- }
995
-
996
- try {
997
- const data = dataBuffer.data;
998
- const labelBuffer = Buffer.from(this.label, 'utf8');
999
-
1000
- // Message format: <length:4><label-length:2><label><data>
1001
- // Length includes label-length field + label + data
1002
- const totalLength = 2 + labelBuffer.length + data.length;
1003
- const header = Buffer.allocUnsafe(6);
1004
- header.writeUInt32BE(totalLength, 0);
1005
- header.writeUInt16BE(labelBuffer.length, 4);
1006
-
1007
- const message = Buffer.concat([header, labelBuffer, data]);
1008
-
1009
- this._bufferedAmount += message.length;
1010
- this._socket.write(message, () => {
1011
- this._bufferedAmount = Math.max(0, this._bufferedAmount - message.length);
1012
- if (this._bufferedAmount === 0) {
1013
- this.emit('bufferedamountlow', 0);
1014
- }
1015
- });
1016
-
1017
- console.log(`[NativeDataChannel] ${this.label} - Sent ${data.length} bytes`);
1018
- } catch (err) {
1019
- console.error(`[NativeDataChannel] ${this.label} - Send error:`, err);
1020
- throw err;
1021
- }
1022
- }
1023
-
1024
- /**
1025
- * Close the channel
1026
- */
1027
- close() {
1028
- if (this._closed) {
1029
- return;
1030
- }
1031
-
1032
- this._closed = true;
1033
- this._state = 2; // closing
1034
- this.emit('statechange', this._state);
1035
-
1036
- setTimeout(() => {
1037
- this._state = 3; // closed
1038
- this._socket = null;
1039
- this.emit('statechange', this._state);
1040
- }, 10);
1041
- }
1042
- }
1043
-
1044
- module.exports = NativePeerConnectionFactory;