node-datachannel 0.7.0 → 0.9.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.
@@ -13,7 +13,7 @@ jobs:
13
13
  - name: Set up Node.js
14
14
  uses: actions/setup-node@v1
15
15
  with:
16
- node-version: 16.x
16
+ node-version: 20.x
17
17
  registry-url: "https://registry.npmjs.org"
18
18
  - name: Publish
19
19
  run: |
package/CMakeLists.txt CHANGED
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.15)
2
2
  cmake_policy(SET CMP0091 NEW)
3
3
  cmake_policy(SET CMP0042 NEW)
4
4
 
5
- project(node_datachannel VERSION 0.7.0)
5
+ project(node_datachannel VERSION 0.9.0)
6
6
 
7
7
  # -Dnapi_build_version=8
8
8
  add_definitions(-DNAPI_VERSION=8)
@@ -29,7 +29,7 @@ include(FetchContent)
29
29
  FetchContent_Declare(
30
30
  libdatachannel
31
31
  GIT_REPOSITORY https://github.com/paullouisageneau/libdatachannel.git
32
- GIT_TAG "v0.20.2"
32
+ GIT_TAG "v0.21.0"
33
33
  )
34
34
 
35
35
  option(NO_MEDIA "Disable media transport support in libdatachannel" OFF)
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "electron-squirrel-startup": "^1.0.0",
37
- "node-datachannel": "^0.5.2"
37
+ "node-datachannel": "^0.8.0"
38
38
  },
