node-rtc-connection 1.0.12 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,505 +0,0 @@
1
- const EventEmitter = require('events');
2
- const RTCDataChannel = require('./RTCDataChannel');
3
- const RTCSessionDescription = require('./RTCSessionDescription');
4
- const RTCIceCandidate = require('./RTCIceCandidate');
5
-
6
- // Lazy-load factory to avoid circular dependency
7
- let _defaultFactory = null;
8
- function getDefaultFactory() {
9
- if (!_defaultFactory) {
10
- const NativePeerConnectionFactory = require('./NativePeerConnectionFactory');
11
- _defaultFactory = new NativePeerConnectionFactory();
12
- }
13
- return _defaultFactory;
14
- }
15
-
16
- /**
17
- * RTCPeerConnection represents a WebRTC connection between the local computer and a remote peer.
18
- * This is a DataChannel-only implementation ported from Chromium.
19
- */
20
- class RTCPeerConnection extends EventEmitter {
21
- constructor(configuration, nativePeerConnectionFactory) {
22
- super();
23
-
24
- this._configuration = this._parseConfiguration(configuration || {});
25
- this._signalingState = 'stable';
26
- this._iceGatheringState = 'new';
27
- this._iceConnectionState = 'new';
28
- this._connectionState = 'new';
29
- this._pendingLocalDescription = null;
30
- this._currentLocalDescription = null;
31
- this._pendingRemoteDescription = null;
32
- this._currentRemoteDescription = null;
33
- this._dataChannels = new Map();
34
- this._closed = false;
35
-
36
- // Native peer connection (would be native WebRTC binding)
37
- this._nativePeerConnection = null;
38
- // Use provided factory or default factory
39
- this._nativePeerConnectionFactory = nativePeerConnectionFactory || getDefaultFactory();
40
-
41
- // Initialize native peer connection
42
- this._initializeNativePeerConnection();
43
- }
44
-
45
- /**
46
- * Parse and validate configuration
47
- * @private
48
- */
49
- _parseConfiguration(config) {
50
- const configuration = {
51
- iceServers: [],
52
- iceTransportPolicy: 'all',
53
- bundlePolicy: 'balanced',
54
- rtcpMuxPolicy: 'require',
55
- iceCandidatePoolSize: 0
56
- };
57
-
58
- if (config.iceServers) {
59
- configuration.iceServers = config.iceServers.map(server => ({
60
- urls: Array.isArray(server.urls) ? server.urls : [server.urls],
61
- username: server.username || '',
62
- credential: server.credential || ''
63
- }));
64
- }
65
-
66
- if (config.iceTransportPolicy) {
67
- configuration.iceTransportPolicy = config.iceTransportPolicy;
68
- }
69
-
70
- if (config.bundlePolicy) {
71
- configuration.bundlePolicy = config.bundlePolicy;
72
- }
73
-
74
- if (config.rtcpMuxPolicy) {
75
- configuration.rtcpMuxPolicy = config.rtcpMuxPolicy;
76
- }
77
-
78
- if (config.iceCandidatePoolSize !== undefined) {
79
- configuration.iceCandidatePoolSize = config.iceCandidatePoolSize;
80
- }
81
-
82
- return configuration;
83
- }
84
-
85
- /**
86
- * Initialize native peer connection with factory
87
- * @private
88
- */
89
- _initializeNativePeerConnection() {
90
- if (!this._nativePeerConnectionFactory) {
91
- throw new Error('Native PeerConnection factory not provided');
92
- }
93
-
94
- // Create native peer connection
95
- this._nativePeerConnection = this._nativePeerConnectionFactory.createPeerConnection(
96
- this._configuration
97
- );
98
-
99
- if (!this._nativePeerConnection) {
100
- throw new Error('Failed to create native peer connection');
101
- }
102
-
103
- // Setup observers
104
- this._setupObservers();
105
- }
106
-
107
- /**
108
- * Setup observers for native peer connection events
109
- * @private
110
- */
111
- _setupObservers() {
112
- if (!this._nativePeerConnection) {
113
- return;
114
- }
115
-
116
- // Signaling state change
117
- this._nativePeerConnection.on('signalingstatechange', (state) => {
118
- this._signalingState = this._convertSignalingState(state);
119
- this.emit('signalingstatechange');
120
- });
121
-
122
- // ICE connection state change
123
- this._nativePeerConnection.on('iceconnectionstatechange', (state) => {
124
- this._iceConnectionState = this._convertIceConnectionState(state);
125
- this.emit('iceconnectionstatechange');
126
- });
127
-
128
- // ICE gathering state change
129
- this._nativePeerConnection.on('icegatheringstatechange', (state) => {
130
- this._iceGatheringState = this._convertIceGatheringState(state);
131
- this.emit('icegatheringstatechange');
132
- });
133
-
134
- // Connection state change
135
- this._nativePeerConnection.on('connectionstatechange', (state) => {
136
- this._connectionState = this._convertConnectionState(state);
137
- this.emit('connectionstatechange');
138
- });
139
-
140
- // ICE candidate
141
- this._nativePeerConnection.on('icecandidate', (candidate) => {
142
- const iceCandidate = candidate ? new RTCIceCandidate(candidate) : null;
143
- this.emit('icecandidate', { candidate: iceCandidate });
144
- });
145
-
146
- // Data channel (remote)
147
- this._nativePeerConnection.on('datachannel', (nativeChannel) => {
148
- const dataChannel = new RTCDataChannel(nativeChannel, this);
149
- this._dataChannels.set(dataChannel.label, dataChannel);
150
- this.emit('datachannel', { channel: dataChannel });
151
- });
152
-
153
- // Negotiation needed
154
- this._nativePeerConnection.on('negotiationneeded', () => {
155
- this.emit('negotiationneeded');
156
- });
157
- }
158
-
159
- /**
160
- * The current signaling state
161
- */
162
- get signalingState() {
163
- return this._signalingState;
164
- }
165
-
166
- /**
167
- * The current ICE gathering state
168
- */
169
- get iceGatheringState() {
170
- return this._iceGatheringState;
171
- }
172
-
173
- /**
174
- * The current ICE connection state
175
- */
176
- get iceConnectionState() {
177
- return this._iceConnectionState;
178
- }
179
-
180
- /**
181
- * The current connection state
182
- */
183
- get connectionState() {
184
- return this._connectionState;
185
- }
186
-
187
- /**
188
- * The local description
189
- */
190
- get localDescription() {
191
- return this._currentLocalDescription || this._pendingLocalDescription;
192
- }
193
-
194
- /**
195
- * The remote description
196
- */
197
- get remoteDescription() {
198
- return this._currentRemoteDescription || this._pendingRemoteDescription;
199
- }
200
-
201
- /**
202
- * The pending local description
203
- */
204
- get pendingLocalDescription() {
205
- return this._pendingLocalDescription;
206
- }
207
-
208
- /**
209
- * The pending remote description
210
- */
211
- get pendingRemoteDescription() {
212
- return this._pendingRemoteDescription;
213
- }
214
-
215
- /**
216
- * The current local description
217
- */
218
- get currentLocalDescription() {
219
- return this._currentLocalDescription;
220
- }
221
-
222
- /**
223
- * The current remote description
224
- */
225
- get currentRemoteDescription() {
226
- return this._currentRemoteDescription;
227
- }
228
-
229
- /**
230
- * Create an offer
231
- * @param {Object} options - Offer options
232
- * @returns {Promise<RTCSessionDescriptionInit>}
233
- */
234
- async createOffer(options = {}) {
235
- this._checkClosed();
236
-
237
- if (!this._nativePeerConnection) {
238
- throw new Error('Native peer connection not available');
239
- }
240
-
241
- try {
242
- const nativeDescription = await this._nativePeerConnection.createOffer(options);
243
- return new RTCSessionDescription({
244
- type: nativeDescription.type,
245
- sdp: nativeDescription.sdp
246
- });
247
- } catch (error) {
248
- throw new Error(`Failed to create offer: ${error.message}`);
249
- }
250
- }
251
-
252
- /**
253
- * Create an answer
254
- * @param {Object} options - Answer options
255
- * @returns {Promise<RTCSessionDescriptionInit>}
256
- */
257
- async createAnswer(options = {}) {
258
- this._checkClosed();
259
-
260
- if (!this._nativePeerConnection) {
261
- throw new Error('Native peer connection not available');
262
- }
263
-
264
- try {
265
- const nativeDescription = await this._nativePeerConnection.createAnswer(options);
266
- return new RTCSessionDescription({
267
- type: nativeDescription.type,
268
- sdp: nativeDescription.sdp
269
- });
270
- } catch (error) {
271
- throw new Error(`Failed to create answer: ${error.message}`);
272
- }
273
- }
274
-
275
- /**
276
- * Set the local description
277
- * @param {RTCSessionDescriptionInit} description
278
- * @returns {Promise<void>}
279
- */
280
- async setLocalDescription(description) {
281
- this._checkClosed();
282
-
283
- if (!this._nativePeerConnection) {
284
- throw new Error('Native peer connection not available');
285
- }
286
-
287
- try {
288
- await this._nativePeerConnection.setLocalDescription(description);
289
-
290
- if (description.type === 'offer') {
291
- this._pendingLocalDescription = new RTCSessionDescription(description);
292
- } else if (description.type === 'answer') {
293
- this._currentLocalDescription = new RTCSessionDescription(description);
294
- this._pendingLocalDescription = null;
295
- } else if (description.type === 'rollback') {
296
- this._pendingLocalDescription = null;
297
- }
298
- } catch (error) {
299
- throw new Error(`Failed to set local description: ${error.message}`);
300
- }
301
- }
302
-
303
- /**
304
- * Set the remote description
305
- * @param {RTCSessionDescriptionInit} description
306
- * @returns {Promise<void>}
307
- */
308
- async setRemoteDescription(description) {
309
- this._checkClosed();
310
-
311
- if (!this._nativePeerConnection) {
312
- throw new Error('Native peer connection not available');
313
- }
314
-
315
- try {
316
- await this._nativePeerConnection.setRemoteDescription(description);
317
-
318
- if (description.type === 'offer') {
319
- this._pendingRemoteDescription = new RTCSessionDescription(description);
320
- } else if (description.type === 'answer') {
321
- this._currentRemoteDescription = new RTCSessionDescription(description);
322
- this._pendingRemoteDescription = null;
323
- } else if (description.type === 'rollback') {
324
- this._pendingRemoteDescription = null;
325
- }
326
- } catch (error) {
327
- throw new Error(`Failed to set remote description: ${error.message}`);
328
- }
329
- }
330
-
331
- /**
332
- * Add an ICE candidate
333
- * @param {RTCIceCandidateInit} candidate
334
- * @returns {Promise<void>}
335
- */
336
- async addIceCandidate(candidate) {
337
- this._checkClosed();
338
-
339
- if (!this._nativePeerConnection) {
340
- throw new Error('Native peer connection not available');
341
- }
342
-
343
- if (!candidate) {
344
- // End of candidates
345
- return;
346
- }
347
-
348
- try {
349
- await this._nativePeerConnection.addIceCandidate(candidate);
350
- } catch (error) {
351
- throw new Error(`Failed to add ICE candidate: ${error.message}`);
352
- }
353
- }
354
-
355
- /**
356
- * Create a data channel
357
- * @param {string} label - Channel label
358
- * @param {Object} dataChannelDict - Channel options
359
- * @returns {RTCDataChannel}
360
- */
361
- createDataChannel(label, dataChannelDict = {}) {
362
- this._checkClosed();
363
-
364
- if (!this._nativePeerConnection) {
365
- throw new Error('Native peer connection not available');
366
- }
367
-
368
- const options = {
369
- ordered: dataChannelDict.ordered !== undefined ? dataChannelDict.ordered : true,
370
- maxPacketLifeTime: dataChannelDict.maxPacketLifeTime,
371
- maxRetransmits: dataChannelDict.maxRetransmits,
372
- protocol: dataChannelDict.protocol || '',
373
- negotiated: dataChannelDict.negotiated || false,
374
- id: dataChannelDict.id
375
- };
376
-
377
- try {
378
- const nativeChannel = this._nativePeerConnection.createDataChannel(label, options);
379
- const dataChannel = new RTCDataChannel(nativeChannel, this);
380
- this._dataChannels.set(label, dataChannel);
381
- return dataChannel;
382
- } catch (error) {
383
- throw new Error(`Failed to create data channel: ${error.message}`);
384
- }
385
- }
386
-
387
- /**
388
- * Get configuration
389
- * @returns {Object}
390
- */
391
- getConfiguration() {
392
- return { ...this._configuration };
393
- }
394
-
395
- /**
396
- * Set configuration
397
- * @param {Object} configuration
398
- */
399
- setConfiguration(configuration) {
400
- this._checkClosed();
401
- this._configuration = this._parseConfiguration(configuration);
402
-
403
- if (this._nativePeerConnection) {
404
- this._nativePeerConnection.setConfiguration(this._configuration);
405
- }
406
- }
407
-
408
- /**
409
- * Close the peer connection
410
- */
411
- close() {
412
- if (this._closed) {
413
- return;
414
- }
415
-
416
- this._closed = true;
417
- this._signalingState = 'closed';
418
-
419
- // Close all data channels
420
- for (const [label, channel] of this._dataChannels) {
421
- channel.close();
422
- channel.dispose();
423
- }
424
- this._dataChannels.clear();
425
-
426
- // Close native peer connection
427
- if (this._nativePeerConnection) {
428
- this._nativePeerConnection.close();
429
- this._nativePeerConnection.removeAllListeners();
430
- this._nativePeerConnection = null;
431
- }
432
-
433
- this.emit('signalingstatechange');
434
- this.removeAllListeners();
435
- }
436
-
437
- /**
438
- * Get stats
439
- * @returns {Promise<Object>}
440
- */
441
- async getStats() {
442
- this._checkClosed();
443
-
444
- if (!this._nativePeerConnection) {
445
- throw new Error('Native peer connection not available');
446
- }
447
-
448
- try {
449
- return await this._nativePeerConnection.getStats();
450
- } catch (error) {
451
- throw new Error(`Failed to get stats: ${error.message}`);
452
- }
453
- }
454
-
455
- /**
456
- * Check if connection is closed
457
- * @private
458
- */
459
- _checkClosed() {
460
- if (this._closed || this._signalingState === 'closed') {
461
- throw new Error("The RTCPeerConnection's signalingState is 'closed'");
462
- }
463
- }
464
-
465
- /**
466
- * Convert native signaling state to string
467
- * @private
468
- */
469
- _convertSignalingState(state) {
470
- const states = ['stable', 'have-local-offer', 'have-remote-offer',
471
- 'have-local-pranswer', 'have-remote-pranswer', 'closed'];
472
- return states[state] || 'stable';
473
- }
474
-
475
- /**
476
- * Convert native ICE connection state to string
477
- * @private
478
- */
479
- _convertIceConnectionState(state) {
480
- const states = ['new', 'checking', 'connected', 'completed',
481
- 'failed', 'disconnected', 'closed'];
482
- return states[state] || 'new';
483
- }
484
-
485
- /**
486
- * Convert native ICE gathering state to string
487
- * @private
488
- */
489
- _convertIceGatheringState(state) {
490
- const states = ['new', 'gathering', 'complete'];
491
- return states[state] || 'new';
492
- }
493
-
494
- /**
495
- * Convert native connection state to string
496
- * @private
497
- */
498
- _convertConnectionState(state) {
499
- const states = ['new', 'connecting', 'connected', 'disconnected',
500
- 'failed', 'closed'];
501
- return states[state] || 'new';
502
- }
503
- }
504
-
505
- module.exports = RTCPeerConnection;
@@ -1,58 +0,0 @@
1
- /**
2
- * RTCPeerConnectionIceEvent is fired when an ICE candidate is available.
3
- * Ported from Chromium's implementation.
4
- */
5
- class RTCPeerConnectionIceEvent {
6
- constructor(type, eventInitDict = {}) {
7
- this._type = type;
8
- this._candidate = eventInitDict.candidate || null;
9
- this._url = eventInitDict.url || null;
10
- this._bubbles = eventInitDict.bubbles || false;
11
- this._cancelable = eventInitDict.cancelable || false;
12
- this._timestamp = Date.now();
13
- }
14
-
15
- /**
16
- * The event type
17
- */
18
- get type() {
19
- return this._type;
20
- }
21
-
22
- /**
23
- * The RTCIceCandidate associated with the event
24
- */
25
- get candidate() {
26
- return this._candidate;
27
- }
28
-
29
- /**
30
- * The URL of the TURN or STUN server
31
- */
32
- get url() {
33
- return this._url;
34
- }
35
-
36
- /**
37
- * Whether the event bubbles
38
- */
39
- get bubbles() {
40
- return this._bubbles;
41
- }
42
-
43
- /**
44
- * Whether the event is cancelable
45
- */
46
- get cancelable() {
47
- return this._cancelable;
48
- }
49
-
50
- /**
51
- * The timestamp when the event was created
52
- */
53
- get timeStamp() {
54
- return this._timestamp;
55
- }
56
- }
57
-
58
- module.exports = RTCPeerConnectionIceEvent;
@@ -1,62 +0,0 @@
1
- /**
2
- * RTCSessionDescription represents a session description.
3
- * Ported from Chromium's implementation.
4
- */
5
- class RTCSessionDescription {
6
- constructor(descriptionInitDict = {}) {
7
- this._type = descriptionInitDict.type || '';
8
- this._sdp = descriptionInitDict.sdp || '';
9
-
10
- // Validate type
11
- const validTypes = ['offer', 'answer', 'pranswer', 'rollback'];
12
- if (this._type && !validTypes.includes(this._type)) {
13
- throw new Error(`Invalid type: ${this._type}`);
14
- }
15
- }
16
-
17
- /**
18
- * The type of session description
19
- * Values: 'offer', 'answer', 'pranswer', 'rollback'
20
- */
21
- get type() {
22
- return this._type;
23
- }
24
-
25
- set type(value) {
26
- const validTypes = ['offer', 'answer', 'pranswer', 'rollback'];
27
- if (value && !validTypes.includes(value)) {
28
- throw new Error(`Invalid type: ${value}`);
29
- }
30
- this._type = value;
31
- }
32
-
33
- /**
34
- * The SDP string
35
- */
36
- get sdp() {
37
- return this._sdp;
38
- }
39
-
40
- set sdp(value) {
41
- this._sdp = value || '';
42
- }
43
-
44
- /**
45
- * Convert to JSON
46
- */
47
- toJSON() {
48
- return {
49
- type: this._type,
50
- sdp: this._sdp
51
- };
52
- }
53
-
54
- /**
55
- * Convert to string
56
- */
57
- toString() {
58
- return `RTCSessionDescription { type: "${this._type}", sdp: "${this._sdp.substring(0, 50)}..." }`;
59
- }
60
- }
61
-
62
- module.exports = RTCSessionDescription;