@agentdance/node-webrtc 1.0.0

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 (66) hide show
  1. package/dist/data-channel.d.ts +33 -0
  2. package/dist/data-channel.d.ts.map +1 -0
  3. package/dist/data-channel.js +138 -0
  4. package/dist/data-channel.js.map +1 -0
  5. package/dist/ice-candidate.d.ts +30 -0
  6. package/dist/ice-candidate.d.ts.map +1 -0
  7. package/dist/ice-candidate.js +78 -0
  8. package/dist/ice-candidate.js.map +1 -0
  9. package/dist/index.d.ts +9 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +9 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/internal/peer-internals.d.ts +55 -0
  14. package/dist/internal/peer-internals.d.ts.map +1 -0
  15. package/dist/internal/peer-internals.js +412 -0
  16. package/dist/internal/peer-internals.js.map +1 -0
  17. package/dist/internal/sdp-factory.d.ts +9 -0
  18. package/dist/internal/sdp-factory.d.ts.map +1 -0
  19. package/dist/internal/sdp-factory.js +58 -0
  20. package/dist/internal/sdp-factory.js.map +1 -0
  21. package/dist/internal/sdp-parser.d.ts +15 -0
  22. package/dist/internal/sdp-parser.d.ts.map +1 -0
  23. package/dist/internal/sdp-parser.js +70 -0
  24. package/dist/internal/sdp-parser.js.map +1 -0
  25. package/dist/peer-connection.d.ts +72 -0
  26. package/dist/peer-connection.d.ts.map +1 -0
  27. package/dist/peer-connection.js +198 -0
  28. package/dist/peer-connection.js.map +1 -0
  29. package/dist/rtp-receiver.d.ts +28 -0
  30. package/dist/rtp-receiver.d.ts.map +1 -0
  31. package/dist/rtp-receiver.js +39 -0
  32. package/dist/rtp-receiver.js.map +1 -0
  33. package/dist/rtp-sender.d.ts +29 -0
  34. package/dist/rtp-sender.d.ts.map +1 -0
  35. package/dist/rtp-sender.js +58 -0
  36. package/dist/rtp-sender.js.map +1 -0
  37. package/dist/rtp-transceiver.d.ts +25 -0
  38. package/dist/rtp-transceiver.d.ts.map +1 -0
  39. package/dist/rtp-transceiver.js +28 -0
  40. package/dist/rtp-transceiver.js.map +1 -0
  41. package/dist/session-description.d.ts +8 -0
  42. package/dist/session-description.d.ts.map +1 -0
  43. package/dist/session-description.js +12 -0
  44. package/dist/session-description.js.map +1 -0
  45. package/dist/stats.d.ts +16 -0
  46. package/dist/stats.d.ts.map +1 -0
  47. package/dist/stats.js +22 -0
  48. package/dist/stats.js.map +1 -0
  49. package/dist/types.d.ts +113 -0
  50. package/dist/types.d.ts.map +1 -0
  51. package/dist/types.js +2 -0
  52. package/dist/types.js.map +1 -0
  53. package/package.json +68 -0
  54. package/src/data-channel.ts +165 -0
  55. package/src/ice-candidate.ts +91 -0
  56. package/src/index.ts +8 -0
  57. package/src/internal/peer-internals.ts +477 -0
  58. package/src/internal/sdp-factory.ts +81 -0
  59. package/src/internal/sdp-parser.ts +69 -0
  60. package/src/peer-connection.ts +253 -0
  61. package/src/rtp-receiver.ts +51 -0
  62. package/src/rtp-sender.ts +77 -0
  63. package/src/rtp-transceiver.ts +40 -0
  64. package/src/session-description.ts +15 -0
  65. package/src/stats.ts +35 -0
  66. package/src/types.ts +156 -0