39
39
  "cmake-js": {
40
40
  "runtime": "electron",
@@ -0,0 +1,314 @@
1
+ import * as stream from 'stream';
2
+
3
+ export as namespace NodeDataChannel;
4
+
5
+ // Enum in d.ts is tricky
6
+ export type LogLevel = 'Verbose' | 'Debug' | 'Info' | 'Warning' | 'Error' | 'Fatal';
7
+
8
+ // SCTP Settings
9
+ export interface SctpSettings {
10
+ recvBufferSize?: number;
11
+ sendBufferSize?: number;
12
+ maxChunksOnQueue?: number;
13
+ initialCongestionWindow?: number;
14
+ congestionControlModule?: number;
15
+ delayedSackTime?: number;
16
+ }
17
+
18
+ // Functions
19
+ export function preload(): void;
20
+ export function initLogger(level: LogLevel, callback?: (level: LogLevel, message: string) => void): void;
21
+ export function cleanup(): void;
22
+ export function setSctpSettings(settings: SctpSettings): void;
23
+
24
+ // Proxy Server
25
+ export type ProxyServerType = 'Socks5' | 'Http';
26
+ export interface ProxyServer {
27
+ type: ProxyServerType;
28
+ ip: string;
29
+ port: number;
30
+ username?: string;
31
+ password?: string;
32
+ }
33
+
34
+ export const enum RelayType {
35
+ TurnUdp = 'TurnUdp',
36
+ TurnTcp = 'TurnTcp',
37
+ TurnTls = 'TurnTls',
38
+ }
39
+
40
+ export interface IceServer {
41
+ hostname: string;
42
+ port: number;
43
+ username?: string;
44
+ password?: string;
45
+ relayType?: RelayType;
46
+ }
47
+
48
+ export type TransportPolicy = 'all' | 'relay';
49
+
50
+ export interface RtcConfig {
51
+ iceServers: (string | IceServer)[];
52
+ proxyServer?: ProxyServer;
53
+ bindAddress?: string;
54
+ enableIceTcp?: boolean;
55
+ enableIceUdpMux?: boolean;
56
+ disableAutoNegotiation?: boolean;
57
+ forceMediaTransport?: boolean;
58
+ portRangeBegin?: number;
59
+ portRangeEnd?: number;
60
+ maxMessageSize?: number;
61
+ mtu?: number;
62
+ iceTransportPolicy?: TransportPolicy;
63
+ }
64
+
65
+ // Lowercase to match the description type string from libdatachannel
66
+ export const enum DescriptionType {
67
+ Unspec = 'unspec',
68
+ Offer = 'offer',
69
+ Answer = 'answer',
70
+ Pranswer = 'pranswer',
71
+ Rollback = 'rollback',
72
+ }
73
+
74
+ export const enum ReliabilityType {
75
+ Reliable = 0,
76
+ Rexmit = 1,
77
+ Timed = 2,
78
+ }
79
+
80
+ export interface DataChannelInitConfig {
81
+ protocol?: string;
82
+ negotiated?: boolean;
83
+ id?: number;
84
+ ordered?: boolean;
85
+ maxPacketLifeTime?: number;
86
+ maxRetransmits?: number;
87
+
88
+ // Deprecated, use ordered, maxPacketLifeTime, and maxRetransmits
89
+ reliability?: {
90
+ type?: ReliabilityType;
91
+ unordered?: boolean;
92
+ rexmit?: number;
93
+ };
94
+ }
95
+
96
+ export interface SelectedCandidateInfo {
97
+ address: string;
98
+ port: number;
99
+ type: string;
100
+ transportType: string;
101
+ candidate: string;
102
+ mid: string;
103
+ priority: number;
104
+ }
105
+
106
+ // Must be same as rtc enum class Direction
107
+ export const enum Direction {
108
+ SendOnly = 'SendOnly',
109
+ RecvOnly = 'RecvOnly',
110
+ SendRecv = 'SendRecv',
111
+ Inactive = 'Inactive',
112
+ Unknown = 'Unknown',
113
+ }
114
+
115
+ export class RtcpReceivingSession {
116
+ requestBitrate(bitRate: number): void;
117
+ requestKeyframe(): boolean;
118
+ }
119
+
120
+ export class Audio {
121
+ constructor(mid: string, dir: Direction);
122
+ addAudioCodec(payloadType: number, codec: string, profile?: string): void;
123
+ addOpusCodec(payloadType: number, profile?: string): string;
124
+
125
+ direction(): Direction;
126
+ generateSdp(eol: string, addr: string, port: number): string;
127
+ mid(): string;
128
+ setDirection(dir: Direction): void;
129
+ description(): string;
130
+ removeFormat(fmt: string): void;
131
+ addSSRC(ssrc: number, name?: string, msid?: string, trackID?: string): void;
132
+ removeSSRC(ssrc: number): void;
133
+ replaceSSRC(oldSsrc: number, ssrc: number, name?: string, msid?: string, trackID?: string): void;
134
+ hasSSRC(ssrc: number): boolean;
135
+ getSSRCs(): number[];
136
+ getCNameForSsrc(ssrc: number): string;
137
+ setBitrate(bitRate: number): void;
138
+ getBitrate(): number;
139
+ hasPayloadType(payloadType: number): boolean;
140
+ addRTXCodec(payloadType: number, originalPayloadType: number, clockRate: number): void;
141
+ addRTPMap(): void;
142
+ parseSdpLine(line: string): void;
143
+ }
144
+
145
+ export class Video {
146
+ constructor(mid: string, dir: Direction);
147
+ addVideoCodec(payloadType: number, codec: string, profile?: string): void;
148
+ addH264Codec(payloadType: number, profile?: string): void;
149
+ addVP8Codec(payloadType: number): void;
150
+ addVP9Codec(payloadType: number): void;
151
+
152
+ direction(): Direction;
153
+ generateSdp(eol: string, addr: string, port: number): string;
154
+ mid(): string;
155
+ setDirection(dir: Direction): void;
156
+ description(): string;
157
+ removeFormat(fmt: string): void;
158
+ addSSRC(ssrc: number, name?: string, msid?: string, trackID?: string): void;
159
+ removeSSRC(ssrc: number): void;
160
+ replaceSSRC(oldSsrc: number, ssrc: number, name?: string, msid?: string, trackID?: string): void;
161
+ hasSSRC(ssrc: number): boolean;
162
+ getSSRCs(): number[];
163
+ getCNameForSsrc(ssrc: number): string;
164
+ setBitrate(bitRate: number): void;
165
+ getBitrate(): number;
166
+ hasPayloadType(payloadType: number): boolean;
167
+ addRTXCodec(payloadType: number, originalPayloadType: number, clockRate: number): void;
168
+ addRTPMap(): void;
169
+ parseSdpLine(line: string): void;
170
+ }
171
+
172
+ export class Track {
173
+ direction(): Direction;
174
+ mid(): string;
175
+ type(): string;
176
+ close(): void;
177
+ sendMessage(msg: string): boolean;
178
+ sendMessageBinary(buffer: Buffer): boolean;
179
+ isOpen(): boolean;
180
+ isClosed(): boolean;
181
+ bufferedAmount(): number;
182
+ maxMessageSize(): number;
183
+ requestBitrate(bitRate: number): boolean;
184
+ setBufferedAmountLowThreshold(newSize: number): void;
185
+ requestKeyframe(): boolean;
186
+ setMediaHandler(handler: RtcpReceivingSession): void;
187
+ onOpen(cb: () => void): void;
188
+ onClosed(cb: () => void): void;
189
+ onError(cb: (err: string) => void): void;
190
+ onMessage(cb: (msg: Buffer) => void): void;
191
+ }
192
+
193
+ export interface Channel {
194
+ close(): void;
195
+ sendMessage(msg: string): boolean;
196
+ sendMessageBinary(buffer: Uint8Array): boolean;
197
+ isOpen(): boolean;
198
+ bufferedAmount(): number;
199
+ maxMessageSize(): number;
200
+ setBufferedAmountLowThreshold(newSize: number): void;
201
+ onOpen(cb: () => void): void;
202
+ onClosed(cb: () => void): void;
203
+ onError(cb: (err: string) => void): void;
204
+ onBufferedAmountLow(cb: () => void): void;
205
+ onMessage(cb: (msg: string | Buffer) => void): void;
206
+ }
207
+ export class DataChannel implements Channel {
208
+ getLabel(): string;
209
+ getId(): number;
210
+ getProtocol(): string;
211
+
212
+ // Channel implementation
213
+ close(): void;
214
+ sendMessage(msg: string): boolean;
215
+ sendMessageBinary(buffer: Uint8Array): boolean;
216
+ isOpen(): boolean;
217
+ bufferedAmount(): number;
218
+ maxMessageSize(): number;
219
+ setBufferedAmountLowThreshold(newSize: number): void;
220
+ onOpen(cb: () => void): void;
221
+ onClosed(cb: () => void): void;
222
+ onError(cb: (err: string) => void): void;
223
+ onBufferedAmountLow(cb: () => void): void;
224
+ onMessage(cb: (msg: string | Buffer) => void): void;
225
+ }
226
+
227
+ export interface WebSocketConfiguration {
228
+ disableTlsVerification?: boolean; // default = false, if true, don't verify the TLS certificate
229
+ proxyServer?: ProxyServer; // only non-authenticated http supported for now
230
+ protocols?: string[];
231
+ connectionTimeout?: number; // miliseconds, zero to disable
232
+ pingInterval?: number; // millisecondrs, zero to disable
233
+ maxOutstandingPings?: number;
234
+ caCertificatePemFile?: string;
235
+ certificatePemFile?: string;
236
+ keyPemFile?: string;
237
+ keyPemPass?: string;
238
+ maxMessageSize: number;
239
+ }
240
+ export class WebSocket implements Channel {
241
+ constructor(config?: WebSocketConfiguration);
242
+ open(url: string): void;
243
+ forceClose(): void;
244
+ remoteAddress(): string | undefined;
245
+ path(): string | undefined;
246
+
247
+ // Channel implementation
248
+ close(): void;
249
+ sendMessage(msg: string): boolean;
250
+ sendMessageBinary(buffer: Uint8Array): boolean;
251
+ isOpen(): boolean;
252
+ bufferedAmount(): number;
253
+ maxMessageSize(): number;
254
+ setBufferedAmountLowThreshold(newSize: number): void;
255
+ onOpen(cb: () => void): void;
256
+ onClosed(cb: () => void): void;
257
+ onError(cb: (err: string) => void): void;
258
+ onBufferedAmountLow(cb: () => void): void;
259
+ onMessage(cb: (msg: string | Buffer) => void): void;
260
+ }
261
+
262
+ export interface WebSocketServerConfiguration {
263
+ port?: number; // default 8080
264
+ enableTls?: boolean; // default = false;
265
+ certificatePemFile?: string;
266
+ keyPemFile?: string;
267
+ keyPemPass?: string;
268
+ bindAddress?: string;
269
+ connectionTimeout?: number; // milliseconds
270
+ maxMessageSize?: number;
271
+ }
272
+
273
+ export class WebSocketServer {
274
+ constructor(config?: WebSocketServerConfiguration);
275
+ port(): number;
276
+ stop(): void;
277
+ onClient(cb: (ws: WebSocket) => void): void;
278
+ }
279
+
280
+ export class PeerConnection {
281
+ constructor(peerName: string, config: RtcConfig);
282
+ close(): void;
283
+ setLocalDescription(type?: DescriptionType): void;
284
+ setRemoteDescription(sdp: string, type: DescriptionType): void;
285
+ localDescription(): { type: string; sdp: string } | null;
286
+ remoteDescription(): { type: string; sdp: string } | null;
287
+ addRemoteCandidate(candidate: string, mid: string): void;
288
+ createDataChannel(label: string, config?: DataChannelInitConfig): DataChannel;
289
+ addTrack(media: Video | Audio): Track;
290
+ hasMedia(): boolean;
291
+ state(): RTCPeerConnectionState;
292
+ iceState(): RTCIceConnectionState;
293
+ signalingState(): RTCSignalingState;
294
+ gatheringState(): RTCIceGatheringState;
295
+ onLocalDescription(cb: (sdp: string, type: DescriptionType) => void): void;
296
+ onLocalCandidate(cb: (candidate: string, mid: string) => void): void;
297
+ onStateChange(cb: (state: string) => void): void;
298
+ onIceStateChange(cb: (state: string) => void): void;
299
+ onSignalingStateChange(cb: (state: string) => void): void;
300
+ onGatheringStateChange(cb: (state: string) => void): void;
301
+ onDataChannel(cb: (dc: DataChannel) => void): void;
302
+ onTrack(cb: (track: Track) => void): void;
303
+ bytesSent(): number;
304
+ bytesReceived(): number;
305
+ rtt(): number;
306
+ getSelectedCandidatePair(): { local: SelectedCandidateInfo; remote: SelectedCandidateInfo } | null;
307
+ maxDataChannelId(): number;
308
+ maxMessageSize(): number;
309
+ }
310
+
311
+ export class DataChannelStream extends stream.Duplex {
312
+ constructor(rawChannel: DataChannel, options?: Omit<stream.DuplexOptions, 'objectMode'>);
313
+ get label(): string;
314
+ }
package/lib/index.d.ts CHANGED
@@ -180,6 +180,7 @@ export class Track {
180
180
  isClosed(): boolean;
181
181
  bufferedAmount(): number;
182
182
  maxMessageSize(): number;
183
+ requestBitrate(bitRate: number): boolean;
183
184
  setBufferedAmountLowThreshold(newSize: number): void;
184
185
  requestKeyframe(): boolean;
185
186
  setMediaHandler(handler: RtcpReceivingSession): void;
@@ -238,6 +239,10 @@ export interface WebSocketConfiguration {
238
239
  }
239
240
  export class WebSocket implements Channel {
240
241
  constructor(config?: WebSocketConfiguration);
242
+ open(url: string): void;
243
+ forceClose(): void;
244
+ remoteAddress(): string | undefined;
245
+ path(): string | undefined;
241
246
 
242
247
  // Channel implementation
243
248
  close(): void;
package/package.json CHANGED
@@ -1,19 +1,30 @@
1
1
  {
2
2
  "name": "node-datachannel",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "libdatachannel node bindings",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
8
- "import": "./lib/index.js",
9
- "require": "./lib/index.cjs"
8
+ "import": {
9
+ "default": "./lib/index.js",
10
+ "types": "./lib/index.d.ts"
11
+ },
12
+ "require": {
13
+ "default": "./lib/index.cjs",
14
+ "types": "./lib/index.d.cts"
15
+ }
10
16
  },
11
17
  "./polyfill": {
12
- "import": "./polyfill/index.js",
13
- "require": "./polyfill/polyfill.cjs"
18
+ "import": {
19
+ "default": "./polyfill/index.js",
20
+ "types": "./polyfill/index.d.ts"
21
+ },
22
+ "require": {
23
+ "default": "./polyfill/index.cjs",
24
+ "types": "./polyfill/index.d.cts"
25
+ }
14
26
  }
15
27
  },
16
- "typings": "lib/index.d.ts",
17
28
  "engines": {
18
29
  "node": ">=16.0.0"
19
30
  },
@@ -34,7 +45,7 @@
34
45
  "test": "NODE_OPTIONS=--experimental-vm-modules jest",
35
46
  "wpt-test": "node test/wpt.js",
36
47
  "test-types": "tsc",
37
- "prepublishOnly": "rollup lib/index.js --file lib/index.cjs --format cjs && rollup polyfill/index.js --file polyfill/polyfill.cjs --format cjs"
48
+ "prepublishOnly": "cp lib/index.d.ts lib/index.d.cts && cp polyfill/index.d.ts polyfill/index.d.cts && rollup lib/index.js --file lib/index.cjs --format cjs && rollup polyfill/index.js --file polyfill/index.cjs --format cjs"
38
49
  },
39
50
  "binary": {
40
51
  "napi_versions": [
@@ -88,4 +99,4 @@
88
99
  "prebuild-install": "^7.0.1",
89
100
  "rollup": "^4.14.1"
90
101
  }
91
- }
102
+ }
@@ -547,7 +547,7 @@ class DataChannelStream extends stream.Duplex {
547
547
  }
