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.
@@ -0,0 +1,301 @@
1
+ /**
2
+ * @fileoverview RTCIceCandidate - ICE candidate representation.
3
+ *
4
+ * Ported from Chromium's WebRTC implementation:
5
+ * chromium/src/third_party/blink/renderer/modules/peerconnection/rtc_ice_candidate.{h,cc}
6
+ *
7
+ * Represents an ICE (Interactive Connectivity Establishment) candidate that
8
+ * describes a potential way to establish a connection with a peer.
9
+ *
10
+ * @license BSD-3-Clause
11
+ * @author nmhung1210
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ /**
17
+ * RTCIceCandidate represents a potential method for establishing connectivity.
18
+ *
19
+ * ICE candidates are described using SDP (Session Description Protocol) syntax.
20
+ * Each candidate describes a single address/port combination and transport protocol.
21
+ */
22
+ class RTCIceCandidate {
23
+ /**
24
+ * Creates a new RTCIceCandidate.
25
+ *
26
+ * @param {RTCIceCandidateInit} [candidateInit={}] - Initialization dictionary
27
+ * @param {string} [candidateInit.candidate=''] - SDP candidate string
28
+ * @param {string|null} [candidateInit.sdpMid] - Media stream ID
29
+ * @param {number|null} [candidateInit.sdpMLineIndex] - M-line index
30
+ * @param {string} [candidateInit.usernameFragment] - ICE username fragment
31
+ * @throws {TypeError} If both sdpMid and sdpMLineIndex are null
32
+ */
33
+ constructor(candidateInit = {}) {
34
+ // Validate that at least one of sdpMid or sdpMLineIndex is present
35
+ if (candidateInit.sdpMid === null && candidateInit.sdpMLineIndex === null) {
36
+ throw new TypeError('sdpMid and sdpMLineIndex are both null');
37
+ }
38
+
39
+ /**
40
+ * SDP candidate string.
41
+ * @private {string}
42
+ */
43
+ this._candidate = candidateInit.candidate || '';
44
+
45
+ /**
46
+ * Media stream identification.
47
+ * @private {string|null}
48
+ */
49
+ this._sdpMid = candidateInit.sdpMid !== undefined ? candidateInit.sdpMid : null;
50
+
51
+ /**
52
+ * Media line index (zero-based).
53
+ * @private {number|null}
54
+ */
55
+ this._sdpMLineIndex = candidateInit.sdpMLineIndex !== undefined ?
56
+ candidateInit.sdpMLineIndex : null;
57
+
58
+ /**
59
+ * ICE username fragment.
60
+ * @private {string|null}
61
+ */
62
+ this._usernameFragment = candidateInit.usernameFragment || null;
63
+
64
+ // Parse candidate string for detailed attributes
65
+ this._parsedAttributes = this._parseCandidate(this._candidate);
66
+ }
67
+
68
+ /**
69
+ * Parses an ICE candidate string to extract attributes.
70
+ * Format: "candidate:foundation component protocol priority address port typ type [raddr reladdr] [rport relport]"
71
+ *
72
+ * @private
73
+ * @param {string} candidateStr - Candidate string to parse
74
+ * @returns {Object} Parsed attributes
75
+ */
76
+ _parseCandidate(candidateStr) {
77
+ const attrs = {
78
+ foundation: null,
79
+ component: null,
80
+ protocol: null,
81
+ priority: null,
82
+ address: null,
83
+ port: null,
84
+ type: null,
85
+ tcpType: null,
86
+ relatedAddress: null,
87
+ relatedPort: null
88
+ };
89
+
90
+ if (!candidateStr || !candidateStr.startsWith('candidate:')) {
91
+ return attrs;
92
+ }
93
+
94
+ // Remove "candidate:" prefix
95
+ const parts = candidateStr.substring(10).trim().split(/\s+/);
96
+
97
+ if (parts.length < 8) {
98
+ return attrs;
99
+ }
100
+
101
+ // Parse fixed fields
102
+ attrs.foundation = parts[0];
103
+ attrs.component = parts[1];
104
+ attrs.protocol = parts[2].toLowerCase();
105
+ attrs.priority = parseInt(parts[3], 10);
106
+ attrs.address = parts[4];
107
+ attrs.port = parseInt(parts[5], 10);
108
+
109
+ // parts[6] should be "typ"
110
+ if (parts[6] === 'typ') {
111
+ attrs.type = parts[7];
112
+ }
113
+
114
+ // Parse optional attributes
115
+ for (let i = 8; i < parts.length; i += 2) {
116
+ const key = parts[i];
117
+ const value = parts[i + 1];
118
+
119
+ if (key === 'raddr') {
120
+ attrs.relatedAddress = value;
121
+ } else if (key === 'rport') {
122
+ attrs.relatedPort = parseInt(value, 10);
123
+ } else if (key === 'tcptype') {
124
+ attrs.tcpType = value;
125
+ }
126
+ }
127
+
128
+ return attrs;
129
+ }
130
+
131
+ /**
132
+ * SDP candidate attribute containing the candidate description.
133
+ * @type {string}
134
+ */
135
+ get candidate() {
136
+ return this._candidate;
137
+ }
138
+
139
+ /**
140
+ * Media stream identification tag.
141
+ * @type {string|null}
142
+ */
143
+ get sdpMid() {
144
+ return this._sdpMid;
145
+ }
146
+
147
+ /**
148
+ * Index of the m-line in the SDP this candidate is associated with.
149
+ * @type {number|null}
150
+ */
151
+ get sdpMLineIndex() {
152
+ return this._sdpMLineIndex;
153
+ }
154
+
155
+ /**
156
+ * ICE username fragment.
157
+ * @type {string|null}
158
+ */
159
+ get usernameFragment() {
160
+ return this._usernameFragment;
161
+ }
162
+
163
+ /**
164
+ * Unique identifier for this candidate.
165
+ * @type {string|null}
166
+ */
167
+ get foundation() {
168
+ return this._parsedAttributes.foundation;
169
+ }
170
+
171
+ /**
172
+ * Component identifier (rtp=1, rtcp=2).
173
+ * @type {string|null}
174
+ */
175
+ get component() {
176
+ return this._parsedAttributes.component;
177
+ }
178
+
179
+ /**
180
+ * Priority value for this candidate.
181
+ * Higher priority candidates are preferred.
182
+ * @type {number|null}
183
+ */
184
+ get priority() {
185
+ return this._parsedAttributes.priority;
186
+ }
187
+
188
+ /**
189
+ * IP address of this candidate.
190
+ * @type {string|null}
191
+ */
192
+ get address() {
193
+ return this._parsedAttributes.address;
194
+ }
195
+
196
+ /**
197
+ * Transport protocol (udp/tcp).
198
+ * @type {string|null}
199
+ */
200
+ get protocol() {
201
+ return this._parsedAttributes.protocol;
202
+ }
203
+
204
+ /**
205
+ * Port number.
206
+ * @type {number|null}
207
+ */
208
+ get port() {
209
+ return this._parsedAttributes.port;
210
+ }
211
+
212
+ /**
213
+ * Type of candidate (host, srflx, prflx, relay).
214
+ * @type {string|null}
215
+ */
216
+ get type() {
217
+ return this._parsedAttributes.type;
218
+ }
219
+
220
+ /**
221
+ * TCP candidate type (active, passive, so).
222
+ * Only applicable for TCP candidates.
223
+ * @type {string|null}
224
+ */
225
+ get tcpType() {
226
+ return this._parsedAttributes.tcpType;
227
+ }
228
+
229
+ /**
230
+ * Related address for reflexive/relay candidates.
231
+ * @type {string|null}
232
+ */
233
+ get relatedAddress() {
234
+ return this._parsedAttributes.relatedAddress;
235
+ }
236
+
237
+ /**
238
+ * Related port for reflexive/relay candidates.
239
+ * @type {number|null}
240
+ */
241
+ get relatedPort() {
242
+ return this._parsedAttributes.relatedPort;
243
+ }
244
+
245
+ /**
246
+ * Converts candidate to JSON representation.
247
+ * @returns {Object} JSON representation
248
+ */
249
+ toJSON() {
250
+ const json = {
251
+ candidate: this._candidate,
252
+ sdpMid: this._sdpMid,
253
+ sdpMLineIndex: this._sdpMLineIndex
254
+ };
255
+
256
+ if (this._usernameFragment) {
257
+ json.usernameFragment = this._usernameFragment;
258
+ }
259
+
260
+ return json;
261
+ }
262
+
263
+ /**
264
+ * Creates an RTCIceCandidate from a candidate string.
265
+ *
266
+ * @param {string} candidateStr - ICE candidate string
267
+ * @param {string|null} [sdpMid=null] - Media stream ID
268
+ * @param {number|null} [sdpMLineIndex=0] - M-line index
269
+ * @returns {RTCIceCandidate}
270
+ */
271
+ static fromString(candidateStr, sdpMid = null, sdpMLineIndex = 0) {
272
+ return new RTCIceCandidate({
273
+ candidate: candidateStr,
274
+ sdpMid,
275
+ sdpMLineIndex
276
+ });
277
+ }
278
+
279
+ /**
280
+ * Validates if a string is a valid candidate format.
281
+ *
282
+ * @param {string} candidateStr - String to validate
283
+ * @returns {boolean} True if valid candidate format
284
+ */
285
+ static isValid(candidateStr) {
286
+ if (!candidateStr || typeof candidateStr !== 'string') {
287
+ return false;
288
+ }
289
+
290
+ // Must start with "candidate:"
291
+ if (!candidateStr.startsWith('candidate:')) {
292
+ return false;
293
+ }
294
+
295
+ // Must have at least the minimum required fields
296
+ const parts = candidateStr.substring(10).trim().split(/\s+/);
297
+ return parts.length >= 8;
298
+ }
299
+ }
300
+
301
+ module.exports = RTCIceCandidate;