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 +10 -3
- package/package.json +14 -5
- package/src/data-channel-wrapper.cpp +62 -30
- package/src/data-channel-wrapper.h +19 -9
- package/src/peer-connection-wrapper.cpp +56 -28
- package/src/peer-connection-wrapper.h +19 -9
- package/src/rtc-wrapper.cpp +11 -1
- package/src/rtc-wrapper.h +3 -1
- package/src/thread-safe-callback.cpp +69 -0
- package/src/thread-safe-callback.h +57 -0
- package/test/connectivity.js +2 -1
- package/test/test.js +111 -112
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
"
|
|
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.
|
|
39
|
-
"node-addon-api": "^
|
|
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
|
-
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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 <
|
|
10
|
-
|
|
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::
|
|
45
|
-
std::
|
|
46
|
-
std::
|
|
47
|
-
std::
|
|
48
|
-
std::
|
|
49
|
-
std::
|
|
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::
|
|
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
|
-
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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 <
|
|
10
|
-
|
|
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::
|
|
51
|
+
std::unique_ptr<rtc::PeerConnection> mRtcPeerConnPtr = nullptr;
|
|
42
52
|
|
|
43
53
|
// Callback Ptrs
|
|
44
|
-
std::
|
|
45
|
-
std::
|
|
46
|
-
std::
|
|
47
|
-
std::
|
|
48
|
-
std::
|
|
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
|
package/src/rtc-wrapper.cpp
CHANGED
|
@@ -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
|
-
|
|
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
|
+
|
package/test/connectivity.js
CHANGED
package/test/test.js
CHANGED
|
@@ -35,115 +35,114 @@ describe('PeerConnection Classes', () => {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
+
});
|