548
548
 
549
549
  // createRequire is native in node version >= 12
550
- const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('polyfill.cjs', document.baseURI).href)));
550
+ const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
551
551
 
552
552
  const nodeDataChannel = require$1('../build/Release/node_datachannel.node');
553
553
 
@@ -0,0 +1,37 @@
1
+ import RTCCertificate from './RTCCertificate.js';
2
+ import RTCDataChannel from './RTCDataChannel.js';
3
+ import RTCDtlsTransport from './RTCDtlsTransport.js';
4
+ import RTCIceCandidate from './RTCIceCandidate.js';
5
+ import RTCIceTransport from './RTCIceTransport.js';
6
+ import RTCPeerConnection from './RTCPeerConnection.js';
7
+ import RTCSctpTransport from './RTCSctpTransport.js';
8
+ import RTCSessionDescription from './RTCSessionDescription.js';
9
+ import { RTCDataChannelEvent, RTCPeerConnectionIceEvent } from './Events.js';
10
+
11
+ export {
12
+ RTCCertificate,
13
+ RTCDataChannel,
14
+ RTCDtlsTransport,
15
+ RTCIceCandidate,
16
+ RTCIceTransport,
17
+ RTCPeerConnection,
18
+ RTCSctpTransport,
19
+ RTCSessionDescription,
20
+ RTCDataChannelEvent,
21
+ RTCPeerConnectionIceEvent,
22
+ };
23
+
24
+ declare const _default: {
25
+ RTCCertificate: typeof RTCCertificate;
26
+ RTCDataChannel: typeof RTCDataChannel;
27
+ RTCDtlsTransport: typeof RTCDtlsTransport;
28
+ RTCIceCandidate: typeof RTCIceCandidate;
29
+ RTCIceTransport: typeof RTCIceTransport;
30
+ RTCPeerConnection: typeof RTCPeerConnection;
31
+ RTCSctpTransport: typeof RTCSctpTransport;
32
+ RTCSessionDescription: typeof RTCSessionDescription;
33
+ RTCDataChannelEvent: typeof RTCDataChannelEvent;
34
+ RTCPeerConnectionIceEvent: typeof RTCPeerConnectionIceEvent;
35
+ };
36
+
37
+ export default _default;
@@ -58,6 +58,10 @@ DataChannelWrapper::DataChannelWrapper(const Napi::CallbackInfo &info) : Napi::O
58
58
  PLOG_DEBUG << "Constructor called";