@@ -0,0 +1,113 @@
1
+ export type RTCSignalingState = 'stable' | 'have-local-offer' | 'have-remote-offer' | 'have-local-pranswer' | 'have-remote-pranswer' | 'closed';
2
+ export type RTCPeerConnectionState = 'new' | 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed';
3
+ export type RTCIceConnectionState = 'new' | 'checking' | 'connected' | 'completed' | 'failed' | 'disconnected' | 'closed';
4
+ export type RTCIceGatheringState = 'new' | 'gathering' | 'complete';
5
+ export type RTCSdpType = 'offer' | 'pranswer' | 'answer' | 'rollback';
6
+ export interface RTCSessionDescriptionInit {
7
+ type: RTCSdpType;
8
+ sdp: string;
9
+ }
10
+ export interface RTCSessionDescription {
11
+ type: RTCSdpType;
12
+ sdp: string;
13
+ }
14
+ export interface RTCIceCandidateInit {
15
+ candidate: string;
16
+ sdpMid?: string;
17
+ sdpMLineIndex?: number;
18
+ usernameFragment?: string;
19
+ }
20
+ export interface RTCIceServer {
21
+ urls: string | string[];
22
+ username?: string;
23
+ credential?: string;
24
+ }
25
+ export interface RTCConfiguration {
26
+ iceServers?: RTCIceServer[];
27
+ iceTransportPolicy?: 'all' | 'relay';
28
+ bundlePolicy?: 'balanced' | 'max-bundle' | 'max-compat';
29
+ rtcpMuxPolicy?: 'require';
30
+ iceCandidatePoolSize?: number;
31
+ certificates?: RTCDtlsTransportCertificate[];
32
+ }
33
+ export interface RTCDtlsTransportCertificate {
34
+ fingerprint: {
35
+ algorithm: string;
36
+ value: string;
37
+ };
38
+ }
39
+ export interface RTCOfferOptions {
40
+ iceRestart?: boolean;
41
+ offerToReceiveAudio?: boolean;
42
+ offerToReceiveVideo?: boolean;
43
+ }
44
+ export interface RTCAnswerOptions {
45
+ }
46
+ export type RTCRtpTransceiverDirection = 'sendrecv' | 'sendonly' | 'recvonly' | 'inactive' | 'stopped';
47
+ export interface RTCRtpCodecParameters {
48
+ mimeType: string;
49
+ clockRate: number;
50
+ channels?: number;
51
+ sdpFmtpLine?: string;
52
+ payloadType: number;
53
+ }
54
+ export interface RTCRtpHeaderExtensionParameters {
55
+ uri: string;
56
+ id: number;
57
+ encrypted?: boolean;
58
+ }
59
+ export interface RTCRtpParameters {
60
+ codecs: RTCRtpCodecParameters[];
61
+ headerExtensions: RTCRtpHeaderExtensionParameters[];
62
+ }
63
+ export interface RTCRtpSendParameters extends RTCRtpParameters {
64
+ encodings: RTCRtpEncodingParameters[];
65
+ transactionId: string;
66
+ }
67
+ export interface RTCRtpReceiveParameters extends RTCRtpParameters {
68
+ }
69
+ export interface RTCRtpEncodingParameters {
70
+ rid?: string;
71
+ active?: boolean;
72
+ maxBitrate?: number;
73
+ scaleResolutionDownBy?: number;
74
+ ssrc?: number;
75
+ }
76
+ export interface RTCSctpTransportInit {
77
+ maxMessageSize: number;
78
+ maxChannels: number;
79
+ state: 'new' | 'connecting' | 'connected' | 'closed';
80
+ }
81
+ export type RTCDataChannelState = 'connecting' | 'open' | 'closing' | 'closed';
82
+ export interface RTCDataChannelInit {
83
+ ordered?: boolean;
84
+ maxPacketLifeTime?: number;
85
+ maxRetransmits?: number;
86
+ protocol?: string;
87
+ negotiated?: boolean;
88
+ id?: number;
89
+ priority?: 'very-low' | 'low' | 'medium' | 'high';
90
+ }
91
+ export interface RTCStats {
92
+ id: string;
93
+ type: string;
94
+ timestamp: number;
95
+ }
96
+ export interface RTCIceCandidatePairStats extends RTCStats {
97
+ type: 'candidate-pair';
98
+ localCandidateId: string;
99
+ remoteCandidateId: string;
100
+ state: string;
101
+ nominated: boolean;
102
+ bytesSent: number;
103
+ bytesReceived: number;
104
+ totalRoundTripTime: number;
105
+ currentRoundTripTime?: number;
106
+ }
107
+ export interface RTCStatsReport {
108
+ entries(): IterableIterator<[string, RTCStats]>;
109
+ get(id: string): RTCStats | undefined;
110
+ has(id: string): boolean;
111
+ forEach(callbackfn: (value: RTCStats, key: string) => void): void;
112
+ }
113
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@agentdance/node-webrtc",
3
+ "version": "1.0.0",
4
+ "description": "Production-grade WebRTC in pure TypeScript — RTCPeerConnection, DataChannel, ICE, DTLS, SCTP, SRTP. Zero native bindings.",
5
+ "keywords": [
6
+ "webrtc",
7
+ "rtcpeerconnection",
8
+ "datachannel",
9
+ "ice",
10
+ "dtls",
11
+ "sctp",
12
+ "srtp",
13
+ "p2p",
14
+ "typescript",
15
+ "node"
16
+ ],
17
+ "license": "MIT",
18
+ "homepage": "https://github.com/agent-dance/node-webrtc#readme",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/agent-dance/node-webrtc.git",
22
+ "directory": "packages/webrtc"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/agent-dance/node-webrtc/issues"
26
+ },
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "type": "module",
31
+ "main": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "import": "./dist/index.js",
36
+ "types": "./dist/index.d.ts"
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "src"
42
+ ],
43
+ "sideEffects": false,
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "registry": "https://registry.npmjs.org/"
47
+ },
48
+ "dependencies": {
49
+ "@agentdance/node-webrtc-sdp": "1.0.0",
50
+ "@agentdance/node-webrtc-ice": "1.0.0",
51
+ "@agentdance/node-webrtc-dtls": "1.0.0",
52
+ "@agentdance/node-webrtc-srtp": "1.0.0",
53
+ "@agentdance/node-webrtc-rtp": "1.0.0",
54
+ "@agentdance/node-webrtc-sctp": "1.0.0",
55
+ "@agentdance/node-webrtc-stun": "1.0.0"
56
+ },
57
+ "devDependencies": {
58
+ "typescript": "*",
59
+ "vitest": "*",
60
+ "@types/node": "*"
61
+ },
62
+ "scripts": {
63
+ "build": "tsc",
64
+ "test": "vitest run",
65
+ "typecheck": "tsc --noEmit",
66
+ "clean": "rm -rf dist"
67
+ }
68
+ }
@@ -0,0 +1,165 @@
1
+ import { EventEmitter } from 'events';
2
+ import type { RTCDataChannelInit, RTCDataChannelState } from './types.js';
3
+ import type { SctpDataChannel } from '@agentdance/node-webrtc-sctp';
4
+
5
+ export declare interface RTCDataChannel {
6
+ on(event: 'open', listener: () => void): this;
7
+ on(event: 'message', listener: (data: Buffer | string | ArrayBuffer) => void): this;
8
+ on(event: 'close', listener: () => void): this;
9
+ on(event: 'closing', listener: () => void): this;
10
+ on(event: 'error', listener: (err: Error) => void): this;
11
+ on(event: 'bufferedamountlow', listener: () => void): this;
12
+ }
13
+
14
+ export class RTCDataChannel extends EventEmitter {
15
+ readonly label: string;
16
+ readonly ordered: boolean;
17
+ readonly maxPacketLifeTime: number | null;
18
+ readonly maxRetransmits: number | null;
19
+ readonly protocol: string;
20
+ readonly negotiated: boolean;
21
+ id: number | null;
22
+ readyState: RTCDataChannelState = 'connecting';
23
+ binaryType: 'blob' | 'arraybuffer' = 'arraybuffer';
24
+
25
+ // bufferedAmount tracks bytes enqueued but not yet acknowledged
26
+ private _bufferedAmount = 0;
27
+ private _bufferedAmountLowThreshold = 0;
28
+
29
+ // Internal reference to the SCTP layer channel
30
+ _sctpChannel: SctpDataChannel | null;
31
+
32
+ constructor(
33
+ label: string,
34
+ init: RTCDataChannelInit,
35
+ sctpChannel: SctpDataChannel | null,
36
+ ) {
37
+ super();
38
+ this.label = label;
39
+ this.ordered = init.ordered ?? true;
40
+ this.maxPacketLifeTime = init.maxPacketLifeTime ?? null;
41
+ this.maxRetransmits = init.maxRetransmits ?? null;
42
+ this.protocol = init.protocol ?? '';
43
+ this.negotiated = init.negotiated ?? false;
44
+ this.id = init.id ?? null;
45
+ this._sctpChannel = sctpChannel;
46
+
47
+ if (sctpChannel) {
48
+ this._bindSctpChannel(sctpChannel);
49
+ }
50
+ }
51
+
52
+ get bufferedAmount(): number {
53
+ // Delegate to the underlying SCTP channel so that bufferedAmount tracks
54
+ // only bytes that have been enqueued but not yet acknowledged by the remote
55
+ // peer. This allows backpressure checks (channel.bufferedAmount >
56
+ // HIGH_WATERMARK) to work correctly.
57
+ if (this._sctpChannel) {
58
+ return this._sctpChannel.bufferedAmount;
59
+ }
60
+ return this._bufferedAmount;
61
+ }
62
+
63
+ get bufferedAmountLowThreshold(): number {
64
+ return this._bufferedAmountLowThreshold;
65
+ }
66
+
67
+ set bufferedAmountLowThreshold(value: number) {
68
+ this._bufferedAmountLowThreshold = value;
69
+ // Propagate to underlying SCTP channel if bound
70
+ if (this._sctpChannel) {
71
+ this._sctpChannel.bufferedAmountLowThreshold = value;
72
+ }
73
+ }
74
+
75
+ _bindSctpChannel(channel: SctpDataChannel): void {
76
+ this._sctpChannel = channel;
77
+ this.id = channel.id;
78
+
79
+ // Sync threshold
80
+ channel.bufferedAmountLowThreshold = this._bufferedAmountLowThreshold;
81
+
82
+ channel.on('open', () => {
83
+ console.log(`[RTCDataChannel] SCTP 'open' for label="${this.label}" (streamId=${this.id})`);
84
+ this.readyState = 'open';
85
+ this.emit('open');
86
+ });
87
+
88
+ channel.on('message', (data: Buffer | string) => {
89
+ if (data instanceof Buffer && this.binaryType === 'arraybuffer') {
90
+ // Expose as ArrayBuffer to match browser behaviour
91
+ const ab = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
92
+ this.emit('message', ab);
93
+ } else {
94
+ this.emit('message', data);
95
+ }
96
+ });
97
+
98
+ channel.on('close', () => {
99
+ this.readyState = 'closed';
100
+ this._bufferedAmount = 0;
101
+ this.emit('close');
102
+ });
103
+
104
+ channel.on('error', (err: Error) => {
105
+ this.emit('error', err);
106
+ });
107
+
108
+ // Relay bufferedamountlow from SCTP layer
109
+ channel.on('bufferedamountlow', () => {
110
+ this.emit('bufferedamountlow');
111
+ });
112
+
113
+ // If already open (e.g. negotiated channel)
114
+ if (channel.state === 'open') {
115
+ this.readyState = 'open';
116
+ }
117
+ }
118
+
119
+ send(data: string | Buffer | ArrayBuffer | ArrayBufferView): void {
120
+ if (this.readyState !== 'open') {
121
+ const err = new Error(`DataChannel "${this.label}" is not open (state: ${this.readyState})`);
122
+ (err as NodeJS.ErrnoException).code = 'InvalidStateError';
123
+ throw err;
124
+ }
125
+ if (!this._sctpChannel) {
126
+ throw new Error(`DataChannel "${this.label}" has no underlying SCTP channel`);
127
+ }
128
+
129
+ let buf: Buffer;
130
+ if (typeof data === 'string') {
131
+ // Track buffered bytes (UTF-8 encoded size)
132
+ buf = Buffer.from(data, 'utf8');
133
+ this._bufferedAmount += buf.byteLength;
134
+ this._sctpChannel.send(data);
135
+ } else if (data instanceof Buffer) {
136
+ buf = data;
137
+ this._bufferedAmount += buf.byteLength;
138
+ this._sctpChannel.send(buf);
139
+ } else if (data instanceof ArrayBuffer) {
140
+ buf = Buffer.from(data);
141
+ this._bufferedAmount += buf.byteLength;
142
+ this._sctpChannel.send(buf);
143
+ } else {
144
+ // ArrayBufferView
145
+ const view = data as ArrayBufferView;
146
+ buf = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
147
+ this._bufferedAmount += buf.byteLength;
148
+ this._sctpChannel.send(buf);
149
+ }
150
+ }
151
+
152
+ close(): void {
153
+ if (this.readyState === 'closed' || this.readyState === 'closing') return;
154
+ this.readyState = 'closing';
155
+ this.emit('closing');
156
+ if (this._sctpChannel) {
157
+ this._sctpChannel.close();
158
+ // The 'close' event on _sctpChannel transitions us to 'closed'
159
+ } else {
160
+ // No underlying channel – transition immediately
161
+ this.readyState = 'closed';
162
+ this.emit('close');
163
+ }
164
+ }
165
+ }
@@ -0,0 +1,91 @@
1
+ export class RTCIceCandidate {
2
+ readonly candidate: string;
3
+ readonly sdpMid: string | null;
4
+ readonly sdpMLineIndex: number | null;
5
+ readonly usernameFragment: string | null;
6
+
7
+ // Parsed fields
8
+ readonly foundation: string | null = null;
9
+ readonly component: 'rtp' | 'rtcp' | null = null;
10
+ readonly protocol: 'udp' | 'tcp' | null = null;
11
+ readonly priority: number | null = null;
12
+ readonly address: string | null = null;
13
+ readonly port: number | null = null;
14
+ readonly type: 'host' | 'srflx' | 'relay' | 'prflx' | null = null;
15
+ relatedAddress: string | null = null;
16
+ relatedPort: number | null = null;
17
+ tcpType: string | null = null;
18
+
19
+ constructor(init: { candidate: string; sdpMid?: string; sdpMLineIndex?: number; usernameFragment?: string }) {
20
+ this.candidate = init.candidate ?? '';
21
+ this.sdpMid = init.sdpMid ?? null;
22
+ this.sdpMLineIndex = init.sdpMLineIndex ?? null;
23
+ this.usernameFragment = init.usernameFragment ?? null;
24
+
25
+ // Parse candidate string
26
+ const parsed = this._parse(this.candidate);
27
+ if (parsed) {
28
+ (this as { foundation: string | null }).foundation = parsed.foundation;
29
+ (this as { component: 'rtp' | 'rtcp' | null }).component = parsed.component;
30
+ (this as { protocol: 'udp' | 'tcp' | null }).protocol = parsed.protocol;
31
+ (this as { priority: number | null }).priority = parsed.priority;
32
+ (this as { address: string | null }).address = parsed.address;
33
+ (this as { port: number | null }).port = parsed.port;
34
+ (this as { type: 'host' | 'srflx' | 'relay' | 'prflx' | null }).type = parsed.type;
35
+ this.relatedAddress = parsed.relatedAddress;
36
+ this.relatedPort = parsed.relatedPort;
37
+ this.tcpType = parsed.tcpType;
38
+ }
39
+ }
40
+
41
+ private _parse(candidateStr: string): {
42
+ foundation: string | null;
43
+ component: 'rtp' | 'rtcp' | null;
44
+ protocol: 'udp' | 'tcp' | null;
45
+ priority: number | null;
46
+ address: string | null;
47
+ port: number | null;
48
+ type: 'host' | 'srflx' | 'relay' | 'prflx' | null;
49
+ relatedAddress: string | null;
50
+ relatedPort: number | null;
51
+ tcpType: string | null;
52
+ } | null {
53
+ // "candidate:foundation component protocol priority address port typ type ..."
54
+ const str = candidateStr.replace(/^candidate:/, '');
55
+ const parts = str.split(' ');
56
+ if (parts.length < 8) return null;
57
+ const [foundation, componentStr, protocol, priorityStr, address, portStr, , type, ...rest] = parts;
58
+
59
+ const result = {
60
+ foundation: foundation ?? null,
61
+ component: (componentStr === '1' ? 'rtp' : 'rtcp') as 'rtp' | 'rtcp' | null,
62
+ protocol: (protocol?.toLowerCase() as 'udp' | 'tcp') ?? null,
63
+ priority: priorityStr ? parseInt(priorityStr, 10) : null,
64
+ address: address ?? null,
65
+ port: portStr ? parseInt(portStr, 10) : null,
66
+ type: (type as 'host' | 'srflx' | 'relay' | 'prflx') ?? null,
67
+ relatedAddress: null as string | null,
68
+ relatedPort: null as number | null,
69
+ tcpType: null as string | null,
70
+ };
71
+
72
+ // Parse extensions
73
+ for (let i = 0; i < rest.length - 1; i += 2) {
74
+ const key = rest[i];
75
+ const val = rest[i + 1];
76
+ if (key === 'raddr') result.relatedAddress = val ?? null;
77
+ else if (key === 'rport') result.relatedPort = val ? parseInt(val, 10) : null;
78
+ else if (key === 'tcptype') result.tcpType = val ?? null;
79
+ }
80
+ return result;
81
+ }
82
+
83
+ toJSON() {
84
+ return {
85
+ candidate: this.candidate,
86
+ sdpMid: this.sdpMid,
87
+ sdpMLineIndex: this.sdpMLineIndex,
88
+ usernameFragment: this.usernameFragment,
89
+ };
90
+ }
91
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export { RTCPeerConnection } from './peer-connection.js';
2
+ export { RTCSessionDescription } from './session-description.js';
3
+ export { RTCIceCandidate } from './ice-candidate.js';
4
+ export { RTCDataChannel } from './data-channel.js';
5
+ export { RTCRtpSender } from './rtp-sender.js';
6
+ export { RTCRtpReceiver } from './rtp-receiver.js';
7
+ export { RTCRtpTransceiver } from './rtp-transceiver.js';
8
+ export * from './types.js';