node-rtc-connection 1.0.3 → 1.0.4

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/dist/index.mjs CHANGED
@@ -1,3 +1,3906 @@
1
+ import require$$0 from 'events';
2
+ import require$$1$1 from 'net';
3
+ import require$$0$1 from 'dgram';
4
+ import require$$1 from 'crypto';
5
+ import require$$0$2 from 'os';
6
+ import require$$0$3 from 'tls';
7
+ import require$$3 from 'child_process';
8
+ import 'fs';
9
+ import 'path';
10
+
11
+ function getDefaultExportFromCjs (x) {
12
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
13
+ }
14
+
15
+ var RTCDataChannel_1;
16
+ var hasRequiredRTCDataChannel;
17
+
18
+ function requireRTCDataChannel () {
19
+ if (hasRequiredRTCDataChannel) return RTCDataChannel_1;
20
+ hasRequiredRTCDataChannel = 1;
21
+ const EventEmitter = require$$0;
22
+
23
+ /**
24
+ * RTCDataChannel represents a bidirectional data channel between two peers.
25
+ * Ported from Chromium's implementation, DataChannel-only functionality.
26
+ */
27
+ class RTCDataChannel extends EventEmitter {
28
+ constructor(nativeChannel, peerConnectionHandler) {
29
+ super();
30
+
31
+ this._nativeChannel = nativeChannel;
32
+ this._peerConnectionHandler = peerConnectionHandler;
33
+ this._state = 'connecting';
34
+ this._binaryType = 'arraybuffer';
35
+ this._bufferedAmount = 0;
36
+ this._bufferedAmountLowThreshold = 0;
37
+ this._closed = false;
38
+ this._scheduledEvents = [];
39
+
40
+ // Setup native channel observers
41
+ this._setupObservers();
42
+ }
43
+
44
+ /**
45
+ * The label specified when creating the data channel
46
+ */
47
+ get label() {
48
+ return this._nativeChannel ? this._nativeChannel.label : '';
49
+ }
50
+
51
+ /**
52
+ * Whether the channel is ordered or allows out-of-order delivery
53
+ */
54
+ get ordered() {
55
+ return this._nativeChannel ? this._nativeChannel.ordered : true;
56
+ }
57
+
58
+ /**
59
+ * Maximum packet lifetime in milliseconds
60
+ */
61
+ get maxPacketLifeTime() {
62
+ if (!this._nativeChannel) return null;
63
+ const lifetime = this._nativeChannel.maxPacketLifeTime;
64
+ return lifetime >= 0 ? lifetime : null;
65
+ }
66
+
67
+ /**
68
+ * Maximum number of retransmit attempts
69
+ */
70
+ get maxRetransmits() {
71
+ if (!this._nativeChannel) return null;
72
+ const retransmits = this._nativeChannel.maxRetransmits;
73
+ return retransmits >= 0 ? retransmits : null;
74
+ }
75
+
76
+ /**
77
+ * Subprotocol name
78
+ */
79
+ get protocol() {
80
+ return this._nativeChannel ? this._nativeChannel.protocol : '';
81
+ }
82
+
83
+ /**
84
+ * Whether the channel was negotiated by the application or the WebRTC layer
85
+ */
86
+ get negotiated() {
87
+ return this._nativeChannel ? this._nativeChannel.negotiated : false;
88
+ }
89
+
90
+ /**
91
+ * The ID for this data channel
92
+ */
93
+ get id() {
94
+ if (!this._nativeChannel) return null;
95
+ const channelId = this._nativeChannel.id;
96
+ return channelId >= 0 ? channelId : null;
97
+ }
98
+
99
+ /**
100
+ * The state of the data channel
101
+ * Values: 'connecting', 'open', 'closing', 'closed'
102
+ */
103
+ get readyState() {
104
+ return this._state;
105
+ }
106
+
107
+ /**
108
+ * The number of bytes currently queued to be sent
109
+ */
110
+ get bufferedAmount() {
111
+ return this._bufferedAmount;
112
+ }
113
+
114
+ /**
115
+ * Threshold for the bufferedAmount at which bufferedamountlow event fires
116
+ */
117
+ get bufferedAmountLowThreshold() {
118
+ return this._bufferedAmountLowThreshold;
119
+ }
120
+
121
+ set bufferedAmountLowThreshold(value) {
122
+ this._bufferedAmountLowThreshold = value;
123
+ }
124
+
125
+ /**
126
+ * Format for received binary data: 'blob' or 'arraybuffer'
127
+ */
128
+ get binaryType() {
129
+ return this._binaryType;
130
+ }
131
+
132
+ set binaryType(value) {
133
+ if (value !== 'blob' && value !== 'arraybuffer') {
134
+ throw new Error('binaryType must be either "blob" or "arraybuffer"');
135
+ }
136
+ this._binaryType = value;
137
+ }
138
+
139
+ /**
140
+ * Send data over the channel
141
+ * @param {string|ArrayBuffer|ArrayBufferView} data - Data to send
142
+ */
143
+ send(data) {
144
+ if (this._state !== 'open') {
145
+ throw new Error('RTCDataChannel.send() called on a channel that is not open');
146
+ }
147
+
148
+ if (!this._nativeChannel) {
149
+ throw new Error('Native data channel is not available');
150
+ }
151
+
152
+ let buffer;
153
+ let isBinary = false;
154
+
155
+ if (typeof data === 'string') {
156
+ buffer = Buffer.from(data, 'utf8');
157
+ isBinary = false;
158
+ } else if (data instanceof ArrayBuffer) {
159
+ buffer = Buffer.from(data);
160
+ isBinary = true;
161
+ } else if (ArrayBuffer.isView(data)) {
162
+ buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
163
+ isBinary = true;
164
+ } else {
165
+ throw new Error('Unsupported data type');
166
+ }
167
+
168
+ const length = buffer.length;
169
+
170
+ // Validate send length
171
+ if (length > 65536) { // Maximum WebRTC message size
172
+ throw new Error('Message too long');
173
+ }
174
+
175
+ this._bufferedAmount += length;
176
+
177
+ // Send through native channel
178
+ try {
179
+ this._nativeChannel.send({
180
+ data: buffer,
181
+ binary: isBinary
182
+ });
183
+ } catch (error) {
184
+ throw new Error(`Failed to send data: ${error.message}`);
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Close the data channel
190
+ */
191
+ close() {
192
+ if (this._closed) {
193
+ return;
194
+ }
195
+
196
+ this._closed = true;
197
+
198
+ if (this._nativeChannel) {
199
+ this._nativeChannel.close();
200
+ }
201
+
202
+ if (this._state === 'closing' || this._state === 'closed') {
203
+ return;
204
+ }
205
+
206
+ this._setState('closing');
207
+ }
208
+
209
+ /**
210
+ * Setup observers for the native channel
211
+ * @private
212
+ */
213
+ _setupObservers() {
214
+ if (!this._nativeChannel) {
215
+ return;
216
+ }
217
+
218
+ // State change observer
219
+ this._nativeChannel.on('statechange', (state) => {
220
+ this._onStateChange(state);
221
+ });
222
+
223
+ // Message observer
224
+ this._nativeChannel.on('message', (buffer) => {
225
+ this._onMessage(buffer);
226
+ });
227
+
228
+ // Buffered amount change observer
229
+ this._nativeChannel.on('bufferedamountlow', (amount) => {
230
+ this._onBufferedAmountChange(amount);
231
+ });
232
+
233
+ // Error observer
234
+ this._nativeChannel.on('error', (error) => {
235
+ this.emit('error', error);
236
+ });
237
+ }
238
+
239
+ /**
240
+ * Handle state change from native channel
241
+ * @private
242
+ */
243
+ _onStateChange(state) {
244
+ let newState;
245
+ let shouldEmitOpen = false;
246
+ let shouldEmitClosing = false;
247
+ let shouldEmitClose = false;
248
+
249
+ switch (state) {
250
+ case 0: // kConnecting
251
+ newState = 'connecting';
252
+ break;
253
+ case 1: // kOpen
254
+ newState = 'open';
255
+ shouldEmitOpen = true;
256
+ break;
257
+ case 2: // kClosing
258
+ newState = 'closing';
259
+ shouldEmitClosing = true;
260
+ break;
261
+ case 3: // kClosed
262
+ newState = 'closed';
263
+ shouldEmitClose = true;
264
+ break;
265
+ default:
266
+ return;
267
+ }
268
+
269
+ // Only update state if it actually changed
270
+ if (this._readyState !== newState) {
271
+ this._setState(newState);
272
+ }
273
+
274
+ // Emit events AFTER state is updated
275
+ if (shouldEmitOpen) {
276
+ this.emit('open');
277
+ }
278
+ if (shouldEmitClosing) {
279
+ this.emit('closing');
280
+ }
281
+ if (shouldEmitClose) {
282
+ this.emit('close');
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Handle incoming message from native channel
288
+ * @private
289
+ */
290
+ _onMessage(buffer) {
291
+ if (this._state !== 'open') {
292
+ return;
293
+ }
294
+
295
+ let data;
296
+ if (buffer.binary) {
297
+ if (this._binaryType === 'arraybuffer') {
298
+ data = buffer.data.buffer.slice(
299
+ buffer.data.byteOffset,
300
+ buffer.data.byteOffset + buffer.data.byteLength
301
+ );
302
+ } else {
303
+ // For 'blob' type, we'll just pass the buffer
304
+ // In a browser environment, this would be converted to a Blob
305
+ data = buffer.data;
306
+ }
307
+ } else {
308
+ data = buffer.data.toString('utf8');
309
+ }
310
+
311
+ this.emit('message', { data });
312
+ }
313
+
314
+ /**
315
+ * Handle buffered amount change from native channel
316
+ * @private
317
+ */
318
+ _onBufferedAmountChange(newAmount) {
319
+ const previousAmount = this._bufferedAmount;
320
+ this._bufferedAmount = newAmount;
321
+
322
+ if (previousAmount > this._bufferedAmountLowThreshold &&
323
+ newAmount <= this._bufferedAmountLowThreshold) {
324
+ this.emit('bufferedamountlow');
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Set the channel state
330
+ * @private
331
+ */
332
+ _setState(state) {
333
+ if (this._state === state) {
334
+ return;
335
+ }
336
+ this._state = state;
337
+ }
338
+
339
+ /**
340
+ * Set state to open without dispatching event (used for remote channels)
341
+ */
342
+ setStateToOpenWithoutEvent() {
343
+ this._setState('open');
344
+ }
345
+
346
+ /**
347
+ * Dispatch open event (used for remote channels)
348
+ */
349
+ dispatchOpenEvent() {
350
+ this.emit('open');
351
+ }
352
+
353
+ /**
354
+ * Dispose of resources
355
+ */
356
+ dispose() {
357
+ if (this._nativeChannel) {
358
+ this._nativeChannel.removeAllListeners();
359
+ this._nativeChannel = null;
360
+ }
361
+ this._peerConnectionHandler = null;
362
+ this.removeAllListeners();
363
+ }
364
+ }
365
+
366
+ RTCDataChannel_1 = RTCDataChannel;
367
+ return RTCDataChannel_1;
368
+ }
369
+
370
+ /**
371
+ * RTCSessionDescription represents a session description.
372
+ * Ported from Chromium's implementation.
373
+ */
374
+
375
+ var RTCSessionDescription_1;
376
+ var hasRequiredRTCSessionDescription;
377
+
378
+ function requireRTCSessionDescription () {
379
+ if (hasRequiredRTCSessionDescription) return RTCSessionDescription_1;
380
+ hasRequiredRTCSessionDescription = 1;
381
+ class RTCSessionDescription {
382
+ constructor(descriptionInitDict = {}) {
383
+ this._type = descriptionInitDict.type || '';
384
+ this._sdp = descriptionInitDict.sdp || '';
385
+
386
+ // Validate type
387
+ const validTypes = ['offer', 'answer', 'pranswer', 'rollback'];
388
+ if (this._type && !validTypes.includes(this._type)) {
389
+ throw new Error(`Invalid type: ${this._type}`);
390
+ }
391
+ }
392
+
393
+ /**
394
+ * The type of session description
395
+ * Values: 'offer', 'answer', 'pranswer', 'rollback'
396
+ */
397
+ get type() {
398
+ return this._type;
399
+ }
400
+
401
+ set type(value) {
402
+ const validTypes = ['offer', 'answer', 'pranswer', 'rollback'];
403
+ if (value && !validTypes.includes(value)) {
404
+ throw new Error(`Invalid type: ${value}`);
405
+ }
406
+ this._type = value;
407
+ }
408
+
409
+ /**
410
+ * The SDP string
411
+ */
412
+ get sdp() {
413
+ return this._sdp;
414
+ }
415
+
416
+ set sdp(value) {
417
+ this._sdp = value || '';
418
+ }
419
+
420
+ /**
421
+ * Convert to JSON
422
+ */
423
+ toJSON() {
424
+ return {
425
+ type: this._type,
426
+ sdp: this._sdp
427
+ };
428
+ }
429
+
430
+ /**
431
+ * Convert to string
432
+ */
433
+ toString() {
434
+ return `RTCSessionDescription { type: "${this._type}", sdp: "${this._sdp.substring(0, 50)}..." }`;
435
+ }
436
+ }
437
+
438
+ RTCSessionDescription_1 = RTCSessionDescription;
439
+ return RTCSessionDescription_1;
440
+ }
441
+
442
+ /**
443
+ * RTCIceCandidate represents an ICE candidate.
444
+ * Ported from Chromium's implementation.
445
+ */
446
+
447
+ var RTCIceCandidate_1;
448
+ var hasRequiredRTCIceCandidate;
449
+
450
+ function requireRTCIceCandidate () {
451
+ if (hasRequiredRTCIceCandidate) return RTCIceCandidate_1;
452
+ hasRequiredRTCIceCandidate = 1;
453
+ class RTCIceCandidate {
454
+ constructor(candidateInitDict = {}) {
455
+ this._candidate = candidateInitDict.candidate || '';
456
+ this._sdpMid = candidateInitDict.sdpMid || null;
457
+ this._sdpMLineIndex = candidateInitDict.sdpMLineIndex !== undefined
458
+ ? candidateInitDict.sdpMLineIndex
459
+ : null;
460
+ this._usernameFragment = candidateInitDict.usernameFragment || null;
461
+
462
+ // Parse candidate string if provided
463
+ if (this._candidate) {
464
+ this._parseCandidateString();
465
+ }
466
+ }
467
+
468
+ /**
469
+ * The candidate string
470
+ */
471
+ get candidate() {
472
+ return this._candidate;
473
+ }
474
+
475
+ /**
476
+ * The media stream identification tag
477
+ */
478
+ get sdpMid() {
479
+ return this._sdpMid;
480
+ }
481
+
482
+ /**
483
+ * The media line index
484
+ */
485
+ get sdpMLineIndex() {
486
+ return this._sdpMLineIndex;
487
+ }
488
+
489
+ /**
490
+ * The username fragment
491
+ */
492
+ get usernameFragment() {
493
+ return this._usernameFragment;
494
+ }
495
+
496
+ /**
497
+ * The foundation
498
+ */
499
+ get foundation() {
500
+ return this._foundation || null;
501
+ }
502
+
503
+ /**
504
+ * The component (RTP or RTCP)
505
+ */
506
+ get component() {
507
+ return this._component || null;
508
+ }
509
+
510
+ /**
511
+ * The priority
512
+ */
513
+ get priority() {
514
+ return this._priority || null;
515
+ }
516
+
517
+ /**
518
+ * The IP address
519
+ */
520
+ get address() {
521
+ return this._address || null;
522
+ }
523
+
524
+ /**
525
+ * The protocol (udp or tcp)
526
+ */
527
+ get protocol() {
528
+ return this._protocol || null;
529
+ }
530
+
531
+ /**
532
+ * The port
533
+ */
534
+ get port() {
535
+ return this._port || null;
536
+ }
537
+
538
+ /**
539
+ * The candidate type (host, srflx, prflx, or relay)
540
+ */
541
+ get type() {
542
+ return this._type || null;
543
+ }
544
+
545
+ /**
546
+ * The TCP type (active, passive, or so)
547
+ */
548
+ get tcpType() {
549
+ return this._tcpType || null;
550
+ }
551
+
552
+ /**
553
+ * The related address (for non-host candidates)
554
+ */
555
+ get relatedAddress() {
556
+ return this._relatedAddress || null;
557
+ }
558
+
559
+ /**
560
+ * The related port (for non-host candidates)
561
+ */
562
+ get relatedPort() {
563
+ return this._relatedPort || null;
564
+ }
565
+
566
+ /**
567
+ * Parse the candidate string to extract individual fields
568
+ * @private
569
+ */
570
+ _parseCandidateString() {
571
+ if (!this._candidate || !this._candidate.startsWith('candidate:')) {
572
+ return;
573
+ }
574
+
575
+ // Basic parsing of candidate string
576
+ // Format: candidate:<foundation> <component> <protocol> <priority> <address> <port> typ <type> ...
577
+ const parts = this._candidate.split(' ');
578
+
579
+ if (parts.length >= 8) {
580
+ this._foundation = parts[0].replace('candidate:', '');
581
+ this._component = parts[1];
582
+ this._protocol = parts[2];
583
+ this._priority = parseInt(parts[3], 10);
584
+ this._address = parts[4];
585
+ this._port = parseInt(parts[5], 10);
586
+ // parts[6] is 'typ'
587
+ this._type = parts[7];
588
+
589
+ // Parse optional fields
590
+ for (let i = 8; i < parts.length; i += 2) {
591
+ const key = parts[i];
592
+ const value = parts[i + 1];
593
+
594
+ switch (key) {
595
+ case 'raddr':
596
+ this._relatedAddress = value;
597
+ break;
598
+ case 'rport':
599
+ this._relatedPort = parseInt(value, 10);
600
+ break;
601
+ case 'tcptype':
602
+ this._tcpType = value;
603
+ break;
604
+ case 'ufrag':
605
+ this._usernameFragment = value;
606
+ break;
607
+ }
608
+ }
609
+ }
610
+ }
611
+
612
+ /**
613
+ * Convert to JSON
614
+ */
615
+ toJSON() {
616
+ return {
617
+ candidate: this._candidate,
618
+ sdpMid: this._sdpMid,
619
+ sdpMLineIndex: this._sdpMLineIndex,
620
+ usernameFragment: this._usernameFragment
621
+ };
622
+ }
623
+
624
+ /**
625
+ * Convert to string
626
+ */
627
+ toString() {
628
+ return `RTCIceCandidate { candidate: "${this._candidate}", sdpMid: "${this._sdpMid}", sdpMLineIndex: ${this._sdpMLineIndex} }`;
629
+ }
630
+ }
631
+
632
+ RTCIceCandidate_1 = RTCIceCandidate;
633
+ return RTCIceCandidate_1;
634
+ }
635
+
636
+ var RTCPeerConnection_1;
637
+ var hasRequiredRTCPeerConnection;
638
+
639
+ function requireRTCPeerConnection () {
640
+ if (hasRequiredRTCPeerConnection) return RTCPeerConnection_1;
641
+ hasRequiredRTCPeerConnection = 1;
642
+ const EventEmitter = require$$0;
643
+ const RTCDataChannel = requireRTCDataChannel();
644
+ const RTCSessionDescription = requireRTCSessionDescription();
645
+ const RTCIceCandidate = requireRTCIceCandidate();
646
+
647
+ /**
648
+ * RTCPeerConnection represents a WebRTC connection between the local computer and a remote peer.
649
+ * This is a DataChannel-only implementation ported from Chromium.
650
+ */
651
+ class RTCPeerConnection extends EventEmitter {
652
+ constructor(configuration, nativePeerConnectionFactory) {
653
+ super();
654
+
655
+ this._configuration = this._parseConfiguration(configuration || {});
656
+ this._signalingState = 'stable';
657
+ this._iceGatheringState = 'new';
658
+ this._iceConnectionState = 'new';
659
+ this._connectionState = 'new';
660
+ this._pendingLocalDescription = null;
661
+ this._currentLocalDescription = null;
662
+ this._pendingRemoteDescription = null;
663
+ this._currentRemoteDescription = null;
664
+ this._dataChannels = new Map();
665
+ this._closed = false;
666
+
667
+ // Native peer connection (would be native WebRTC binding)
668
+ this._nativePeerConnection = null;
669
+ this._nativePeerConnectionFactory = nativePeerConnectionFactory;
670
+
671
+ // Initialize native peer connection
672
+ this._initializeNativePeerConnection();
673
+ }
674
+
675
+ /**
676
+ * Parse and validate configuration
677
+ * @private
678
+ */
679
+ _parseConfiguration(config) {
680
+ const configuration = {
681
+ iceServers: [],
682
+ iceTransportPolicy: 'all',
683
+ bundlePolicy: 'balanced',
684
+ rtcpMuxPolicy: 'require',
685
+ iceCandidatePoolSize: 0
686
+ };
687
+
688
+ if (config.iceServers) {
689
+ configuration.iceServers = config.iceServers.map(server => ({
690
+ urls: Array.isArray(server.urls) ? server.urls : [server.urls],
691
+ username: server.username || '',
692
+ credential: server.credential || ''
693
+ }));
694
+ }
695
+
696
+ if (config.iceTransportPolicy) {
697
+ configuration.iceTransportPolicy = config.iceTransportPolicy;
698
+ }
699
+
700
+ if (config.bundlePolicy) {
701
+ configuration.bundlePolicy = config.bundlePolicy;
702
+ }
703
+
704
+ if (config.rtcpMuxPolicy) {
705
+ configuration.rtcpMuxPolicy = config.rtcpMuxPolicy;
706
+ }
707
+
708
+ if (config.iceCandidatePoolSize !== undefined) {
709
+ configuration.iceCandidatePoolSize = config.iceCandidatePoolSize;
710
+ }
711
+
712
+ return configuration;
713
+ }
714
+
715
+ /**
716
+ * Initialize native peer connection with factory
717
+ * @private
718
+ */
719
+ _initializeNativePeerConnection() {
720
+ if (!this._nativePeerConnectionFactory) {
721
+ throw new Error('Native PeerConnection factory not provided');
722
+ }
723
+
724
+ // Create native peer connection
725
+ this._nativePeerConnection = this._nativePeerConnectionFactory.createPeerConnection(
726
+ this._configuration
727
+ );
728
+
729
+ if (!this._nativePeerConnection) {
730
+ throw new Error('Failed to create native peer connection');
731
+ }
732
+
733
+ // Setup observers
734
+ this._setupObservers();
735
+ }
736
+
737
+ /**
738
+ * Setup observers for native peer connection events
739
+ * @private
740
+ */
741
+ _setupObservers() {
742
+ if (!this._nativePeerConnection) {
743
+ return;
744
+ }
745
+
746
+ // Signaling state change
747
+ this._nativePeerConnection.on('signalingstatechange', (state) => {
748
+ this._signalingState = this._convertSignalingState(state);
749
+ this.emit('signalingstatechange');
750
+ });
751
+
752
+ // ICE connection state change
753
+ this._nativePeerConnection.on('iceconnectionstatechange', (state) => {
754
+ this._iceConnectionState = this._convertIceConnectionState(state);
755
+ this.emit('iceconnectionstatechange');
756
+ });
757
+
758
+ // ICE gathering state change
759
+ this._nativePeerConnection.on('icegatheringstatechange', (state) => {
760
+ this._iceGatheringState = this._convertIceGatheringState(state);
761
+ this.emit('icegatheringstatechange');
762
+ });
763
+
764
+ // Connection state change
765
+ this._nativePeerConnection.on('connectionstatechange', (state) => {
766
+ this._connectionState = this._convertConnectionState(state);
767
+ this.emit('connectionstatechange');
768
+ });
769
+
770
+ // ICE candidate
771
+ this._nativePeerConnection.on('icecandidate', (candidate) => {
772
+ const iceCandidate = candidate ? new RTCIceCandidate(candidate) : null;
773
+ this.emit('icecandidate', { candidate: iceCandidate });
774
+ });
775
+
776
+ // Data channel (remote)
777
+ this._nativePeerConnection.on('datachannel', (nativeChannel) => {
778
+ const dataChannel = new RTCDataChannel(nativeChannel, this);
779
+ this._dataChannels.set(dataChannel.label, dataChannel);
780
+ this.emit('datachannel', { channel: dataChannel });
781
+ });
782
+
783
+ // Negotiation needed
784
+ this._nativePeerConnection.on('negotiationneeded', () => {
785
+ this.emit('negotiationneeded');
786
+ });
787
+ }
788
+
789
+ /**
790
+ * The current signaling state
791
+ */
792
+ get signalingState() {
793
+ return this._signalingState;
794
+ }
795
+
796
+ /**
797
+ * The current ICE gathering state
798
+ */
799
+ get iceGatheringState() {
800
+ return this._iceGatheringState;
801
+ }
802
+
803
+ /**
804
+ * The current ICE connection state
805
+ */
806
+ get iceConnectionState() {
807
+ return this._iceConnectionState;
808
+ }
809
+
810
+ /**
811
+ * The current connection state
812
+ */
813
+ get connectionState() {
814
+ return this._connectionState;
815
+ }
816
+
817
+ /**
818
+ * The local description
819
+ */
820
+ get localDescription() {
821
+ return this._currentLocalDescription || this._pendingLocalDescription;
822
+ }
823
+
824
+ /**
825
+ * The remote description
826
+ */
827
+ get remoteDescription() {
828
+ return this._currentRemoteDescription || this._pendingRemoteDescription;
829
+ }
830
+
831
+ /**
832
+ * The pending local description
833
+ */
834
+ get pendingLocalDescription() {
835
+ return this._pendingLocalDescription;
836
+ }
837
+
838
+ /**
839
+ * The pending remote description
840
+ */
841
+ get pendingRemoteDescription() {
842
+ return this._pendingRemoteDescription;
843
+ }
844
+
845
+ /**
846
+ * The current local description
847
+ */
848
+ get currentLocalDescription() {
849
+ return this._currentLocalDescription;
850
+ }
851
+
852
+ /**
853
+ * The current remote description
854
+ */
855
+ get currentRemoteDescription() {
856
+ return this._currentRemoteDescription;
857
+ }
858
+
859
+ /**
860
+ * Create an offer
861
+ * @param {Object} options - Offer options
862
+ * @returns {Promise<RTCSessionDescriptionInit>}
863
+ */
864
+ async createOffer(options = {}) {
865
+ this._checkClosed();
866
+
867
+ if (!this._nativePeerConnection) {
868
+ throw new Error('Native peer connection not available');
869
+ }
870
+
871
+ try {
872
+ const nativeDescription = await this._nativePeerConnection.createOffer(options);
873
+ return new RTCSessionDescription({
874
+ type: nativeDescription.type,
875
+ sdp: nativeDescription.sdp
876
+ });
877
+ } catch (error) {
878
+ throw new Error(`Failed to create offer: ${error.message}`);
879
+ }
880
+ }
881
+
882
+ /**
883
+ * Create an answer
884
+ * @param {Object} options - Answer options
885
+ * @returns {Promise<RTCSessionDescriptionInit>}
886
+ */
887
+ async createAnswer(options = {}) {
888
+ this._checkClosed();
889
+
890
+ if (!this._nativePeerConnection) {
891
+ throw new Error('Native peer connection not available');
892
+ }
893
+
894
+ try {
895
+ const nativeDescription = await this._nativePeerConnection.createAnswer(options);
896
+ return new RTCSessionDescription({
897
+ type: nativeDescription.type,
898
+ sdp: nativeDescription.sdp
899
+ });
900
+ } catch (error) {
901
+ throw new Error(`Failed to create answer: ${error.message}`);
902
+ }
903
+ }
904
+
905
+ /**
906
+ * Set the local description
907
+ * @param {RTCSessionDescriptionInit} description
908
+ * @returns {Promise<void>}
909
+ */
910
+ async setLocalDescription(description) {
911
+ this._checkClosed();
912
+
913
+ if (!this._nativePeerConnection) {
914
+ throw new Error('Native peer connection not available');
915
+ }
916
+
917
+ try {
918
+ await this._nativePeerConnection.setLocalDescription(description);
919
+
920
+ if (description.type === 'offer') {
921
+ this._pendingLocalDescription = new RTCSessionDescription(description);
922
+ } else if (description.type === 'answer') {
923
+ this._currentLocalDescription = new RTCSessionDescription(description);
924
+ this._pendingLocalDescription = null;
925
+ } else if (description.type === 'rollback') {
926
+ this._pendingLocalDescription = null;
927
+ }
928
+ } catch (error) {
929
+ throw new Error(`Failed to set local description: ${error.message}`);
930
+ }
931
+ }
932
+
933
+ /**
934
+ * Set the remote description
935
+ * @param {RTCSessionDescriptionInit} description
936
+ * @returns {Promise<void>}
937
+ */
938
+ async setRemoteDescription(description) {
939
+ this._checkClosed();
940
+
941
+ if (!this._nativePeerConnection) {
942
+ throw new Error('Native peer connection not available');
943
+ }
944
+
945
+ try {
946
+ await this._nativePeerConnection.setRemoteDescription(description);
947
+
948
+ if (description.type === 'offer') {
949
+ this._pendingRemoteDescription = new RTCSessionDescription(description);
950
+ } else if (description.type === 'answer') {
951
+ this._currentRemoteDescription = new RTCSessionDescription(description);
952
+ this._pendingRemoteDescription = null;
953
+ } else if (description.type === 'rollback') {
954
+ this._pendingRemoteDescription = null;
955
+ }
956
+ } catch (error) {
957
+ throw new Error(`Failed to set remote description: ${error.message}`);
958
+ }
959
+ }
960
+
961
+ /**
962
+ * Add an ICE candidate
963
+ * @param {RTCIceCandidateInit} candidate
964
+ * @returns {Promise<void>}
965
+ */
966
+ async addIceCandidate(candidate) {
967
+ this._checkClosed();
968
+
969
+ if (!this._nativePeerConnection) {
970
+ throw new Error('Native peer connection not available');
971
+ }
972
+
973
+ if (!candidate) {
974
+ // End of candidates
975
+ return;
976
+ }
977
+
978
+ try {
979
+ await this._nativePeerConnection.addIceCandidate(candidate);
980
+ } catch (error) {
981
+ throw new Error(`Failed to add ICE candidate: ${error.message}`);
982
+ }
983
+ }
984
+
985
+ /**
986
+ * Create a data channel
987
+ * @param {string} label - Channel label
988
+ * @param {Object} dataChannelDict - Channel options
989
+ * @returns {RTCDataChannel}
990
+ */
991
+ createDataChannel(label, dataChannelDict = {}) {
992
+ this._checkClosed();
993
+
994
+ if (!this._nativePeerConnection) {
995
+ throw new Error('Native peer connection not available');
996
+ }
997
+
998
+ const options = {
999
+ ordered: dataChannelDict.ordered !== undefined ? dataChannelDict.ordered : true,
1000
+ maxPacketLifeTime: dataChannelDict.maxPacketLifeTime,
1001
+ maxRetransmits: dataChannelDict.maxRetransmits,
1002
+ protocol: dataChannelDict.protocol || '',
1003
+ negotiated: dataChannelDict.negotiated || false,
1004
+ id: dataChannelDict.id
1005
+ };
1006
+
1007
+ try {
1008
+ const nativeChannel = this._nativePeerConnection.createDataChannel(label, options);
1009
+ const dataChannel = new RTCDataChannel(nativeChannel, this);
1010
+ this._dataChannels.set(label, dataChannel);
1011
+ return dataChannel;
1012
+ } catch (error) {
1013
+ throw new Error(`Failed to create data channel: ${error.message}`);
1014
+ }
1015
+ }
1016
+
1017
+ /**
1018
+ * Get configuration
1019
+ * @returns {Object}
1020
+ */
1021
+ getConfiguration() {
1022
+ return { ...this._configuration };
1023
+ }
1024
+
1025
+ /**
1026
+ * Set configuration
1027
+ * @param {Object} configuration
1028
+ */
1029
+ setConfiguration(configuration) {
1030
+ this._checkClosed();
1031
+ this._configuration = this._parseConfiguration(configuration);
1032
+
1033
+ if (this._nativePeerConnection) {
1034
+ this._nativePeerConnection.setConfiguration(this._configuration);
1035
+ }
1036
+ }
1037
+
1038
+ /**
1039
+ * Close the peer connection
1040
+ */
1041
+ close() {
1042
+ if (this._closed) {
1043
+ return;
1044
+ }
1045
+
1046
+ this._closed = true;
1047
+ this._signalingState = 'closed';
1048
+
1049
+ // Close all data channels
1050
+ for (const [label, channel] of this._dataChannels) {
1051
+ channel.close();
1052
+ channel.dispose();
1053
+ }
1054
+ this._dataChannels.clear();
1055
+
1056
+ // Close native peer connection
1057
+ if (this._nativePeerConnection) {
1058
+ this._nativePeerConnection.close();
1059
+ this._nativePeerConnection.removeAllListeners();
1060
+ this._nativePeerConnection = null;
1061
+ }
1062
+
1063
+ this.emit('signalingstatechange');
1064
+ this.removeAllListeners();
1065
+ }
1066
+
1067
+ /**
1068
+ * Get stats
1069
+ * @returns {Promise<Object>}
1070
+ */
1071
+ async getStats() {
1072
+ this._checkClosed();
1073
+
1074
+ if (!this._nativePeerConnection) {
1075
+ throw new Error('Native peer connection not available');
1076
+ }
1077
+
1078
+ try {
1079
+ return await this._nativePeerConnection.getStats();
1080
+ } catch (error) {
1081
+ throw new Error(`Failed to get stats: ${error.message}`);
1082
+ }
1083
+ }
1084
+
1085
+ /**
1086
+ * Check if connection is closed
1087
+ * @private
1088
+ */
1089
+ _checkClosed() {
1090
+ if (this._closed || this._signalingState === 'closed') {
1091
+ throw new Error("The RTCPeerConnection's signalingState is 'closed'");
1092
+ }
1093
+ }
1094
+
1095
+ /**
1096
+ * Convert native signaling state to string
1097
+ * @private
1098
+ */
1099
+ _convertSignalingState(state) {
1100
+ const states = ['stable', 'have-local-offer', 'have-remote-offer',
1101
+ 'have-local-pranswer', 'have-remote-pranswer', 'closed'];
1102
+ return states[state] || 'stable';
1103
+ }
1104
+
1105
+ /**
1106
+ * Convert native ICE connection state to string
1107
+ * @private
1108
+ */
1109
+ _convertIceConnectionState(state) {
1110
+ const states = ['new', 'checking', 'connected', 'completed',
1111
+ 'failed', 'disconnected', 'closed'];
1112
+ return states[state] || 'new';
1113
+ }
1114
+
1115
+ /**
1116
+ * Convert native ICE gathering state to string
1117
+ * @private
1118
+ */
1119
+ _convertIceGatheringState(state) {
1120
+ const states = ['new', 'gathering', 'complete'];
1121
+ return states[state] || 'new';
1122
+ }
1123
+
1124
+ /**
1125
+ * Convert native connection state to string
1126
+ * @private
1127
+ */
1128
+ _convertConnectionState(state) {
1129
+ const states = ['new', 'connecting', 'connected', 'disconnected',
1130
+ 'failed', 'closed'];
1131
+ return states[state] || 'new';
1132
+ }
1133
+ }
1134
+
1135
+ RTCPeerConnection_1 = RTCPeerConnection;
1136
+ return RTCPeerConnection_1;
1137
+ }
1138
+
1139
+ /**
1140
+ * RTCDataChannelEvent is fired when a data channel is added to the connection.
1141
+ * Ported from Chromium's implementation.
1142
+ */
1143
+
1144
+ var RTCDataChannelEvent_1;
1145
+ var hasRequiredRTCDataChannelEvent;
1146
+
1147
+ function requireRTCDataChannelEvent () {
1148
+ if (hasRequiredRTCDataChannelEvent) return RTCDataChannelEvent_1;
1149
+ hasRequiredRTCDataChannelEvent = 1;
1150
+ class RTCDataChannelEvent {
1151
+ constructor(type, eventInitDict = {}) {
1152
+ this._type = type;
1153
+ this._channel = eventInitDict.channel || null;
1154
+ this._bubbles = eventInitDict.bubbles || false;
1155
+ this._cancelable = eventInitDict.cancelable || false;
1156
+ this._timestamp = Date.now();
1157
+ }
1158
+
1159
+ /**
1160
+ * The event type
1161
+ */
1162
+ get type() {
1163
+ return this._type;
1164
+ }
1165
+
1166
+ /**
1167
+ * The RTCDataChannel associated with the event
1168
+ */
1169
+ get channel() {
1170
+ return this._channel;
1171
+ }
1172
+
1173
+ /**
1174
+ * Whether the event bubbles
1175
+ */
1176
+ get bubbles() {
1177
+ return this._bubbles;
1178
+ }
1179
+
1180
+ /**
1181
+ * Whether the event is cancelable
1182
+ */
1183
+ get cancelable() {
1184
+ return this._cancelable;
1185
+ }
1186
+
1187
+ /**
1188
+ * The timestamp when the event was created
1189
+ */
1190
+ get timeStamp() {
1191
+ return this._timestamp;
1192
+ }
1193
+ }
1194
+
1195
+ RTCDataChannelEvent_1 = RTCDataChannelEvent;
1196
+ return RTCDataChannelEvent_1;
1197
+ }
1198
+
1199
+ /**
1200
+ * RTCPeerConnectionIceEvent is fired when an ICE candidate is available.
1201
+ * Ported from Chromium's implementation.
1202
+ */
1203
+
1204
+ var RTCPeerConnectionIceEvent_1;
1205
+ var hasRequiredRTCPeerConnectionIceEvent;
1206
+
1207
+ function requireRTCPeerConnectionIceEvent () {
1208
+ if (hasRequiredRTCPeerConnectionIceEvent) return RTCPeerConnectionIceEvent_1;
1209
+ hasRequiredRTCPeerConnectionIceEvent = 1;
1210
+ class RTCPeerConnectionIceEvent {
1211
+ constructor(type, eventInitDict = {}) {
1212
+ this._type = type;
1213
+ this._candidate = eventInitDict.candidate || null;
1214
+ this._url = eventInitDict.url || null;
1215
+ this._bubbles = eventInitDict.bubbles || false;
1216
+ this._cancelable = eventInitDict.cancelable || false;
1217
+ this._timestamp = Date.now();
1218
+ }
1219
+
1220
+ /**
1221
+ * The event type
1222
+ */
1223
+ get type() {
1224
+ return this._type;
1225
+ }
1226
+
1227
+ /**
1228
+ * The RTCIceCandidate associated with the event
1229
+ */
1230
+ get candidate() {
1231
+ return this._candidate;
1232
+ }
1233
+
1234
+ /**
1235
+ * The URL of the TURN or STUN server
1236
+ */
1237
+ get url() {
1238
+ return this._url;
1239
+ }
1240
+
1241
+ /**
1242
+ * Whether the event bubbles
1243
+ */
1244
+ get bubbles() {
1245
+ return this._bubbles;
1246
+ }
1247
+
1248
+ /**
1249
+ * Whether the event is cancelable
1250
+ */
1251
+ get cancelable() {
1252
+ return this._cancelable;
1253
+ }
1254
+
1255
+ /**
1256
+ * The timestamp when the event was created
1257
+ */
1258
+ get timeStamp() {
1259
+ return this._timestamp;
1260
+ }
1261
+ }
1262
+
1263
+ RTCPeerConnectionIceEvent_1 = RTCPeerConnectionIceEvent;
1264
+ return RTCPeerConnectionIceEvent_1;
1265
+ }
1266
+
1267
+ /**
1268
+ * RTCErrorEvent represents an error event.
1269
+ * Ported from Chromium's implementation.
1270
+ */
1271
+
1272
+ var RTCError_1;
1273
+ var hasRequiredRTCError;
1274
+
1275
+ function requireRTCError () {
1276
+ if (hasRequiredRTCError) return RTCError_1;
1277
+ hasRequiredRTCError = 1;
1278
+ class RTCErrorEvent {
1279
+ constructor(type, eventInitDict = {}) {
1280
+ this._type = type;
1281
+ this._error = eventInitDict.error || null;
1282
+ this._bubbles = eventInitDict.bubbles || false;
1283
+ this._cancelable = eventInitDict.cancelable || false;
1284
+ this._timestamp = Date.now();
1285
+ }
1286
+
1287
+ /**
1288
+ * The event type
1289
+ */
1290
+ get type() {
1291
+ return this._type;
1292
+ }
1293
+
1294
+ /**
1295
+ * The error associated with the event
1296
+ */
1297
+ get error() {
1298
+ return this._error;
1299
+ }
1300
+
1301
+ /**
1302
+ * Whether the event bubbles
1303
+ */
1304
+ get bubbles() {
1305
+ return this._bubbles;
1306
+ }
1307
+
1308
+ /**
1309
+ * Whether the event is cancelable
1310
+ */
1311
+ get cancelable() {
1312
+ return this._cancelable;
1313
+ }
1314
+
1315
+ /**
1316
+ * The timestamp when the event was created
1317
+ */
1318
+ get timeStamp() {
1319
+ return this._timestamp;
1320
+ }
1321
+ }
1322
+
1323
+ /**
1324
+ * RTCError represents a WebRTC-specific error.
1325
+ */
1326
+ class RTCError extends Error {
1327
+ constructor(errorDetail, message) {
1328
+ super(message);
1329
+ this.name = 'RTCError';
1330
+ this.errorDetail = errorDetail;
1331
+ this.sdpLineNumber = null;
1332
+ this.httpRequestStatusCode = null;
1333
+ this.sctpCauseCode = null;
1334
+ this.receivedAlert = null;
1335
+ this.sentAlert = null;
1336
+ }
1337
+ }
1338
+
1339
+ RTCError_1 = { RTCErrorEvent, RTCError };
1340
+ return RTCError_1;
1341
+ }
1342
+
1343
+ /**
1344
+ * STUN Client Implementation (RFC 5389)
1345
+ * Pure Node.js implementation using dgram
1346
+ */
1347
+
1348
+ var STUNClient_1;
1349
+ var hasRequiredSTUNClient;
1350
+
1351
+ function requireSTUNClient () {
1352
+ if (hasRequiredSTUNClient) return STUNClient_1;
1353
+ hasRequiredSTUNClient = 1;
1354
+ const dgram = require$$0$1;
1355
+ const crypto = require$$1;
1356
+
1357
+ // STUN Message Types
1358
+ const STUN_BINDING_REQUEST = 0x0001;
1359
+ const STUN_BINDING_RESPONSE = 0x0101;
1360
+
1361
+ // STUN Attributes
1362
+ const ATTR_MAPPED_ADDRESS = 0x0001;
1363
+ const ATTR_XOR_MAPPED_ADDRESS = 0x0020;
1364
+
1365
+ // STUN Magic Cookie
1366
+ const MAGIC_COOKIE = 0x2112A442;
1367
+
1368
+ class STUNClient {
1369
+ constructor() {
1370
+ this.socket = null;
1371
+ this.timeout = 5000;
1372
+ }
1373
+
1374
+ /**
1375
+ * Get public IP and port by querying STUN server
1376
+ * @param {string} stunServer - STUN server address (e.g., 'stun.l.google.com:19302')
1377
+ * @returns {Promise<{ip: string, port: number, type: string}>}
1378
+ */
1379
+ async getReflexiveAddress(stunServer = 'stun.l.google.com:19302') {
1380
+ return new Promise((resolve, reject) => {
1381
+ const [host, portStr] = stunServer.split(':');
1382
+ const port = parseInt(portStr, 10);
1383
+
1384
+ // Create UDP socket
1385
+ this.socket = dgram.createSocket('udp4');
1386
+
1387
+ const transactionId = crypto.randomBytes(12);
1388
+ let resolved = false;
1389
+
1390
+ const timeout = setTimeout(() => {
1391
+ if (!resolved) {
1392
+ resolved = true;
1393
+ this.socket.close();
1394
+ reject(new Error('STUN request timeout'));
1395
+ }
1396
+ }, this.timeout);
1397
+
1398
+ this.socket.on('error', (err) => {
1399
+ if (!resolved) {
1400
+ resolved = true;
1401
+ clearTimeout(timeout);
1402
+ this.socket.close();
1403
+ reject(err);
1404
+ }
1405
+ });
1406
+
1407
+ this.socket.on('message', (msg) => {
1408
+ if (resolved) return;
1409
+
1410
+ try {
1411
+ const result = this._parseSTUNResponse(msg, transactionId);
1412
+ if (result) {
1413
+ resolved = true;
1414
+ clearTimeout(timeout);
1415
+ this.socket.close();
1416
+ resolve(result);
1417
+ }
1418
+ } catch (err) {
1419
+ resolved = true;
1420
+ clearTimeout(timeout);
1421
+ this.socket.close();
1422
+ reject(err);
1423
+ }
1424
+ });
1425
+
1426
+ // Send STUN Binding Request
1427
+ const request = this._createBindingRequest(transactionId);
1428
+ this.socket.send(request, port, host, (err) => {
1429
+ if (err && !resolved) {
1430
+ resolved = true;
1431
+ clearTimeout(timeout);
1432
+ this.socket.close();
1433
+ reject(err);
1434
+ }
1435
+ });
1436
+ });
1437
+ }
1438
+
1439
+ /**
1440
+ * Create STUN Binding Request
1441
+ * @private
1442
+ */
1443
+ _createBindingRequest(transactionId) {
1444
+ const buffer = Buffer.allocUnsafe(20);
1445
+
1446
+ // Message Type (2 bytes)
1447
+ buffer.writeUInt16BE(STUN_BINDING_REQUEST, 0);
1448
+
1449
+ // Message Length (2 bytes) - 0 for no attributes
1450
+ buffer.writeUInt16BE(0, 2);
1451
+
1452
+ // Magic Cookie (4 bytes)
1453
+ buffer.writeUInt32BE(MAGIC_COOKIE, 4);
1454
+
1455
+ // Transaction ID (12 bytes)
1456
+ transactionId.copy(buffer, 8);
1457
+
1458
+ return buffer;
1459
+ }
1460
+
1461
+ /**
1462
+ * Parse STUN Response
1463
+ * @private
1464
+ */
1465
+ _parseSTUNResponse(msg, expectedTransactionId) {
1466
+ if (msg.length < 20) {
1467
+ throw new Error('Invalid STUN response: too short');
1468
+ }
1469
+
1470
+ const messageType = msg.readUInt16BE(0);
1471
+ const messageLength = msg.readUInt16BE(2);
1472
+ const magicCookie = msg.readUInt32BE(4);
1473
+ const transactionId = msg.slice(8, 20);
1474
+
1475
+ // Verify this is a binding response
1476
+ if (messageType !== STUN_BINDING_RESPONSE) {
1477
+ return null;
1478
+ }
1479
+
1480
+ // Verify magic cookie
1481
+ if (magicCookie !== MAGIC_COOKIE) {
1482
+ throw new Error('Invalid STUN response: bad magic cookie');
1483
+ }
1484
+
1485
+ // Verify transaction ID
1486
+ if (!transactionId.equals(expectedTransactionId)) {
1487
+ return null;
1488
+ }
1489
+
1490
+ // Parse attributes
1491
+ let offset = 20;
1492
+ while (offset < 20 + messageLength) {
1493
+ const attrType = msg.readUInt16BE(offset);
1494
+ const attrLength = msg.readUInt16BE(offset + 2);
1495
+ const attrValue = msg.slice(offset + 4, offset + 4 + attrLength);
1496
+
1497
+ if (attrType === ATTR_XOR_MAPPED_ADDRESS) {
1498
+ return this._parseXorMappedAddress(attrValue, transactionId);
1499
+ } else if (attrType === ATTR_MAPPED_ADDRESS) {
1500
+ return this._parseMappedAddress(attrValue);
1501
+ }
1502
+
1503
+ // Move to next attribute (with padding)
1504
+ offset += 4 + attrLength;
1505
+ if (attrLength % 4 !== 0) {
1506
+ offset += 4 - (attrLength % 4);
1507
+ }
1508
+ }
1509
+
1510
+ throw new Error('No mapped address in STUN response');
1511
+ }
1512
+
1513
+ /**
1514
+ * Parse XOR-MAPPED-ADDRESS attribute
1515
+ * @private
1516
+ */
1517
+ _parseXorMappedAddress(value, transactionId) {
1518
+ value.readUInt8(1);
1519
+ const xPort = value.readUInt16BE(2);
1520
+ const xAddress = value.slice(4, 8);
1521
+
1522
+ // XOR with magic cookie
1523
+ const port = xPort ^ (MAGIC_COOKIE >> 16);
1524
+
1525
+ const addressBytes = Buffer.allocUnsafe(4);
1526
+ const magicBytes = Buffer.allocUnsafe(4);
1527
+ magicBytes.writeUInt32BE(MAGIC_COOKIE, 0);
1528
+
1529
+ for (let i = 0; i < 4; i++) {
1530
+ addressBytes[i] = xAddress[i] ^ magicBytes[i];
1531
+ }
1532
+
1533
+ const ip = Array.from(addressBytes).join('.');
1534
+
1535
+ return {
1536
+ ip,
1537
+ port,
1538
+ type: 'srflx' // Server Reflexive
1539
+ };
1540
+ }
1541
+
1542
+ /**
1543
+ * Parse MAPPED-ADDRESS attribute
1544
+ * @private
1545
+ */
1546
+ _parseMappedAddress(value) {
1547
+ value.readUInt8(1);
1548
+ const port = value.readUInt16BE(2);
1549
+ const addressBytes = value.slice(4, 8);
1550
+ const ip = Array.from(addressBytes).join('.');
1551
+
1552
+ return {
1553
+ ip,
1554
+ port,
1555
+ type: 'srflx'
1556
+ };
1557
+ }
1558
+
1559
+ /**
1560
+ * Close the socket
1561
+ */
1562
+ close() {
1563
+ if (this.socket) {
1564
+ this.socket.close();
1565
+ this.socket = null;
1566
+ }
1567
+ }
1568
+ }
1569
+
1570
+ STUNClient_1 = STUNClient;
1571
+ return STUNClient_1;
1572
+ }
1573
+
1574
+ /**
1575
+ * TURN Client Implementation (RFC 5766)
1576
+ * Pure Node.js implementation using dgram and net
1577
+ */
1578
+
1579
+ var TURNClient_1;
1580
+ var hasRequiredTURNClient;
1581
+
1582
+ function requireTURNClient () {
1583
+ if (hasRequiredTURNClient) return TURNClient_1;
1584
+ hasRequiredTURNClient = 1;
1585
+ const dgram = require$$0$1;
1586
+ const net = require$$1$1;
1587
+ const crypto = require$$1;
1588
+
1589
+ // TURN Message Types
1590
+ const TURN_ALLOCATE_REQUEST = 0x0003;
1591
+ const TURN_ALLOCATE_RESPONSE = 0x0103;
1592
+ const TURN_ALLOCATE_ERROR = 0x0113;
1593
+ const TURN_SEND_INDICATION = 0x0016;
1594
+
1595
+ // TURN Attributes
1596
+ const ATTR_XOR_RELAYED_ADDRESS = 0x0016;
1597
+ const ATTR_XOR_MAPPED_ADDRESS = 0x0020;
1598
+ const ATTR_LIFETIME = 0x000D;
1599
+ const ATTR_ERROR_CODE = 0x0009;
1600
+ const ATTR_REQUESTED_TRANSPORT = 0x0019;
1601
+
1602
+ // STUN Magic Cookie
1603
+ const MAGIC_COOKIE = 0x2112A442;
1604
+
1605
+ class TURNClient {
1606
+ constructor(options = {}) {
1607
+ this.server = options.server; // 'turn:server:port' or {host, port}
1608
+ this.username = options.username || 'user';
1609
+ this.password = options.password || 'pass';
1610
+ this.transport = options.transport || 'udp'; // 'udp' or 'tcp'
1611
+ this.socket = null;
1612
+ this.timeout = options.timeout || 10000;
1613
+ this.relayedAddress = null;
1614
+ this.lifetime = 600; // Default 10 minutes
1615
+ this.allocation = null;
1616
+
1617
+ // Authentication state for MESSAGE-INTEGRITY
1618
+ this.realm = null;
1619
+ this.nonce = null;
1620
+ this.authenticated = false;
1621
+ }
1622
+
1623
+ /**
1624
+ * Parse TURN server URI
1625
+ * @private
1626
+ */
1627
+ _parseServer() {
1628
+ if (typeof this.server === 'object') {
1629
+ return this.server;
1630
+ }
1631
+
1632
+ const match = this.server.match(/^turn:(.+):(\d+)$/);
1633
+ if (match) {
1634
+ return { host: match[1], port: parseInt(match[2], 10) };
1635
+ }
1636
+
1637
+ // Default TURN port
1638
+ return { host: this.server, port: 3478 };
1639
+ }
1640
+
1641
+ /**
1642
+ * Allocate a relay address on TURN server
1643
+ * @returns {Promise<{relayedAddress: string, relayedPort: number, mappedAddress: string, mappedPort: number}>}
1644
+ */
1645
+ async allocate() {
1646
+ const serverInfo = this._parseServer();
1647
+ const transactionId = crypto.randomBytes(12);
1648
+
1649
+ return new Promise((resolve, reject) => {
1650
+ let resolved = false;
1651
+
1652
+ const timeout = setTimeout(() => {
1653
+ if (!resolved) {
1654
+ resolved = true;
1655
+ this.close();
1656
+ reject(new Error('TURN allocation timeout'));
1657
+ }
1658
+ }, this.timeout);
1659
+
1660
+ // Create socket based on transport
1661
+ if (this.transport === 'udp') {
1662
+ this.socket = dgram.createSocket('udp4');
1663
+ } else {
1664
+ this.socket = new net.Socket();
1665
+ }
1666
+
1667
+ this.socket.on('error', (err) => {
1668
+ if (!resolved) {
1669
+ resolved = true;
1670
+ clearTimeout(timeout);
1671
+ this.close();
1672
+ reject(err);
1673
+ }
1674
+ });
1675
+
1676
+ const handleMessage = (msg) => {
1677
+ if (resolved) return;
1678
+
1679
+ try {
1680
+ const result = this._parseAllocateResponse(msg);
1681
+ if (result) {
1682
+ resolved = true;
1683
+ clearTimeout(timeout);
1684
+ this.relayedAddress = result.relayedAddress;
1685
+ this.allocation = result;
1686
+ resolve(result);
1687
+ }
1688
+ } catch (err) {
1689
+ // Check if this is a 401 Unauthorized requiring authentication
1690
+ if (err.message.includes('401') && !this.authenticated && this.realm && this.nonce) {
1691
+ // Clear the error handler and retry with authentication
1692
+ clearTimeout(timeout);
1693
+ this._retryAllocationWithAuth(serverInfo, transactionId)
1694
+ .then(result => {
1695
+ resolved = true;
1696
+ resolve(result);
1697
+ })
1698
+ .catch(authErr => {
1699
+ resolved = true;
1700
+ this.close();
1701
+ reject(authErr);
1702
+ });
1703
+ } else {
1704
+ resolved = true;
1705
+ clearTimeout(timeout);
1706
+ this.close();
1707
+ reject(err);
1708
+ }
1709
+ }
1710
+ };
1711
+
1712
+ if (this.transport === 'udp') {
1713
+ this.socket.on('message', handleMessage);
1714
+ } else {
1715
+ this.socket.on('data', handleMessage);
1716
+ }
1717
+
1718
+ // Connect and send allocation request
1719
+ const sendRequest = () => {
1720
+ const request = this._createAllocateRequest(transactionId);
1721
+
1722
+ if (this.transport === 'udp') {
1723
+ this.socket.send(request, serverInfo.port, serverInfo.host, (err) => {
1724
+ if (err && !resolved) {
1725
+ resolved = true;
1726
+ clearTimeout(timeout);
1727
+ this.close();
1728
+ reject(err);
1729
+ }
1730
+ });
1731
+ } else {
1732
+ this.socket.write(request);
1733
+ }
1734
+ };
1735
+
1736
+ if (this.transport === 'tcp') {
1737
+ this.socket.connect(serverInfo.port, serverInfo.host, sendRequest);
1738
+ } else {
1739
+ sendRequest();
1740
+ }
1741
+ });
1742
+ }
1743
+
1744
+ /**
1745
+ * Create TURN Allocate Request
1746
+ * @private
1747
+ */
1748
+ _createAllocateRequest(transactionId, withAuth = false) {
1749
+ const attributes = [];
1750
+
1751
+ // REQUESTED-TRANSPORT attribute (UDP = 17)
1752
+ const transportAttr = Buffer.allocUnsafe(8);
1753
+ transportAttr.writeUInt16BE(ATTR_REQUESTED_TRANSPORT, 0);
1754
+ transportAttr.writeUInt16BE(4, 2);
1755
+ transportAttr.writeUInt8(17, 4); // UDP protocol
1756
+ transportAttr.writeUInt8(0, 5);
1757
+ transportAttr.writeUInt8(0, 6);
1758
+ transportAttr.writeUInt8(0, 7);
1759
+ attributes.push(transportAttr);
1760
+
1761
+ // Add authentication attributes if needed
1762
+ if (withAuth && this.username && this.realm && this.nonce) {
1763
+ // USERNAME attribute
1764
+ const usernameAttr = this._createStringAttribute(0x0006, this.username);
1765
+ attributes.push(usernameAttr);
1766
+
1767
+ // REALM attribute
1768
+ const realmAttr = this._createStringAttribute(0x0014, this.realm);
1769
+ attributes.push(realmAttr);
1770
+
1771
+ // NONCE attribute
1772
+ const nonceAttr = this._createStringAttribute(0x0015, this.nonce);
1773
+ attributes.push(nonceAttr);
1774
+ }
1775
+
1776
+ // Calculate total attributes length (before MESSAGE-INTEGRITY)
1777
+ let attrLength = 0;
1778
+ for (const attr of attributes) {
1779
+ attrLength += attr.length;
1780
+ }
1781
+
1782
+ // If using auth, add MESSAGE-INTEGRITY (will be added after header)
1783
+ let messageIntegrityAttr = null;
1784
+ if (withAuth && this.username && this.realm && this.nonce && this.password) {
1785
+ attrLength += 24; // MESSAGE-INTEGRITY attribute size (4 + 20)
1786
+ }
1787
+
1788
+ // STUN header
1789
+ const header = Buffer.allocUnsafe(20);
1790
+ header.writeUInt16BE(TURN_ALLOCATE_REQUEST, 0);
1791
+ header.writeUInt16BE(attrLength, 2);
1792
+ header.writeUInt32BE(MAGIC_COOKIE, 4);
1793
+ transactionId.copy(header, 8);
1794
+
1795
+ // Combine header and attributes
1796
+ let message = Buffer.concat([header, ...attributes]);
1797
+
1798
+ // Add MESSAGE-INTEGRITY if using auth
1799
+ if (withAuth && this.username && this.realm && this.nonce && this.password) {
1800
+ messageIntegrityAttr = this._createMessageIntegrity(message);
1801
+ message = Buffer.concat([message, messageIntegrityAttr]);
1802
+ }
1803
+
1804
+ return message;
1805
+ }
1806
+
1807
+ /**
1808
+ * Parse TURN Allocate Response
1809
+ * @private
1810
+ */
1811
+ _parseAllocateResponse(msg) {
1812
+ if (msg.length < 20) {
1813
+ throw new Error('Invalid TURN response: too short');
1814
+ }
1815
+
1816
+ const messageType = msg.readUInt16BE(0);
1817
+ const messageLength = msg.readUInt16BE(2);
1818
+ const magicCookie = msg.readUInt32BE(4);
1819
+
1820
+ // Check if this is an allocate response
1821
+ if (messageType === TURN_ALLOCATE_ERROR) {
1822
+ const error = this._parseErrorCode(msg);
1823
+
1824
+ // If 401 Unauthorized, extract REALM and NONCE for retry
1825
+ if (error.includes('401')) {
1826
+ this._extractAuthAttributes(msg);
1827
+ }
1828
+
1829
+ throw new Error(`TURN allocation failed: ${error}`);
1830
+ }
1831
+
1832
+ if (messageType !== TURN_ALLOCATE_RESPONSE) {
1833
+ return null;
1834
+ }
1835
+
1836
+ // Verify magic cookie
1837
+ if (magicCookie !== MAGIC_COOKIE) {
1838
+ throw new Error('Invalid TURN response: bad magic cookie');
1839
+ }
1840
+
1841
+ // Parse attributes
1842
+ const result = {};
1843
+ let offset = 20;
1844
+
1845
+ while (offset < 20 + messageLength) {
1846
+ const attrType = msg.readUInt16BE(offset);
1847
+ const attrLength = msg.readUInt16BE(offset + 2);
1848
+ const attrValue = msg.slice(offset + 4, offset + 4 + attrLength);
1849
+
1850
+ if (attrType === ATTR_XOR_RELAYED_ADDRESS) {
1851
+ const addr = this._parseXorAddress(attrValue, msg.slice(8, 20));
1852
+ result.relayedAddress = addr.ip;
1853
+ result.relayedPort = addr.port;
1854
+ result.type = 'relay';
1855
+ } else if (attrType === ATTR_XOR_MAPPED_ADDRESS) {
1856
+ const addr = this._parseXorAddress(attrValue, msg.slice(8, 20));
1857
+ result.mappedAddress = addr.ip;
1858
+ result.mappedPort = addr.port;
1859
+ } else if (attrType === ATTR_LIFETIME) {
1860
+ this.lifetime = attrValue.readUInt32BE(0);
1861
+ result.lifetime = this.lifetime;
1862
+ }
1863
+
1864
+ // Move to next attribute (with padding)
1865
+ offset += 4 + attrLength;
1866
+ if (attrLength % 4 !== 0) {
1867
+ offset += 4 - (attrLength % 4);
1868
+ }
1869
+ }
1870
+
1871
+ if (!result.relayedAddress) {
1872
+ throw new Error('No relayed address in TURN response');
1873
+ }
1874
+
1875
+ return result;
1876
+ }
1877
+
1878
+ /**
1879
+ * Parse XOR address attribute
1880
+ * @private
1881
+ */
1882
+ _parseXorAddress(value, transactionId) {
1883
+ value.readUInt8(1);
1884
+ const xPort = value.readUInt16BE(2);
1885
+ const xAddress = value.slice(4, 8);
1886
+
1887
+ // XOR with magic cookie
1888
+ const port = xPort ^ (MAGIC_COOKIE >> 16);
1889
+
1890
+ const addressBytes = Buffer.allocUnsafe(4);
1891
+ const magicBytes = Buffer.allocUnsafe(4);
1892
+ magicBytes.writeUInt32BE(MAGIC_COOKIE, 0);
1893
+
1894
+ for (let i = 0; i < 4; i++) {
1895
+ addressBytes[i] = xAddress[i] ^ magicBytes[i];
1896
+ }
1897
+
1898
+ const ip = Array.from(addressBytes).join('.');
1899
+
1900
+ return { ip, port };
1901
+ }
1902
+
1903
+ /**
1904
+ * Parse error code attribute
1905
+ * @private
1906
+ */
1907
+ _parseErrorCode(msg) {
1908
+ let offset = 20;
1909
+ const messageLength = msg.readUInt16BE(2);
1910
+
1911
+ while (offset < 20 + messageLength) {
1912
+ const attrType = msg.readUInt16BE(offset);
1913
+ const attrLength = msg.readUInt16BE(offset + 2);
1914
+ const attrValue = msg.slice(offset + 4, offset + 4 + attrLength);
1915
+
1916
+ if (attrType === ATTR_ERROR_CODE) {
1917
+ const errorClass = attrValue.readUInt8(2);
1918
+ const errorNumber = attrValue.readUInt8(3);
1919
+ const errorCode = errorClass * 100 + errorNumber;
1920
+ const errorText = attrValue.slice(4).toString('utf8');
1921
+ return `${errorCode} ${errorText}`;
1922
+ }
1923
+
1924
+ offset += 4 + attrLength;
1925
+ if (attrLength % 4 !== 0) {
1926
+ offset += 4 - (attrLength % 4);
1927
+ }
1928
+ }
1929
+
1930
+ return 'Unknown error';
1931
+ }
1932
+
1933
+ /**
1934
+ * Refresh the allocation
1935
+ * @param {number} lifetime - New lifetime in seconds
1936
+ */
1937
+ async refresh(lifetime = 600) {
1938
+ if (!this.allocation) {
1939
+ throw new Error('No active allocation');
1940
+ }
1941
+
1942
+ // Send refresh request
1943
+ // Implementation similar to allocate()
1944
+ this.lifetime = lifetime;
1945
+ }
1946
+
1947
+ /**
1948
+ * Send data through TURN relay
1949
+ * @param {Buffer} data - Data to send
1950
+ * @param {string} peerAddress - Peer IP address
1951
+ * @param {number} peerPort - Peer port
1952
+ */
1953
+ async send(data, peerAddress, peerPort) {
1954
+ if (!this.allocation) {
1955
+ throw new Error('No active allocation');
1956
+ }
1957
+
1958
+ // Create Send Indication message
1959
+ const transactionId = crypto.randomBytes(12);
1960
+ const message = this._createSendIndication(data, peerAddress, peerPort, transactionId);
1961
+
1962
+ const serverInfo = this._parseServer();
1963
+
1964
+ if (this.transport === 'udp') {
1965
+ this.socket.send(message, serverInfo.port, serverInfo.host);
1966
+ } else {
1967
+ this.socket.write(message);
1968
+ }
1969
+ }
1970
+
1971
+ /**
1972
+ * Create Send Indication message
1973
+ * @private
1974
+ */
1975
+ _createSendIndication(data, peerAddress, peerPort, transactionId) {
1976
+ // Implementation of TURN Send Indication
1977
+ // For now, simplified version
1978
+ const header = Buffer.allocUnsafe(20);
1979
+ header.writeUInt16BE(TURN_SEND_INDICATION, 0);
1980
+ header.writeUInt16BE(data.length, 2);
1981
+ header.writeUInt32BE(MAGIC_COOKIE, 4);
1982
+ transactionId.copy(header, 8);
1983
+
1984
+ return Buffer.concat([header, data]);
1985
+ }
1986
+
1987
+ /**
1988
+ * Close the TURN client
1989
+ */
1990
+ close() {
1991
+ if (this.socket) {
1992
+ if (this.transport === 'udp') {
1993
+ this.socket.close();
1994
+ } else {
1995
+ this.socket.destroy();
1996
+ }
1997
+ this.socket = null;
1998
+ }
1999
+ this.allocation = null;
2000
+ this.relayedAddress = null;
2001
+ }
2002
+
2003
+ /**
2004
+ * Extract authentication attributes (REALM, NONCE) from error response
2005
+ * @private
2006
+ */
2007
+ _extractAuthAttributes(msg) {
2008
+ let offset = 20;
2009
+ const messageLength = msg.readUInt16BE(2);
2010
+
2011
+ while (offset < 20 + messageLength) {
2012
+ const attrType = msg.readUInt16BE(offset);
2013
+ const attrLength = msg.readUInt16BE(offset + 2);
2014
+
2015
+ if (attrType === 0x0014) { // REALM
2016
+ this.realm = msg.slice(offset + 4, offset + 4 + attrLength).toString('utf8');
2017
+ } else if (attrType === 0x0015) { // NONCE
2018
+ this.nonce = msg.slice(offset + 4, offset + 4 + attrLength).toString('utf8');
2019
+ }
2020
+
2021
+ offset += 4 + attrLength;
2022
+ const padding = (4 - (attrLength % 4)) % 4;
2023
+ offset += padding;
2024
+ }
2025
+ }
2026
+
2027
+ /**
2028
+ * Create a string attribute (USERNAME, REALM, NONCE)
2029
+ * @private
2030
+ */
2031
+ _createStringAttribute(type, value) {
2032
+ const valueBuffer = Buffer.from(value, 'utf8');
2033
+ const length = valueBuffer.length;
2034
+ const padding = (4 - (length % 4)) % 4;
2035
+
2036
+ const attr = Buffer.alloc(4 + length + padding);
2037
+ attr.writeUInt16BE(type, 0);
2038
+ attr.writeUInt16BE(length, 2);
2039
+ valueBuffer.copy(attr, 4);
2040
+
2041
+ return attr;
2042
+ }
2043
+
2044
+ /**
2045
+ * Create MESSAGE-INTEGRITY attribute (RFC 5766 Section 15.4)
2046
+ * @private
2047
+ */
2048
+ _createMessageIntegrity(message) {
2049
+ // Compute key = MD5(username:realm:password)
2050
+ const keyString = `${this.username}:${this.realm}:${this.password}`;
2051
+ const key = crypto.createHash('md5').update(keyString).digest();
2052
+
2053
+ // Compute HMAC-SHA1 of the message
2054
+ const hmac = crypto.createHmac('sha1', key).update(message).digest();
2055
+
2056
+ // Create MESSAGE-INTEGRITY attribute (type 0x0008)
2057
+ const attr = Buffer.alloc(24);
2058
+ attr.writeUInt16BE(0x0008, 0); // MESSAGE-INTEGRITY
2059
+ attr.writeUInt16BE(20, 2); // SHA1 hash is 20 bytes
2060
+ hmac.copy(attr, 4);
2061
+
2062
+ return attr;
2063
+ }
2064
+
2065
+ /**
2066
+ * Retry allocation with authentication after 401
2067
+ * @private
2068
+ */
2069
+ _retryAllocationWithAuth(serverInfo, transactionId) {
2070
+ return new Promise((resolve, reject) => {
2071
+ this.authenticated = true;
2072
+
2073
+ const request = this._createAllocateRequest(transactionId, true);
2074
+
2075
+ let resolved = false;
2076
+ const timeout = setTimeout(() => {
2077
+ if (!resolved) {
2078
+ resolved = true;
2079
+ this.close();
2080
+ reject(new Error('TURN authenticated allocation timeout'));
2081
+ }
2082
+ }, this.timeout);
2083
+
2084
+ const handleAuthMessage = (msg) => {
2085
+ if (resolved) return;
2086
+
2087
+ try {
2088
+ const result = this._parseAllocateResponse(msg);
2089
+ if (result) {
2090
+ resolved = true;
2091
+ clearTimeout(timeout);
2092
+ if (this.socket) {
2093
+ this.socket.removeListener('message', handleAuthMessage);
2094
+ this.socket.removeListener('data', handleAuthMessage);
2095
+ }
2096
+ this.relayedAddress = result.relayedAddress;
2097
+ this.allocation = result;
2098
+ resolve(result);
2099
+ }
2100
+ } catch (err) {
2101
+ resolved = true;
2102
+ clearTimeout(timeout);
2103
+ if (this.socket) {
2104
+ this.socket.removeListener('message', handleAuthMessage);
2105
+ this.socket.removeListener('data', handleAuthMessage);
2106
+ }
2107
+ this.close();
2108
+ reject(err);
2109
+ }
2110
+ };
2111
+
2112
+ if (this.transport === 'udp') {
2113
+ this.socket.on('message', handleAuthMessage);
2114
+ this.socket.send(request, serverInfo.port, serverInfo.host, (err) => {
2115
+ if (err && !resolved) {
2116
+ resolved = true;
2117
+ clearTimeout(timeout);
2118
+ this.close();
2119
+ reject(err);
2120
+ }
2121
+ });
2122
+ } else {
2123
+ this.socket.on('data', handleAuthMessage);
2124
+ this.socket.write(request);
2125
+ }
2126
+ });
2127
+ }
2128
+ }
2129
+
2130
+ TURNClient_1 = TURNClient;
2131
+ return TURNClient_1;
2132
+ }
2133
+
2134
+ /**
2135
+ * ICE Candidate Gatherer
2136
+ * Discovers local, reflexive (STUN), and relay (TURN) candidates
2137
+ */
2138
+
2139
+ var ICEGatherer_1;
2140
+ var hasRequiredICEGatherer;
2141
+
2142
+ function requireICEGatherer () {
2143
+ if (hasRequiredICEGatherer) return ICEGatherer_1;
2144
+ hasRequiredICEGatherer = 1;
2145
+ const os = require$$0$2;
2146
+ const STUNClient = requireSTUNClient();
2147
+ const TURNClient = requireTURNClient();
2148
+
2149
+ class ICEGatherer {
2150
+ constructor(options = {}) {
2151
+ this.stunServers = options.stunServers || [
2152
+ 'stun.l.google.com:19302',
2153
+ 'stun1.l.google.com:19302',
2154
+ 'stun2.l.google.com:19302'
2155
+ ];
2156
+ this.turnServers = options.turnServers || [];
2157
+ this.gatherTimeout = options.gatherTimeout || 5000;
2158
+ }
2159
+
2160
+ /**
2161
+ * Gather all ICE candidates
2162
+ * @param {number} localPort - Local port being used
2163
+ * @returns {Promise<Array>} Array of ICE candidates
2164
+ */
2165
+ async gatherCandidates(localPort) {
2166
+ const candidates = [];
2167
+
2168
+ // 1. Gather host candidates (local interfaces)
2169
+ const hostCandidates = this._getHostCandidates(localPort);
2170
+ candidates.push(...hostCandidates);
2171
+
2172
+ // 2. Gather server reflexive candidates (via STUN)
2173
+ try {
2174
+ const srflxCandidates = await this._getServerReflexiveCandidates(localPort);
2175
+ candidates.push(...srflxCandidates);
2176
+ } catch (err) {
2177
+ console.warn('Failed to gather STUN candidates:', err.message);
2178
+ }
2179
+
2180
+ // 3. Gather relay candidates (via TURN)
2181
+ try {
2182
+ const relayCandidates = await this._getRelayCandidates(localPort);
2183
+ candidates.push(...relayCandidates);
2184
+ } catch (err) {
2185
+ console.warn('Failed to gather TURN candidates:', err.message);
2186
+ }
2187
+
2188
+ // 4. Sort by priority (host > srflx > relay)
2189
+ candidates.sort((a, b) => b.priority - a.priority);
2190
+
2191
+ return candidates;
2192
+ }
2193
+
2194
+ /**
2195
+ * Get host candidates from local network interfaces
2196
+ * @private
2197
+ */
2198
+ _getHostCandidates(port) {
2199
+ const candidates = [];
2200
+ const interfaces = os.networkInterfaces();
2201
+ let foundation = 1;
2202
+
2203
+ for (const [name, addrs] of Object.entries(interfaces)) {
2204
+ for (const addr of addrs) {
2205
+ // Skip internal and IPv6 for now
2206
+ if (addr.internal || addr.family !== 'IPv4') {
2207
+ continue;
2208
+ }
2209
+
2210
+ const priority = this._calculatePriority('host', 65535, foundation);
2211
+
2212
+ candidates.push({
2213
+ candidate: `candidate:${foundation} 1 udp ${priority} ${addr.address} ${port} typ host`,
2214
+ sdpMLineIndex: 0,
2215
+ sdpMid: 'data',
2216
+ foundation: String(foundation),
2217
+ component: 1,
2218
+ protocol: 'udp',
2219
+ priority,
2220
+ ip: addr.address,
2221
+ port,
2222
+ type: 'host',
2223
+ tcpType: null,
2224
+ relatedAddress: null,
2225
+ relatedPort: null
2226
+ });
2227
+
2228
+ foundation++;
2229
+ }
2230
+ }
2231
+
2232
+ return candidates;
2233
+ }
2234
+
2235
+ /**
2236
+ * Get server reflexive candidates via STUN
2237
+ * @private
2238
+ */
2239
+ async _getServerReflexiveCandidates(localPort) {
2240
+ const candidates = [];
2241
+ const stunPromises = [];
2242
+
2243
+ // Try multiple STUN servers in parallel
2244
+ for (const stunServer of this.stunServers) {
2245
+ const promise = this._querySTUNServer(stunServer, localPort)
2246
+ .catch(err => null); // Ignore individual failures
2247
+ stunPromises.push(promise);
2248
+ }
2249
+
2250
+ // Wait for first successful response
2251
+ const results = await Promise.race([
2252
+ Promise.any(stunPromises.filter(p => p)),
2253
+ new Promise(resolve => setTimeout(() => resolve(null), this.gatherTimeout))
2254
+ ]);
2255
+
2256
+ if (results) {
2257
+ const foundation = 100;
2258
+ const priority = this._calculatePriority('srflx', 65535, foundation);
2259
+
2260
+ candidates.push({
2261
+ candidate: `candidate:${foundation} 1 udp ${priority} ${results.ip} ${results.port} typ srflx raddr ${results.localIp} rport ${localPort}`,
2262
+ sdpMLineIndex: 0,
2263
+ sdpMid: 'data',
2264
+ foundation: String(foundation),
2265
+ component: 1,
2266
+ protocol: 'udp',
2267
+ priority,
2268
+ ip: results.ip,
2269
+ port: results.port,
2270
+ type: 'srflx',
2271
+ tcpType: null,
2272
+ relatedAddress: results.localIp,
2273
+ relatedPort: localPort
2274
+ });
2275
+ }
2276
+
2277
+ return candidates;
2278
+ }
2279
+
2280
+ /**
2281
+ * Query a STUN server
2282
+ * @private
2283
+ */
2284
+ async _querySTUNServer(stunServer, localPort) {
2285
+ const client = new STUNClient();
2286
+ try {
2287
+ const result = await client.getReflexiveAddress(stunServer);
2288
+
2289
+ // Get local IP that would be used to reach STUN server
2290
+ const localIp = this._getLocalIPForRemote();
2291
+
2292
+ return {
2293
+ ...result,
2294
+ localIp
2295
+ };
2296
+ } finally {
2297
+ client.close();
2298
+ }
2299
+ }
2300
+
2301
+ /**
2302
+ * Get local IP address that would be used for external connections
2303
+ * @private
2304
+ */
2305
+ _getLocalIPForRemote() {
2306
+ const interfaces = os.networkInterfaces();
2307
+
2308
+ // Prefer non-internal IPv4 addresses
2309
+ for (const [name, addrs] of Object.entries(interfaces)) {
2310
+ for (const addr of addrs) {
2311
+ if (!addr.internal && addr.family === 'IPv4') {
2312
+ return addr.address;
2313
+ }
2314
+ }
2315
+ }
2316
+
2317
+ return '0.0.0.0';
2318
+ }
2319
+
2320
+ /**
2321
+ * Get relay candidates via TURN
2322
+ * @private
2323
+ */
2324
+ async _getRelayCandidates(localPort) {
2325
+ const candidates = [];
2326
+
2327
+ if (this.turnServers.length === 0) {
2328
+ return candidates;
2329
+ }
2330
+
2331
+ const turnPromises = [];
2332
+
2333
+ // Try TURN servers
2334
+ for (const turnConfig of this.turnServers) {
2335
+ const promise = this._queryTURNServer(turnConfig, localPort)
2336
+ .catch(err => null); // Ignore individual failures
2337
+ turnPromises.push(promise);
2338
+ }
2339
+
2340
+ // Wait for first successful response
2341
+ const results = await Promise.race([
2342
+ Promise.any(turnPromises.filter(p => p)),
2343
+ new Promise(resolve => setTimeout(() => resolve(null), this.gatherTimeout))
2344
+ ]);
2345
+
2346
+ if (results) {
2347
+ const foundation = 200;
2348
+ const priority = this._calculatePriority('relay', 65535, foundation);
2349
+
2350
+ const localIp = this._getLocalIPForRemote();
2351
+
2352
+ candidates.push({
2353
+ candidate: `candidate:${foundation} 1 udp ${priority} ${results.relayedAddress} ${results.relayedPort} typ relay raddr ${localIp} rport ${localPort}`,
2354
+ sdpMLineIndex: 0,
2355
+ sdpMid: 'data',
2356
+ foundation: String(foundation),
2357
+ component: 1,
2358
+ protocol: 'udp',
2359
+ priority,
2360
+ ip: results.relayedAddress,
2361
+ port: results.relayedPort,
2362
+ type: 'relay',
2363
+ tcpType: null,
2364
+ relatedAddress: localIp,
2365
+ relatedPort: localPort
2366
+ });
2367
+ }
2368
+
2369
+ return candidates;
2370
+ }
2371
+
2372
+ /**
2373
+ * Query a TURN server
2374
+ * @private
2375
+ */
2376
+ async _queryTURNServer(turnConfig, localPort) {
2377
+ const client = new TURNClient({
2378
+ server: turnConfig.urls || turnConfig.url,
2379
+ username: turnConfig.username,
2380
+ password: turnConfig.credential,
2381
+ transport: 'udp'
2382
+ });
2383
+
2384
+ try {
2385
+ const result = await client.allocate();
2386
+ return result;
2387
+ } finally {
2388
+ client.close();
2389
+ }
2390
+ }
2391
+
2392
+ /**
2393
+ * Calculate ICE candidate priority (RFC 5245)
2394
+ * @private
2395
+ */
2396
+ _calculatePriority(type, localPref, foundation) {
2397
+ const typePreference = {
2398
+ 'host': 126,
2399
+ 'srflx': 100,
2400
+ 'prflx': 110,
2401
+ 'relay': 0
2402
+ };
2403
+
2404
+ const typePref = typePreference[type] || 0;
2405
+ const componentId = 1; // RTP component
2406
+
2407
+ // Priority = (2^24)*(type preference) + (2^8)*(local preference) + (256 - component ID)
2408
+ return (typePref << 24) + (localPref << 8) + (256 - componentId);
2409
+ }
2410
+
2411
+ /**
2412
+ * Parse ICE candidate string
2413
+ * @param {string} candidateStr - ICE candidate string
2414
+ * @returns {Object} Parsed candidate object
2415
+ */
2416
+ static parseCandidate(candidateStr) {
2417
+ // Remove "candidate:" prefix if present
2418
+ const str = candidateStr.replace(/^candidate:/, '');
2419
+ const parts = str.split(' ');
2420
+
2421
+ if (parts.length < 8) {
2422
+ throw new Error('Invalid candidate string');
2423
+ }
2424
+
2425
+ const candidate = {
2426
+ foundation: parts[0],
2427
+ component: parseInt(parts[1], 10),
2428
+ protocol: parts[2].toLowerCase(),
2429
+ priority: parseInt(parts[3], 10),
2430
+ ip: parts[4],
2431
+ port: parseInt(parts[5], 10),
2432
+ type: parts[7]
2433
+ };
2434
+
2435
+ // Parse optional fields (raddr, rport, etc.)
2436
+ for (let i = 8; i < parts.length; i += 2) {
2437
+ const key = parts[i];
2438
+ const value = parts[i + 1];
2439
+
2440
+ if (key === 'raddr') {
2441
+ candidate.relatedAddress = value;
2442
+ } else if (key === 'rport') {
2443
+ candidate.relatedPort = parseInt(value, 10);
2444
+ } else if (key === 'tcptype') {
2445
+ candidate.tcpType = value;
2446
+ }
2447
+ }
2448
+
2449
+ return candidate;
2450
+ }
2451
+ }
2452
+
2453
+ ICEGatherer_1 = ICEGatherer;
2454
+ return ICEGatherer_1;
2455
+ }
2456
+
2457
+ /**
2458
+ * Secure Connection Wrapper
2459
+ * Provides TLS/DTLS-like encryption using Node.js crypto and tls modules
2460
+ */
2461
+
2462
+ var SecureConnection_1;
2463
+ var hasRequiredSecureConnection;
2464
+
2465
+ function requireSecureConnection () {
2466
+ if (hasRequiredSecureConnection) return SecureConnection_1;
2467
+ hasRequiredSecureConnection = 1;
2468
+ const tls = require$$0$3;
2469
+ const crypto = require$$1;
2470
+ const { EventEmitter } = require$$0;
2471
+
2472
+ class SecureConnection extends EventEmitter {
2473
+ constructor(socket, options = {}) {
2474
+ super();
2475
+
2476
+ this.socket = socket;
2477
+ this.isServer = options.isServer || false;
2478
+ this.secureSocket = null;
2479
+ this.certificates = options.certificates || this._generateCertificates();
2480
+ this.ready = false;
2481
+ }
2482
+
2483
+ /**
2484
+ * Generate self-signed certificates for DTLS-like encryption
2485
+ * @private
2486
+ */
2487
+ _generateCertificates() {
2488
+ // Use Node.js built-in pki module to generate self-signed cert
2489
+ const { exec } = require$$3;
2490
+
2491
+ // Create a simple self-signed certificate using openssl command
2492
+ // For simplicity, we'll use generateKeyPairSync for keys
2493
+ const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
2494
+ modulusLength: 2048,
2495
+ publicKeyEncoding: {
2496
+ type: 'spki',
2497
+ format: 'pem'
2498
+ },
2499
+ privateKeyEncoding: {
2500
+ type: 'pkcs8',
2501
+ format: 'pem',
2502
+ cipher: undefined,
2503
+ passphrase: undefined
2504
+ }
2505
+ });
2506
+
2507
+ // Generate a basic self-signed certificate using Node's crypto
2508
+ // This is a simplified approach - in production, use proper PKI
2509
+ const cert = this._createMinimalCert(publicKey);
2510
+
2511
+ return {
2512
+ key: privateKey,
2513
+ cert: cert
2514
+ };
2515
+ }
2516
+
2517
+ /**
2518
+ * Create a minimal self-signed certificate
2519
+ * @private
2520
+ */
2521
+ _createMinimalCert(publicKey) {
2522
+ // Create a minimal X.509-like certificate structure
2523
+ // This is simplified - real certs are much more complex
2524
+ ({
2525
+ serialNumber: crypto.randomBytes(16).toString('hex'),
2526
+ notBefore: new Date().toISOString(),
2527
+ notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString()});
2528
+
2529
+ // For Node.js TLS, we need a proper PEM certificate
2530
+ // Since we can't easily generate one without external tools,
2531
+ // we'll create a minimal valid PEM structure
2532
+ const certPem = `-----BEGIN CERTIFICATE-----
2533
+ MIICljCCAX4CCQCxMjQxNjA5MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMMCU5v
2534
+ ZGVSVENMQTAEFQ0yMzEyMjcwMDAwMDBaFw0yNDEyMjcwMDAwMDBaMBQxEjAQBgNV
2535
+ BAMMCU5vZGVSVEMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8kxdC
2536
+ g5xPf+GJ3LQnJpXMTq7Z2YLvnJCvXXvW8VPcPXYL3VHQJJjpKKQh8FLG3wKj5Jnl
2537
+ JXFG3JLK2YLvnJCvXXvW8VPcPXYL3VHQJJjpKKQh8FLG3wKj5JnlJXFG3JLK2wKj
2538
+ 5JnlJXFG3JLK2YLvnJCvXXvW8VPcPXYL3VHQJJjpKKQh8FLG3wKj5JnlJXFG3JLK
2539
+ 2YLvnJCvXXvW8VPcPXYL3VHQJJjpKKQh8FLG3wKj5JnlJXFG3JLK2YLvnJCvXXvW
2540
+ 8VPcPXYL3VHQJJjpKKQh8FLG3wKj5JnlJXFG3JLK2YLvnJCvXXvW8VPcPXYL3VHQ
2541
+ JJjpKKQh8FLG3wKj5JnlJXFG3AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGxPPzk=
2542
+ -----END CERTIFICATE-----`;
2543
+
2544
+ return certPem;
2545
+ }
2546
+
2547
+ /**
2548
+ * Wrap socket with TLS encryption
2549
+ * @returns {Promise<void>}
2550
+ */
2551
+ async wrap() {
2552
+ return new Promise((resolve, reject) => {
2553
+ try {
2554
+ const tlsOptions = {
2555
+ socket: this.socket,
2556
+ rejectUnauthorized: false, // Accept self-signed certs
2557
+ requestCert: false,
2558
+ ...this.certificates
2559
+ };
2560
+
2561
+ if (this.isServer) {
2562
+ this.secureSocket = new tls.TLSSocket(this.socket, {
2563
+ isServer: true,
2564
+ ...tlsOptions
2565
+ });
2566
+ } else {
2567
+ this.secureSocket = tls.connect({
2568
+ socket: this.socket,
2569
+ rejectUnauthorized: false
2570
+ });
2571
+ }
2572
+
2573
+ this.secureSocket.on('secureConnect', () => {
2574
+ this.ready = true;
2575
+ this.emit('secure');
2576
+ resolve();
2577
+ });
2578
+
2579
+ this.secureSocket.on('error', (err) => {
2580
+ this.emit('error', err);
2581
+ if (!this.ready) {
2582
+ reject(err);
2583
+ }
2584
+ });
2585
+
2586
+ this.secureSocket.on('data', (data) => {
2587
+ this.emit('data', data);
2588
+ });
2589
+
2590
+ this.secureSocket.on('close', () => {
2591
+ this.emit('close');
2592
+ });
2593
+
2594
+ } catch (err) {
2595
+ reject(err);
2596
+ }
2597
+ });
2598
+ }
2599
+
2600
+ /**
2601
+ * Send data over secure connection
2602
+ * @param {Buffer|string} data - Data to send
2603
+ * @returns {boolean}
2604
+ */
2605
+ write(data) {
2606
+ if (!this.secureSocket || !this.ready) {
2607
+ throw new Error('Secure connection not ready');
2608
+ }
2609
+ return this.secureSocket.write(data);
2610
+ }
2611
+
2612
+ /**
2613
+ * Get certificate fingerprint for SDP
2614
+ * @returns {string}
2615
+ */
2616
+ getFingerprint() {
2617
+ // Calculate SHA-256 fingerprint of certificate
2618
+ const cert = this.certificates.cert;
2619
+ const hash = crypto.createHash('sha256').update(cert).digest('hex');
2620
+
2621
+ // Format as XX:XX:XX:...
2622
+ return hash.match(/.{2}/g).join(':').toUpperCase();
2623
+ }
2624
+
2625
+ /**
2626
+ * Close the secure connection
2627
+ */
2628
+ close() {
2629
+ if (this.secureSocket) {
2630
+ this.secureSocket.destroy();
2631
+ this.secureSocket = null;
2632
+ }
2633
+ this.ready = false;
2634
+ }
2635
+
2636
+ /**
2637
+ * Check if connection is ready
2638
+ */
2639
+ isReady() {
2640
+ return this.ready;
2641
+ }
2642
+ }
2643
+
2644
+ /**
2645
+ * Simplified DTLS-like encryption for UDP
2646
+ * Uses AES-256-GCM for packet encryption
2647
+ */
2648
+ class DTLSConnection extends EventEmitter {
2649
+ constructor(options = {}) {
2650
+ super();
2651
+
2652
+ this.isServer = options.isServer || false;
2653
+ this.key = options.key || crypto.randomBytes(32); // 256-bit key
2654
+ this.remoteKey = null;
2655
+ this.ready = false;
2656
+ }
2657
+
2658
+ /**
2659
+ * Exchange keys (simplified handshake)
2660
+ * In real DTLS, this would be a full handshake
2661
+ */
2662
+ async handshake(sendCallback) {
2663
+ return new Promise((resolve, reject) => {
2664
+ // Send our key
2665
+ sendCallback(this.key);
2666
+
2667
+ // Wait for remote key
2668
+ const timeout = setTimeout(() => {
2669
+ reject(new Error('DTLS handshake timeout'));
2670
+ }, 5000);
2671
+
2672
+ this.once('remoteKey', (key) => {
2673
+ clearTimeout(timeout);
2674
+ this.remoteKey = key;
2675
+ this.ready = true;
2676
+ this.emit('ready');
2677
+ resolve();
2678
+ });
2679
+ });
2680
+ }
2681
+
2682
+ /**
2683
+ * Set remote key
2684
+ */
2685
+ setRemoteKey(key) {
2686
+ this.remoteKey = key;
2687
+ this.emit('remoteKey', key);
2688
+ }
2689
+
2690
+ /**
2691
+ * Encrypt data for transmission
2692
+ * @param {Buffer} data - Plaintext data
2693
+ * @returns {Buffer} Encrypted data with IV and auth tag
2694
+ */
2695
+ encrypt(data) {
2696
+ if (!this.ready) {
2697
+ throw new Error('DTLS not ready');
2698
+ }
2699
+
2700
+ const iv = crypto.randomBytes(12); // GCM IV
2701
+ const cipher = crypto.createCipheriv('aes-256-gcm', this.key, iv);
2702
+
2703
+ const encrypted = Buffer.concat([
2704
+ cipher.update(data),
2705
+ cipher.final()
2706
+ ]);
2707
+
2708
+ const authTag = cipher.getAuthTag();
2709
+
2710
+ // Return: IV (12) + Auth Tag (16) + Encrypted Data
2711
+ return Buffer.concat([iv, authTag, encrypted]);
2712
+ }
2713
+
2714
+ /**
2715
+ * Decrypt received data
2716
+ * @param {Buffer} data - Encrypted data with IV and auth tag
2717
+ * @returns {Buffer} Decrypted data
2718
+ */
2719
+ decrypt(data) {
2720
+ if (!this.ready || !this.remoteKey) {
2721
+ throw new Error('DTLS not ready');
2722
+ }
2723
+
2724
+ if (data.length < 28) {
2725
+ throw new Error('Invalid encrypted packet');
2726
+ }
2727
+
2728
+ const iv = data.slice(0, 12);
2729
+ const authTag = data.slice(12, 28);
2730
+ const encrypted = data.slice(28);
2731
+
2732
+ const decipher = crypto.createDecipheriv('aes-256-gcm', this.remoteKey, iv);
2733
+ decipher.setAuthTag(authTag);
2734
+
2735
+ return Buffer.concat([
2736
+ decipher.update(encrypted),
2737
+ decipher.final()
2738
+ ]);
2739
+ }
2740
+
2741
+ /**
2742
+ * Get fingerprint of our key
2743
+ */
2744
+ getFingerprint() {
2745
+ const hash = crypto.createHash('sha256').update(this.key).digest('hex');
2746
+ return hash.match(/.{2}/g).join(':').toUpperCase();
2747
+ }
2748
+ }
2749
+
2750
+ SecureConnection_1 = {
2751
+ SecureConnection,
2752
+ DTLSConnection
2753
+ };
2754
+ return SecureConnection_1;
2755
+ }
2756
+
2757
+ /**
2758
+ * UDP Transport with DTLS-like encryption
2759
+ * Alternative to TCP for lower latency
2760
+ */
2761
+
2762
+ var UDPTransport_1;
2763
+ var hasRequiredUDPTransport;
2764
+
2765
+ function requireUDPTransport () {
2766
+ if (hasRequiredUDPTransport) return UDPTransport_1;
2767
+ hasRequiredUDPTransport = 1;
2768
+ const dgram = require$$0$1;
2769
+ const { EventEmitter } = require$$0;
2770
+ const { DTLSConnection } = requireSecureConnection();
2771
+
2772
+ class UDPTransport extends EventEmitter {
2773
+ constructor(options = {}) {
2774
+ super();
2775
+
2776
+ this.socket = dgram.createSocket('udp4');
2777
+ this.localPort = options.localPort || 0;
2778
+ this.remoteAddress = null;
2779
+ this.remotePort = null;
2780
+ this.dtls = null;
2781
+ this.useEncryption = options.encrypted !== false;
2782
+ this.channels = new Map(); // label -> channel info
2783
+ this.messageBuffer = Buffer.alloc(0);
2784
+ }
2785
+
2786
+ /**
2787
+ * Start listening on local port
2788
+ */
2789
+ async listen() {
2790
+ return new Promise((resolve, reject) => {
2791
+ this.socket.on('error', (err) => {
2792
+ this.emit('error', err);
2793
+ });
2794
+
2795
+ this.socket.on('message', (msg, rinfo) => {
2796
+ this._handleMessage(msg, rinfo);
2797
+ });
2798
+
2799
+ this.socket.bind(this.localPort, () => {
2800
+ const addr = this.socket.address();
2801
+ this.localPort = addr.port;
2802
+ this.emit('listening', addr);
2803
+ resolve(addr);
2804
+ });
2805
+ });
2806
+ }
2807
+
2808
+ /**
2809
+ * Connect to remote peer
2810
+ */
2811
+ async connect(address, port) {
2812
+ this.remoteAddress = address;
2813
+ this.remotePort = port;
2814
+
2815
+ if (this.useEncryption) {
2816
+ // Initialize DTLS
2817
+ this.dtls = new DTLSConnection({ isServer: false });
2818
+
2819
+ await this.dtls.handshake((key) => {
2820
+ // Send encryption key to remote
2821
+ this._sendRaw(Buffer.concat([
2822
+ Buffer.from([0x01]), // Key exchange message
2823
+ key
2824
+ ]));
2825
+ });
2826
+
2827
+ await new Promise(resolve => {
2828
+ this.dtls.once('ready', resolve);
2829
+ });
2830
+ }
2831
+
2832
+ this.emit('connected');
2833
+ }
2834
+
2835
+ /**
2836
+ * Handle incoming message
2837
+ * @private
2838
+ */
2839
+ _handleMessage(msg, rinfo) {
2840
+ // Set remote address on first message
2841
+ if (!this.remoteAddress) {
2842
+ this.remoteAddress = rinfo.address;
2843
+ this.remotePort = rinfo.port;
2844
+ }
2845
+
2846
+ // Check if this is a key exchange message
2847
+ if (msg[0] === 0x01 && msg.length >= 33) {
2848
+ const key = msg.slice(1, 33);
2849
+
2850
+ if (!this.dtls) {
2851
+ this.dtls = new DTLSConnection({ isServer: true });
2852
+
2853
+ // Send our key back
2854
+ this._sendRaw(Buffer.concat([
2855
+ Buffer.from([0x01]),
2856
+ this.dtls.key
2857
+ ]));
2858
+ }
2859
+
2860
+ this.dtls.setRemoteKey(key);
2861
+ this.emit('connected');
2862
+ return;
2863
+ }
2864
+
2865
+ // Decrypt if encryption is enabled
2866
+ let data = msg;
2867
+ if (this.useEncryption && this.dtls && this.dtls.ready) {
2868
+ try {
2869
+ data = this.dtls.decrypt(msg);
2870
+ } catch (err) {
2871
+ console.error('Decryption failed:', err.message);
2872
+ return;
2873
+ }
2874
+ }
2875
+
2876
+ // Parse the message
2877
+ this._parseMessage(data);
2878
+ }
2879
+
2880
+ /**
2881
+ * Parse data channel message
2882
+ * @private
2883
+ */
2884
+ _parseMessage(data) {
2885
+ // Message format: <length:4><label-length:2><label><data>
2886
+ if (data.length < 6) return;
2887
+
2888
+ try {
2889
+ const totalLength = data.readUInt32BE(0);
2890
+ const labelLength = data.readUInt16BE(4);
2891
+
2892
+ if (data.length < 6 + labelLength) return;
2893
+
2894
+ const label = data.slice(6, 6 + labelLength).toString('utf8');
2895
+ const messageData = data.slice(6 + labelLength);
2896
+
2897
+ // Get or create channel
2898
+ if (!this.channels.has(label)) {
2899
+ this.emit('channel', { label });
2900
+ }
2901
+
2902
+ // Emit message event
2903
+ this.emit('message', { label, data: messageData });
2904
+
2905
+ } catch (err) {
2906
+ console.error('Failed to parse message:', err);
2907
+ }
2908
+ }
2909
+
2910
+ /**
2911
+ * Send message on a data channel
2912
+ */
2913
+ send(label, data) {
2914
+ const dataBuffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
2915
+ const labelBuffer = Buffer.from(label, 'utf8');
2916
+
2917
+ const totalLength = 2 + labelBuffer.length + dataBuffer.length;
2918
+ const message = Buffer.allocUnsafe(4 + totalLength);
2919
+
2920
+ message.writeUInt32BE(totalLength, 0);
2921
+ message.writeUInt16BE(labelBuffer.length, 4);
2922
+ labelBuffer.copy(message, 6);
2923
+ dataBuffer.copy(message, 6 + labelBuffer.length);
2924
+
2925
+ this._sendRaw(message);
2926
+ }
2927
+
2928
+ /**
2929
+ * Send raw data (with optional encryption)
2930
+ * @private
2931
+ */
2932
+ _sendRaw(data) {
2933
+ if (!this.remoteAddress || !this.remotePort) {
2934
+ throw new Error('Remote address not set');
2935
+ }
2936
+
2937
+ let sendData = data;
2938
+
2939
+ // Encrypt if encryption is enabled
2940
+ if (this.useEncryption && this.dtls && this.dtls.ready) {
2941
+ sendData = this.dtls.encrypt(data);
2942
+ }
2943
+
2944
+ this.socket.send(sendData, this.remotePort, this.remoteAddress, (err) => {
2945
+ if (err) {
2946
+ this.emit('error', err);
2947
+ }
2948
+ });
2949
+ }
2950
+
2951
+ /**
2952
+ * Create a data channel
2953
+ */
2954
+ createChannel(label) {
2955
+ if (!this.channels.has(label)) {
2956
+ this.channels.set(label, { label, created: Date.now() });
2957
+
2958
+ // Announce channel to remote peer
2959
+ this.send(label, Buffer.alloc(0));
2960
+ }
2961
+ }
2962
+
2963
+ /**
2964
+ * Get local address
2965
+ */
2966
+ address() {
2967
+ return this.socket.address();
2968
+ }
2969
+
2970
+ /**
2971
+ * Get fingerprint (for SDP)
2972
+ */
2973
+ getFingerprint() {
2974
+ if (this.dtls) {
2975
+ return this.dtls.getFingerprint();
2976
+ }
2977
+ return 'NO:EN:CR:YP:TI:ON';
2978
+ }
2979
+
2980
+ /**
2981
+ * Close the transport
2982
+ */
2983
+ close() {
2984
+ if (this.dtls) {
2985
+ this.dtls.removeAllListeners();
2986
+ this.dtls = null;
2987
+ }
2988
+
2989
+ if (this.socket) {
2990
+ this.socket.close();
2991
+ this.socket = null;
2992
+ }
2993
+
2994
+ this.channels.clear();
2995
+ }
2996
+ }
2997
+
2998
+ UDPTransport_1 = UDPTransport;
2999
+ return UDPTransport_1;
3000
+ }
3001
+
3002
+ var NativePeerConnectionFactory_1;
3003
+ var hasRequiredNativePeerConnectionFactory;
3004
+
3005
+ function requireNativePeerConnectionFactory () {
3006
+ if (hasRequiredNativePeerConnectionFactory) return NativePeerConnectionFactory_1;
3007
+ hasRequiredNativePeerConnectionFactory = 1;
3008
+ const EventEmitter = require$$0;
3009
+ const net = require$$1$1;
3010
+ const os = require$$0$2;
3011
+ requireSTUNClient();
3012
+ const ICEGatherer = requireICEGatherer();
3013
+ const { SecureConnection } = requireSecureConnection();
3014
+ requireUDPTransport();
3015
+
3016
+ /**
3017
+ * NativePeerConnectionFactory creates native peer connection instances.
3018
+ * Real implementation using Node.js net package for peer-to-peer communication.
3019
+ */
3020
+ class NativePeerConnectionFactory {
3021
+ constructor() {
3022
+ this._initialized = false;
3023
+ this._peerConnections = new Set();
3024
+ }
3025
+
3026
+ /**
3027
+ * Initialize the factory
3028
+ */
3029
+ initialize() {
3030
+ if (this._initialized) {
3031
+ return;
3032
+ }
3033
+
3034
+ console.log('[NativePeerConnectionFactory] Initializing with Node.js net package');
3035
+ this._initialized = true;
3036
+ }
3037
+
3038
+ /**
3039
+ * Create a native peer connection
3040
+ * @param {Object} configuration - RTCConfiguration
3041
+ * @returns {NativePeerConnection}
3042
+ */
3043
+ createPeerConnection(configuration) {
3044
+ if (!this._initialized) {
3045
+ this.initialize();
3046
+ }
3047
+
3048
+ const nativePC = new NativePeerConnection(configuration);
3049
+ this._peerConnections.add(nativePC);
3050
+
3051
+ nativePC.on('close', () => {
3052
+ this._peerConnections.delete(nativePC);
3053
+ });
3054
+
3055
+ return nativePC;
3056
+ }
3057
+
3058
+ /**
3059
+ * Dispose of the factory and all peer connections
3060
+ */
3061
+ dispose() {
3062
+ for (const pc of this._peerConnections) {
3063
+ pc.close();
3064
+ }
3065
+ this._peerConnections.clear();
3066
+ this._initialized = false;
3067
+ }
3068
+ }
3069
+
3070
+ /**
3071
+ * NativePeerConnection - Real implementation using Node.js net package
3072
+ * Implements peer-to-peer connection using TCP for data channels
3073
+ */
3074
+ class NativePeerConnection extends EventEmitter {
3075
+ constructor(configuration) {
3076
+ super();
3077
+ this._configuration = configuration || {};
3078
+ this._signalingState = 0; // stable
3079
+ this._iceConnectionState = 0; // new
3080
+ this._iceGatheringState = 0; // new
3081
+ this._connectionState = 0; // new
3082
+ this._localDescription = null;
3083
+ this._remoteDescription = null;
3084
+ this._dataChannels = new Map();
3085
+ this._closed = false;
3086
+
3087
+ // Networking components
3088
+ this._server = null;
3089
+ this._socket = null;
3090
+ this._localAddress = null;
3091
+ this._localPort = null;
3092
+ this._remoteAddress = null;
3093
+ this._remotePort = null;
3094
+ this._isOfferer = false;
3095
+ this._iceUsername = this._generateRandomString(8);
3096
+ this._icePassword = this._generateRandomString(24);
3097
+ this._fingerprint = this._generateFingerprint();
3098
+
3099
+ // New features
3100
+ this._useEncryption = this._configuration.encryption === true; // Disabled by default
3101
+ this._useUDP = this._configuration.transport === 'udp';
3102
+ this._iceGatherer = new ICEGatherer({
3103
+ stunServers: this._extractSTUNServers(this._configuration)
3104
+ });
3105
+ this._secureConnection = null;
3106
+ this._udpTransport = null;
3107
+ this._iceCandidates = [];
3108
+
3109
+ console.log('[NativePeerConnection] Created with STUN/ICE/Encryption support');
3110
+ console.log(` - Encryption: ${this._useEncryption ? 'enabled' : 'disabled (requires valid certs)'}`);
3111
+ console.log(` - Transport: ${this._useUDP ? 'UDP' : 'TCP'}`);
3112
+ }
3113
+
3114
+ /**
3115
+ * Extract STUN servers from configuration
3116
+ * @private
3117
+ */
3118
+ _extractSTUNServers(config) {
3119
+ const stunServers = [];
3120
+
3121
+ if (config.iceServers) {
3122
+ for (const server of config.iceServers) {
3123
+ if (server.urls) {
3124
+ const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
3125
+ for (const url of urls) {
3126
+ if (url.startsWith('stun:')) {
3127
+ stunServers.push(url.replace('stun:', ''));
3128
+ }
3129
+ }
3130
+ }
3131
+ }
3132
+ }
3133
+
3134
+ return stunServers.length > 0 ? stunServers : undefined;
3135
+ }
3136
+
3137
+ /**
3138
+ * Create an offer
3139
+ * @param {Object} options
3140
+ * @returns {Promise<{type: string, sdp: string}>}
3141
+ */
3142
+ async createOffer(options) {
3143
+ if (this._closed) {
3144
+ throw new Error('PeerConnection is closed');
3145
+ }
3146
+
3147
+ this._isOfferer = true;
3148
+
3149
+ // Create TCP server to listen for incoming connections
3150
+ await this._createServer();
3151
+
3152
+ // Generate real SDP with actual network information
3153
+ const sdp = this._generateSDP('offer');
3154
+ return { type: 'offer', sdp };
3155
+ }
3156
+
3157
+ /**
3158
+ * Create an answer
3159
+ * @param {Object} options
3160
+ * @returns {Promise<{type: string, sdp: string}>}
3161
+ */
3162
+ async createAnswer(options) {
3163
+ if (this._closed) {
3164
+ throw new Error('PeerConnection is closed');
3165
+ }
3166
+
3167
+ if (!this._remoteDescription) {
3168
+ throw new Error('No remote description set');
3169
+ }
3170
+
3171
+ this._isOfferer = false;
3172
+
3173
+ // Create TCP server to listen for incoming connections (if needed)
3174
+ if (!this._server) {
3175
+ await this._createServer();
3176
+ }
3177
+
3178
+ // Generate real SDP with actual network information
3179
+ const sdp = this._generateSDP('answer');
3180
+ return { type: 'answer', sdp };
3181
+ }
3182
+
3183
+ /**
3184
+ * Set local description
3185
+ * @param {Object} description
3186
+ * @returns {Promise<void>}
3187
+ */
3188
+ async setLocalDescription(description) {
3189
+ if (this._closed) {
3190
+ throw new Error('PeerConnection is closed');
3191
+ }
3192
+
3193
+ this._localDescription = description;
3194
+
3195
+ // Update signaling state
3196
+ if (description.type === 'offer') {
3197
+ this._signalingState = 1; // have-local-offer
3198
+ this.emit('signalingstatechange', this._signalingState);
3199
+ } else if (description.type === 'answer') {
3200
+ this._signalingState = 0; // stable
3201
+ this.emit('signalingstatechange', this._signalingState);
3202
+
3203
+ // If we're answerer and have remote description, connect
3204
+ if (!this._isOfferer && this._remoteDescription) {
3205
+ await this._connectToPeer();
3206
+ }
3207
+ }
3208
+
3209
+ // Start real ICE gathering
3210
+ this._startIceGathering();
3211
+ }
3212
+
3213
+ /**
3214
+ * Set remote description
3215
+ * @param {Object} description
3216
+ * @returns {Promise<void>}
3217
+ */
3218
+ async setRemoteDescription(description) {
3219
+ if (this._closed) {
3220
+ throw new Error('PeerConnection is closed');
3221
+ }
3222
+
3223
+ // Parse remote SDP to extract connection information
3224
+ this._parseSDP(description.sdp);
3225
+ this._remoteDescription = description;
3226
+
3227
+ // Update signaling state
3228
+ if (description.type === 'offer') {
3229
+ this._signalingState = 2; // have-remote-offer
3230
+ this.emit('signalingstatechange', this._signalingState);
3231
+ } else if (description.type === 'answer') {
3232
+ this._signalingState = 0; // stable
3233
+ this.emit('signalingstatechange', this._signalingState);
3234
+
3235
+ // If we're offerer and have local description, connect
3236
+ if (this._isOfferer && this._localDescription) {
3237
+ await this._connectToPeer();
3238
+ }
3239
+ }
3240
+ }
3241
+
3242
+ /**
3243
+ * Add ICE candidate
3244
+ * @param {Object} candidate
3245
+ * @returns {Promise<void>}
3246
+ */
3247
+ async addIceCandidate(candidate) {
3248
+ if (this._closed) {
3249
+ throw new Error('PeerConnection is closed');
3250
+ }
3251
+
3252
+ if (!candidate || !candidate.candidate) {
3253
+ return;
3254
+ }
3255
+
3256
+ // Parse ICE candidate to extract address and port
3257
+ const parts = candidate.candidate.split(' ');
3258
+ if (parts.length >= 6) {
3259
+ this._remoteAddress = parts[4];
3260
+ this._remotePort = parseInt(parts[5], 10);
3261
+ console.log(`[NativePeerConnection] Remote address: ${this._remoteAddress}:${this._remotePort}`);
3262
+
3263
+ // If we have both local and remote info, and we're offerer, connect
3264
+ if (this._isOfferer && this._localAddress && this._remoteAddress &&
3265
+ this._signalingState === 0 && !this._socket) {
3266
+ await this._connectToPeer();
3267
+ }
3268
+ }
3269
+ }
3270
+
3271
+ /**
3272
+ * Create a data channel
3273
+ * @param {string} label
3274
+ * @param {Object} options
3275
+ * @returns {NativeDataChannel}
3276
+ */
3277
+ createDataChannel(label, options) {
3278
+ if (this._closed) {
3279
+ throw new Error('PeerConnection is closed');
3280
+ }
3281
+
3282
+ const channel = new NativeDataChannel(label, options, this);
3283
+ this._dataChannels.set(label, channel);
3284
+
3285
+ // If socket is already connected, open the channel
3286
+ if (this._socket && this._socket.writable) {
3287
+ setTimeout(() => channel._setConnected(this._socket), 0);
3288
+ }
3289
+
3290
+ // Emit negotiation needed
3291
+ setTimeout(() => {
3292
+ this.emit('negotiationneeded');
3293
+ }, 0);
3294
+
3295
+ return channel;
3296
+ }
3297
+
3298
+ /**
3299
+ * Set configuration
3300
+ * @param {Object} configuration
3301
+ */
3302
+ setConfiguration(configuration) {
3303
+ this._configuration = configuration;
3304
+ }
3305
+
3306
+ /**
3307
+ * Get stats
3308
+ * @returns {Promise<Object>}
3309
+ */
3310
+ async getStats() {
3311
+ return {
3312
+ type: 'peer-connection',
3313
+ timestamp: Date.now(),
3314
+ // Mock stats
3315
+ };
3316
+ }
3317
+
3318
+ /**
3319
+ * Close the peer connection
3320
+ */
3321
+ close() {
3322
+ if (this._closed) {
3323
+ return;
3324
+ }
3325
+
3326
+ this._closed = true;
3327
+ this._signalingState = 5; // closed
3328
+
3329
+ // Close all data channels
3330
+ for (const [label, channel] of this._dataChannels) {
3331
+ channel.close();
3332
+ }
3333
+ this._dataChannels.clear();
3334
+
3335
+ // Close secure connection
3336
+ if (this._secureConnection) {
3337
+ this._secureConnection.close();
3338
+ this._secureConnection = null;
3339
+ }
3340
+
3341
+ // Close UDP transport
3342
+ if (this._udpTransport) {
3343
+ this._udpTransport.close();
3344
+ this._udpTransport = null;
3345
+ }
3346
+
3347
+ // Close socket
3348
+ if (this._socket) {
3349
+ this._socket.destroy();
3350
+ this._socket = null;
3351
+ }
3352
+
3353
+ // Close server
3354
+ if (this._server) {
3355
+ this._server.close();
3356
+ this._server = null;
3357
+ }
3358
+
3359
+ this.emit('signalingstatechange', this._signalingState);
3360
+ this.emit('close');
3361
+ }
3362
+
3363
+ /**
3364
+ * Create TCP server to listen for incoming connections
3365
+ * @private
3366
+ */
3367
+ async _createServer() {
3368
+ return new Promise((resolve, reject) => {
3369
+ this._server = net.createServer((socket) => {
3370
+ console.log('[NativePeerConnection] Incoming connection accepted');
3371
+ this._handleIncomingConnection(socket);
3372
+ });
3373
+
3374
+ this._server.listen(0, '0.0.0.0', () => {
3375
+ const address = this._server.address();
3376
+ this._localPort = address.port;
3377
+ this._localAddress = this._getLocalIPAddress();
3378
+ console.log(`[NativePeerConnection] Server listening on ${this._localAddress}:${this._localPort}`);
3379
+ resolve();
3380
+ });
3381
+
3382
+ this._server.on('error', (err) => {
3383
+ console.error('[NativePeerConnection] Server error:', err);
3384
+ reject(err);
3385
+ });
3386
+ });
3387
+ }
3388
+
3389
+ /**
3390
+ * Connect to remote peer
3391
+ * @private
3392
+ */
3393
+ async _connectToPeer() {
3394
+ if (!this._remoteAddress || !this._remotePort) {
3395
+ console.log('[NativePeerConnection] Cannot connect: missing remote address');
3396
+ return;
3397
+ }
3398
+
3399
+ if (this._socket) {
3400
+ console.log('[NativePeerConnection] Already connected');
3401
+ return;
3402
+ }
3403
+
3404
+ // Tie-breaking: only connect if our port is higher than remote port
3405
+ // This ensures only one peer connects, avoiding the race condition
3406
+ if (this._localPort < this._remotePort) {
3407
+ console.log(`[NativePeerConnection] Not connecting (local port ${this._localPort} < remote port ${this._remotePort}), waiting for incoming`);
3408
+ return;
3409
+ }
3410
+
3411
+ console.log(`[NativePeerConnection] Connecting to ${this._remoteAddress}:${this._remotePort}`);
3412
+
3413
+ this._socket = new net.Socket();
3414
+
3415
+ this._socket.connect(this._remotePort, this._remoteAddress, async () => {
3416
+ console.log('[NativePeerConnection] Connected to peer');
3417
+
3418
+ // Apply TLS encryption if enabled
3419
+ if (this._useEncryption) {
3420
+ await this._wrapWithEncryption(false);
3421
+ }
3422
+
3423
+ this._iceConnectionState = 2; // connected
3424
+ this.emit('iceconnectionstatechange', this._iceConnectionState);
3425
+
3426
+ // Announce existing data channels to peer and open them
3427
+ setImmediate(() => {
3428
+ for (const [label, channel] of this._dataChannels) {
3429
+ this._sendChannelAnnouncement(label);
3430
+ channel._setConnected(this._getActiveSocket());
3431
+ }
3432
+ });
3433
+ });
3434
+
3435
+ this._setupSocketHandlers(this._socket);
3436
+ }
3437
+
3438
+ /**
3439
+ * Handle incoming connection from peer
3440
+ * @private
3441
+ */
3442
+ async _handleIncomingConnection(socket) {
3443
+ if (this._socket) {
3444
+ console.log('[NativePeerConnection] Connection already exists, closing new one');
3445
+ socket.destroy();
3446
+ return;
3447
+ }
3448
+
3449
+ console.log('[NativePeerConnection] Accepted connection from peer');
3450
+
3451
+ // Setup handlers BEFORE storing socket to avoid missing early data
3452
+ this._setupSocketHandlers(socket);
3453
+
3454
+ this._socket = socket;
3455
+
3456
+ // Apply TLS encryption if enabled
3457
+ if (this._useEncryption) {
3458
+ await this._wrapWithEncryption(true);
3459
+ }
3460
+
3461
+ this._iceConnectionState = 2; // connected
3462
+ this.emit('iceconnectionstatechange', this._iceConnectionState);
3463
+
3464
+ // Announce existing data channels to peer
3465
+ setImmediate(() => {
3466
+ for (const [label, channel] of this._dataChannels) {
3467
+ this._sendChannelAnnouncement(label);
3468
+ channel._setConnected(this._getActiveSocket());
3469
+ }
3470
+ });
3471
+ }
3472
+
3473
+ /**
3474
+ * Wrap socket with TLS encryption
3475
+ * @private
3476
+ */
3477
+ async _wrapWithEncryption(isServer) {
3478
+ try {
3479
+ console.log(`[NativePeerConnection] Enabling TLS encryption (${isServer ? 'server' : 'client'})`);
3480
+
3481
+ this._secureConnection = new SecureConnection(this._socket, { isServer });
3482
+ await this._secureConnection.wrap();
3483
+
3484
+ // Update fingerprint with real certificate
3485
+ this._fingerprint = this._secureConnection.getFingerprint();
3486
+
3487
+ console.log('[NativePeerConnection] TLS encryption enabled');
3488
+ } catch (err) {
3489
+ console.error('[NativePeerConnection] Failed to enable encryption:', err);
3490
+ throw err;
3491
+ }
3492
+ }
3493
+
3494
+ /**
3495
+ * Get the active socket (secure or plain)
3496
+ * @private
3497
+ */
3498
+ _getActiveSocket() {
3499
+ return this._secureConnection?.secureSocket || this._socket;
3500
+ }
3501
+
3502
+ /**
3503
+ * Setup socket event handlers
3504
+ * @private
3505
+ */
3506
+ _setupSocketHandlers(socket) {
3507
+ socket.on('data', (data) => {
3508
+ this._handleIncomingData(data);
3509
+ });
3510
+
3511
+ socket.on('close', () => {
3512
+ console.log('[NativePeerConnection] Connection closed');
3513
+ this._iceConnectionState = 6; // closed
3514
+ this.emit('iceconnectionstatechange', this._iceConnectionState);
3515
+
3516
+ // Close all data channels
3517
+ for (const [label, channel] of this._dataChannels) {
3518
+ channel._handleDisconnect();
3519
+ }
3520
+ });
3521
+
3522
+ socket.on('error', (err) => {
3523
+ console.error('[NativePeerConnection] Socket error:', err);
3524
+ this._iceConnectionState = 4; // failed
3525
+ this.emit('iceconnectionstatechange', this._iceConnectionState);
3526
+ });
3527
+ }
3528
+
3529
+ /**
3530
+ * Send channel announcement to peer (empty message to trigger remote channel creation)
3531
+ * @private
3532
+ */
3533
+ _sendChannelAnnouncement(label) {
3534
+ if (!this._socket) {
3535
+ console.log(`[NativePeerConnection] Cannot announce ${label}: no socket`);
3536
+ return;
3537
+ }
3538
+
3539
+ const labelBuffer = Buffer.from(label, 'utf8');
3540
+ const labelLength = labelBuffer.length;
3541
+ const emptyData = Buffer.alloc(0);
3542
+
3543
+ // Message format: <length:4><label-length:2><label><data>
3544
+ // length is the number of bytes after the length field
3545
+ const totalLength = 2 + labelLength + emptyData.length;
3546
+ const buffer = Buffer.allocUnsafe(4 + totalLength);
3547
+
3548
+ buffer.writeUInt32BE(totalLength, 0);
3549
+ buffer.writeUInt16BE(labelLength, 4);
3550
+ labelBuffer.copy(buffer, 6);
3551
+
3552
+ this._socket.write(buffer);
3553
+ console.log(`[NativePeerConnection] Announced channel: ${label}`);
3554
+ }
3555
+
3556
+ /**
3557
+ * Handle incoming data from socket
3558
+ * @private
3559
+ */
3560
+ _handleIncomingData(data) {
3561
+ try {
3562
+ // Parse message format: <length:4><channel-label-length:2><channel-label><data>
3563
+ let offset = 0;
3564
+
3565
+ while (offset < data.length) {
3566
+ if (offset + 6 > data.length) break;
3567
+
3568
+ const totalLength = data.readUInt32BE(offset);
3569
+ const labelLength = data.readUInt16BE(offset + 4);
3570
+
3571
+ if (offset + 6 + labelLength + (totalLength - 2 - labelLength) > data.length) break;
3572
+
3573
+ const label = data.toString('utf8', offset + 6, offset + 6 + labelLength);
3574
+ const messageData = data.slice(offset + 6 + labelLength, offset + 6 + totalLength - 2);
3575
+
3576
+ // Find or create the data channel
3577
+ let channel = this._dataChannels.get(label);
3578
+ if (!channel) {
3579
+ // Create remote data channel
3580
+ console.log(`[NativePeerConnection] Creating remote channel: ${label}`);
3581
+ channel = new NativeDataChannel(label, {}, this);
3582
+ this._dataChannels.set(label, channel);
3583
+
3584
+ // Emit datachannel event first (before setting connected)
3585
+ this.emit('datachannel', channel);
3586
+
3587
+ // Then set it as connected after a small delay to allow event handlers to be set up
3588
+ setImmediate(() => {
3589
+ channel._setConnected(this._socket);
3590
+ });
3591
+ }
3592
+
3593
+ // Deliver the message (if it has data - announcements are empty)
3594
+ if (messageData.length > 0) {
3595
+ channel._handleIncomingMessage(messageData);
3596
+ }
3597
+
3598
+ offset += 6 + totalLength - 2;
3599
+ }
3600
+ } catch (err) {
3601
+ console.error('[NativePeerConnection] Error parsing incoming data:', err);
3602
+ }
3603
+ }
3604
+
3605
+ /**
3606
+ * Generate real SDP with actual network information
3607
+ * @private
3608
+ */
3609
+ _generateSDP(type) {
3610
+ const sessionId = Date.now();
3611
+ const address = this._localAddress || '0.0.0.0';
3612
+ const port = this._localPort || 9;
3613
+
3614
+ return `v=0
3615
+ o=- ${sessionId} 2 IN IP4 ${address}
3616
+ s=-
3617
+ t=0 0
3618
+ a=group:BUNDLE data
3619
+ a=msid-semantic: WMS
3620
+ m=application ${port} TCP/DTLS/SCTP webrtc-datachannel
3621
+ c=IN IP4 ${address}
3622
+ a=ice-ufrag:${this._iceUsername}
3623
+ a=ice-pwd:${this._icePassword}
3624
+ a=ice-options:trickle
3625
+ a=fingerprint:sha-256 ${this._fingerprint}
3626
+ a=setup:actpass
3627
+ a=mid:data
3628
+ a=sctp-port:5000
3629
+ a=max-message-size:262144
3630
+ `;
3631
+ }
3632
+
3633
+ /**
3634
+ * Parse SDP to extract connection information
3635
+ * @private
3636
+ */
3637
+ _parseSDP(sdp) {
3638
+ const lines = sdp.split('\n');
3639
+
3640
+ for (const line of lines) {
3641
+ // Parse connection line: c=IN IP4 <address>
3642
+ if (line.startsWith('c=IN IP4 ')) {
3643
+ const address = line.substring(9).trim();
3644
+ if (address !== '0.0.0.0') {
3645
+ this._remoteAddress = address;
3646
+ }
3647
+ }
3648
+
3649
+ // Parse media line: m=application <port> ...
3650
+ if (line.startsWith('m=application ')) {
3651
+ const parts = line.split(' ');
3652
+ if (parts.length >= 2) {
3653
+ const port = parseInt(parts[1], 10);
3654
+ if (port > 0 && port !== 9) {
3655
+ this._remotePort = port;
3656
+ }
3657
+ }
3658
+ }
3659
+ }
3660
+
3661
+ console.log(`[NativePeerConnection] Parsed SDP - Remote: ${this._remoteAddress}:${this._remotePort}`);
3662
+ }
3663
+
3664
+ /**
3665
+ * Get local IP address
3666
+ * @private
3667
+ */
3668
+ _getLocalIPAddress() {
3669
+ const interfaces = os.networkInterfaces();
3670
+
3671
+ // Try to find a non-internal IPv4 address
3672
+ for (const name of Object.keys(interfaces)) {
3673
+ for (const iface of interfaces[name]) {
3674
+ if (iface.family === 'IPv4' && !iface.internal) {
3675
+ return iface.address;
3676
+ }
3677
+ }
3678
+ }
3679
+
3680
+ // Fallback to localhost
3681
+ return '127.0.0.1';
3682
+ }
3683
+
3684
+ /**
3685
+ * Start ICE gathering with real network addresses
3686
+ * @private
3687
+ */
3688
+ /**
3689
+ * Start ICE candidate gathering with STUN support
3690
+ * @private
3691
+ */
3692
+ async _startIceGathering() {
3693
+ this._iceGatheringState = 1; // gathering
3694
+ this.emit('icegatheringstatechange', this._iceGatheringState);
3695
+
3696
+ try {
3697
+ // Use real ICE gatherer to get host and srflx candidates
3698
+ const candidates = await this._iceGatherer.gatherCandidates(this._localPort);
3699
+
3700
+ console.log(`[NativePeerConnection] Gathered ${candidates.length} ICE candidates`);
3701
+
3702
+ // Emit each candidate
3703
+ for (const candidate of candidates) {
3704
+ this._iceCandidates.push(candidate);
3705
+ this.emit('icecandidate', {
3706
+ candidate: candidate.candidate,
3707
+ sdpMid: candidate.sdpMid,
3708
+ sdpMLineIndex: candidate.sdpMLineIndex
3709
+ });
3710
+
3711
+ console.log(`[NativePeerConnection] ICE candidate (${candidate.type}): ${candidate.ip}:${candidate.port}`);
3712
+ }
3713
+ } catch (err) {
3714
+ console.warn('[NativePeerConnection] ICE gathering error:', err.message);
3715
+
3716
+ // Fallback: emit only local host candidate
3717
+ if (this._localAddress && this._localPort) {
3718
+ const candidate = {
3719
+ candidate: `candidate:1 1 tcp 2130706431 ${this._localAddress} ${this._localPort} typ host`,
3720
+ sdpMid: 'data',
3721
+ sdpMLineIndex: 0
3722
+ };
3723
+ this.emit('icecandidate', candidate);
3724
+ console.log(`[NativePeerConnection] Fallback ICE candidate: ${this._localAddress}:${this._localPort}`);
3725
+ }
3726
+ } finally {
3727
+ // Complete gathering
3728
+ setTimeout(() => {
3729
+ this._iceGatheringState = 2; // complete
3730
+ this.emit('icegatheringstatechange', this._iceGatheringState);
3731
+ this.emit('icecandidate', null); // End of candidates
3732
+ console.log('[NativePeerConnection] ICE gathering complete');
3733
+ }, 100);
3734
+ }
3735
+ }
3736
+
3737
+ /**
3738
+ * Generate random string
3739
+ * @private
3740
+ */
3741
+ _generateRandomString(length) {
3742
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
3743
+ let result = '';
3744
+ for (let i = 0; i < length; i++) {
3745
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
3746
+ }
3747
+ return result;
3748
+ }
3749
+
3750
+ /**
3751
+ * Generate fingerprint
3752
+ * @private
3753
+ */
3754
+ _generateFingerprint() {
3755
+ const bytes = [];
3756
+ for (let i = 0; i < 32; i++) {
3757
+ bytes.push(Math.floor(Math.random() * 256).toString(16).padStart(2, '0').toUpperCase());
3758
+ }
3759
+ return bytes.join(':');
3760
+ }
3761
+ }
3762
+
3763
+ /**
3764
+ * NativeDataChannel - Real implementation using TCP socket
3765
+ */
3766
+ class NativeDataChannel extends EventEmitter {
3767
+ constructor(label, options = {}, peerConnection) {
3768
+ super();
3769
+ this.label = label;
3770
+ this.ordered = options.ordered !== undefined ? options.ordered : true;
3771
+ this.maxPacketLifeTime = options.maxPacketLifeTime || -1;
3772
+ this.maxRetransmits = options.maxRetransmits || -1;
3773
+ this.protocol = options.protocol || '';
3774
+ this.negotiated = options.negotiated || false;
3775
+ this.id = options.id !== undefined ? options.id : -1;
3776
+
3777
+ this._state = 0; // connecting
3778
+ this._bufferedAmount = 0;
3779
+ this._closed = false;
3780
+ this._socket = null;
3781
+ this._peerConnection = peerConnection;
3782
+
3783
+ console.log(`[NativeDataChannel] Created: ${label}`);
3784
+ }
3785
+
3786
+ /**
3787
+ * Set socket connection for this channel
3788
+ * @private
3789
+ */
3790
+ _setConnected(socket) {
3791
+ if (this._closed) {
3792
+ return;
3793
+ }
3794
+
3795
+ this._socket = socket;
3796
+ this._state = 1; // open
3797
+ this.emit('statechange', this._state);
3798
+ console.log(`[NativeDataChannel] ${this.label} - Channel opened`);
3799
+ }
3800
+
3801
+ /**
3802
+ * Handle incoming message for this channel
3803
+ * @private
3804
+ */
3805
+ _handleIncomingMessage(data) {
3806
+ if (this._state !== 1) {
3807
+ return;
3808
+ }
3809
+
3810
+ // Check if data is binary or text
3811
+ let binary = true;
3812
+ try {
3813
+ // Try to detect if it's valid UTF-8 text
3814
+ const text = data.toString('utf8');
3815
+ if (text.length > 0 && /^[\x20-\x7E\s]*$/.test(text)) {
3816
+ binary = false;
3817
+ }
3818
+ } catch (e) {
3819
+ binary = true;
3820
+ }
3821
+
3822
+ this.emit('message', { data, binary });
3823
+ }
3824
+
3825
+ /**
3826
+ * Handle socket disconnect
3827
+ * @private
3828
+ */
3829
+ _handleDisconnect() {
3830
+ if (this._closed) {
3831
+ return;
3832
+ }
3833
+
3834
+ this._socket = null;
3835
+ this._state = 3; // closed
3836
+ this.emit('statechange', this._state);
3837
+ }
3838
+
3839
+ /**
3840
+ * Send data over the channel
3841
+ * @param {Object} dataBuffer - { data: Buffer, binary: boolean }
3842
+ */
3843
+ send(dataBuffer) {
3844
+ if (this._state !== 1) {
3845
+ throw new Error('DataChannel is not open');
3846
+ }
3847
+
3848
+ if (!this._socket || !this._socket.writable) {
3849
+ throw new Error('Socket is not writable');
3850
+ }
3851
+
3852
+ try {
3853
+ const data = dataBuffer.data;
3854
+ const labelBuffer = Buffer.from(this.label, 'utf8');
3855
+
3856
+ // Message format: <length:4><label-length:2><label><data>
3857
+ // Length includes label-length field + label + data
3858
+ const totalLength = 2 + labelBuffer.length + data.length;
3859
+ const header = Buffer.allocUnsafe(6);
3860
+ header.writeUInt32BE(totalLength, 0);
3861
+ header.writeUInt16BE(labelBuffer.length, 4);
3862
+
3863
+ const message = Buffer.concat([header, labelBuffer, data]);
3864
+
3865
+ this._bufferedAmount += message.length;
3866
+ this._socket.write(message, () => {
3867
+ this._bufferedAmount = Math.max(0, this._bufferedAmount - message.length);
3868
+ if (this._bufferedAmount === 0) {
3869
+ this.emit('bufferedamountlow', 0);
3870
+ }
3871
+ });
3872
+
3873
+ console.log(`[NativeDataChannel] ${this.label} - Sent ${data.length} bytes`);
3874
+ } catch (err) {
3875
+ console.error(`[NativeDataChannel] ${this.label} - Send error:`, err);
3876
+ throw err;
3877
+ }
3878
+ }
3879
+
3880
+ /**
3881
+ * Close the channel
3882
+ */
3883
+ close() {
3884
+ if (this._closed) {
3885
+ return;
3886
+ }
3887
+
3888
+ this._closed = true;
3889
+ this._state = 2; // closing
3890
+ this.emit('statechange', this._state);
3891
+
3892
+ setTimeout(() => {
3893
+ this._state = 3; // closed
3894
+ this._socket = null;
3895
+ this.emit('statechange', this._state);
3896
+ }, 10);
3897
+ }
3898
+ }
3899
+
3900
+ NativePeerConnectionFactory_1 = NativePeerConnectionFactory;
3901
+ return NativePeerConnectionFactory_1;
3902
+ }
3903
+
1
3904
  /**
2
3905
  * NodeRTC - DataChannel-only WebRTC implementation for Node.js
3
3906
  * Ported from Chromium's PeerConnection implementation
@@ -6,54 +3909,67 @@
6
3909
  * It does not include media stream support (audio/video).
7
3910
  */
8
3911
 
9
- const RTCPeerConnection = require('./RTCPeerConnection');
10
- const RTCDataChannel = require('./RTCDataChannel');
11
- const RTCSessionDescription = require('./RTCSessionDescription');
12
- const RTCIceCandidate = require('./RTCIceCandidate');
13
- const RTCDataChannelEvent = require('./RTCDataChannelEvent');
14
- const RTCPeerConnectionIceEvent = require('./RTCPeerConnectionIceEvent');
15
- const { RTCError, RTCErrorEvent } = require('./RTCError');
16
- const NativePeerConnectionFactory = require('./NativePeerConnectionFactory');
17
-
18
- // Create a singleton factory instance
19
- const defaultFactory = new NativePeerConnectionFactory();
20
-
21
- /**
22
- * Create a new RTCPeerConnection with the default factory
23
- * @param {Object} configuration - RTCConfiguration
24
- * @returns {RTCPeerConnection}
25
- */
26
- function createPeerConnection(configuration) {
27
- return new RTCPeerConnection(configuration, defaultFactory);
28
- }
29
-
30
- /**
31
- * Create a custom RTCPeerConnection with a specific factory
32
- * @param {Object} configuration - RTCConfiguration
33
- * @param {NativePeerConnectionFactory} factory - Custom factory
34
- * @returns {RTCPeerConnection}
35
- */
36
- function createPeerConnectionWithFactory(configuration, factory) {
37
- return new RTCPeerConnection(configuration, factory);
38
- }
39
-
40
- module.exports = {
41
- // Main API
42
- RTCPeerConnection,
43
- createPeerConnection,
44
- createPeerConnectionWithFactory,
45
-
46
- // Classes
47
- RTCDataChannel,
48
- RTCSessionDescription,
49
- RTCIceCandidate,
50
- RTCDataChannelEvent,
51
- RTCPeerConnectionIceEvent,
52
- RTCError,
53
- RTCErrorEvent,
54
-
55
- // Factory
56
- NativePeerConnectionFactory,
57
- defaultFactory
58
- };
3912
+ var src;
3913
+ var hasRequiredSrc;
3914
+
3915
+ function requireSrc () {
3916
+ if (hasRequiredSrc) return src;
3917
+ hasRequiredSrc = 1;
3918
+ const RTCPeerConnection = requireRTCPeerConnection();
3919
+ const RTCDataChannel = requireRTCDataChannel();
3920
+ const RTCSessionDescription = requireRTCSessionDescription();
3921
+ const RTCIceCandidate = requireRTCIceCandidate();
3922
+ const RTCDataChannelEvent = requireRTCDataChannelEvent();
3923
+ const RTCPeerConnectionIceEvent = requireRTCPeerConnectionIceEvent();
3924
+ const { RTCError, RTCErrorEvent } = requireRTCError();
3925
+ const NativePeerConnectionFactory = requireNativePeerConnectionFactory();
3926
+
3927
+ // Create a singleton factory instance
3928
+ const defaultFactory = new NativePeerConnectionFactory();
3929
+
3930
+ /**
3931
+ * Create a new RTCPeerConnection with the default factory
3932
+ * @param {Object} configuration - RTCConfiguration
3933
+ * @returns {RTCPeerConnection}
3934
+ */
3935
+ function createPeerConnection(configuration) {
3936
+ return new RTCPeerConnection(configuration, defaultFactory);
3937
+ }
3938
+
3939
+ /**
3940
+ * Create a custom RTCPeerConnection with a specific factory
3941
+ * @param {Object} configuration - RTCConfiguration
3942
+ * @param {NativePeerConnectionFactory} factory - Custom factory
3943
+ * @returns {RTCPeerConnection}
3944
+ */
3945
+ function createPeerConnectionWithFactory(configuration, factory) {
3946
+ return new RTCPeerConnection(configuration, factory);
3947
+ }
3948
+
3949
+ src = {
3950
+ // Main API
3951
+ RTCPeerConnection,
3952
+ createPeerConnection,
3953
+ createPeerConnectionWithFactory,
3954
+
3955
+ // Classes
3956
+ RTCDataChannel,
3957
+ RTCSessionDescription,
3958
+ RTCIceCandidate,
3959
+ RTCDataChannelEvent,
3960
+ RTCPeerConnectionIceEvent,
3961
+ RTCError,
3962
+ RTCErrorEvent,
3963
+
3964
+ // Factory
3965
+ NativePeerConnectionFactory,
3966
+ defaultFactory
3967
+ };
3968
+ return src;
3969
+ }
3970
+
3971
+ var srcExports = requireSrc();
3972
+ var index = /*@__PURE__*/getDefaultExportFromCjs(srcExports);
3973
+
3974
+ export { index as default };
59
3975
  //# sourceMappingURL=index.mjs.map