59
59
  mDataChannelPtr = *(info[0].As<Napi::External<std::shared_ptr<rtc::DataChannel>>>().Data());
60
60
  PLOG_DEBUG << "Data Channel created";
61
+
62
+ // Closed callback must be set to trigger cleanup
63
+ mOnClosedCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo&){}));
64
+
61
65
  instances.insert(this);
62
66
  }
63
67
 
@@ -390,9 +390,8 @@ void AudioWrapper::setBitrate(const Napi::CallbackInfo &info)
390
390
  return;
391
391
  }
392
392
 
393
- int bitRate = static_cast<int>(info[0].As<Napi::Number>().ToNumber());
394
-
395
- mAudioPtr->setBitrate(bitRate);
393
+ unsigned int bitrate = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber());
394
+ mAudioPtr->setBitrate(bitrate);
396
395
  }
397
396
 
398
397
  Napi::Value AudioWrapper::getBitrate(const Napi::CallbackInfo &info)
@@ -428,9 +427,9 @@ void AudioWrapper::addRTXCodec(const Napi::CallbackInfo &info)
428
427
  return;
429
428
  }
430
429
 
431
- unsigned int payloadType = static_cast<unsigned int>(info[0].As<Napi::Number>().ToNumber());
432
- unsigned int originalPayloadType = static_cast<unsigned int>(info[1].As<Napi::Number>().ToNumber());
433
- unsigned int clockRate = static_cast<unsigned int>(info[2].As<Napi::Number>().ToNumber());
430
+ int payloadType = static_cast<int32_t>(info[0].As<Napi::Number>().ToNumber());
431
+ int originalPayloadType = static_cast<int32_t>(info[1].As<Napi::Number>().ToNumber());
432
+ unsigned int clockRate = static_cast<uint32_t>(info[2].As<Napi::Number>().ToNumber());
434
433
 
435
434
  mAudioPtr->addRtxCodec(payloadType, originalPayloadType, clockRate);
436
435
  }
@@ -51,13 +51,12 @@ void RtcpReceivingSessionWrapper::requestBitrate(const Napi::CallbackInfo &info)
51
51
  return;
52
52
  }
53
53
 
54
- unsigned int bitRate = static_cast<unsigned int>(info[0].As<Napi::Number>().ToNumber());
55
-
56
- mSessionPtr->requestBitrate(bitRate);
54
+ unsigned int bitrate = static_cast<uint32_t>(info[0].As<Napi::Number>().ToNumber());
55
+ mSessionPtr->requestBitrate(bitrate);
57
56
  }
58
57
 
59
58
  Napi::Value RtcpReceivingSessionWrapper::requestKeyframe(const Napi::CallbackInfo &info)
60
59
  {
61
60
  Napi::Env env = info.Env();
62
61
  return Napi::Boolean::New(env, mSessionPtr->requestKeyframe());
63
- }
62
+ }
@@ -39,6 +39,7 @@ Napi::Object TrackWrapper::Init(Napi::Env env, Napi::Object exports)
39
39
  InstanceMethod("isOpen", &TrackWrapper::isOpen),
40
40
  InstanceMethod("isClosed", &TrackWrapper::isClosed),
41
41
  InstanceMethod("maxMessageSize", &TrackWrapper::maxMessageSize),
42
+ InstanceMethod("requestBitrate", &TrackWrapper::requestBitrate),
42
43
  InstanceMethod("requestKeyframe", &TrackWrapper::requestKeyframe),
