node-datachannel 0.1.13 → 0.1.14-dev

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/CMakeLists.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  cmake_minimum_required(VERSION 3.15)
2
2
  cmake_policy(SET CMP0091 NEW)
3
- project(node_datachannel VERSION 0.1.13)
3
+ project(node_datachannel VERSION 0.1.14)
4
4
 
5
5
  include_directories(${CMAKE_JS_INC})
6
6
 
@@ -27,7 +27,7 @@ include(FetchContent)
27
27
  FetchContent_Declare(
28
28
  libdatachannel
29
29
  GIT_REPOSITORY https://github.com/paullouisageneau/libdatachannel.git
30
- GIT_TAG "v0.15.6"
30
+ GIT_TAG "v0.16.0"
31
31
  )
32
32
 
33
33
  option(NO_MEDIA "Disable media transport support in libdatachannel" ON)
@@ -44,6 +44,7 @@ add_library(${PROJECT_NAME} SHARED
44
44
  src/rtc-wrapper.cpp
45
45
  src/data-channel-wrapper.cpp
46
46
  src/peer-connection-wrapper.cpp
47
+ src/thread-safe-callback.cpp
47
48
  src/main.cpp
48
49
  ${CMAKE_JS_SRC}
49
50
  )
@@ -53,10 +54,16 @@ set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")
53
54
 
54
55
  target_include_directories(${PROJECT_NAME} PRIVATE
55
56
  ${CMAKE_SOURCE_DIR}/node_modules/node-addon-api
56
- ${CMAKE_SOURCE_DIR}/node_modules/napi-thread-safe-callback
57
57
  ${CMAKE_BINARY_DIR}/_deps/libdatachannel-src/include
58
58
  )
59
59
 
60
+ # For compatibility with Node.js 12
61
+ # This should be removed along with the napi-thread-safe-callback-cancellable dependency when dropping support at end-of-life
62
+ target_compile_definitions(${PROJECT_NAME} PRIVATE -DLEGACY_NAPI_THREAD_SAFE_CALLBACK)
63
+ target_include_directories(${PROJECT_NAME} PRIVATE
64
+ ${CMAKE_SOURCE_DIR}/node_modules/napi-thread-safe-callback-cancellable
65
+ )
66
+
60
67
  set(LINK_LIBRARIES
61
68
  ${CMAKE_JS_LIB}
62
69
  datachannel-static
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-datachannel",
3
- "version": "0.1.13",
3
+ "version": "0.1.14-dev",
4
4
  "description": "libdatachannel node bindings",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
@@ -15,7 +15,7 @@
15
15
  "build": "cmake-js build",
16
16
  "_prebuild": "prebuild --backend cmake-js",
17
17
  "clean": "cmake-js clean",
18
- "test": "jest --forceExit"
18
+ "test": "jest"
19
19
  },
