node-rtc-connection 1.0.18 → 2.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.
Files changed (65) hide show
  1. package/README.md +94 -85
  2. package/dist/index.cjs +20 -5421
  3. package/dist/index.mjs +25 -5413
  4. package/dist/types/crypto/der.d.ts +107 -0
  5. package/dist/types/crypto/x509.d.ts +56 -0
  6. package/dist/types/datachannel/RTCDataChannel.d.ts +179 -0
  7. package/dist/types/dtls/RTCCertificate.d.ts +163 -0
  8. package/dist/types/dtls/cipher.d.ts +81 -0
  9. package/dist/types/dtls/connection.d.ts +81 -0
  10. package/dist/types/dtls/prf.d.ts +29 -0
  11. package/dist/types/dtls/protocol.d.ts +127 -0
  12. package/dist/types/foundation/ByteBufferQueue.d.ts +71 -0
  13. package/dist/types/foundation/RTCError.d.ts +152 -0
  14. package/dist/types/ice/RTCIceCandidate.d.ts +161 -0
  15. package/dist/types/ice/ice-agent.d.ts +154 -0
  16. package/dist/types/ice/stun-message.d.ts +92 -0
  17. package/dist/types/index.d.ts +29 -0
  18. package/dist/types/peerconnection/RTCPeerConnection.d.ts +74 -0
  19. package/dist/types/sctp/association.d.ts +77 -0
  20. package/dist/types/sctp/chunks.d.ts +200 -0
  21. package/dist/types/sctp/crc32c.d.ts +24 -0
  22. package/dist/types/sctp/datachannel-manager.d.ts +51 -0
  23. package/dist/types/sctp/dcep.d.ts +56 -0
  24. package/dist/types/sdp/RTCSessionDescription.d.ts +73 -0
  25. package/dist/types/sdp/sdp-utils.d.ts +103 -0
  26. package/dist/types/stun/stun-client.d.ts +119 -0
  27. package/dist/types/transport-stack.d.ts +68 -0
  28. package/package.json +26 -21
  29. package/src/crypto/der.ts +205 -0
  30. package/src/crypto/x509.ts +146 -0
  31. package/src/datachannel/RTCDataChannel.ts +388 -0
  32. package/src/dtls/RTCCertificate.ts +396 -0
  33. package/src/dtls/cipher.ts +198 -0
  34. package/src/dtls/connection.ts +974 -0
  35. package/src/dtls/prf.ts +62 -0
  36. package/src/dtls/protocol.ts +204 -0
  37. package/src/foundation/{ByteBufferQueue.js → ByteBufferQueue.ts} +74 -72
  38. package/src/foundation/{RTCError.js → RTCError.ts} +110 -60
  39. package/src/ice/{RTCIceCandidate.js → RTCIceCandidate.ts} +140 -92
  40. package/src/ice/ice-agent.ts +609 -0
  41. package/src/ice/stun-message.ts +260 -0
  42. package/src/index.ts +72 -0
  43. package/src/peerconnection/RTCPeerConnection.ts +430 -0
  44. package/src/sctp/association.ts +523 -0
  45. package/src/sctp/chunks.ts +350 -0
  46. package/src/sctp/crc32c.ts +57 -0
  47. package/src/sctp/datachannel-manager.ts +187 -0
  48. package/src/sctp/dcep.ts +94 -0
  49. package/src/sdp/{RTCSessionDescription.js → RTCSessionDescription.ts} +42 -29
  50. package/src/sdp/sdp-utils.ts +229 -0
  51. package/src/stun/stun-client.ts +936 -0
  52. package/src/transport-stack.ts +165 -0
  53. package/dist/index.cjs.map +0 -1
  54. package/dist/index.mjs.map +0 -1
  55. package/src/datachannel/RTCDataChannel.js +0 -354
  56. package/src/dtls/RTCCertificate.js +0 -310
  57. package/src/dtls/RTCDtlsTransport.js +0 -247
  58. package/src/ice/RTCIceTransport.js +0 -998
  59. package/src/index.d.ts +0 -400
  60. package/src/index.js +0 -92
  61. package/src/network/network-transport.js +0 -478
  62. package/src/peerconnection/RTCPeerConnection.js +0 -851
  63. package/src/sctp/RTCSctpTransport.js +0 -253
  64. package/src/sdp/sdp-utils.js +0 -224
  65. package/src/stun/stun-client.js +0 -643