43
44
  InstanceMethod("setMediaHandler", &TrackWrapper::setMediaHandler),
44
45
  InstanceMethod("onOpen", &TrackWrapper::onOpen),
@@ -56,7 +57,12 @@ Napi::Object TrackWrapper::Init(Napi::Env env, Napi::Object exports)
56
57
 
57
58
  TrackWrapper::TrackWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<TrackWrapper>(info)
58
59
  {
60
+ PLOG_DEBUG << "Constructor called";
59
61
  mTrackPtr = *(info[0].As<Napi::External<std::shared_ptr<rtc::Track>>>().Data());
62
+ PLOG_DEBUG << "Track created";
63
+
64
+ // Closed callback must be set to trigger cleanup
65
+ mOnClosedCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo&){}));
60
66
 
61
67
  instances.insert(this);
62
68
  }
@@ -237,6 +243,27 @@ Napi::Value TrackWrapper::maxMessageSize(const Napi::CallbackInfo &info)
237
243
  }
238
244
  }
239
245
 
246
+ Napi::Value TrackWrapper::requestBitrate(const Napi::CallbackInfo &info)
247
+ {
248
+ if (!mTrackPtr)
249
+ {
250
+ Napi::Error::New(info.Env(), "requestBitrate() called on destroyed track").ThrowAsJavaScriptException();
251
+ return info.Env().Null();
252
+ }
253
+
254
+ Napi::Env env = info.Env();
255
+ int length = info.Length();
256
+
257
+ if (length < 1 || !info[0].IsNumber())
258
+ {
259
+ Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException();
260
+ return info.Env().Null();
261
+ }
262
+
263
+ unsigned int bitrate = static_cast<uint32_t>(info[0].As<Napi::Number>());
264
+ return Napi::Boolean::New(env, mTrackPtr->requestBitrate(bitrate));
265
+ }
266
+
240
267
  Napi::Value TrackWrapper::requestKeyframe(const Napi::CallbackInfo &info)
241
268
  {
242
269
  if (!mTrackPtr)
@@ -30,6 +30,7 @@ public:
30
30
  Napi::Value isOpen(const Napi::CallbackInfo &info);
31
31
  Napi::Value isClosed(const Napi::CallbackInfo &info);
32
32
  Napi::Value maxMessageSize(const Napi::CallbackInfo &info);
33
+ Napi::Value requestBitrate(const Napi::CallbackInfo &info);
33
34
  Napi::Value requestKeyframe(const Napi::CallbackInfo &info);
34
35
  void setMediaHandler(const Napi::CallbackInfo &info);
35
36
 
@@ -423,14 +423,12 @@ void VideoWrapper::setBitrate(const Napi::CallbackInfo &info)
423
423
  return;
424
424
  }
425
425
 
426
- int bitRate = static_cast<int>(info[0].As<Napi::Number>().ToNumber());
427
-
428
- mVideoPtr->setBitrate(bitRate);
426
+ unsigned int bitrate = info[0].As<Napi::Number>().ToNumber().Uint32Value();
427
+ mVideoPtr->setBitrate(bitrate);
429
428
  }
430
429
 
431
430
  Napi::Value VideoWrapper::getBitrate(const Napi::CallbackInfo &info)
432
431
  {
433
-
434
432
  return Napi::Number::New(info.Env(), mVideoPtr->bitrate());
435
433
  }
436
434
 
@@ -445,7 +443,7 @@ Napi::Value VideoWrapper::hasPayloadType(const Napi::CallbackInfo &info)
445
443
  return env.Null();
446
444
  }
447
445
 
448
- int payloadType = static_cast<int>(info[0].As<Napi::Number>().ToNumber());
446
+ int payloadType = static_cast<int32_t>(info[0].As<Napi::Number>().ToNumber());
449
447
 
450
448
  return Napi::Boolean::New(env, mVideoPtr->hasPayloadType(payloadType));
451
449
  }
@@ -461,9 +459,9 @@ void VideoWrapper::addRTXCodec(const Napi::CallbackInfo &info)
461
459
  return;
462
460
  }
463
461
 
464
- unsigned int payloadType = static_cast<unsigned int>(info[0].As<Napi::Number>().ToNumber());
465
- unsigned int originalPayloadType = static_cast<unsigned int>(info[1].As<Napi::Number>().ToNumber());
466
- unsigned int clockRate = static_cast<unsigned int>(info[2].As<Napi::Number>().ToNumber());
462
+ int payloadType = static_cast<int32_t>(info[0].As<Napi::Number>().ToNumber());
463
+ int originalPayloadType = static_cast<int32_t>(info[1].As<Napi::Number>().ToNumber());
464
+ unsigned int clockRate = static_cast<uint32_t>(info[2].As<Napi::Number>().ToNumber());
467
465
 
468
466
  mVideoPtr->addRtxCodec(payloadType, originalPayloadType, clockRate);
469
467
  }
@@ -237,7 +237,7 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N
237
237
  // Create peer-connection
238
238
  try
239
239
  {
240
- PLOG_DEBUG << "Creating a new peer";
240
+ PLOG_DEBUG << "Creating a new Peer Connection";
241
241
  mRtcPeerConnPtr = std::make_unique<rtc::PeerConnection>(rtcConfig);
242
242
  }
243
243
  catch (std::exception &ex)
@@ -246,6 +246,11 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N
246
246
  return;
247
247
  }
248
248
 
249
+ PLOG_DEBUG << "Peer Connection created";
250
+
251
+ // State change callback must be set to trigger cleanup on close
252
+ mOnStateChangeCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo&){}));
253
+
249
254
  instances.insert(this);
250
255
  }
251
256
 
