node-rtc-connection 1.0.3

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.
@@ -0,0 +1,895 @@
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
+ });
100
+ this._secureConnection = null;
101
+ this._udpTransport = null;
102
+ this._iceCandidates = [];
103
+
104
+ console.log('[NativePeerConnection] Created with STUN/ICE/Encryption support');
105
+ console.log(` - Encryption: ${this._useEncryption ? 'enabled' : 'disabled (requires valid certs)'}`);
106
+ console.log(` - Transport: ${this._useUDP ? 'UDP' : 'TCP'}`);
107
+ }
108
+
109
+ /**
110
+ * Extract STUN servers from configuration
111
+ * @private
112
+ */
113
+ _extractSTUNServers(config) {
114
+ const stunServers = [];
115
+
116
+ if (config.iceServers) {
117
+ for (const server of config.iceServers) {
118
+ if (server.urls) {
119
+ const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
120
+ for (const url of urls) {
121
+ if (url.startsWith('stun:')) {
122
+ stunServers.push(url.replace('stun:', ''));
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ return stunServers.length > 0 ? stunServers : undefined;
130
+ }
131
+
132
+ /**
133
+ * Create an offer
134
+ * @param {Object} options
135
+ * @returns {Promise<{type: string, sdp: string}>}
136
+ */
137
+ async createOffer(options) {
138
+ if (this._closed) {
139
+ throw new Error('PeerConnection is closed');
140
+ }
141
+
142
+ this._isOfferer = true;
143
+
144
+ // Create TCP server to listen for incoming connections
145
+ await this._createServer();
146
+
147
+ // Generate real SDP with actual network information
148
+ const sdp = this._generateSDP('offer');
149
+ return { type: 'offer', sdp };
150
+ }
151
+
152
+ /**
153
+ * Create an answer
154
+ * @param {Object} options
155
+ * @returns {Promise<{type: string, sdp: string}>}
156
+ */
157
+ async createAnswer(options) {
158
+ if (this._closed) {
159
+ throw new Error('PeerConnection is closed');
160
+ }
161
+
162
+ if (!this._remoteDescription) {
163
+ throw new Error('No remote description set');
164
+ }
165
+
166
+ this._isOfferer = false;
167
+
168
+ // Create TCP server to listen for incoming connections (if needed)
169
+ if (!this._server) {
170
+ await this._createServer();
171
+ }
172
+
173
+ // Generate real SDP with actual network information
174
+ const sdp = this._generateSDP('answer');
175
+ return { type: 'answer', sdp };
176
+ }
177
+
178
+ /**
179
+ * Set local description
180
+ * @param {Object} description
181
+ * @returns {Promise<void>}
182
+ */
183
+ async setLocalDescription(description) {
184
+ if (this._closed) {
185
+ throw new Error('PeerConnection is closed');
186
+ }
187
+
188
+ this._localDescription = description;
189
+
190
+ // Update signaling state
191
+ if (description.type === 'offer') {
192
+ this._signalingState = 1; // have-local-offer
193
+ this.emit('signalingstatechange', this._signalingState);
194
+ } else if (description.type === 'answer') {
195
+ this._signalingState = 0; // stable
196
+ this.emit('signalingstatechange', this._signalingState);
197
+
198
+ // If we're answerer and have remote description, connect
199
+ if (!this._isOfferer && this._remoteDescription) {
200
+ await this._connectToPeer();
201
+ }
202
+ }
203
+
204
+ // Start real ICE gathering
205
+ this._startIceGathering();
206
+ }
207
+
208
+ /**
209
+ * Set remote description
210
+ * @param {Object} description
211
+ * @returns {Promise<void>}
212
+ */
213
+ async setRemoteDescription(description) {
214
+ if (this._closed) {
215
+ throw new Error('PeerConnection is closed');
216
+ }
217
+
218
+ // Parse remote SDP to extract connection information
219
+ this._parseSDP(description.sdp);
220
+ this._remoteDescription = description;
221
+
222
+ // Update signaling state
223
+ if (description.type === 'offer') {
224
+ this._signalingState = 2; // have-remote-offer
225
+ this.emit('signalingstatechange', this._signalingState);
226
+ } else if (description.type === 'answer') {
227
+ this._signalingState = 0; // stable
228
+ this.emit('signalingstatechange', this._signalingState);
229
+
230
+ // If we're offerer and have local description, connect
231
+ if (this._isOfferer && this._localDescription) {
232
+ await this._connectToPeer();
233
+ }
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Add ICE candidate
239
+ * @param {Object} candidate
240
+ * @returns {Promise<void>}
241
+ */
242
+ async addIceCandidate(candidate) {
243
+ if (this._closed) {
244
+ throw new Error('PeerConnection is closed');
245
+ }
246
+
247
+ if (!candidate || !candidate.candidate) {
248
+ return;
249
+ }
250
+
251
+ // Parse ICE candidate to extract address and port
252
+ const parts = candidate.candidate.split(' ');
253
+ if (parts.length >= 6) {
254
+ this._remoteAddress = parts[4];
255
+ this._remotePort = parseInt(parts[5], 10);
256
+ console.log(`[NativePeerConnection] Remote address: ${this._remoteAddress}:${this._remotePort}`);
257
+
258
+ // If we have both local and remote info, and we're offerer, connect
259
+ if (this._isOfferer && this._localAddress && this._remoteAddress &&
260
+ this._signalingState === 0 && !this._socket) {
261
+ await this._connectToPeer();
262
+ }
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Create a data channel
268
+ * @param {string} label
269
+ * @param {Object} options
270
+ * @returns {NativeDataChannel}
271
+ */
272
+ createDataChannel(label, options) {
273
+ if (this._closed) {
274
+ throw new Error('PeerConnection is closed');
275
+ }
276
+
277
+ const channel = new NativeDataChannel(label, options, this);
278
+ this._dataChannels.set(label, channel);
279
+
280
+ // If socket is already connected, open the channel
281
+ if (this._socket && this._socket.writable) {
282
+ setTimeout(() => channel._setConnected(this._socket), 0);
283
+ }
284
+
285
+ // Emit negotiation needed
286
+ setTimeout(() => {
287
+ this.emit('negotiationneeded');
288
+ }, 0);
289
+
290
+ return channel;
291
+ }
292
+
293
+ /**
294
+ * Set configuration
295
+ * @param {Object} configuration
296
+ */
297
+ setConfiguration(configuration) {
298
+ this._configuration = configuration;
299
+ }
300
+
301
+ /**
302
+ * Get stats
303
+ * @returns {Promise<Object>}
304
+ */
305
+ async getStats() {
306
+ return {
307
+ type: 'peer-connection',
308
+ timestamp: Date.now(),
309
+ // Mock stats
310
+ };
311
+ }
312
+
313
+ /**
314
+ * Close the peer connection
315
+ */
316
+ close() {
317
+ if (this._closed) {
318
+ return;
319
+ }
320
+
321
+ this._closed = true;
322
+ this._signalingState = 5; // closed
323
+
324
+ // Close all data channels
325
+ for (const [label, channel] of this._dataChannels) {
326
+ channel.close();
327
+ }
328
+ this._dataChannels.clear();
329
+
330
+ // Close secure connection
331
+ if (this._secureConnection) {
332
+ this._secureConnection.close();
333
+ this._secureConnection = null;
334
+ }
335
+
336
+ // Close UDP transport
337
+ if (this._udpTransport) {
338
+ this._udpTransport.close();
339
+ this._udpTransport = null;
340
+ }
341
+
342
+ // Close socket
343
+ if (this._socket) {
344
+ this._socket.destroy();
345
+ this._socket = null;
346
+ }
347
+
348
+ // Close server
349
+ if (this._server) {
350
+ this._server.close();
351
+ this._server = null;
352
+ }
353
+
354
+ this.emit('signalingstatechange', this._signalingState);
355
+ this.emit('close');
356
+ }
357
+
358
+ /**
359
+ * Create TCP server to listen for incoming connections
360
+ * @private
361
+ */
362
+ async _createServer() {
363
+ return new Promise((resolve, reject) => {
364
+ this._server = net.createServer((socket) => {
365
+ console.log('[NativePeerConnection] Incoming connection accepted');
366
+ this._handleIncomingConnection(socket);
367
+ });
368
+
369
+ this._server.listen(0, '0.0.0.0', () => {
370
+ const address = this._server.address();
371
+ this._localPort = address.port;
372
+ this._localAddress = this._getLocalIPAddress();
373
+ console.log(`[NativePeerConnection] Server listening on ${this._localAddress}:${this._localPort}`);
374
+ resolve();
375
+ });
376
+
377
+ this._server.on('error', (err) => {
378
+ console.error('[NativePeerConnection] Server error:', err);
379
+ reject(err);
380
+ });
381
+ });
382
+ }
383
+
384
+ /**
385
+ * Connect to remote peer
386
+ * @private
387
+ */
388
+ async _connectToPeer() {
389
+ if (!this._remoteAddress || !this._remotePort) {
390
+ console.log('[NativePeerConnection] Cannot connect: missing remote address');
391
+ return;
392
+ }
393
+
394
+ if (this._socket) {
395
+ console.log('[NativePeerConnection] Already connected');
396
+ return;
397
+ }
398
+
399
+ // Tie-breaking: only connect if our port is higher than remote port
400
+ // This ensures only one peer connects, avoiding the race condition
401
+ if (this._localPort < this._remotePort) {
402
+ console.log(`[NativePeerConnection] Not connecting (local port ${this._localPort} < remote port ${this._remotePort}), waiting for incoming`);
403
+ return;
404
+ }
405
+
406
+ console.log(`[NativePeerConnection] Connecting to ${this._remoteAddress}:${this._remotePort}`);
407
+
408
+ this._socket = new net.Socket();
409
+
410
+ this._socket.connect(this._remotePort, this._remoteAddress, async () => {
411
+ console.log('[NativePeerConnection] Connected to peer');
412
+
413
+ // Apply TLS encryption if enabled
414
+ if (this._useEncryption) {
415
+ await this._wrapWithEncryption(false);
416
+ }
417
+
418
+ this._iceConnectionState = 2; // connected
419
+ this.emit('iceconnectionstatechange', this._iceConnectionState);
420
+
421
+ // Announce existing data channels to peer and open them
422
+ setImmediate(() => {
423
+ for (const [label, channel] of this._dataChannels) {
424
+ this._sendChannelAnnouncement(label);
425
+ channel._setConnected(this._getActiveSocket());
426
+ }
427
+ });
428
+ });
429
+
430
+ this._setupSocketHandlers(this._socket);
431
+ }
432
+
433
+ /**
434
+ * Handle incoming connection from peer
435
+ * @private
436
+ */
437
+ async _handleIncomingConnection(socket) {
438
+ if (this._socket) {
439
+ console.log('[NativePeerConnection] Connection already exists, closing new one');
440
+ socket.destroy();
441
+ return;
442
+ }
443
+
444
+ console.log('[NativePeerConnection] Accepted connection from peer');
445
+
446
+ // Setup handlers BEFORE storing socket to avoid missing early data
447
+ this._setupSocketHandlers(socket);
448
+
449
+ this._socket = socket;
450
+
451
+ // Apply TLS encryption if enabled
452
+ if (this._useEncryption) {
453
+ await this._wrapWithEncryption(true);
454
+ }
455
+
456
+ this._iceConnectionState = 2; // connected
457
+ this.emit('iceconnectionstatechange', this._iceConnectionState);
458
+
459
+ // Announce existing data channels to peer
460
+ setImmediate(() => {
461
+ for (const [label, channel] of this._dataChannels) {
462
+ this._sendChannelAnnouncement(label);
463
+ channel._setConnected(this._getActiveSocket());
464
+ }
465
+ });
466
+ }
467
+
468
+ /**
469
+ * Wrap socket with TLS encryption
470
+ * @private
471
+ */
472
+ async _wrapWithEncryption(isServer) {
473
+ try {
474
+ console.log(`[NativePeerConnection] Enabling TLS encryption (${isServer ? 'server' : 'client'})`);
475
+
476
+ this._secureConnection = new SecureConnection(this._socket, { isServer });
477
+ await this._secureConnection.wrap();
478
+
479
+ // Update fingerprint with real certificate
480
+ this._fingerprint = this._secureConnection.getFingerprint();
481
+
482
+ console.log('[NativePeerConnection] TLS encryption enabled');
483
+ } catch (err) {
484
+ console.error('[NativePeerConnection] Failed to enable encryption:', err);
485
+ throw err;
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Get the active socket (secure or plain)
491
+ * @private
492
+ */
493
+ _getActiveSocket() {
494
+ return this._secureConnection?.secureSocket || this._socket;
495
+ }
496
+
497
+ /**
498
+ * Setup socket event handlers
499
+ * @private
500
+ */
501
+ _setupSocketHandlers(socket) {
502
+ socket.on('data', (data) => {
503
+ this._handleIncomingData(data);
504
+ });
505
+
506
+ socket.on('close', () => {
507
+ console.log('[NativePeerConnection] Connection closed');
508
+ this._iceConnectionState = 6; // closed
509
+ this.emit('iceconnectionstatechange', this._iceConnectionState);
510
+
511
+ // Close all data channels
512
+ for (const [label, channel] of this._dataChannels) {
513
+ channel._handleDisconnect();
514
+ }
515
+ });
516
+
517
+ socket.on('error', (err) => {
518
+ console.error('[NativePeerConnection] Socket error:', err);
519
+ this._iceConnectionState = 4; // failed
520
+ this.emit('iceconnectionstatechange', this._iceConnectionState);
521
+ });
522
+ }
523
+
524
+ /**
525
+ * Send channel announcement to peer (empty message to trigger remote channel creation)
526
+ * @private
527
+ */
528
+ _sendChannelAnnouncement(label) {
529
+ if (!this._socket) {
530
+ console.log(`[NativePeerConnection] Cannot announce ${label}: no socket`);
531
+ return;
532
+ }
533
+
534
+ const labelBuffer = Buffer.from(label, 'utf8');
535
+ const labelLength = labelBuffer.length;
536
+ const emptyData = Buffer.alloc(0);
537
+
538
+ // Message format: <length:4><label-length:2><label><data>
539
+ // length is the number of bytes after the length field
540
+ const totalLength = 2 + labelLength + emptyData.length;
541
+ const buffer = Buffer.allocUnsafe(4 + totalLength);
542
+
543
+ buffer.writeUInt32BE(totalLength, 0);
544
+ buffer.writeUInt16BE(labelLength, 4);
545
+ labelBuffer.copy(buffer, 6);
546
+
547
+ this._socket.write(buffer);
548
+ console.log(`[NativePeerConnection] Announced channel: ${label}`);
549
+ }
550
+
551
+ /**
552
+ * Handle incoming data from socket
553
+ * @private
554
+ */
555
+ _handleIncomingData(data) {
556
+ try {
557
+ // Parse message format: <length:4><channel-label-length:2><channel-label><data>
558
+ let offset = 0;
559
+
560
+ while (offset < data.length) {
561
+ if (offset + 6 > data.length) break;
562
+
563
+ const totalLength = data.readUInt32BE(offset);
564
+ const labelLength = data.readUInt16BE(offset + 4);
565
+
566
+ if (offset + 6 + labelLength + (totalLength - 2 - labelLength) > data.length) break;
567
+
568
+ const label = data.toString('utf8', offset + 6, offset + 6 + labelLength);
569
+ const messageData = data.slice(offset + 6 + labelLength, offset + 6 + totalLength - 2);
570
+
571
+ // Find or create the data channel
572
+ let channel = this._dataChannels.get(label);
573
+ if (!channel) {
574
+ // Create remote data channel
575
+ console.log(`[NativePeerConnection] Creating remote channel: ${label}`);
576
+ channel = new NativeDataChannel(label, {}, this);
577
+ this._dataChannels.set(label, channel);
578
+
579
+ // Emit datachannel event first (before setting connected)
580
+ this.emit('datachannel', channel);
581
+
582
+ // Then set it as connected after a small delay to allow event handlers to be set up
583
+ setImmediate(() => {
584
+ channel._setConnected(this._socket);
585
+ });
586
+ }
587
+
588
+ // Deliver the message (if it has data - announcements are empty)
589
+ if (messageData.length > 0) {
590
+ channel._handleIncomingMessage(messageData);
591
+ }
592
+
593
+ offset += 6 + totalLength - 2;
594
+ }
595
+ } catch (err) {
596
+ console.error('[NativePeerConnection] Error parsing incoming data:', err);
597
+ }
598
+ }
599
+
600
+ /**
601
+ * Generate real SDP with actual network information
602
+ * @private
603
+ */
604
+ _generateSDP(type) {
605
+ const sessionId = Date.now();
606
+ const address = this._localAddress || '0.0.0.0';
607
+ const port = this._localPort || 9;
608
+
609
+ return `v=0
610
+ o=- ${sessionId} 2 IN IP4 ${address}
611
+ s=-
612
+ t=0 0
613
+ a=group:BUNDLE data
614
+ a=msid-semantic: WMS
615
+ m=application ${port} TCP/DTLS/SCTP webrtc-datachannel
616
+ c=IN IP4 ${address}
617
+ a=ice-ufrag:${this._iceUsername}
618
+ a=ice-pwd:${this._icePassword}
619
+ a=ice-options:trickle
620
+ a=fingerprint:sha-256 ${this._fingerprint}
621
+ a=setup:actpass
622
+ a=mid:data
623
+ a=sctp-port:5000
624
+ a=max-message-size:262144
625
+ `;
626
+ }
627
+
628
+ /**
629
+ * Parse SDP to extract connection information
630
+ * @private
631
+ */
632
+ _parseSDP(sdp) {
633
+ const lines = sdp.split('\n');
634
+
635
+ for (const line of lines) {
636
+ // Parse connection line: c=IN IP4 <address>
637
+ if (line.startsWith('c=IN IP4 ')) {
638
+ const address = line.substring(9).trim();
639
+ if (address !== '0.0.0.0') {
640
+ this._remoteAddress = address;
641
+ }
642
+ }
643
+
644
+ // Parse media line: m=application <port> ...
645
+ if (line.startsWith('m=application ')) {
646
+ const parts = line.split(' ');
647
+ if (parts.length >= 2) {
648
+ const port = parseInt(parts[1], 10);
649
+ if (port > 0 && port !== 9) {
650
+ this._remotePort = port;
651
+ }
652
+ }
653
+ }
654
+ }
655
+
656
+ console.log(`[NativePeerConnection] Parsed SDP - Remote: ${this._remoteAddress}:${this._remotePort}`);
657
+ }
658
+
659
+ /**
660
+ * Get local IP address
661
+ * @private
662
+ */
663
+ _getLocalIPAddress() {
664
+ const interfaces = os.networkInterfaces();
665
+
666
+ // Try to find a non-internal IPv4 address
667
+ for (const name of Object.keys(interfaces)) {
668
+ for (const iface of interfaces[name]) {
669
+ if (iface.family === 'IPv4' && !iface.internal) {
670
+ return iface.address;
671
+ }
672
+ }
673
+ }
674
+
675
+ // Fallback to localhost
676
+ return '127.0.0.1';
677
+ }
678
+
679
+ /**
680
+ * Start ICE gathering with real network addresses
681
+ * @private
682
+ */
683
+ /**
684
+ * Start ICE candidate gathering with STUN support
685
+ * @private
686
+ */
687
+ async _startIceGathering() {
688
+ this._iceGatheringState = 1; // gathering
689
+ this.emit('icegatheringstatechange', this._iceGatheringState);
690
+
691
+ try {
692
+ // Use real ICE gatherer to get host and srflx candidates
693
+ const candidates = await this._iceGatherer.gatherCandidates(this._localPort);
694
+
695
+ console.log(`[NativePeerConnection] Gathered ${candidates.length} ICE candidates`);
696
+
697
+ // Emit each candidate
698
+ for (const candidate of candidates) {
699
+ this._iceCandidates.push(candidate);
700
+ this.emit('icecandidate', {
701
+ candidate: candidate.candidate,
702
+ sdpMid: candidate.sdpMid,
703
+ sdpMLineIndex: candidate.sdpMLineIndex
704
+ });
705
+
706
+ console.log(`[NativePeerConnection] ICE candidate (${candidate.type}): ${candidate.ip}:${candidate.port}`);
707
+ }
708
+ } catch (err) {
709
+ console.warn('[NativePeerConnection] ICE gathering error:', err.message);
710
+
711
+ // Fallback: emit only local host candidate
712
+ if (this._localAddress && this._localPort) {
713
+ const candidate = {
714
+ candidate: `candidate:1 1 tcp 2130706431 ${this._localAddress} ${this._localPort} typ host`,
715
+ sdpMid: 'data',
716
+ sdpMLineIndex: 0
717
+ };
718
+ this.emit('icecandidate', candidate);
719
+ console.log(`[NativePeerConnection] Fallback ICE candidate: ${this._localAddress}:${this._localPort}`);
720
+ }
721
+ } finally {
722
+ // Complete gathering
723
+ setTimeout(() => {
724
+ this._iceGatheringState = 2; // complete
725
+ this.emit('icegatheringstatechange', this._iceGatheringState);
726
+ this.emit('icecandidate', null); // End of candidates
727
+ console.log('[NativePeerConnection] ICE gathering complete');
728
+ }, 100);
729
+ }
730
+ }
731
+
732
+ /**
733
+ * Generate random string
734
+ * @private
735
+ */
736
+ _generateRandomString(length) {
737
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
738
+ let result = '';
739
+ for (let i = 0; i < length; i++) {
740
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
741
+ }
742
+ return result;
743
+ }
744
+
745
+ /**
746
+ * Generate fingerprint
747
+ * @private
748
+ */
749
+ _generateFingerprint() {
750
+ const bytes = [];
751
+ for (let i = 0; i < 32; i++) {
752
+ bytes.push(Math.floor(Math.random() * 256).toString(16).padStart(2, '0').toUpperCase());
753
+ }
754
+ return bytes.join(':');
755
+ }
756
+ }
757
+
758
+ /**
759
+ * NativeDataChannel - Real implementation using TCP socket
760
+ */
761
+ class NativeDataChannel extends EventEmitter {
762
+ constructor(label, options = {}, peerConnection) {
763
+ super();
764
+ this.label = label;
765
+ this.ordered = options.ordered !== undefined ? options.ordered : true;
766
+ this.maxPacketLifeTime = options.maxPacketLifeTime || -1;
767
+ this.maxRetransmits = options.maxRetransmits || -1;
768
+ this.protocol = options.protocol || '';
769
+ this.negotiated = options.negotiated || false;
770
+ this.id = options.id !== undefined ? options.id : -1;
771
+
772
+ this._state = 0; // connecting
773
+ this._bufferedAmount = 0;
774
+ this._closed = false;
775
+ this._socket = null;
776
+ this._peerConnection = peerConnection;
777
+
778
+ console.log(`[NativeDataChannel] Created: ${label}`);
779
+ }
780
+
781
+ /**
782
+ * Set socket connection for this channel
783
+ * @private
784
+ */
785
+ _setConnected(socket) {
786
+ if (this._closed) {
787
+ return;
788
+ }
789
+
790
+ this._socket = socket;
791
+ this._state = 1; // open
792
+ this.emit('statechange', this._state);
793
+ console.log(`[NativeDataChannel] ${this.label} - Channel opened`);
794
+ }
795
+
796
+ /**
797
+ * Handle incoming message for this channel
798
+ * @private
799
+ */
800
+ _handleIncomingMessage(data) {
801
+ if (this._state !== 1) {
802
+ return;
803
+ }
804
+
805
+ // Check if data is binary or text
806
+ let binary = true;
807
+ try {
808
+ // Try to detect if it's valid UTF-8 text
809
+ const text = data.toString('utf8');
810
+ if (text.length > 0 && /^[\x20-\x7E\s]*$/.test(text)) {
811
+ binary = false;
812
+ }
813
+ } catch (e) {
814
+ binary = true;
815
+ }
816
+
817
+ this.emit('message', { data, binary });
818
+ }
819
+
820
+ /**
821
+ * Handle socket disconnect
822
+ * @private
823
+ */
824
+ _handleDisconnect() {
825
+ if (this._closed) {
826
+ return;
827
+ }
828
+
829
+ this._socket = null;
830
+ this._state = 3; // closed
831
+ this.emit('statechange', this._state);
832
+ }
833
+
834
+ /**
835
+ * Send data over the channel
836
+ * @param {Object} dataBuffer - { data: Buffer, binary: boolean }
837
+ */
838
+ send(dataBuffer) {
839
+ if (this._state !== 1) {
840
+ throw new Error('DataChannel is not open');
841
+ }
842
+
843
+ if (!this._socket || !this._socket.writable) {
844
+ throw new Error('Socket is not writable');
845
+ }
846
+
847
+ try {
848
+ const data = dataBuffer.data;
849
+ const labelBuffer = Buffer.from(this.label, 'utf8');
850
+
851
+ // Message format: <length:4><label-length:2><label><data>
852
+ // Length includes label-length field + label + data
853
+ const totalLength = 2 + labelBuffer.length + data.length;
854
+ const header = Buffer.allocUnsafe(6);
855
+ header.writeUInt32BE(totalLength, 0);
856
+ header.writeUInt16BE(labelBuffer.length, 4);
857
+
858
+ const message = Buffer.concat([header, labelBuffer, data]);
859
+
860
+ this._bufferedAmount += message.length;
861
+ this._socket.write(message, () => {
862
+ this._bufferedAmount = Math.max(0, this._bufferedAmount - message.length);
863
+ if (this._bufferedAmount === 0) {
864
+ this.emit('bufferedamountlow', 0);
865
+ }
866
+ });
867
+
868
+ console.log(`[NativeDataChannel] ${this.label} - Sent ${data.length} bytes`);
869
+ } catch (err) {
870
+ console.error(`[NativeDataChannel] ${this.label} - Send error:`, err);
871
+ throw err;
872
+ }
873
+ }
874
+
875
+ /**
876
+ * Close the channel
877
+ */
878
+ close() {
879
+ if (this._closed) {
880
+ return;
881
+ }
882
+
883
+ this._closed = true;
884
+ this._state = 2; // closing
885
+ this.emit('statechange', this._state);
886
+
887
+ setTimeout(() => {
888
+ this._state = 3; // closed
889
+ this._socket = null;
890
+ this.emit('statechange', this._state);
891
+ }, 10);
892
+ }
893
+ }
894
+
895
+ module.exports = NativePeerConnectionFactory;