@@ -1,11 +1,10 @@
1
1
  /**
2
- * @file RTCSessionDescription.js
2
+ * @file RTCSessionDescription.ts
3
3
  * @description Session Description Protocol (SDP) representation
4
4
  * @module sdp/RTCSessionDescription
5
- *
6
- * Ported from Chromium's RTCSessionDescription implementation:
7
- * - cc/rtc_session_description.idl
8
- * - cc/rtc_session_description.h
5
+ *
6
+ * Implements the W3C RTCSessionDescription interface
7
+ * (https://www.w3.org/TR/webrtc/#rtcsessiondescription-class).
9
8
  */
10
9
 
11
10
  'use strict';
@@ -15,37 +14,56 @@
15
14
  * @readonly
16
15
  * @enum {string}
17
16
  */
18
- const RTCSdpType = Object.freeze({
17
+ export const RTCSdpType: Readonly<Record<string, string>> = Object.freeze({
19
18
  OFFER: 'offer',
20
19
  PRANSWER: 'pranswer',
21
20
  ANSWER: 'answer',
22
21
  ROLLBACK: 'rollback'
23
22
  });
24
23
 
24
+ /**
25
+ * Session description init object.
26
+ */
27
+ export interface RTCSessionDescriptionInit {
28
+ type?: string;
29
+ sdp?: string;
30
+ }
31
+
32
+ /**
33
+ * JSON representation of an RTCSessionDescription.
34
+ */
35
+ export interface RTCSessionDescriptionJSON {
36
+ type: string | null;
37
+ sdp: string | null;
38
+ }
39
+
25
40
  /**
26
41
  * @class RTCSessionDescription
27
42
  * @description Represents a WebRTC session description (offer/answer)
28
- *
43
+ *
29
44
  * @example
30
45
  * const desc = new RTCSessionDescription({
31
46
  * type: 'offer',
32
47
  * sdp: 'v=0\r\no=- 123456 2 IN IP4 127.0.0.1\r\n...'
33
48
  * });
34
49
  */
35
- class RTCSessionDescription {
50
+ export class RTCSessionDescription {
51
+ #type: string | null;
52
+ #sdp: string | null;
53
+
36
54
  /**
37
55
  * Create an RTCSessionDescription instance.
38
56
  * @param {Object} [init] - Session description init
39
57
  * @param {string} [init.type] - SDP type (offer/answer/pranswer/rollback)
40
58
  * @param {string} [init.sdp] - SDP string
41
59
  */
42
- constructor(init = {}) {
43
- this._type = init.type || null;
44
- this._sdp = init.sdp || null;
60
+ constructor(init: RTCSessionDescriptionInit = {}) {
61
+ this.#type = init.type || null;
62
+ this.#sdp = init.sdp || null;
45
63
 
46
64
  // Validate type if provided
47
- if (this._type && !Object.values(RTCSdpType).includes(this._type)) {
48
- throw new TypeError(`Invalid SDP type: ${this._type}`);
65
+ if (this.#type && !Object.values(RTCSdpType).includes(this.#type)) {
66
+ throw new TypeError(`Invalid SDP type: ${this.#type}`);
49
67
  }
50
68
  }
51
69
 
@@ -53,50 +71,45 @@ class RTCSessionDescription {
53
71
  * Get the SDP type.
54
72
  * @returns {string|null} SDP type
55
73
  */
56
- get type() {
57
- return this._type;
74
+ get type(): string | null {
75
+ return this.#type;
58
76
  }
59
77
 
60
78
  /**
61
79
  * Set the SDP type.
62
80
  * @param {string} value - SDP type
63
81
  */
64
- set type(value) {
82
+ set type(value: string | null) {
65
83
  if (value && !Object.values(RTCSdpType).includes(value)) {
66
84
  throw new TypeError(`Invalid SDP type: ${value}`);
67
85
  }
68
- this._type = value;
86
+ this.#type = value;
69
87
  }
70
88
 
71
89
  /**
72
90
  * Get the SDP string.
73
91
  * @returns {string|null} SDP string
74
92
  */
75
- get sdp() {
76
- return this._sdp;
93
+ get sdp(): string | null {
94
+ return this.#sdp;
77
95
  }
78
96
 
79
97
  /**
80
98
  * Set the SDP string.
81
99
  * @param {string} value - SDP string
82
100
  */
83
- set sdp(value) {
84
- this._sdp = value;
101
+ set sdp(value: string | null) {
102
+ this.#sdp = value;
85
103
  }
86
104
 
87
105
  /**
88
106
  * Convert to JSON representation.
89
107
  * @returns {Object} JSON representation
90
108
  */
91
- toJSON() {
109
+ toJSON(): RTCSessionDescriptionJSON {
92
110
  return {
93
- type: this._type,
94
- sdp: this._sdp
111
+ type: this.#type,
112
+ sdp: this.#sdp
95
113
  };
96
114
  }
97
115
  }
98
-
99
- module.exports = {
100
- RTCSessionDescription,
101
- RTCSdpType
102
- };
@@ -0,0 +1,229 @@
1
+ /**
2
+ * @file sdp-utils.ts
3
+ * @description SDP generation/parsing for a WebRTC data-channel m-section.
4
+ * @module sdp/sdp-utils
5
+ *
6
+ * Emits the standard application m-line with the SCTP-over-DTLS profile and
7
+ * conveys transport addresses through ICE candidates (not the c=/m= lines, per
8
+ * the WebRTC JSEP/SDP rules). This is what browsers expect.
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ import * as crypto from 'crypto';
14
+
15
+ /** ICE credentials. */
16
+ export interface IceCredentials {
17
+ usernameFragment: string;
18
+ password: string;
19
+ }
20
+
21
+ /** DTLS certificate fingerprint. */
22
+ export interface Fingerprint {
23
+ algorithm: string;
24
+ value: string;
25
+ }
26
+
27
+ /** A candidate descriptor as accepted by {@link buildSdp}. */
28
+ export interface CandidateInput {
29
+ sdp?: string;
30
+ candidate?: string;
31
+ }
32
+
33
+ /** Options accepted by {@link buildSdp}. */
34
+ export interface BuildSdpOptions {
35
+ kind?: 'offer' | 'answer';
36
+ iceUfrag: string;
37
+ icePwd: string;
38
+ fingerprint?: Fingerprint;
39
+ setup?: string;
40
+ candidates?: CandidateInput[];
41
+ sctpPort?: number;
42
+ maxMessageSize?: number;
43
+ }
44
+
45
+ /** Options accepted by {@link generateOffer} / {@link generateAnswer}. */
46
+ export interface GenerateOptions extends BuildSdpOptions {}
47
+
48
+ /** Parsed ICE parameters. */
49
+ export interface IceParameters {
50
+ usernameFragment: string | null;
51
+ password: string | null;
52
+ }
53
+
54
+ /** Parsed DTLS parameters. */
55
+ export interface DtlsParameters {
56
+ role: string;
57
+ fingerprints: Fingerprint[];
58
+ setup?: string;
59
+ }
60
+
61
+ /** Parsed SCTP parameters. */
62
+ export interface SctpParameters {
63
+ port: number;
64
+ maxMessageSize: number;
65
+ }
66
+
67
+ /** A parsed ICE candidate. */
68
+ export interface ParsedCandidate {
69
+ candidate: string;
70
+ foundation: string;
71
+ component: number;
72
+ protocol: string;
73
+ priority: number;
74
+ address: string;
75
+ port: number;
76
+ type: string;
77
+ }
78
+
79
+ /**
80
+ * Generate ICE credentials (ufrag >= 4 chars, pwd >= 22 chars per RFC 8445).
81
+ * @returns {{usernameFragment:string, password:string}}
82
+ */
83
+ export function generateIceCredentials(): IceCredentials {
84
+ return {
85
+ usernameFragment: crypto.randomBytes(3).toString('base64').replace(/[^a-zA-Z0-9]/g, '').slice(0, 4).padEnd(4, 'x'),
86
+ password: crypto.randomBytes(18).toString('base64').replace(/[^a-zA-Z0-9]/g, '').slice(0, 24).padEnd(24, 'x'),
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Build the SDP for a data-channel-only session.
92
+ * @param {Object} o
93
+ * @param {'offer'|'answer'} o.kind
94
+ * @param {string} o.iceUfrag
95
+ * @param {string} o.icePwd
96
+ * @param {{algorithm:string,value:string}} o.fingerprint - DTLS cert fingerprint
97
+ * @param {string} o.setup - 'actpass' | 'active' | 'passive'
98
+ * @param {Array<{sdp?:string,candidate?:string}>} [o.candidates]
99
+ * @param {number} [o.sctpPort=5000]
100
+ * @param {number} [o.maxMessageSize=262144]
101
+ * @returns {string}
102
+ */
103
+ export function buildSdp(o: BuildSdpOptions): string {
104
+ const {
105
+ iceUfrag,
106
+ icePwd,
107
+ fingerprint,
108
+ setup = 'actpass',
109
+ candidates = [],
110
+ sctpPort = 5000,
111
+ maxMessageSize = 262144,
112
+ } = o;
113
+
114
+ const lines: string[] = [];
115
+ lines.push('v=0');
116
+ // Session id is arbitrary; use random to avoid Date.now noise.
117
+ const sessId = crypto.randomBytes(4).readUInt32BE(0);
118
+ lines.push(`o=- ${sessId} 2 IN IP4 127.0.0.1`);
119
+ lines.push('s=-');
120
+ lines.push('t=0 0');
121
+ lines.push('a=group:BUNDLE 0');
122
+ lines.push('a=msid-semantic: WMS');
123
+
124
+ // The port in the m-line is the standard placeholder 9; addresses come from
125
+ // ICE candidates. Proto reflects the real transport: DTLS/SCTP.
126
+ lines.push('m=application 9 UDP/DTLS/SCTP webrtc-datachannel');
127
+ lines.push('c=IN IP4 0.0.0.0');
128
+ lines.push('a=ice-ufrag:' + iceUfrag);
129
+ lines.push('a=ice-pwd:' + icePwd);
130
+ lines.push('a=ice-options:trickle');
131
+ if (fingerprint) {
132
+ lines.push(`a=fingerprint:${fingerprint.algorithm} ${fingerprint.value}`);
133
+ }
134
+ lines.push(`a=setup:${setup}`);
135
+ lines.push('a=mid:0');
136
+ lines.push('a=sctp-port:' + sctpPort);
137
+ lines.push('a=max-message-size:' + maxMessageSize);
138
+
139
+ for (const c of candidates) {
140
+ const cstr = c.sdp || c.candidate;
141
+ if (cstr) lines.push('a=' + (cstr.startsWith('candidate:') ? cstr : 'candidate:' + cstr));
142
+ }
143
+
144
+ return lines.join('\r\n') + '\r\n';
145
+ }
146
+
147
+ export function generateOffer(opts: GenerateOptions): string {
148
+ return buildSdp({ ...opts, kind: 'offer', setup: opts.setup || 'actpass' });
149
+ }
150
+
151
+ export function generateAnswer(opts: GenerateOptions): string {
152
+ return buildSdp({ ...opts, kind: 'answer', setup: opts.setup || 'active' });
153
+ }
154
+
155
+ /** Parse ICE ufrag/pwd. */
156
+ export function parseIceParameters(sdp: string): IceParameters {
157
+ const params: IceParameters = { usernameFragment: null, password: null };
158
+ for (const line of sdp.split(/\r?\n/)) {
159
+ if (line.startsWith('a=ice-ufrag:')) params.usernameFragment = line.slice(12).trim();
160
+ else if (line.startsWith('a=ice-pwd:')) params.password = line.slice(10).trim();
161
+ }
162
+ return params;
163
+ }
164
+
165
+ /** Parse DTLS setup role + fingerprints. */
166
+ export function parseDtlsParameters(sdp: string): DtlsParameters {
167
+ const params: DtlsParameters = { role: 'auto', fingerprints: [] };
168
+ for (const line of sdp.split(/\r?\n/)) {
169
+ if (line.startsWith('a=setup:')) {
170
+ const setup = line.slice(8).trim();
171
+ if (setup === 'active') params.role = 'client';
172
+ else if (setup === 'passive') params.role = 'server';
173
+ else params.role = 'actpass';
174
+ params.setup = setup;
175
+ } else if (line.startsWith('a=fingerprint:')) {
176
+ const parts = line.slice(14).trim().split(/\s+/);
177
+ if (parts.length === 2) {
178
+ params.fingerprints.push({ algorithm: parts[0]!.toLowerCase(), value: parts[1]!.toUpperCase() });
179
+ }
180
+ }
181
+ }
182
+ return params;
183
+ }
184
+
185
+ /** Parse SCTP port / max message size. */
186
+ export function parseSctpParameters(sdp: string): SctpParameters {
187
+ const params: SctpParameters = { port: 5000, maxMessageSize: 262144 };
188
+ for (const line of sdp.split(/\r?\n/)) {
189
+ if (line.startsWith('a=sctp-port:')) params.port = parseInt(line.slice(12), 10);
190
+ else if (line.startsWith('a=max-message-size:')) params.maxMessageSize = parseInt(line.slice(19), 10);
191
+ }
192
+ return params;
193
+ }
194
+
195
+ /**
196
+ * Parse ICE candidate lines into structured objects.
197
+ * @param {string} sdp
198
+ * @returns {Array<{candidate:string,foundation:string,component:number,protocol:string,priority:number,address:string,port:number,type:string}>}
199
+ */
200
+ export function parseCandidates(sdp: string): ParsedCandidate[] {
201
+ const out: ParsedCandidate[] = [];
202
+ for (const line of sdp.split(/\r?\n/)) {
203
+ if (!line.startsWith('a=candidate:')) continue;
204
+ const c = parseCandidateLine(line.slice(2));
205
+ if (c) out.push(c);
206
+ }
207
+ return out;
208
+ }
209
+
210
+ /**
211
+ * Parse a single "candidate:..." string.
212
+ * @param {string} str
213
+ */
214
+ export function parseCandidateLine(str: string): ParsedCandidate | null {
215
+ // candidate:<foundation> <component> <protocol> <priority> <address> <port> typ <type> ...
216
+ const s = str.startsWith('candidate:') ? str.slice('candidate:'.length) : str;
217
+ const t = s.split(/\s+/);
218
+ if (t.length < 8) return null;
219
+ return {
220
+ candidate: str.startsWith('candidate:') ? str : 'candidate:' + str,
221
+ foundation: t[0]!,
222
+ component: parseInt(t[1]!, 10),
223
+ protocol: t[2]!.toLowerCase(),
224
+ priority: parseInt(t[3]!, 10) >>> 0,
225
+ address: t[4]!,
226
+ port: parseInt(t[5]!, 10),
227
+ type: t[7]!,
228
+ };
229
+ }