@@ -552,10 +557,10 @@ Napi::Value PeerConnectionWrapper::createDataChannel(const Napi::CallbackInfo &i
552
557
  switch (reliability.Get("type").As<Napi::Number>().Uint32Value())
553
558
  {
554
559
  case 1:
555
- init.reliability.rexmit = static_cast<int>(reliability.Get("rexmit").As<Napi::Number>().ToNumber());
560
+ init.reliability.rexmit = int(reliability.Get("rexmit").As<Napi::Number>().ToNumber().Uint32Value());
556
561
  break;
557
562
  case 2:
558
- init.reliability.rexmit = std::chrono::milliseconds(reliability.Get("rexmit").As<Napi::Number>().Uint32Value());
563
+ init.reliability.rexmit = std::chrono::milliseconds(reliability.Get("rexmit").As<Napi::Number>().Int32Value());
559
564
  break;
560
565
  }
561
566
  }
@@ -572,25 +577,31 @@ Napi::Value PeerConnectionWrapper::createDataChannel(const Napi::CallbackInfo &i
572
577
  init.reliability.unordered = !initConfig.Get("ordered").As<Napi::Boolean>();
573
578
  }
574
579
 
575
- if (initConfig.Get("maxPacketLifeTime").IsNumber() && initConfig.Get("maxRetransmits").IsNumber())
580
+
581
+ if (!initConfig.Get("maxPacketLifeTime").IsUndefined() && !initConfig.Get("maxPacketLifeTime").IsNull() &&
582
+ !initConfig.Get("maxRetransmits").IsUndefined() && !initConfig.Get("maxRetransmits").IsNull())
576
583
  {
577
584
  Napi::TypeError::New(env, "Wrong DataChannel Init Config, maxPacketLifeTime and maxRetransmits are exclusive").ThrowAsJavaScriptException();
578
585
  return info.Env().Null();
579
586
  }
580
587
 
581
- if (initConfig.Get("maxPacketLifeTime").IsNumber())
588
+ if (!initConfig.Get("maxPacketLifeTime").IsUndefined() && !initConfig.Get("maxPacketLifeTime").IsNull())
582
589
  {
583
- init.reliability.type = rtc::Reliability::Type::Timed;
584
- init.reliability.rexmit = std::chrono::milliseconds(initConfig.Get("maxPacketLifeTime").As<Napi::Number>().Uint32Value());
585
- }
586
- else if (initConfig.Get("maxRetransmits").IsNumber())
587
- {
588
- init.reliability.type = rtc::Reliability::Type::Rexmit;
589
- init.reliability.rexmit = int(initConfig.Get("maxRetransmits").As<Napi::Number>().Uint32Value());
590
+ if (!initConfig.Get("maxPacketLifeTime").IsNumber())
591
+ {
592
+ Napi::TypeError::New(env, "Wrong DataChannel Init Config (maxPacketLifeTime)").ThrowAsJavaScriptException();
593
+ return info.Env().Null();
594
+ }
595
+ init.reliability.maxPacketLifeTime = std::chrono::milliseconds(initConfig.Get("maxPacketLifeTime").As<Napi::Number>().Uint32Value());
590
596
  }
591
- else
597
+ else if (!initConfig.Get("maxRetransmits").IsUndefined() && !initConfig.Get("maxRetransmits").IsNull())
592
598
  {
593
- init.reliability.type = rtc::Reliability::Type::Reliable;
599
+ if (!initConfig.Get("maxRetransmits").IsNumber())
600
+ {
601
+ Napi::TypeError::New(env, "Wrong DataChannel Init Config (maxRetransmits)").ThrowAsJavaScriptException();
602
+ return info.Env().Null();
603
+ }
604
+ init.reliability.maxRetransmits = int(initConfig.Get("maxRetransmits").As<Napi::Number>().Int32Value());
594
605
  }
595
606
  }
596
607
 
@@ -9,16 +9,16 @@ const char *ThreadSafeCallback::CancelException::what() const throw()
9
9
 
10
10
  ThreadSafeCallback::ThreadSafeCallback(Napi::Function callback)
11
11
  {
12
- if (!callback.IsFunction())
13
- throw Napi::Error::New(callback.Env(), "Callback must be a function");
14
-
15
12
  Napi::Env env = callback.Env();
16
13
 
17
- receiver = Napi::Persistent(static_cast<Napi::Value>(Napi::Object::New(env)));
14
+ if (!callback.IsFunction())
15
+ throw Napi::Error::New(env, "Callback must be a function");
16
+
18
17
  tsfn = tsfn_t::New(env,
19
18
  std::move(callback),
20
19
  "ThreadSafeCallback callback",
21
- 0, 1, &receiver);
20
+ 0, // unlimited queue
21
+ 1);
22
22
  }
23
23
 
24
24
  ThreadSafeCallback::~ThreadSafeCallback()
@@ -38,10 +38,10 @@ void ThreadSafeCallback::call(arg_func_t argFunc, cleanup_func_t cleanupFunc)
38
38
 
39
39
  void ThreadSafeCallback::callbackFunc(Napi::Env env,
40
40
  Napi::Function callback,
41
- Napi::Reference<Napi::Value> *context,
41
+ ContextType *context,
42
42
  CallbackData *data)
43
43
  {
44
- // if env is gone this could mean cb fn has changed. See issue#176
44
+ // if env is gone, it could mean this cb was destroyed. See issue#176
45
45
  if (!data || !env)
46
46
  return;
47
47
 
@@ -59,9 +59,10 @@ void ThreadSafeCallback::callbackFunc(Napi::Env env,
59
59
  return;
60
60
  }
61
61
 
62
- if (env && callback)
62
+ if (callback)
63
63
  {
64
- callback.Call(context->Value(), args);
65
- cleanup();
64
+ callback.Call(args);
66
65
  }
66
+
67
+ cleanup();
67
68
  }
@@ -30,6 +30,7 @@ public:
30
30
  };
31
31
 
32
32
  private:
33
+ using ContextType = std::nullptr_t;
33
34
  struct CallbackData
