node-rtc-connection 1.0.11 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +355 -289
- package/dist/index.cjs +4318 -3095
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +4318 -3095
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/datachannel/RTCDataChannel.js +354 -0
- package/src/dtls/RTCCertificate.js +310 -0
- package/src/dtls/RTCDtlsTransport.js +247 -0
- package/src/foundation/ByteBufferQueue.js +235 -0
- package/src/foundation/RTCError.js +226 -0
- package/src/ice/RTCIceCandidate.js +301 -0
- package/src/ice/RTCIceTransport.js +956 -0
- package/src/index.d.ts +316 -145
- package/src/index.js +78 -45
- package/src/network/network-transport.js +478 -0
- package/src/peerconnection/RTCPeerConnection.js +847 -0
- package/src/sctp/RTCSctpTransport.js +253 -0
- package/src/sdp/RTCSessionDescription.js +102 -0
- package/src/sdp/sdp-utils.js +224 -0
- package/src/stun/stun-client.js +643 -0
- package/src/ICEGatherer.js +0 -341
- package/src/NativePeerConnectionFactory.js +0 -1044
- package/src/RTCDataChannel.js +0 -346
- package/src/RTCDataChannelEvent.js +0 -50
- package/src/RTCError.js +0 -66
- package/src/RTCIceCandidate.js +0 -184
- package/src/RTCPeerConnection.js +0 -505
- package/src/RTCPeerConnectionIceEvent.js +0 -58
- package/src/RTCSessionDescription.js +0 -62
- package/src/STUNClient.js +0 -222
- package/src/SecureConnection.js +0 -298
- package/src/TURNClient.js +0 -561
- package/src/UDPTransport.js +0 -236
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file RTCSctpTransport.js
|
|
3
|
+
* @description SCTP transport implementation for WebRTC data channels.
|
|
4
|
+
* @module sctp/RTCSctpTransport
|
|
5
|
+
*
|
|
6
|
+
* Ported from Chromium's RTCSctpTransport implementation:
|
|
7
|
+
* - cc/rtc_sctp_transport.h
|
|
8
|
+
* - cc/rtc_sctp_transport.cc
|
|
9
|
+
* - cc/rtc_sctp_transport.idl
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const EventEmitter = require('events');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* RTCSctpTransportState - Current state of the SCTP transport
|
|
16
|
+
* @readonly
|
|
17
|
+
* @enum {string}
|
|
18
|
+
*/
|
|
19
|
+
const RTCSctpTransportState = Object.freeze({
|
|
20
|
+
CONNECTING: 'connecting',
|
|
21
|
+
CONNECTED: 'connected',
|
|
22
|
+
CLOSED: 'closed'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default SCTP configuration values
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
const SCTP_DEFAULTS = {
|
|
30
|
+
MAX_MESSAGE_SIZE: 256 * 1024, // 256 KB default
|
|
31
|
+
MAX_CHANNELS: 65535 // Maximum number of SCTP streams
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @class RTCSctpTransport
|
|
36
|
+
* @extends EventEmitter
|
|
37
|
+
* @description Represents the SCTP transport layer for WebRTC data channels.
|
|
38
|
+
* SCTP (Stream Control Transmission Protocol) provides reliable, message-oriented
|
|
39
|
+
* transport over DTLS for data channels.
|
|
40
|
+
*
|
|
41
|
+
* Events:
|
|
42
|
+
* - 'statechange': Fired when the transport state changes
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* const sctpTransport = new RTCSctpTransport(dtlsTransport);
|
|
46
|
+
* sctpTransport.on('statechange', () => {
|
|
47
|
+
* console.log('SCTP state:', sctpTransport.state);
|
|
48
|
+
* console.log('Max message size:', sctpTransport.maxMessageSize);
|
|
49
|
+
* });
|
|
50
|
+
*/
|
|
51
|
+
class RTCSctpTransport extends EventEmitter {
|
|
52
|
+
/**
|
|
53
|
+
* Create an RTCSctpTransport instance.
|
|
54
|
+
* @param {RTCDtlsTransport} dtlsTransport - The underlying DTLS transport
|
|
55
|
+
* @param {Object} [options] - SCTP configuration options
|
|
56
|
+
* @param {number} [options.maxMessageSize] - Maximum message size in bytes
|
|
57
|
+
* @param {number} [options.maxChannels] - Maximum number of channels
|
|
58
|
+
* @throws {TypeError} If dtlsTransport is not provided or invalid
|
|
59
|
+
*/
|
|
60
|
+
constructor(dtlsTransport, options = {}) {
|
|
61
|
+
super();
|
|
62
|
+
|
|
63
|
+
if (!dtlsTransport || typeof dtlsTransport !== 'object') {
|
|
64
|
+
throw new TypeError('dtlsTransport is required');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Store the DTLS transport
|
|
68
|
+
this._dtlsTransport = dtlsTransport;
|
|
69
|
+
|
|
70
|
+
// Internal state
|
|
71
|
+
this._state = RTCSctpTransportState.CONNECTING;
|
|
72
|
+
|
|
73
|
+
// SCTP configuration
|
|
74
|
+
// Keep null if explicitly passed, otherwise use default
|
|
75
|
+
if (options.maxMessageSize === null) {
|
|
76
|
+
this._maxMessageSize = null;
|
|
77
|
+
} else {
|
|
78
|
+
this._maxMessageSize = options.maxMessageSize || SCTP_DEFAULTS.MAX_MESSAGE_SIZE;
|
|
79
|
+
}
|
|
80
|
+
this._maxChannels = options.maxChannels !== undefined ? options.maxChannels : SCTP_DEFAULTS.MAX_CHANNELS;
|
|
81
|
+
|
|
82
|
+
// Closed flags
|
|
83
|
+
this._closed = false;
|
|
84
|
+
this._closedFromOwner = false;
|
|
85
|
+
this._startCompleted = false;
|
|
86
|
+
|
|
87
|
+
// Listen to DTLS transport state changes
|
|
88
|
+
this._dtlsTransport.on('statechange', () => {
|
|
89
|
+
this._onDtlsStateChange();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Start SCTP if DTLS is already connected
|
|
93
|
+
if (this._dtlsTransport.state === 'connected') {
|
|
94
|
+
this._start();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the underlying DTLS transport.
|
|
100
|
+
* @returns {RTCDtlsTransport} The DTLS transport
|
|
101
|
+
*/
|
|
102
|
+
get transport() {
|
|
103
|
+
return this._dtlsTransport;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the current SCTP transport state.
|
|
108
|
+
* @returns {string} The transport state
|
|
109
|
+
*/
|
|
110
|
+
get state() {
|
|
111
|
+
if (this._closedFromOwner) {
|
|
112
|
+
return RTCSctpTransportState.CLOSED;
|
|
113
|
+
}
|
|
114
|
+
return this._state;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get the maximum message size in bytes.
|
|
119
|
+
* This represents the maximum size of data that can be sent in a single message.
|
|
120
|
+
*
|
|
121
|
+
* @returns {number} Maximum message size in bytes, or Infinity if unlimited
|
|
122
|
+
*/
|
|
123
|
+
get maxMessageSize() {
|
|
124
|
+
// Return Infinity if explicitly null or undefined (unlimited)
|
|
125
|
+
if (this._maxMessageSize === null) {
|
|
126
|
+
return Infinity;
|
|
127
|
+
}
|
|
128
|
+
return this._maxMessageSize;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get the maximum number of channels.
|
|
133
|
+
* This represents the maximum number of data channels that can be opened.
|
|
134
|
+
*
|
|
135
|
+
* @returns {number|null} Maximum number of channels, or null if unknown
|
|
136
|
+
*/
|
|
137
|
+
get maxChannels() {
|
|
138
|
+
return this._maxChannels;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Start the SCTP association.
|
|
143
|
+
* Called internally when DTLS is connected.
|
|
144
|
+
* @private
|
|
145
|
+
*/
|
|
146
|
+
_start() {
|
|
147
|
+
if (this._startCompleted || this._closed) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this._startCompleted = true;
|
|
152
|
+
|
|
153
|
+
// With real network transport, SCTP is handled by the network layer
|
|
154
|
+
// Transition to connected immediately since we're using raw TCP/UDP
|
|
155
|
+
setImmediate(() => {
|
|
156
|
+
if (!this._closed && this._state === RTCSctpTransportState.CONNECTING) {
|
|
157
|
+
this._setState(RTCSctpTransportState.CONNECTED);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Close the SCTP transport.
|
|
164
|
+
* Called by the owning peer connection when it closes.
|
|
165
|
+
*/
|
|
166
|
+
close() {
|
|
167
|
+
if (this._closed) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this._closedFromOwner = true;
|
|
172
|
+
this._closed = true;
|
|
173
|
+
|
|
174
|
+
// Emit state change if not already closed
|
|
175
|
+
if (this._state !== RTCSctpTransportState.CLOSED) {
|
|
176
|
+
this._state = RTCSctpTransportState.CLOSED;
|
|
177
|
+
this.emit('statechange');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Internal close method.
|
|
183
|
+
* @param {string} reason - Reason for closing
|
|
184
|
+
* @private
|
|
185
|
+
*/
|
|
186
|
+
_close(reason) {
|
|
187
|
+
if (this._closed) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this._closed = true;
|
|
192
|
+
this._setState(RTCSctpTransportState.CLOSED);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Set the transport state and emit event if changed.
|
|
197
|
+
* @param {string} newState - The new state
|
|
198
|
+
* @private
|
|
199
|
+
*/
|
|
200
|
+
_setState(newState) {
|
|
201
|
+
if (this._state !== newState) {
|
|
202
|
+
this._state = newState;
|
|
203
|
+
this.emit('statechange');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Handle DTLS transport state changes.
|
|
209
|
+
* @private
|
|
210
|
+
*/
|
|
211
|
+
_onDtlsStateChange() {
|
|
212
|
+
const dtlsState = this._dtlsTransport.state;
|
|
213
|
+
|
|
214
|
+
// Start SCTP when DTLS is connected
|
|
215
|
+
if (dtlsState === 'connected' && !this._startCompleted) {
|
|
216
|
+
this._start();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Close SCTP when DTLS closes or fails
|
|
220
|
+
if (dtlsState === 'closed' || dtlsState === 'failed') {
|
|
221
|
+
this._close('dtls-closed');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Update SCTP configuration.
|
|
227
|
+
* @param {Object} info - SCTP transport information
|
|
228
|
+
* @param {number} [info.maxMessageSize] - Maximum message size
|
|
229
|
+
* @param {number} [info.maxChannels] - Maximum number of channels
|
|
230
|
+
* @private
|
|
231
|
+
*/
|
|
232
|
+
_updateConfiguration(info) {
|
|
233
|
+
if (info.maxMessageSize !== undefined) {
|
|
234
|
+
this._maxMessageSize = info.maxMessageSize;
|
|
235
|
+
}
|
|
236
|
+
if (info.maxChannels !== undefined) {
|
|
237
|
+
this._maxChannels = info.maxChannels;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Check if the transport is closed.
|
|
243
|
+
* @returns {boolean} True if closed, false otherwise
|
|
244
|
+
*/
|
|
245
|
+
isClosed() {
|
|
246
|
+
return this._state === RTCSctpTransportState.CLOSED;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
module.exports = {
|
|
251
|
+
RTCSctpTransport,
|
|
252
|
+
RTCSctpTransportState
|
|
253
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file RTCSessionDescription.js
|
|
3
|
+
* @description Session Description Protocol (SDP) representation
|
|
4
|
+
* @module sdp/RTCSessionDescription
|
|
5
|
+
*
|
|
6
|
+
* Ported from Chromium's RTCSessionDescription implementation:
|
|
7
|
+
* - cc/rtc_session_description.idl
|
|
8
|
+
* - cc/rtc_session_description.h
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* RTCSdpType - Types of session descriptions
|
|
15
|
+
* @readonly
|
|
16
|
+
* @enum {string}
|
|
17
|
+
*/
|
|
18
|
+
const RTCSdpType = Object.freeze({
|
|
19
|
+
OFFER: 'offer',
|
|
20
|
+
PRANSWER: 'pranswer',
|
|
21
|
+
ANSWER: 'answer',
|
|
22
|
+
ROLLBACK: 'rollback'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @class RTCSessionDescription
|
|
27
|
+
* @description Represents a WebRTC session description (offer/answer)
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const desc = new RTCSessionDescription({
|
|
31
|
+
* type: 'offer',
|
|
32
|
+
* sdp: 'v=0\r\no=- 123456 2 IN IP4 127.0.0.1\r\n...'
|
|
33
|
+
* });
|
|
34
|
+
*/
|
|
35
|
+
class RTCSessionDescription {
|
|
36
|
+
/**
|
|
37
|
+
* Create an RTCSessionDescription instance.
|
|
38
|
+
* @param {Object} [init] - Session description init
|
|
39
|
+
* @param {string} [init.type] - SDP type (offer/answer/pranswer/rollback)
|
|
40
|
+
* @param {string} [init.sdp] - SDP string
|
|
41
|
+
*/
|
|
42
|
+
constructor(init = {}) {
|
|
43
|
+
this._type = init.type || null;
|
|
44
|
+
this._sdp = init.sdp || null;
|
|
45
|
+
|
|
46
|
+
// Validate type if provided
|
|
47
|
+
if (this._type && !Object.values(RTCSdpType).includes(this._type)) {
|
|
48
|
+
throw new TypeError(`Invalid SDP type: ${this._type}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get the SDP type.
|
|
54
|
+
* @returns {string|null} SDP type
|
|
55
|
+
*/
|
|
56
|
+
get type() {
|
|
57
|
+
return this._type;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Set the SDP type.
|
|
62
|
+
* @param {string} value - SDP type
|
|
63
|
+
*/
|
|
64
|
+
set type(value) {
|
|
65
|
+
if (value && !Object.values(RTCSdpType).includes(value)) {
|
|
66
|
+
throw new TypeError(`Invalid SDP type: ${value}`);
|
|
67
|
+
}
|
|
68
|
+
this._type = value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get the SDP string.
|
|
73
|
+
* @returns {string|null} SDP string
|
|
74
|
+
*/
|
|
75
|
+
get sdp() {
|
|
76
|
+
return this._sdp;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Set the SDP string.
|
|
81
|
+
* @param {string} value - SDP string
|
|
82
|
+
*/
|
|
83
|
+
set sdp(value) {
|
|
84
|
+
this._sdp = value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Convert to JSON representation.
|
|
89
|
+
* @returns {Object} JSON representation
|
|
90
|
+
*/
|
|
91
|
+
toJSON() {
|
|
92
|
+
return {
|
|
93
|
+
type: this._type,
|
|
94
|
+
sdp: this._sdp
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
RTCSessionDescription,
|
|
101
|
+
RTCSdpType
|
|
102
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file sdp-utils.js
|
|
3
|
+
* @description SDP parsing and generation utilities
|
|
4
|
+
* @module sdp/sdp-utils
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate a simple SDP offer for data channel only
|
|
11
|
+
* @param {Object} options - Generation options
|
|
12
|
+
* @param {string} options.iceUfrag - ICE username fragment
|
|
13
|
+
* @param {string} options.icePwd - ICE password
|
|
14
|
+
* @param {Array<Object>} options.fingerprints - DTLS fingerprints
|
|
15
|
+
* @param {Array<Object>} options.candidates - ICE candidates
|
|
16
|
+
* @param {string} [options.setup='actpass'] - DTLS setup role
|
|
17
|
+
* @param {string} [options.connectionAddress] - Connection IP address
|
|
18
|
+
* @param {number} [options.connectionPort] - Connection port
|
|
19
|
+
* @returns {string} SDP offer
|
|
20
|
+
*/
|
|
21
|
+
function generateOffer(options) {
|
|
22
|
+
const {
|
|
23
|
+
iceUfrag,
|
|
24
|
+
icePwd,
|
|
25
|
+
fingerprints = [],
|
|
26
|
+
candidates = [],
|
|
27
|
+
setup = 'actpass',
|
|
28
|
+
connectionAddress = '0.0.0.0',
|
|
29
|
+
connectionPort = 9
|
|
30
|
+
} = options;
|
|
31
|
+
|
|
32
|
+
const sessionId = Date.now();
|
|
33
|
+
const sessionVersion = 2;
|
|
34
|
+
|
|
35
|
+
// Get primary fingerprint (SHA-256)
|
|
36
|
+
const fingerprint = fingerprints.find(fp => fp.algorithm === 'sha-256') || fingerprints[0];
|
|
37
|
+
|
|
38
|
+
let sdp = '';
|
|
39
|
+
|
|
40
|
+
// Session description
|
|
41
|
+
sdp += 'v=0\r\n';
|
|
42
|
+
sdp += `o=- ${sessionId} ${sessionVersion} IN IP4 ${connectionAddress}\r\n`;
|
|
43
|
+
sdp += 's=-\r\n';
|
|
44
|
+
sdp += 't=0 0\r\n';
|
|
45
|
+
|
|
46
|
+
// Bundle group (data channel only)
|
|
47
|
+
sdp += 'a=group:BUNDLE 0\r\n';
|
|
48
|
+
sdp += 'a=msid-semantic: WMS\r\n';
|
|
49
|
+
|
|
50
|
+
// Media description for data channel (application)
|
|
51
|
+
sdp += `m=application ${connectionPort} UDP/DTLS/SCTP webrtc-datachannel\r\n`;
|
|
52
|
+
sdp += `c=IN IP4 ${connectionAddress}\r\n`;
|
|
53
|
+
sdp += 'a=ice-ufrag:' + iceUfrag + '\r\n';
|
|
54
|
+
sdp += 'a=ice-pwd:' + icePwd + '\r\n';
|
|
55
|
+
sdp += 'a=ice-options:trickle\r\n';
|
|
56
|
+
|
|
57
|
+
// DTLS fingerprint
|
|
58
|
+
if (fingerprint) {
|
|
59
|
+
sdp += `a=fingerprint:${fingerprint.algorithm.toUpperCase()} ${fingerprint.value}\r\n`;
|
|
60
|
+
}
|
|
61
|
+
sdp += `a=setup:${setup}\r\n`;
|
|
62
|
+
|
|
63
|
+
// SCTP
|
|
64
|
+
sdp += 'a=mid:0\r\n';
|
|
65
|
+
sdp += 'a=sctp-port:5000\r\n';
|
|
66
|
+
sdp += 'a=max-message-size:262144\r\n';
|
|
67
|
+
|
|
68
|
+
// ICE candidates
|
|
69
|
+
for (const candidate of candidates) {
|
|
70
|
+
if (candidate.candidate) {
|
|
71
|
+
sdp += `a=${candidate.candidate}\r\n`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return sdp;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate a simple SDP answer for data channel only
|
|
80
|
+
* @param {Object} options - Generation options
|
|
81
|
+
* @param {string} options.iceUfrag - ICE username fragment
|
|
82
|
+
* @param {string} options.icePwd - ICE password
|
|
83
|
+
* @param {Array<Object>} options.fingerprints - DTLS fingerprints
|
|
84
|
+
* @param {Array<Object>} options.candidates - ICE candidates
|
|
85
|
+
* @param {string} [options.setup='active'] - DTLS setup role
|
|
86
|
+
* @returns {string} SDP answer
|
|
87
|
+
*/
|
|
88
|
+
function generateAnswer(options) {
|
|
89
|
+
// Answer is similar to offer but with different setup role
|
|
90
|
+
return generateOffer({
|
|
91
|
+
...options,
|
|
92
|
+
setup: options.setup || 'active'
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parse SDP string to extract ICE candidates
|
|
98
|
+
* @param {string} sdp - SDP string
|
|
99
|
+
* @returns {Array<Object>} Array of candidate objects
|
|
100
|
+
*/
|
|
101
|
+
function parseCandidates(sdp) {
|
|
102
|
+
const candidates = [];
|
|
103
|
+
const lines = sdp.split('\r\n');
|
|
104
|
+
|
|
105
|
+
for (const line of lines) {
|
|
106
|
+
if (line.startsWith('a=candidate:')) {
|
|
107
|
+
candidates.push({
|
|
108
|
+
candidate: line,
|
|
109
|
+
sdpMid: '0',
|
|
110
|
+
sdpMLineIndex: 0
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return candidates;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parse SDP to extract ICE parameters
|
|
120
|
+
* @param {string} sdp - SDP string
|
|
121
|
+
* @returns {Object} ICE parameters
|
|
122
|
+
*/
|
|
123
|
+
function parseIceParameters(sdp) {
|
|
124
|
+
const lines = sdp.split('\r\n');
|
|
125
|
+
const params = {
|
|
126
|
+
usernameFragment: null,
|
|
127
|
+
password: null
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
if (line.startsWith('a=ice-ufrag:')) {
|
|
132
|
+
params.usernameFragment = line.substring(12);
|
|
133
|
+
} else if (line.startsWith('a=ice-pwd:')) {
|
|
134
|
+
params.password = line.substring(10);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return params;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Parse SDP to extract DTLS parameters
|
|
143
|
+
* @param {string} sdp - SDP string
|
|
144
|
+
* @returns {Object} DTLS parameters
|
|
145
|
+
*/
|
|
146
|
+
function parseDtlsParameters(sdp) {
|
|
147
|
+
const lines = sdp.split('\r\n');
|
|
148
|
+
const params = {
|
|
149
|
+
role: 'auto',
|
|
150
|
+
fingerprints: []
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
for (const line of lines) {
|
|
154
|
+
if (line.startsWith('a=setup:')) {
|
|
155
|
+
const setup = line.substring(8);
|
|
156
|
+
if (setup === 'active') params.role = 'client';
|
|
157
|
+
else if (setup === 'passive') params.role = 'server';
|
|
158
|
+
else params.role = 'auto';
|
|
159
|
+
} else if (line.startsWith('a=fingerprint:')) {
|
|
160
|
+
const parts = line.substring(14).split(' ');
|
|
161
|
+
if (parts.length === 2) {
|
|
162
|
+
params.fingerprints.push({
|
|
163
|
+
algorithm: parts[0].toLowerCase(),
|
|
164
|
+
value: parts[1]
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return params;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Parse SDP to extract SCTP parameters
|
|
175
|
+
* @param {string} sdp - SDP string
|
|
176
|
+
* @returns {Object} SCTP parameters
|
|
177
|
+
*/
|
|
178
|
+
function parseSctpParameters(sdp) {
|
|
179
|
+
const lines = sdp.split('\r\n');
|
|
180
|
+
const params = {
|
|
181
|
+
port: 5000,
|
|
182
|
+
maxMessageSize: 262144
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
for (const line of lines) {
|
|
186
|
+
if (line.startsWith('a=sctp-port:')) {
|
|
187
|
+
params.port = parseInt(line.substring(12), 10);
|
|
188
|
+
} else if (line.startsWith('a=max-message-size:')) {
|
|
189
|
+
params.maxMessageSize = parseInt(line.substring(19), 10);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return params;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Generate random ICE credentials
|
|
198
|
+
* @returns {Object} ICE credentials
|
|
199
|
+
*/
|
|
200
|
+
function generateIceCredentials() {
|
|
201
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
202
|
+
const randomString = (length) => {
|
|
203
|
+
let result = '';
|
|
204
|
+
for (let i = 0; i < length; i++) {
|
|
205
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
usernameFragment: randomString(4),
|
|
212
|
+
password: randomString(24)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = {
|
|
217
|
+
generateOffer,
|
|
218
|
+
generateAnswer,
|
|
219
|
+
parseCandidates,
|
|
220
|
+
parseIceParameters,
|
|
221
|
+
parseDtlsParameters,
|
|
222
|
+
parseSctpParameters,
|
|
223
|
+
generateIceCredentials
|
|
224
|
+
};
|