20
20
  "repository": {
21
21
  "type": "git",
@@ -24,7 +24,16 @@
24
24
  "keywords": [
25
25
  "libdatachannel"
26
26
  ],
27
- "author": "Murat Doğan",
27
+ "contributors": [
28
+ {
29
+ "name": "Murat Doğan",
30
+ "url": "https://github.com/murat-dogan"
31
+ },
32
+ {
33
+ "name": "Paul-Louis Ageneau",
34
+ "url": "https://github.com/paullouisageneau"
35
+ }
36
+ ],
28
37
  "license": "GPL-2.0-or-later",
29
38
  "gypfile": true,
30
39
  "bugs": {
@@ -35,8 +44,8 @@
35
44
  "cmake-js": "^6.2.1",
36
45
  "jest": "^27.2.5",
37
46
  "nan": "^2.15.0",
38
- "napi-thread-safe-callback": "0.0.6",
39
- "node-addon-api": "^3.2.1",
47
+ "napi-thread-safe-callback-cancellable": "^0.0.7",
48
+ "node-addon-api": "^4.2.0",
40
49
  "prebuild": "^11.0.0"
41
50
  },
42
51
  "bundledDependencies": [
@@ -1,6 +1,14 @@
1
1
  #include "data-channel-wrapper.h"
2
2
 
3
3
  Napi::FunctionReference DataChannelWrapper::constructor;
4
+ std::unordered_set<DataChannelWrapper*> DataChannelWrapper::instances;
5
+
6
+ void DataChannelWrapper::CloseAll()
7
+ {
8
+ auto copy(instances);
9
+ for(auto inst : copy)
10
+ inst->doClose();
11
+ }
4
12
 
5
13
  Napi::Object DataChannelWrapper::Init(Napi::Env env, Napi::Object exports)
6
14
  {
@@ -35,14 +43,30 @@ Napi::Object DataChannelWrapper::Init(Napi::Env env, Napi::Object exports)
35
43
  DataChannelWrapper::DataChannelWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<DataChannelWrapper>(info)
36
44
  {
37
45
  mDataChannelPtr = *(info[0].As<Napi::External<std::shared_ptr<rtc::DataChannel>>>().Data());
46
+
47
+ instances.insert(this);
38
48
  }
39
49
 
40
50
  DataChannelWrapper::~DataChannelWrapper()
41
51
  {
52
+ doClose();
53
+ }
54
+
55
+ void DataChannelWrapper::doClose()
56
+ {
57
+ instances.erase(this);
58
+
42
59
  if (mDataChannelPtr)
43
60
  {
44
61
  try
45
62
  {
63
+ mOnOpenCallback.reset();
64
+ mOnClosedCallback.reset();
65
+ mOnErrorCallback.reset();
66
+ mOnAvailableCallback.reset();
67
+ mOnBufferedAmountLowCallback.reset();
68
+ mOnMessageCallback.reset();
69
+
46
70
  mDataChannelPtr->close();
47
71
  mDataChannelPtr.reset();
48
72
  }
@@ -56,23 +80,7 @@ DataChannelWrapper::~DataChannelWrapper()
56
80
 
57
81
  void DataChannelWrapper::close(const Napi::CallbackInfo &info)
58
82
  {
59
- Napi::Env env = info.Env();
60
- if (!mDataChannelPtr)
61
- {
62
- Napi::Error::New(env, "It seems data-channel is destroyed!").ThrowAsJavaScriptException();
63
- return;
64
- }
65
-
66
- try
67
- {
68
- mDataChannelPtr->close();
69
- mDataChannelPtr.reset();
70
- }
71
- catch (std::exception &ex)
72
- {
73
- Napi::Error::New(env, std::string("libdatachannel error while closing dataChannel# ") + ex.what()).ThrowAsJavaScriptException();
74
- return;
75
- }
83
+ doClose();
76
84
  }
77
85
 
78
86
  Napi::Value DataChannelWrapper::getLabel(const Napi::CallbackInfo &info)
@@ -260,11 +268,15 @@ void DataChannelWrapper::onOpen(const Napi::CallbackInfo &info)
260
268
  }
261
269
 
262
270
  // Callback
263
- mOnOpenCallback = std::make_shared<ThreadSafeCallback>(info[0].As<Napi::Function>());
271
+ mOnOpenCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
264
272
 
265
273
  mDataChannelPtr->onOpen([&]() {
266
274
  if (mOnOpenCallback)
267
- mOnOpenCallback->call([](Napi::Env env, std::vector<napi_value> &args) {
275
+ mOnOpenCallback->call([this](Napi::Env env, std::vector<napi_value> &args) {
276
+ // Check the peer connection is not closed
277
+ if(instances.find(this) == instances.end())
278
+ throw ThreadSafeCallback::CancelException();
279
+
268
280
  // This will run in main thread and needs to construct the
269
281
  // arguments for the call
270
282
  args = {};
@@ -284,11 +296,15 @@ void DataChannelWrapper::onClosed(const Napi::CallbackInfo &info)
284
296
  }
285
297
 
286
298
  // Callback
287
- mOnClosedCallback = std::make_shared<ThreadSafeCallback>(info[0].As<Napi::Function>());
299
+ mOnClosedCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
288
300
 
289
301
  mDataChannelPtr->onClosed([&]() {
290
302
  if (mOnClosedCallback)
291
- mOnClosedCallback->call([](Napi::Env env, std::vector<napi_value> &args) {
303
+ mOnClosedCallback->call([this](Napi::Env env, std::vector<napi_value> &args) {
304
+ // Check the peer connection is not closed
305
+ if(instances.find(this) == instances.end())
306
+ throw ThreadSafeCallback::CancelException();
307
+
292
308
  // This will run in main thread and needs to construct the
293
309
  // arguments for the call
294
310
  args = {};
@@ -308,11 +324,15 @@ void DataChannelWrapper::onError(const Napi::CallbackInfo &info)
308
324
  }
309
325
 
310
326
  // Callback
311
- mOnErrorCallback = std::make_shared<ThreadSafeCallback>(info[0].As<Napi::Function>());
327
+ mOnErrorCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
312
328
 
313
329
  mDataChannelPtr->onError([&](const std::string &error) {
314
330
  if (mOnErrorCallback)
315
- mOnErrorCallback->call([error](Napi::Env env, std::vector<napi_value> &args) {
331
+ mOnErrorCallback->call([this, error](Napi::Env env, std::vector<napi_value> &args) {
332
+ // Check the peer connection is not closed
333
+ if(instances.find(this) == instances.end())
334
+ throw ThreadSafeCallback::CancelException();
335
+
316
336
  // This will run in main thread and needs to construct the
317
337
  // arguments for the call
318
338
  args = {Napi::String::New(env, error)};
@@ -332,11 +352,15 @@ void DataChannelWrapper::onAvailable(const Napi::CallbackInfo &info)
332
352
  }
333
353
 
334
354
  // Callback
335
- mOnAvailableCallback = std::make_shared<ThreadSafeCallback>(info[0].As<Napi::Function>());
355
+ mOnAvailableCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
336
356
 
337
357
  mDataChannelPtr->onAvailable([&]() {
338
358
  if (mOnAvailableCallback)
339
- mOnAvailableCallback->call([](Napi::Env env, std::vector<napi_value> &args) {
359
+ mOnAvailableCallback->call([this](Napi::Env env, std::vector<napi_value> &args) {
360
+ // Check the peer connection is not closed
361
+ if(instances.find(this) == instances.end())
362
+ throw ThreadSafeCallback::CancelException();
363
+
340
364
  // This will run in main thread and needs to construct the
341
365
  // arguments for the call
342
366
  args = {};
@@ -356,11 +380,15 @@ void DataChannelWrapper::onBufferedAmountLow(const Napi::CallbackInfo &info)
356
380
  }
357
381
 
358
382
  // Callback
359
- mOnBufferedAmountLowCallback = std::make_shared<ThreadSafeCallback>(info[0].As<Napi::Function>());
383
+ mOnBufferedAmountLowCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
360
384
 
361
385
  mDataChannelPtr->onBufferedAmountLow([&]() {
362
386
  if (mOnBufferedAmountLowCallback)
363
- mOnBufferedAmountLowCallback->call([](Napi::Env env, std::vector<napi_value> &args) {
387
+ mOnBufferedAmountLowCallback->call([this](Napi::Env env, std::vector<napi_value> &args) {
388
+ // Check the peer connection is not closed
389
+ if(instances.find(this) == instances.end())
390
+ throw ThreadSafeCallback::CancelException();
391
+
364
392
  // This will run in main thread and needs to construct the
365
393
  // arguments for the call
366
394
  args = {};
@@ -380,11 +408,15 @@ void DataChannelWrapper::onMessage(const Napi::CallbackInfo &info)
380
408
  }
381
409
 
382
410
  // Callback
383
- mOnMessageCallback = std::make_shared<ThreadSafeCallback>(info[0].As<Napi::Function>());
411
+ mOnMessageCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
384
412
 
385
413
  mDataChannelPtr->onMessage([&](const std::variant<rtc::binary, std::string> &message) {
386
414
  if (mOnMessageCallback)
387
- mOnMessageCallback->call([message](Napi::Env env, std::vector<napi_value> &args) {
415
+ mOnMessageCallback->call([this, message](Napi::Env env, std::vector<napi_value> &args) {
416
+ // Check the peer connection is not closed
417
+ if(instances.find(this) == instances.end())
418
+ throw ThreadSafeCallback::CancelException();
419
+
388
420
  // This will run in main thread and needs to construct the
389
421
  // arguments for the call
390
422
  Napi::Object payload = Napi::Object::New(env);
@@ -398,4 +430,4 @@ void DataChannelWrapper::onMessage(const Napi::CallbackInfo &info)
398
430
  }
399
431
  });
400
432
  });
401
- }
433
+ }
@@ -5,9 +5,12 @@
5
5
  #include <string>
6
6
  #include <variant>
7
7
  #include <memory>
8
+ #include <unordered_set>
9
+
8
10
  #include <napi.h>
9
- #include <napi-thread-safe-callback.hpp>
10
- #include "rtc/rtc.hpp"
11
+ #include <rtc/rtc.hpp>
12
+
13
+ #include "thread-safe-callback.h"
11
14
 
12
15
  class DataChannelWrapper : public Napi::ObjectWrap<DataChannelWrapper>
13
16
  {
@@ -36,17 +39,24 @@ public:
36
39
  void onBufferedAmountLow(const Napi::CallbackInfo &info);
37
40
  void onMessage(const Napi::CallbackInfo &info);
38
41
 
42
+ // Close all existing DataChannels
43
+ static void CloseAll();
44
+
39
45
  private:
46
+ static std::unordered_set<DataChannelWrapper*> instances;
47
+
48
+ void doClose();
49
+
40
50
  std::string mLabel;
41
51
  std::shared_ptr<rtc::DataChannel> mDataChannelPtr = nullptr;
42
52
 
43
53
  // Callback Ptrs
44
- std::shared_ptr<ThreadSafeCallback> mOnOpenCallback = nullptr;
45
- std::shared_ptr<ThreadSafeCallback> mOnClosedCallback = nullptr;
46
- std::shared_ptr<ThreadSafeCallback> mOnErrorCallback = nullptr;
47
- std::shared_ptr<ThreadSafeCallback> mOnAvailableCallback = nullptr;
48
- std::shared_ptr<ThreadSafeCallback> mOnBufferedAmountLowCallback = nullptr;
49
- std::shared_ptr<ThreadSafeCallback> mOnMessageCallback = nullptr;
54
+ std::unique_ptr<ThreadSafeCallback> mOnOpenCallback = nullptr;
55
+ std::unique_ptr<ThreadSafeCallback> mOnClosedCallback = nullptr;
56
+ std::unique_ptr<ThreadSafeCallback> mOnErrorCallback = nullptr;
57
+ std::unique_ptr<ThreadSafeCallback> mOnAvailableCallback = nullptr;
58
+ std::unique_ptr<ThreadSafeCallback> mOnBufferedAmountLowCallback = nullptr;
59
+ std::unique_ptr<ThreadSafeCallback> mOnMessageCallback = nullptr;
50
60
  };
51
61
 
52
- #endif // DATA_CHANNEL_WRAPPER_H
62
+ #endif // DATA_CHANNEL_WRAPPER_H
@@ -1,8 +1,17 @@
1
1
  #include "peer-connection-wrapper.h"
2
2
  #include "data-channel-wrapper.h"
3
+
3
4
  #include <sstream>
4
5
 
5
6
  Napi::FunctionReference PeerConnectionWrapper::constructor;
7
+ std::unordered_set<PeerConnectionWrapper*> PeerConnectionWrapper::instances;
8
+
9
+ void PeerConnectionWrapper::CloseAll()
10
+ {
11
+ auto copy(instances);
12
+ for(auto inst : copy)
13
+ inst->doClose();
14
+ }
6
15
 
7
16
  Napi::Object PeerConnectionWrapper::Init(Napi::Env env, Napi::Object exports)
8
17
  {
@@ -180,21 +189,36 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N
180
189
  // Create peer-connection
181
190
  try
182
191
  {
183
- mRtcPeerConnPtr = std::make_shared<rtc::PeerConnection>(rtcConfig);
192
+ mRtcPeerConnPtr = std::make_unique<rtc::PeerConnection>(rtcConfig);
184
193
  }
185
194
  catch (std::exception &ex)
186
195
  {
187
196
  Napi::Error::New(env, std::string("libdatachannel error while creating peerConnection# ") + ex.what()).ThrowAsJavaScriptException();
188
197
  return;
189
198
  }
199
+
200
+ instances.insert(this);
190
201
  }
191
202
 
192
203
  PeerConnectionWrapper::~PeerConnectionWrapper()
193
204
  {
205
+ doClose();
206
+ }
207
+
208
+ void PeerConnectionWrapper::doClose()
209
+ {
210
+ instances.erase(this);
211
+
194
212
  if (mRtcPeerConnPtr)
195
213
  {
196
214
  try
197
215
  {
216
+ mOnLocalDescriptionCallback.reset();
217
+ mOnLocalCandidateCallback.reset();
218
+ mOnStateChangeCallback.reset();
219
+ mOnGatheringStateChangeCallback.reset();
220
+ mOnDataChannelCallback.reset();
221
+
198
222
  mRtcPeerConnPtr->close();
199
223
  mRtcPeerConnPtr.reset();
200
224
  }
@@ -208,23 +232,7 @@ PeerConnectionWrapper::~PeerConnectionWrapper()
208
232
 
209
233
  void PeerConnectionWrapper::close(const Napi::CallbackInfo &info)
210
234
  {
211
- Napi::Env env = info.Env();
212
- if (!mRtcPeerConnPtr)
213
- {
214
- Napi::Error::New(info.Env(), "It seems peer-connection is closed").ThrowAsJavaScriptException();
215
- return;
216
- }
217
-
218
- try
219
- {
220
- mRtcPeerConnPtr->close();
221
- mRtcPeerConnPtr.reset();
222
- }
223
- catch (std::exception &ex)
224
- {
225
- Napi::Error::New(env, std::string("libdatachannel error while closing peerConnection# ") + ex.what()).ThrowAsJavaScriptException();
226
- return;
227
- }
235
+ doClose();
228
236
  }
229
237
 
230
238
  void PeerConnectionWrapper::setRemoteDescription(const Napi::CallbackInfo &info)
@@ -468,11 +476,15 @@ void PeerConnectionWrapper::onLocalDescription(const Napi::CallbackInfo &info)
468
476
  }
469
477
 
470
478
  // Callback
471
- mOnLocalDescriptionCallback = std::make_shared<ThreadSafeCallback>(info[0].As<Napi::Function>());
479
+ mOnLocalDescriptionCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
472
480
 
473
481
  mRtcPeerConnPtr->onLocalDescription([&](const rtc::Description &sdp) {
474
482
  if (mOnLocalDescriptionCallback)
475
- mOnLocalDescriptionCallback->call([sdp](Napi::Env env, std::vector<napi_value> &args) {
483
+ mOnLocalDescriptionCallback->call([this, sdp](Napi::Env env, std::vector<napi_value> &args) {
484
+ // Check the peer connection is not closed
485
+ if(instances.find(this) == instances.end())
486
+ throw ThreadSafeCallback::CancelException();
487
+
476
488
  // This will run in main thread and needs to construct the
477
489
  // arguments for the call
478
490
  args = {
@@ -494,11 +506,15 @@ void PeerConnectionWrapper::onLocalCandidate(const Napi::CallbackInfo &info)
494
506
  }
495
507
 
496
508
  // Callback
497
- mOnLocalCandidateCallback = std::make_shared<ThreadSafeCallback>(info[0].As<Napi::Function>());
509
+ mOnLocalCandidateCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
498
510
 
499
511
  mRtcPeerConnPtr->onLocalCandidate([&](const rtc::Candidate &candidate) {
500
512
  if (mOnLocalCandidateCallback)
501
- mOnLocalCandidateCallback->call([candidate](Napi::Env env, std::vector<napi_value> &args) {
513
+ mOnLocalCandidateCallback->call([this, candidate](Napi::Env env, std::vector<napi_value> &args) {
514
+ // Check the peer connection is not closed
515
+ if(instances.find(this) == instances.end())
516
+ throw ThreadSafeCallback::CancelException();
517
+
502
518
  // This will run in main thread and needs to construct the
503
519
  // arguments for the call
504
520
  args = {
@@ -520,11 +536,15 @@ void PeerConnectionWrapper::onStateChange(const Napi::CallbackInfo &info)
520
536
  }
521
537
 
522
538
  // Callback
523
- mOnStateChangeCallback = std::make_shared<ThreadSafeCallback>(info[0].As<Napi::Function>());
539
+ mOnStateChangeCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
524
540
 
525
541
  mRtcPeerConnPtr->onStateChange([&](rtc::PeerConnection::State state) {
526
542
  if (mOnStateChangeCallback)
527
- mOnStateChangeCallback->call([state](Napi::Env env, std::vector<napi_value> &args) {
543
+ mOnStateChangeCallback->call([this, state](Napi::Env env, std::vector<napi_value> &args) {
544
+ // Check the peer connection is not closed
545
+ if(instances.find(this) == instances.end())
546
+ throw ThreadSafeCallback::CancelException();
547
+
528
548
  // This will run in main thread and needs to construct the
529
549
  // arguments for the call
530
550
  std::ostringstream stream;
@@ -546,11 +566,15 @@ void PeerConnectionWrapper::onGatheringStateChange(const Napi::CallbackInfo &inf
546
566
  }
547
567
 
548
568
  // Callback
549
- mOnGatheringStateChangeCallback = std::make_shared<ThreadSafeCallback>(info[0].As<Napi::Function>());
569
+ mOnGatheringStateChangeCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
550
570
 
551
571
  mRtcPeerConnPtr->onGatheringStateChange([&](rtc::PeerConnection::GatheringState state) {
552
572
  if (mOnGatheringStateChangeCallback)
553
- mOnGatheringStateChangeCallback->call([state](Napi::Env env, std::vector<napi_value> &args) {
573
+ mOnGatheringStateChangeCallback->call([this, state](Napi::Env env, std::vector<napi_value> &args) {
574
+ // Check the peer connection is not closed
575
+ if(instances.find(this) == instances.end())
576
+ throw ThreadSafeCallback::CancelException();
577
+
554
578
  // This will run in main thread and needs to construct the
555
579
  // arguments for the call
556
580
  std::ostringstream stream;
@@ -572,11 +596,15 @@ void PeerConnectionWrapper::onDataChannel(const Napi::CallbackInfo &info)
572
596
  }
573
597
 
574
598
  // Callback
575
- mOnDataChannelCallback = std::make_shared<ThreadSafeCallback>(info[0].As<Napi::Function>());
599
+ mOnDataChannelCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
576
600
 
577
601
  mRtcPeerConnPtr->onDataChannel([&](std::shared_ptr<rtc::DataChannel> dc) {
578
602
  if (mOnDataChannelCallback)
579
- mOnDataChannelCallback->call([dc](Napi::Env env, std::vector<napi_value> &args) {
603
+ mOnDataChannelCallback->call([this, dc](Napi::Env env, std::vector<napi_value> &args) {
604
+ // Check the peer connection is not closed
605
+ if(instances.find(this) == instances.end())
606
+ throw ThreadSafeCallback::CancelException();
607
+
580
608
  // This will run in main thread and needs to construct the
581
609
  // arguments for the call
582
610
  std::shared_ptr<rtc::DataChannel> dataChannel = dc;
@@ -5,9 +5,12 @@
5
5
  #include <string>
6
6
  #include <variant>
7
7
  #include <memory>
8
+ #include <unordered_set>
9
+
8
10
  #include <napi.h>
9
- #include <napi-thread-safe-callback.hpp>
10
- #include "rtc/rtc.hpp"
11
+ #include <rtc/rtc.hpp>
12
+
13
+ #include "thread-safe-callback.h"
11
14
 
12
15
  class PeerConnectionWrapper : public Napi::ObjectWrap<PeerConnectionWrapper>
13
16
  {
@@ -35,21 +38,28 @@ public:
35
38
  Napi::Value rtt(const Napi::CallbackInfo &info);
36
39
  Napi::Value getSelectedCandidatePair(const Napi::CallbackInfo &info);
37
40
 
41
+ // Close all existing DataChannels
42
+ static void CloseAll();
43
+
38
44
  private:
39
45
  static Napi::FunctionReference constructor;
46
+ static std::unordered_set<PeerConnectionWrapper*> instances;
47
+
48
+ void doClose();
49
+
40
50
  std::string mPeerName;
41
- std::shared_ptr<rtc::PeerConnection> mRtcPeerConnPtr = nullptr;
51
+ std::unique_ptr<rtc::PeerConnection> mRtcPeerConnPtr = nullptr;
42
52
 
43
53
  // Callback Ptrs
44
- std::shared_ptr<ThreadSafeCallback> mOnLocalDescriptionCallback = nullptr;
45
- std::shared_ptr<ThreadSafeCallback> mOnLocalCandidateCallback = nullptr;
46
- std::shared_ptr<ThreadSafeCallback> mOnStateChangeCallback = nullptr;
47
- std::shared_ptr<ThreadSafeCallback> mOnGatheringStateChangeCallback = nullptr;
48
- std::shared_ptr<ThreadSafeCallback> mOnDataChannelCallback = nullptr;
54
+ std::unique_ptr<ThreadSafeCallback> mOnLocalDescriptionCallback = nullptr;
55
+ std::unique_ptr<ThreadSafeCallback> mOnLocalCandidateCallback = nullptr;
56
+ std::unique_ptr<ThreadSafeCallback> mOnStateChangeCallback = nullptr;
57
+ std::unique_ptr<ThreadSafeCallback> mOnGatheringStateChangeCallback = nullptr;
58
+ std::unique_ptr<ThreadSafeCallback> mOnDataChannelCallback = nullptr;
49
59
 
50
60
  // Helpers
51
61
  std::string candidateTypeToString(const rtc::Candidate::Type &type);
52
62
  std::string candidateTransportTypeToString(const rtc::Candidate::TransportType &transportType);
53
63
  };
54
64
 
55
- #endif // PEER_CONNECTION_WRAPPER_H
65
+ #endif // PEER_CONNECTION_WRAPPER_H
@@ -1,4 +1,9 @@
1
1
  #include "rtc-wrapper.h"
2
+ #include "peer-connection-wrapper.h"
3
+ #include "data-channel-wrapper.h"
4
+
5
+ #include <chrono>
6
+ #include <future>
2
7
 
3
8
  Napi::Object RtcWrapper::Init(Napi::Env env, Napi::Object exports)
4
9
  {
@@ -69,7 +74,12 @@ void RtcWrapper::cleanup(const Napi::CallbackInfo &info)
69
74
  Napi::Env env = info.Env();
70
75
  try
71
76
  {
72
- rtc::Cleanup();
77
+ PeerConnectionWrapper::CloseAll();
78
+ DataChannelWrapper::CloseAll();
79
+
80
+ const auto timeout = std::chrono::seconds(10);
81
+ if(rtc::Cleanup().wait_for(std::chrono::seconds(timeout)) == std::future_status::timeout)
82
+ throw std::runtime_error("cleanup timeout (possible deadlock)");
73
83
  }
74
84
  catch (std::exception &ex)
75
85
  {
package/src/rtc-wrapper.h CHANGED
@@ -3,7 +3,9 @@
3
3
 
4
4
  #include <iostream>
5
5
  #include <string>
6
+
6
7
  #include <napi.h>
8
+
7
9
  #include "rtc/rtc.hpp"
8
10
 
9
11
  class RtcWrapper
@@ -16,4 +18,4 @@ public:
16
18
  static void setSctpSettings(const Napi::CallbackInfo &info);
17
19
  };
18
20
 
19
- #endif // RTC_WRAPPER_H
21
+ #endif // RTC_WRAPPER_H
@@ -0,0 +1,69 @@
1
+ #include "thread-safe-callback.h"
2
+
3
+ #ifdef LEGACY_NAPI_THREAD_SAFE_CALLBACK
4
+
5
+ // Nothing to do
6
+
7
+ #else
8
+
9
+ const char* ThreadSafeCallback::CancelException::what() const throw()
10
+ {
11
+ return "ThreadSafeCallback cancelled";
12
+ }
13
+
14
+ ThreadSafeCallback::ThreadSafeCallback(Napi::Function callback)
15
+ {
16
+ if (!callback.IsFunction())
17
+ throw Napi::Error::New(callback.Env(), "Callback must be a function");
18
+
19
+ Napi::Env env = callback.Env();
20
+
21
+ receiver = Napi::Persistent(static_cast<Napi::Value>(Napi::Object::New(env)));
22
+ tsfn = tsfn_t::New(env,
23
+ std::move(callback),
24
+ "ThreadSafeCallback callback",
25
+ 0, 1, &receiver);
26
+ }
27
+
28
+ ThreadSafeCallback::~ThreadSafeCallback()
29
+ {
30
+ tsfn.Abort();
31
+ }
32
+
33
+ void ThreadSafeCallback::call(arg_func_t argFunc)
34
+ {
35
+ CallbackData *data = new CallbackData{std::move(argFunc)};
36
+ if (tsfn.BlockingCall(data) != napi_ok)
37
+ {
38
+ delete data;
39
+ throw std::runtime_error("Failed to call JavaScript callback");
40
+ }
41
+ }
42
+
43
+ void ThreadSafeCallback::callbackFunc(Napi::Env env,
44
+ Napi::Function callback,
45
+ Napi::Reference<Napi::Value> *context,
46
+ CallbackData *data)
47
+ {
48
+ if (!data)
49
+ return;
50
+
51
+ arg_vector_t args;
52
+ arg_func_t argFunc(std::move(data->argFunc));
53
+ delete data;
54
+
55
+ try
56
+ {
57
+ argFunc(env, args);
58
+ }
59
+ catch (CancelException &)
60
+ {
61
+ return;
62
+ }
63
+
64
+ if (env && callback)
65
+ callback.Call(context->Value(), args);
66
+ }
67
+
68
+ #endif
69
+
@@ -0,0 +1,57 @@
1
+ #ifndef THREAD_SAFE_CALLBACK_H
2
+ #define THREAD_SAFE_CALLBACK_H
3
+
4
+ #ifdef LEGACY_NAPI_THREAD_SAFE_CALLBACK
5
+
6
+ #include "napi-thread-safe-callback.hpp"
7
+
8
+ #else
9
+
10
+ #include <napi.h>
11
+
12
+ #include <vector>
13
+ #include <functional>
14
+
15
+ class ThreadSafeCallback
16
+ {
17
+ public:
18
+ using arg_vector_t = std::vector<napi_value>;
19
+ using arg_func_t = std::function<void(napi_env, arg_vector_t &)>;
20
+
21
+ ThreadSafeCallback(Napi::Function callback);
22
+ ~ThreadSafeCallback();
23
+
24
+ ThreadSafeCallback(const ThreadSafeCallback &) = delete;
25
+ ThreadSafeCallback(ThreadSafeCallback &&) = delete;
26
+
27
+ ThreadSafeCallback& operator=(const ThreadSafeCallback &) = delete;
28
+ ThreadSafeCallback& operator=(ThreadSafeCallback &&) = delete;
29
+
30
+ void call(arg_func_t argFunc);
31
+
32
+ class CancelException : public std::exception
33
+ {
34
+ const char* what() const throw();
35
+ };
36
+
37
+ private:
38
+ struct CallbackData
39
+ {
40
+ arg_func_t argFunc;
41
+ };
42
+
43
+ static void callbackFunc(Napi::Env env,
44
+ Napi::Function callback,
45
+ Napi::Reference<Napi::Value> *context,
46
+ CallbackData *data);
47
+
48
+ using tsfn_t = Napi::TypedThreadSafeFunction<Napi::Reference<Napi::Value>, CallbackData, callbackFunc>;
49
+
50
+ Napi::Reference<Napi::Value> receiver;
51
+ tsfn_t tsfn;
52
+ };
53
+
54
+ #endif // LEGACY_NAPI_THREAD_SAFE_CALLBACK
55
+
56
+ #endif // THREAD_SAFE_CALLBACK_H
57
+
@@ -96,5 +96,6 @@ setTimeout(() => {
96
96
  peer1 = null;
97
97
  peer2 = null;
98
98
  nodeDataChannel.cleanup();
99
- process.exit();
99
+ // No need for this (>= V0.1.14)
100
+ // process.exit();
100
101
  }, 5 * 1000);
package/test/test.js CHANGED
@@ -35,115 +35,114 @@ describe('PeerConnection Classes', () => {
35
35
  });
36
36
 
37
37
 
38
- // describe('P2P', () => {
39
- // let peer1 = new nodeDataChannel.PeerConnection("Peer1", { iceServers: ["stun:stun.l.google.com:19302"] });
40
- // let peer2 = new nodeDataChannel.PeerConnection("Peer2", { iceServers: ["stun:stun.l.google.com:19302"] });
41
- // let dc1 = null;
42
- // let dc2 = null;
43
-
44
- // // Mocks
45
- // const p1StateMock = jest.fn();
46
- // const p1GatheringStateMock = jest.fn();
47
- // const p2StateMock = jest.fn();
48
- // const p2GatheringStateMock = jest.fn();
49
- // const p1SDPMock = jest.fn();
50
- // const p1CandidateMock = jest.fn();
51
- // const p2SDPMock = jest.fn();
52
- // const p2CandidateMock = jest.fn();
53
-
54
- // const p1DCMock = jest.fn();
55
- // const p1DCMessageMock = jest.fn();
56
- // const p2DCMock = jest.fn();
57
- // const p2DCMessageMock = jest.fn();
58
-
59
- // // Set Callbacks
60
- // peer1.onStateChange(p1StateMock);
61
- // peer1.onGatheringStateChange(p1GatheringStateMock);
62
- // peer1.onLocalDescription((sdp, type) => {
63
- // p1SDPMock();
64
- // peer2.setRemoteDescription(sdp, type);
65
- // });
66
- // peer1.onLocalCandidate((candidate, mid) => {
67
- // p1CandidateMock();
68
- // peer2.addRemoteCandidate(candidate, mid);
69
- // });
70
-
71
- // // Set Callbacks
72
- // peer2.onStateChange(p2StateMock);
73
- // peer2.onGatheringStateChange(p2GatheringStateMock);
74
- // peer2.onLocalDescription((sdp, type) => {
75
- // p2SDPMock();
76
- // peer1.setRemoteDescription(sdp, type);
77
- // });
78
- // peer2.onLocalCandidate((candidate, mid) => {
79
- // p2CandidateMock();
80
- // peer1.addRemoteCandidate(candidate, mid);
81
- // });
82
- // peer2.onDataChannel((dc) => {
83
- // p2DCMock();
84
- // dc2 = dc;
85
- // dc2.onMessage((msg) => {
86
- // p2DCMessageMock(msg);
87
- // });
88
- // dc2.sendMessage("Hello From Peer2");
89
- // });
90
-
91
- // dc1 = peer1.createDataChannel("test-p2p");
92
- // dc1.onOpen(() => {
93
- // p1DCMock();
94
- // dc1.sendMessage("Hello From Peer1");
95
- // });
96
- // dc1.onMessage((msg) => {
97
- // p1DCMessageMock(msg);
98
- // });
99
-
100
- // test('P2P', (done) => {
101
- // // Default is 5000 ms but we need more
102
- // jest.setTimeout(30000);
103
-
104
- // setTimeout(() => {
105
- // dc1.close();
106
- // dc2.close();
107
- // peer1.close();
108
- // peer2.close();
109
-
110
- // // Fee memory
111
- // dc1 = null;
112
- // dc2 = null;
113
- // peer1 = null;
114
- // peer2 = null;
115
-
116
- // // Cleanup Threads
117
- // nodeDataChannel.cleanup();
118
- // }, 8 * 1000);
119
-
120
- // setTimeout(() => {
121
- // // State Callbacks
122
- // expect(p1StateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
123
- // expect(p1GatheringStateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
124
- // expect(p2StateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
125
- // expect(p2GatheringStateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
126
-
127
- // // SDP
128
- // expect(p1SDPMock.mock.calls.length).toBe(1);
129
- // expect(p2SDPMock.mock.calls.length).toBe(1);
130
-
131
- // // Candidates
132
- // expect(p1CandidateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
133
- // expect(p2CandidateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
134
-
135
- // // DataChannel
136
- // expect(p1DCMock.mock.calls.length).toBe(1);
137
- // expect(p1DCMessageMock.mock.calls.length).toBe(1);
138
- // expect(p1DCMessageMock.mock.calls[0][0]).toEqual("Hello From Peer2");
139
- // expect(p2DCMock.mock.calls.length).toBe(1);
140
- // // These 2 tests fails randomly
141
- // // Needs investigation ?
142
- // // expect(p2DCMessageMock.mock.calls.length).toBe(1);
143
- // // expect(p2DCMessageMock.mock.calls[0][0]).toEqual("Hello From Peer1");
144
-
145
- // done();
146
- // }, 20 * 1000);
147
-
148
- // });
149
- // });
38
+ describe('P2P', () => {
39
+ // Default is 5000 ms but we need more
40
+ jest.setTimeout(30000);
41
+
42
+ let peer1 = new nodeDataChannel.PeerConnection("Peer1", { iceServers: ["stun:stun.l.google.com:19302"] });
43
+ let peer2 = new nodeDataChannel.PeerConnection("Peer2", { iceServers: ["stun:stun.l.google.com:19302"] });
44
+ let dc1 = null;
45
+ let dc2 = null;
46
+
47
+ // Mocks
48
+ const p1StateMock = jest.fn();
49
+ const p1GatheringStateMock = jest.fn();
50
+ const p2StateMock = jest.fn();
51
+ const p2GatheringStateMock = jest.fn();
52
+ const p1SDPMock = jest.fn();
53
+ const p1CandidateMock = jest.fn();
54
+ const p2SDPMock = jest.fn();
55
+ const p2CandidateMock = jest.fn();
56
+
57
+ const p1DCMock = jest.fn();
58
+ const p1DCMessageMock = jest.fn();
59
+ const p2DCMock = jest.fn();
60
+ const p2DCMessageMock = jest.fn();
61
+
62
+ // Set Callbacks
63
+ peer1.onStateChange(p1StateMock);
64
+ peer1.onGatheringStateChange(p1GatheringStateMock);
65
+ peer1.onLocalDescription((sdp, type) => {
66
+ p1SDPMock();
67
+ peer2.setRemoteDescription(sdp, type);
68
+ });
69
+ peer1.onLocalCandidate((candidate, mid) => {
70
+ p1CandidateMock();
71
+ peer2.addRemoteCandidate(candidate, mid);
72
+ });
73
+
74
+ // Set Callbacks
75
+ peer2.onStateChange(p2StateMock);
76
+ peer2.onGatheringStateChange(p2GatheringStateMock);
77
+ peer2.onLocalDescription((sdp, type) => {
78
+ p2SDPMock();
79
+ peer1.setRemoteDescription(sdp, type);
80
+ });
81
+ peer2.onLocalCandidate((candidate, mid) => {
82
+ p2CandidateMock();
83
+ peer1.addRemoteCandidate(candidate, mid);
84
+ });
85
+ peer2.onDataChannel((dc) => {
86
+ p2DCMock();
87
+ dc2 = dc;
88
+ dc2.onMessage((msg) => {
89
+ p2DCMessageMock(msg);
90
+ });
91
+ dc2.sendMessage("Hello From Peer2");
92
+ });
93
+
94
+ dc1 = peer1.createDataChannel("test-p2p");
95
+ dc1.onOpen(() => {
96
+ p1DCMock();
97
+ dc1.sendMessage("Hello From Peer1");
98
+ });
99
+ dc1.onMessage((msg) => {
100
+ p1DCMessageMock(msg);
101
+ });
102
+
103
+ test('P2P', (done) => {
104
+ setTimeout(() => {
105
+ dc1.close();
106
+ dc2.close();
107
+ peer1.close();
108
+ peer2.close();
109
+
110
+ // Fee memory
111
+ dc1 = null;
112
+ dc2 = null;
113
+ peer1 = null;
114
+ peer2 = null;
115
+
116
+ // Cleanup Threads
117
+ nodeDataChannel.cleanup();
118
+ }, 10 * 1000);
119
+
120
+ setTimeout(() => {
121
+ // State Callbacks
122
+ expect(p1StateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
123
+ expect(p1GatheringStateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
124
+ expect(p2StateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
125
+ expect(p2GatheringStateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
126
+
127
+ // SDP
128
+ expect(p1SDPMock.mock.calls.length).toBe(1);
129
+ expect(p2SDPMock.mock.calls.length).toBe(1);
130
+
131
+ // Candidates
132
+ expect(p1CandidateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
133
+ expect(p2CandidateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
134
+
135
+ // DataChannel
136
+ expect(p1DCMock.mock.calls.length).toBe(1);
137
+ expect(p1DCMessageMock.mock.calls.length).toBe(1);
138
+ expect(p1DCMessageMock.mock.calls[0][0]).toEqual("Hello From Peer2");
139
+ expect(p2DCMock.mock.calls.length).toBe(1);
140
+
141
+ expect(p2DCMessageMock.mock.calls.length).toBe(1);
142
+ expect(p2DCMessageMock.mock.calls[0][0]).toEqual("Hello From Peer1");
143
+
144
+ done();
145
+ }, 12 * 1000);
146
+
147
+ });
148
+ });