34
35
  {
35
36
  arg_func_t argFunc;
@@ -38,12 +39,10 @@ private:
38
39
 
39
40
  static void callbackFunc(Napi::Env env,
40
41
  Napi::Function callback,
41
- Napi::Reference<Napi::Value> *context,
42
+ ContextType *context,
42
43
  CallbackData *data);
43
44
 
44
- using tsfn_t = Napi::TypedThreadSafeFunction<Napi::Reference<Napi::Value>, CallbackData, callbackFunc>;
45
-
46
- Napi::Reference<Napi::Value> receiver;
45
+ using tsfn_t = Napi::TypedThreadSafeFunction<ContextType, CallbackData, callbackFunc>;
47
46
  tsfn_t tsfn;
48
47
  };
49
48
 
@@ -31,6 +31,7 @@ Napi::Object WebSocketWrapper::Init(Napi::Env env, Napi::Object exports)
31
31
  {
32
32
  InstanceMethod("open", &WebSocketWrapper::open),
33
33
  InstanceMethod("close", &WebSocketWrapper::close),
34
+ InstanceMethod("forceClose", &WebSocketWrapper::forceClose),
34
35
  InstanceMethod("sendMessage", &WebSocketWrapper::sendMessage),
35
36
  InstanceMethod("sendMessageBinary", &WebSocketWrapper::sendMessageBinary),
36
37
  InstanceMethod("isOpen", &WebSocketWrapper::isOpen),
@@ -42,6 +43,8 @@ Napi::Object WebSocketWrapper::Init(Napi::Env env, Napi::Object exports)
42
43
  InstanceMethod("onError", &WebSocketWrapper::onError),
43
44
  InstanceMethod("onBufferedAmountLow", &WebSocketWrapper::onBufferedAmountLow),
44
45
  InstanceMethod("onMessage", &WebSocketWrapper::onMessage),
46
+ InstanceMethod("remoteAddress", &WebSocketWrapper::remoteAddress),
47
+ InstanceMethod("path", &WebSocketWrapper::path),
45
48
  });
46
49
 
47
50
  constructor = Napi::Persistent(func);
@@ -235,6 +238,10 @@ WebSocketWrapper::WebSocketWrapper(const Napi::CallbackInfo &info) : Napi::Objec
235
238
  }
236
239
 
237
240
  PLOG_DEBUG << "WebSocket created";
241
+
242
+ // Closed callback must set to trigger cleanup
243
+ mOnClosedCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo&){}));
244
+
238
245
  instances.insert(this);
239
246
  }
240
247
 
@@ -257,7 +264,31 @@ void WebSocketWrapper::doClose()
257
264
  }
258
265
  catch (std::exception &ex)
259
266
  {
260
- std::cerr << std::string("libWebSocket error while closing WebSocket: ") + ex.what() << std::endl;
267
+ std::cerr << std::string("libdatachannel error while closing WebSocket: ") + ex.what() << std::endl;
268
+ return;
269
+ }
270
+ }
271
+
272
+ mOnOpenCallback.reset();
273
+ mOnErrorCallback.reset();
274
+ mOnBufferedAmountLowCallback.reset();
275
+ mOnMessageCallback.reset();
276
+ }
277
+
278
+ void WebSocketWrapper::doForceClose()
279
+ {
280
+ PLOG_DEBUG << "doForceClose() called";
281
+ if (mWebSocketPtr)
282
+ {
283
+ PLOG_DEBUG << "Force closing...";
284
+ try
285
+ {
286
+ mWebSocketPtr->forceClose();
287
+ mWebSocketPtr.reset();
288
+ }
289
+ catch (std::exception &ex)
290
+ {
291
+ std::cerr << std::string("libdatachannel error while force closing WebSocket: ") + ex.what() << std::endl;
261
292
  return;
262
293
  }
263
294
  }
@@ -297,7 +328,7 @@ void WebSocketWrapper::open(const Napi::CallbackInfo &info)
297
328
  }
298
329
  catch (std::exception &ex)
299
330
  {
300
- Napi::Error::New(env, std::string("libWebSocket error while opening WebSocket: ") + ex.what()).ThrowAsJavaScriptException();
331
+ Napi::Error::New(env, std::string("libdatachannel error while opening WebSocket: ") + ex.what()).ThrowAsJavaScriptException();
301
332
  return;
302
333
  }
303
334
  }
@@ -308,6 +339,12 @@ void WebSocketWrapper::close(const Napi::CallbackInfo &info)
308
339
  doClose();
309
340
  }
310
341
 
342
+ void WebSocketWrapper::forceClose(const Napi::CallbackInfo &info)
343
+ {
344
+ PLOG_DEBUG << "forceClose() called";
345
+ doForceClose();
346
+ }
347
+
311
348
  Napi::Value WebSocketWrapper::sendMessage(const Napi::CallbackInfo &info)
312
349
  {
313
350
  PLOG_DEBUG << "sendMessage() called";
@@ -333,7 +370,7 @@ Napi::Value WebSocketWrapper::sendMessage(const Napi::CallbackInfo &info)
333
370
  }
334
371
  catch (std::exception &ex)
335
372
  {
336
- Napi::Error::New(env, std::string("libWebSocket error while sending data channel message: ") + ex.what()).ThrowAsJavaScriptException();
373
+ Napi::Error::New(env, std::string("libdatachannel error while sending data channel message: ") + ex.what()).ThrowAsJavaScriptException();
337
374
  return Napi::Boolean::New(info.Env(), false);
338
375
  }
339
376
  }
@@ -363,7 +400,7 @@ Napi::Value WebSocketWrapper::sendMessageBinary(const Napi::CallbackInfo &info)
363
400
  }
364
401
  catch (std::exception &ex)
365
402
  {
366
- Napi::Error::New(env, std::string("libWebSocket error while sending data channel message: ") + ex.what()).ThrowAsJavaScriptException();
403
+ Napi::Error::New(env, std::string("libdatachannel error while sending data channel message: ") + ex.what()).ThrowAsJavaScriptException();
367
404
  return Napi::Boolean::New(info.Env(), false);
368
405
  }
369
406
  }
