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.
- package/README.md +355 -289
- package/dist/index.cjs +4357 -3092
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +4357 -3092
- 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 +998 -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,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file RTCDtlsTransport.js
|
|
3
|
+
* @description DTLS transport implementation for WebRTC security layer.
|
|
4
|
+
* @module dtls/RTCDtlsTransport
|
|
5
|
+
*
|
|
6
|
+
* Ported from Chromium's RTCDtlsTransport implementation:
|
|
7
|
+
* - cc/rtc_dtls_transport.h
|
|
8
|
+
* - cc/rtc_dtls_transport.cc
|
|
9
|
+
* - cc/rtc_dtls_transport.idl
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const EventEmitter = require('events');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* RTCDtlsTransportState - Current state of the DTLS transport
|
|
16
|
+
* @readonly
|
|
17
|
+
* @enum {string}
|
|
18
|
+
*/
|
|
19
|
+
const RTCDtlsTransportState = Object.freeze({
|
|
20
|
+
NEW: 'new',
|
|
21
|
+
CONNECTING: 'connecting',
|
|
22
|
+
CONNECTED: 'connected',
|
|
23
|
+
CLOSED: 'closed',
|
|
24
|
+
FAILED: 'failed'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @class RTCDtlsTransport
|
|
29
|
+
* @extends EventEmitter
|
|
30
|
+
* @description Represents the DTLS transport layer providing encryption for WebRTC.
|
|
31
|
+
* DTLS (Datagram Transport Layer Security) provides security for data transport
|
|
32
|
+
* over ICE. This class manages the DTLS handshake and connection state.
|
|
33
|
+
*
|
|
34
|
+
* Events:
|
|
35
|
+
* - 'statechange': Fired when the transport state changes
|
|
36
|
+
* - 'error': Fired when an error occurs
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const dtlsTransport = new RTCDtlsTransport(iceTransport);
|
|
40
|
+
* dtlsTransport.on('statechange', () => {
|
|
41
|
+
* console.log('DTLS state:', dtlsTransport.state);
|
|
42
|
+
* });
|
|
43
|
+
* dtlsTransport.on('error', (error) => {
|
|
44
|
+
* console.error('DTLS error:', error);
|
|
45
|
+
* });
|
|
46
|
+
*/
|
|
47
|
+
class RTCDtlsTransport extends EventEmitter {
|
|
48
|
+
/**
|
|
49
|
+
* Create an RTCDtlsTransport instance.
|
|
50
|
+
* @param {RTCIceTransport} iceTransport - The underlying ICE transport
|
|
51
|
+
* @throws {TypeError} If iceTransport is not provided or invalid
|
|
52
|
+
*/
|
|
53
|
+
constructor(iceTransport) {
|
|
54
|
+
super();
|
|
55
|
+
|
|
56
|
+
if (!iceTransport || typeof iceTransport !== 'object') {
|
|
57
|
+
throw new TypeError('iceTransport is required');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Store the ICE transport
|
|
61
|
+
this._iceTransport = iceTransport;
|
|
62
|
+
|
|
63
|
+
// Internal state
|
|
64
|
+
this._state = RTCDtlsTransportState.NEW;
|
|
65
|
+
this._remoteCertificates = [];
|
|
66
|
+
|
|
67
|
+
// Closed flag
|
|
68
|
+
this._closed = false;
|
|
69
|
+
this._closedFromOwner = false;
|
|
70
|
+
|
|
71
|
+
// Listen to ICE transport state changes
|
|
72
|
+
this._iceTransport.on('statechange', () => {
|
|
73
|
+
this._onIceStateChange();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get the underlying ICE transport.
|
|
79
|
+
* @returns {RTCIceTransport} The ICE transport
|
|
80
|
+
*/
|
|
81
|
+
get iceTransport() {
|
|
82
|
+
return this._iceTransport;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the current DTLS transport state.
|
|
87
|
+
* @returns {string} The transport state
|
|
88
|
+
*/
|
|
89
|
+
get state() {
|
|
90
|
+
if (this._closedFromOwner) {
|
|
91
|
+
return RTCDtlsTransportState.CLOSED;
|
|
92
|
+
}
|
|
93
|
+
return this._state;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get the remote peer's certificate chain.
|
|
98
|
+
* Returns an array of certificates in DER format as ArrayBuffers.
|
|
99
|
+
*
|
|
100
|
+
* @returns {Array<ArrayBuffer>} Array of remote certificates
|
|
101
|
+
*/
|
|
102
|
+
getRemoteCertificates() {
|
|
103
|
+
return this._remoteCertificates.map(cert => {
|
|
104
|
+
// Return copies to prevent modification
|
|
105
|
+
return cert.slice(0);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Start the DTLS handshake.
|
|
111
|
+
* This is called internally when the ICE transport is connected.
|
|
112
|
+
* @private
|
|
113
|
+
*/
|
|
114
|
+
_start() {
|
|
115
|
+
if (this._state !== RTCDtlsTransportState.NEW) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this._setState(RTCDtlsTransportState.CONNECTING);
|
|
120
|
+
|
|
121
|
+
// With real network transport, DTLS is handled by the network layer
|
|
122
|
+
// Transition to connected immediately since we're using raw TCP/UDP
|
|
123
|
+
setImmediate(() => {
|
|
124
|
+
if (!this._closed && this._state === RTCDtlsTransportState.CONNECTING) {
|
|
125
|
+
this._setState(RTCDtlsTransportState.CONNECTED);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Close the DTLS transport.
|
|
132
|
+
* Transitions to closed state and stops the underlying ICE transport.
|
|
133
|
+
*/
|
|
134
|
+
close() {
|
|
135
|
+
if (this._closed) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this._closedFromOwner = true;
|
|
140
|
+
|
|
141
|
+
// Emit state change if not already closed
|
|
142
|
+
if (this._state !== RTCDtlsTransportState.CLOSED) {
|
|
143
|
+
this.emit('statechange');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Stop the ICE transport
|
|
147
|
+
if (this._iceTransport && !this._iceTransport.isClosed()) {
|
|
148
|
+
this._iceTransport.stop();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this._closed = true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Internal close method (called when transport fails or times out).
|
|
156
|
+
* @param {string} reason - Reason for closing
|
|
157
|
+
* @private
|
|
158
|
+
*/
|
|
159
|
+
_close(reason) {
|
|
160
|
+
if (this._closed) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this._closed = true;
|
|
165
|
+
|
|
166
|
+
if (reason === 'failed') {
|
|
167
|
+
this._setState(RTCDtlsTransportState.FAILED);
|
|
168
|
+
} else {
|
|
169
|
+
this._setState(RTCDtlsTransportState.CLOSED);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Set the transport state and emit event if changed.
|
|
175
|
+
* @param {string} newState - The new state
|
|
176
|
+
* @private
|
|
177
|
+
*/
|
|
178
|
+
_setState(newState) {
|
|
179
|
+
if (this._state !== newState) {
|
|
180
|
+
this._state = newState;
|
|
181
|
+
this.emit('statechange');
|
|
182
|
+
|
|
183
|
+
// If failed, emit error event
|
|
184
|
+
if (newState === RTCDtlsTransportState.FAILED) {
|
|
185
|
+
this.emit('error', new Error('DTLS transport failed'));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Handle ICE transport state changes.
|
|
192
|
+
* @private
|
|
193
|
+
*/
|
|
194
|
+
_onIceStateChange() {
|
|
195
|
+
const iceState = this._iceTransport.state;
|
|
196
|
+
|
|
197
|
+
// Start DTLS when ICE is connected
|
|
198
|
+
if (iceState === 'connected' || iceState === 'completed') {
|
|
199
|
+
if (this._state === RTCDtlsTransportState.NEW) {
|
|
200
|
+
this._start();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Handle ICE failures
|
|
205
|
+
if (iceState === 'failed') {
|
|
206
|
+
this._close('failed');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Handle ICE closure
|
|
210
|
+
if (iceState === 'closed') {
|
|
211
|
+
this._close('closed');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Set remote certificates (called internally after handshake).
|
|
217
|
+
* @param {Array<ArrayBuffer>} certificates - DER-encoded certificates
|
|
218
|
+
* @private
|
|
219
|
+
*/
|
|
220
|
+
_setRemoteCertificates(certificates) {
|
|
221
|
+
if (!Array.isArray(certificates)) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this._remoteCertificates = certificates.map(cert => {
|
|
226
|
+
// Store copies
|
|
227
|
+
if (cert instanceof ArrayBuffer) {
|
|
228
|
+
return cert.slice(0);
|
|
229
|
+
}
|
|
230
|
+
return cert;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check if the transport is closed.
|
|
236
|
+
* @returns {boolean} True if closed, false otherwise
|
|
237
|
+
*/
|
|
238
|
+
isClosed() {
|
|
239
|
+
return this._state === RTCDtlsTransportState.CLOSED ||
|
|
240
|
+
this._state === RTCDtlsTransportState.FAILED;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = {
|
|
245
|
+
RTCDtlsTransport,
|
|
246
|
+
RTCDtlsTransportState
|
|
247
|
+
};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview ByteBufferQueue - Efficient byte buffer with O(1) append and O(n) read.
|
|
3
|
+
*
|
|
4
|
+
* Ported from Chromium's WebRTC implementation:
|
|
5
|
+
* chromium/src/third_party/blink/renderer/modules/peerconnection/byte_buffer_queue.{h,cc}
|
|
6
|
+
*
|
|
7
|
+
* This class provides efficient management of byte buffers with O(1) append operations
|
|
8
|
+
* and O(n) read operations. Clients can append entire buffers then copy data out across
|
|
9
|
+
* buffer boundaries.
|
|
10
|
+
*
|
|
11
|
+
* @license BSD-3-Clause
|
|
12
|
+
* @author nmhung1210
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A ByteBufferQueue manages a queue of byte buffers with efficient operations.
|
|
19
|
+
*
|
|
20
|
+
* Invariants maintained:
|
|
21
|
+
* - size_ = sum of all buffer sizes - frontBufferOffset_
|
|
22
|
+
* - No buffer in the queue is empty
|
|
23
|
+
* - If queue is empty, frontBufferOffset_ = 0
|
|
24
|
+
* - Otherwise, frontBufferOffset_ < front buffer size
|
|
25
|
+
*/
|
|
26
|
+
class ByteBufferQueue {
|
|
27
|
+
constructor() {
|
|
28
|
+
/**
|
|
29
|
+
* Total number of bytes available to read.
|
|
30
|
+
* @private {number}
|
|
31
|
+
*/
|
|
32
|
+
this._size = 0;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Double-ended queue of byte buffers.
|
|
36
|
+
* Append() pushes to the back, ReadInto() consumes from the front.
|
|
37
|
+
* @private {Buffer[]}
|
|
38
|
+
*/
|
|
39
|
+
this._buffers = [];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Offset from which to start reading the front buffer.
|
|
43
|
+
* @private {number}
|
|
44
|
+
*/
|
|
45
|
+
this._frontBufferOffset = 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Number of bytes that can be read.
|
|
50
|
+
* @returns {number}
|
|
51
|
+
*/
|
|
52
|
+
get size() {
|
|
53
|
+
return this._size;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns true if no bytes are available to read.
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
*/
|
|
60
|
+
get empty() {
|
|
61
|
+
return this._size === 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Copies data into the given buffer. Consumes bytes from the queue.
|
|
66
|
+
* Returns the number of bytes written to bufferOut.
|
|
67
|
+
*
|
|
68
|
+
* @param {Buffer} bufferOut - Destination buffer to read into
|
|
69
|
+
* @returns {number} Number of bytes actually read
|
|
70
|
+
* @throws {TypeError} If bufferOut is not a Buffer
|
|
71
|
+
*/
|
|
72
|
+
readInto(bufferOut) {
|
|
73
|
+
if (!Buffer.isBuffer(bufferOut)) {
|
|
74
|
+
throw new TypeError('bufferOut must be a Buffer');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let readAmount = 0;
|
|
78
|
+
let outputOffset = 0;
|
|
79
|
+
|
|
80
|
+
while (outputOffset < bufferOut.length && this._buffers.length > 0) {
|
|
81
|
+
const frontBuffer = this._buffers[0];
|
|
82
|
+
const availableInFront = frontBuffer.length - this._frontBufferOffset;
|
|
83
|
+
const remainingOutput = bufferOut.length - outputOffset;
|
|
84
|
+
const toCopy = Math.min(availableInFront, remainingOutput);
|
|
85
|
+
|
|
86
|
+
// Copy data from front buffer to output
|
|
87
|
+
frontBuffer.copy(
|
|
88
|
+
bufferOut,
|
|
89
|
+
outputOffset,
|
|
90
|
+
this._frontBufferOffset,
|
|
91
|
+
this._frontBufferOffset + toCopy
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
readAmount += toCopy;
|
|
95
|
+
outputOffset += toCopy;
|
|
96
|
+
|
|
97
|
+
if (toCopy < availableInFront) {
|
|
98
|
+
// Partial read, update offset
|
|
99
|
+
this._frontBufferOffset += toCopy;
|
|
100
|
+
} else {
|
|
101
|
+
// Consumed entire front buffer, remove it
|
|
102
|
+
this._buffers.shift();
|
|
103
|
+
this._frontBufferOffset = 0;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this._size -= readAmount;
|
|
108
|
+
this._checkInvariants();
|
|
109
|
+
return readAmount;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Appends a buffer to the queue. Takes ownership of the buffer.
|
|
114
|
+
* Empty buffers are ignored.
|
|
115
|
+
*
|
|
116
|
+
* @param {Buffer} buffer - Buffer to append
|
|
117
|
+
* @throws {TypeError} If buffer is not a Buffer
|
|
118
|
+
*/
|
|
119
|
+
append(buffer) {
|
|
120
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
121
|
+
throw new TypeError('buffer must be a Buffer');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (buffer.length === 0) {
|
|
125
|
+
return; // Ignore empty buffers
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this._size += buffer.length;
|
|
129
|
+
this._buffers.push(buffer);
|
|
130
|
+
this._checkInvariants();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Clears all stored buffers.
|
|
135
|
+
*/
|
|
136
|
+
clear() {
|
|
137
|
+
this._buffers = [];
|
|
138
|
+
this._frontBufferOffset = 0;
|
|
139
|
+
this._size = 0;
|
|
140
|
+
this._checkInvariants();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Reads and consumes exactly n bytes.
|
|
145
|
+
*
|
|
146
|
+
* @param {number} n - Number of bytes to read
|
|
147
|
+
* @returns {Buffer} Buffer containing exactly n bytes
|
|
148
|
+
* @throws {RangeError} If fewer than n bytes are available
|
|
149
|
+
*/
|
|
150
|
+
read(n) {
|
|
151
|
+
if (n > this._size) {
|
|
152
|
+
throw new RangeError(`Cannot read ${n} bytes, only ${this._size} available`);
|
|
153
|
+
}
|
|
154
|
+
if (n === 0) {
|
|
155
|
+
return Buffer.allocUnsafe(0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const result = Buffer.allocUnsafe(n);
|
|
159
|
+
const bytesRead = this.readInto(result);
|
|
160
|
+
|
|
161
|
+
if (bytesRead !== n) {
|
|
162
|
+
throw new Error(`Internal error: read ${bytesRead} bytes, expected ${n}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Peeks at data without consuming it.
|
|
170
|
+
*
|
|
171
|
+
* @param {number} [n=this._size] - Number of bytes to peek
|
|
172
|
+
* @returns {Buffer} Buffer containing up to n bytes (not consumed)
|
|
173
|
+
*/
|
|
174
|
+
peek(n = this._size) {
|
|
175
|
+
const peekAmount = Math.min(n, this._size);
|
|
176
|
+
if (peekAmount === 0) {
|
|
177
|
+
return Buffer.allocUnsafe(0);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const result = Buffer.allocUnsafe(peekAmount);
|
|
181
|
+
let written = 0;
|
|
182
|
+
let bufferIndex = 0;
|
|
183
|
+
let offset = this._frontBufferOffset;
|
|
184
|
+
|
|
185
|
+
while (written < peekAmount && bufferIndex < this._buffers.length) {
|
|
186
|
+
const buffer = this._buffers[bufferIndex];
|
|
187
|
+
const available = buffer.length - offset;
|
|
188
|
+
const toCopy = Math.min(available, peekAmount - written);
|
|
189
|
+
|
|
190
|
+
buffer.copy(result, written, offset, offset + toCopy);
|
|
191
|
+
written += toCopy;
|
|
192
|
+
|
|
193
|
+
bufferIndex++;
|
|
194
|
+
offset = 0; // Reset offset for subsequent buffers
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Checks internal invariants (development mode only).
|
|
202
|
+
* @private
|
|
203
|
+
* @throws {Error} If invariants are violated
|
|
204
|
+
*/
|
|
205
|
+
_checkInvariants() {
|
|
206
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
207
|
+
let bufferSizeSum = 0;
|
|
208
|
+
for (const buffer of this._buffers) {
|
|
209
|
+
if (buffer.length === 0) {
|
|
210
|
+
throw new Error('Invariant violation: empty buffer in queue');
|
|
211
|
+
}
|
|
212
|
+
bufferSizeSum += buffer.length;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const expectedSize = bufferSizeSum - this._frontBufferOffset;
|
|
216
|
+
if (this._size !== expectedSize) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Invariant violation: size=${this._size}, expected=${expectedSize}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (this._buffers.length === 0) {
|
|
223
|
+
if (this._frontBufferOffset !== 0) {
|
|
224
|
+
throw new Error('Invariant violation: offset non-zero with empty queue');
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
if (this._frontBufferOffset >= this._buffers[0].length) {
|
|
228
|
+
throw new Error('Invariant violation: offset >= front buffer size');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
module.exports = ByteBufferQueue;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview RTCError - WebRTC-specific error types.
|
|
3
|
+
*
|
|
4
|
+
* Ported from Chromium's WebRTC implementation:
|
|
5
|
+
* chromium/src/third_party/blink/renderer/modules/peerconnection/rtc_error.{h,cc}
|
|
6
|
+
*
|
|
7
|
+
* Provides WebRTC-specific error types extending the standard Error class
|
|
8
|
+
* with additional error detail types and metadata fields.
|
|
9
|
+
*
|
|
10
|
+
* @license BSD-3-Clause
|
|
11
|
+
* @author nmhung1210
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* RTCErrorDetailType enum - Standardized WebRTC error details.
|
|
18
|
+
* Maps to RTCErrorDetailType from the WebRTC spec.
|
|
19
|
+
*
|
|
20
|
+
* @readonly
|
|
21
|
+
* @enum {string}
|
|
22
|
+
*/
|
|
23
|
+
const RTCErrorDetailType = Object.freeze({
|
|
24
|
+
NONE: 'none',
|
|
25
|
+
DATA_CHANNEL_FAILURE: 'data-channel-failure',
|
|
26
|
+
DTLS_FAILURE: 'dtls-failure',
|
|
27
|
+
FINGERPRINT_FAILURE: 'fingerprint-failure',
|
|
28
|
+
SCTP_FAILURE: 'sctp-failure',
|
|
29
|
+
SDP_SYNTAX_ERROR: 'sdp-syntax-error',
|
|
30
|
+
HARDWARE_ENCODER_NOT_AVAILABLE: 'hardware-encoder-not-available',
|
|
31
|
+
HARDWARE_ENCODER_ERROR: 'hardware-encoder-error',
|
|
32
|
+
INVALID_STATE: 'invalid-state',
|
|
33
|
+
INVALID_MODIFICATION: 'invalid-modification',
|
|
34
|
+
INVALID_ACCESS_ERROR: 'invalid-access-error',
|
|
35
|
+
OPERATION_ERROR: 'operation-error'
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* RTCError extends Error with WebRTC-specific error details.
|
|
40
|
+
*
|
|
41
|
+
* @extends Error
|
|
42
|
+
*/
|
|
43
|
+
class RTCError extends Error {
|
|
44
|
+
/**
|
|
45
|
+
* Creates a new RTCError.
|
|
46
|
+
*
|
|
47
|
+
* @param {RTCErrorInit} [init={}] - Error initialization dictionary
|
|
48
|
+
* @param {string} [init.errorDetail='none'] - Error detail type
|
|
49
|
+
* @param {number} [init.sdpLineNumber] - SDP line number where error occurred
|
|
50
|
+
* @param {number} [init.httpRequestStatusCode] - HTTP status code if relevant
|
|
51
|
+
* @param {number} [init.sctpCauseCode] - SCTP cause code
|
|
52
|
+
* @param {number} [init.receivedAlert] - TLS alert received
|
|
53
|
+
* @param {number} [init.sentAlert] - TLS alert sent
|
|
54
|
+
* @param {string} [message=''] - Error message
|
|
55
|
+
*/
|
|
56
|
+
constructor(init = {}, message = '') {
|
|
57
|
+
super(message);
|
|
58
|
+
|
|
59
|
+
this.name = 'RTCError';
|
|
60
|
+
|
|
61
|
+
// Maintain stack trace in V8
|
|
62
|
+
if (Error.captureStackTrace) {
|
|
63
|
+
Error.captureStackTrace(this, RTCError);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Validate and set errorDetail
|
|
67
|
+
const errorDetail = init.errorDetail || RTCErrorDetailType.NONE;
|
|
68
|
+
if (typeof errorDetail !== 'string') {
|
|
69
|
+
throw new TypeError('errorDetail must be a string');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Specific error category.
|
|
74
|
+
* @private {string}
|
|
75
|
+
*/
|
|
76
|
+
this._errorDetail = errorDetail;
|
|
77
|
+
|
|
78
|
+
// Optional numeric fields with validation
|
|
79
|
+
this._sdpLineNumber = this._validateInteger(init.sdpLineNumber, 'sdpLineNumber');
|
|
80
|
+
this._httpRequestStatusCode = this._validateInteger(init.httpRequestStatusCode, 'httpRequestStatusCode');
|
|
81
|
+
this._sctpCauseCode = this._validateInteger(init.sctpCauseCode, 'sctpCauseCode');
|
|
82
|
+
this._receivedAlert = this._validateUnsignedInteger(init.receivedAlert, 'receivedAlert');
|
|
83
|
+
this._sentAlert = this._validateUnsignedInteger(init.sentAlert, 'sentAlert');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Validates that a value is an integer or null/undefined.
|
|
88
|
+
* @private
|
|
89
|
+
* @param {*} value - Value to validate
|
|
90
|
+
* @param {string} fieldName - Field name for error messages
|
|
91
|
+
* @returns {number|null}
|
|
92
|
+
* @throws {TypeError} If value is not an integer
|
|
93
|
+
*/
|
|
94
|
+
_validateInteger(value, fieldName) {
|
|
95
|
+
if (value === undefined || value === null) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const num = Number(value);
|
|
99
|
+
if (!Number.isInteger(num)) {
|
|
100
|
+
throw new TypeError(`${fieldName} must be an integer`);
|
|
101
|
+
}
|
|
102
|
+
return num;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validates that a value is an unsigned integer or null/undefined.
|
|
107
|
+
* @private
|
|
108
|
+
* @param {*} value - Value to validate
|
|
109
|
+
* @param {string} fieldName - Field name for error messages
|
|
110
|
+
* @returns {number|null}
|
|
111
|
+
* @throws {TypeError} If value is not an unsigned integer
|
|
112
|
+
*/
|
|
113
|
+
_validateUnsignedInteger(value, fieldName) {
|
|
114
|
+
if (value === undefined || value === null) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const num = Number(value);
|
|
118
|
+
if (!Number.isInteger(num) || num < 0) {
|
|
119
|
+
throw new TypeError(`${fieldName} must be an unsigned integer`);
|
|
120
|
+
}
|
|
121
|
+
return num;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* RTCErrorDetailType - specific error category.
|
|
126
|
+
* @type {string}
|
|
127
|
+
*/
|
|
128
|
+
get errorDetail() {
|
|
129
|
+
return this._errorDetail;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* SDP line number where the error occurred (if applicable).
|
|
134
|
+
* @type {number|null}
|
|
135
|
+
*/
|
|
136
|
+
get sdpLineNumber() {
|
|
137
|
+
return this._sdpLineNumber;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* HTTP request status code (if applicable).
|
|
142
|
+
* @type {number|null}
|
|
143
|
+
*/
|
|
144
|
+
get httpRequestStatusCode() {
|
|
145
|
+
return this._httpRequestStatusCode;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* SCTP cause code (if applicable).
|
|
150
|
+
* @type {number|null}
|
|
151
|
+
*/
|
|
152
|
+
get sctpCauseCode() {
|
|
153
|
+
return this._sctpCauseCode;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* TLS alert value received (if applicable).
|
|
158
|
+
* @type {number|null}
|
|
159
|
+
*/
|
|
160
|
+
get receivedAlert() {
|
|
161
|
+
return this._receivedAlert;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* TLS alert value sent (if applicable).
|
|
166
|
+
* @type {number|null}
|
|
167
|
+
*/
|
|
168
|
+
get sentAlert() {
|
|
169
|
+
return this._sentAlert;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Converts error to JSON representation.
|
|
174
|
+
* @returns {Object} JSON representation of the error
|
|
175
|
+
*/
|
|
176
|
+
toJSON() {
|
|
177
|
+
const json = {
|
|
178
|
+
name: this.name,
|
|
179
|
+
message: this.message,
|
|
180
|
+
errorDetail: this._errorDetail
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
if (this._sdpLineNumber !== null) {
|
|
184
|
+
json.sdpLineNumber = this._sdpLineNumber;
|
|
185
|
+
}
|
|
186
|
+
if (this._httpRequestStatusCode !== null) {
|
|
187
|
+
json.httpRequestStatusCode = this._httpRequestStatusCode;
|
|
188
|
+
}
|
|
189
|
+
if (this._sctpCauseCode !== null) {
|
|
190
|
+
json.sctpCauseCode = this._sctpCauseCode;
|
|
191
|
+
}
|
|
192
|
+
if (this._receivedAlert !== null) {
|
|
193
|
+
json.receivedAlert = this._receivedAlert;
|
|
194
|
+
}
|
|
195
|
+
if (this._sentAlert !== null) {
|
|
196
|
+
json.sentAlert = this._sentAlert;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return json;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Creates RTCError from a native WebRTC error object.
|
|
204
|
+
* @param {Object} nativeError - Native error object
|
|
205
|
+
* @param {string} [nativeError.error_detail] - Error detail type
|
|
206
|
+
* @param {number} [nativeError.sctp_cause_code] - SCTP cause code
|
|
207
|
+
* @param {string} [nativeError.message] - Error message
|
|
208
|
+
* @returns {RTCError}
|
|
209
|
+
*/
|
|
210
|
+
static fromNative(nativeError) {
|
|
211
|
+
const init = {
|
|
212
|
+
errorDetail: nativeError.error_detail || RTCErrorDetailType.NONE
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (nativeError.sctp_cause_code !== undefined) {
|
|
216
|
+
init.sctpCauseCode = nativeError.sctp_cause_code;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return new RTCError(init, nativeError.message || 'Unknown error');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Export error detail types as static property
|
|
224
|
+
RTCError.DetailType = RTCErrorDetailType;
|
|
225
|
+
|
|
226
|
+
module.exports = RTCError;
|