@@ -384,7 +421,7 @@ Napi::Value WebSocketWrapper::isOpen(const Napi::CallbackInfo &info)
384
421
  }
385
422
  catch (std::exception &ex)
386
423
  {
387
- Napi::Error::New(env, std::string("libWebSocket error: ") + ex.what()).ThrowAsJavaScriptException();
424
+ Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException();
388
425
  return Napi::Boolean::New(info.Env(), false);
389
426
  }
390
427
  }
@@ -405,7 +442,7 @@ Napi::Value WebSocketWrapper::bufferedAmount(const Napi::CallbackInfo &info)
405
442
  }
406
443
  catch (std::exception &ex)
407
444
  {
408
- Napi::Error::New(env, std::string("libWebSocket error: ") + ex.what()).ThrowAsJavaScriptException();
445
+ Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException();
409
446
  return Napi::Number::New(info.Env(), 0);
410
447
  }
411
448
  }
@@ -426,11 +463,69 @@ Napi::Value WebSocketWrapper::maxMessageSize(const Napi::CallbackInfo &info)
426
463
  }
427
464
  catch (std::exception &ex)
428
465
  {
429
- Napi::Error::New(env, std::string("libWebSocket error: ") + ex.what()).ThrowAsJavaScriptException();
466
+ Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException();
430
467
  return Napi::Number::New(info.Env(), 0);
431
468
  }
432
469
  }
433
470
 
471
+ Napi::Value WebSocketWrapper::remoteAddress(const Napi::CallbackInfo &info)
472
+ {
473
+ PLOG_DEBUG << "remoteAddress() called";
474
+ Napi::Env env = info.Env();
475
+
476
+ if (!mWebSocketPtr)
477
+ {
478
+ return env.Undefined();
479
+ }
480
+
481
+ try
482
+ {
483
+ auto address = mWebSocketPtr->remoteAddress();
484
+ if (address.has_value())
485
+ {
486
+ return Napi::String::New(info.Env(), address.value());
487
+ }
488
+ else
489
+ {
490
+ return env.Undefined();
491
+ }
492
+ }
493
+ catch (std::exception &ex)
494
+ {
495
+ Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException();
496
+ return env.Undefined();
497
+ }
498
+ }
499
+
500
+ Napi::Value WebSocketWrapper::path(const Napi::CallbackInfo &info)
501
+ {
502
+ PLOG_DEBUG << "path() called";
503
+ Napi::Env env = info.Env();
504
+
505
+ if (!mWebSocketPtr)
506
+ {
507
+ return env.Undefined();
508
+ }
509
+
510
+ try
511
+ {
512
+ auto path = mWebSocketPtr->path();
513
+ if (path.has_value())
514
+ {
515
+ return Napi::String::New(info.Env(), path.value());
516
+ }
517
+ else
518
+ {
519
+ return env.Undefined();
520
+ }
521
+ }
522
+ catch (std::exception &ex)
523
+ {
524
+ Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException();
525
+ return env.Undefined();
526
+ }
527
+ }
528
+
434
529
  void WebSocketWrapper::setBufferedAmountLowThreshold(const Napi::CallbackInfo &info)
435
530
  {
436
531
  PLOG_DEBUG << "setBufferedAmountLowThreshold() called";
@@ -455,7 +550,7 @@ void WebSocketWrapper::setBufferedAmountLowThreshold(const Napi::CallbackInfo &i
455
550
  }
456
551
  catch (std::exception &ex)
457
552
  {
458
- Napi::Error::New(env, std::string("libWebSocket error: ") + ex.what()).ThrowAsJavaScriptException();
553
+ Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException();
459
554
  return;
460
555
  }
461
556
  }
@@ -23,12 +23,15 @@ public:
23
23
  // Functions
24
24
  void open(const Napi::CallbackInfo &info);
25
25
  void close(const Napi::CallbackInfo &info);
26
+ void forceClose(const Napi::CallbackInfo &info);
26
27
  Napi::Value sendMessage(const Napi::CallbackInfo &info);
27
28
  Napi::Value sendMessageBinary(const Napi::CallbackInfo &info);
28
29
  Napi::Value isOpen(const Napi::CallbackInfo &info);
29
30
  Napi::Value bufferedAmount(const Napi::CallbackInfo &info);
30
31
  Napi::Value maxMessageSize(const Napi::CallbackInfo &info);
31
32
  void setBufferedAmountLowThreshold(const Napi::CallbackInfo &info);
33
+ Napi::Value remoteAddress(const Napi::CallbackInfo &info);
34
+ Napi::Value path(const Napi::CallbackInfo &info);
32
35
 
33
36
  // Callbacks
34
37
  void onOpen(const Napi::CallbackInfo &info);
@@ -47,6 +50,7 @@ private:
47
50
  static std::unordered_set<WebSocketWrapper *> instances;
48
51
 
49
52
  void doClose();
53
+ void doForceClose();
50
54
  void doCleanup();
51
55
 
52
56
  std::shared_ptr<rtc::WebSocket> mWebSocketPtr = nullptr;
@@ -6,7 +6,9 @@ nodeDataChannel.preload();
6
6
  const webSocketServer = new nodeDataChannel.WebSocketServer({ bindAddress: '127.0.0.1', port: 1987 });
7
7
 
8
8
  webSocketServer.onClient((serverSocket) => {
9
- console.log('webSocketServer.onClient()');
9
+ console.log(
10
+ 'webSocketServer.onClient() remoteAddress: ' + serverSocket.remoteAddress() + ', path: ' + serverSocket.path(),
11
+ );
10
12
 
11
13
  serverSocket.onOpen(() => {
12
14
  console.log('serverSocket.onOpen()');
@@ -32,7 +34,7 @@ clientSocket.onOpen(() => {
32
34
 
33
35
  clientSocket.onMessage((message) => {
34
36
  console.log('clientSocket.onMessage():', message);
35
- clientSocket.close();
37
+ clientSocket.forceClose();
36
38
  webSocketServer.stop();
37
39
  });
38
40