node-datachannel 0.6.0 → 0.7.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,6 +13,14 @@ void DataChannelWrapper::CloseAll()
13
13
  inst->doClose();
14
14
  }
15
15
 
16
+ void DataChannelWrapper::CleanupAll()
17
+ {
18
+ PLOG_DEBUG << "CleanupAll() called";
19
+ auto copy(instances);
20
+ for (auto inst : copy)
21
+ inst->doCleanup();
22
+ }
23
+
16
24
  Napi::Object DataChannelWrapper::Init(Napi::Env env, Napi::Object exports)
17
25
  {
18
26
  Napi::HandleScope scope(env);
@@ -78,11 +86,15 @@ void DataChannelWrapper::doClose()
78
86
  }
79
87
 
80
88
  mOnOpenCallback.reset();
81
- mOnClosedCallback.reset();
82
89
  mOnErrorCallback.reset();
83
90
  mOnBufferedAmountLowCallback.reset();
84
91
  mOnMessageCallback.reset();
92
+ }
85
93
 
94
+ void DataChannelWrapper::doCleanup()
95
+ {
96
+ PLOG_DEBUG << "doCleanup() called";
97
+ mOnClosedCallback.reset();
86
98
  instances.erase(this);
87
99
  }
88
100
 
@@ -350,6 +362,8 @@ void DataChannelWrapper::onClosed(const Napi::CallbackInfo &info)
350
362
  // This will run in main thread and needs to construct the
351
363
  // arguments for the call
352
364
  args = {};
365
+ },[this]{
366
+ doCleanup();
353
367
  }); });
354
368
  }
355
369
 
@@ -42,10 +42,14 @@ public:
42
42
  // Close all existing DataChannels
43
43
  static void CloseAll();
44
44
 
45
+ // Reset all Callbacks for existing DataChannels
46
+ static void CleanupAll();
47
+
45
48
  private:
46
- static std::unordered_set<DataChannelWrapper*> instances;
49
+ static std::unordered_set<DataChannelWrapper *> instances;
47
50
 
48
51
  void doClose();
52
+ void doCleanup();
49
53
 
50
54
  std::string mLabel;
51
55
  std::shared_ptr<rtc::DataChannel> mDataChannelPtr = nullptr;
@@ -2,6 +2,8 @@
2
2
  #include "media-direction.h"
3
3
  #include "media-rtcpreceivingsession-wrapper.h"
4
4
 
5
+ #include "plog/Log.h"
6
+
5
7
  Napi::FunctionReference TrackWrapper::constructor;
6
8
  std::unordered_set<TrackWrapper *> TrackWrapper::instances;
7
9
 
@@ -12,6 +14,14 @@ void TrackWrapper::CloseAll()
12
14
  inst->doClose();
13
15
  }
14
16
 
17
+ void TrackWrapper::CleanupAll()
18
+ {
19
+ PLOG_DEBUG << "CleanupAll() called";
20
+ auto copy(instances);
21
+ for (auto inst : copy)
22
+ inst->doCleanup();
23
+ }
24
+
15
25
  Napi::Object TrackWrapper::Init(Napi::Env env, Napi::Object exports)
16
26
  {
17
27
  Napi::HandleScope scope(env);
@@ -73,10 +83,14 @@ void TrackWrapper::doClose()
73
83
  }
74
84
 
75
85
  mOnOpenCallback.reset();
76
- mOnClosedCallback.reset();
77
86
  mOnErrorCallback.reset();
78
87
  mOnMessageCallback.reset();
88
+ }
79
89
 
90
+ void TrackWrapper::doCleanup()
91
+ {
92
+ PLOG_DEBUG << "doCleanup() called";
93
+ mOnClosedCallback.reset();
80
94
  instances.erase(this);
81
95
  }
82
96
 
@@ -276,7 +290,8 @@ void TrackWrapper::onOpen(const Napi::CallbackInfo &info)
276
290
  // Callback
277
291
  mOnOpenCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
278
292
 
279
- mTrackPtr->onOpen([&]() {
293
+ mTrackPtr->onOpen([&]()
294
+ {
280
295
  if (mOnOpenCallback)
281
296
  mOnOpenCallback->call([this](Napi::Env env, std::vector<napi_value> &args) {
282
297
  // Check the track is not closed
@@ -309,7 +324,8 @@ void TrackWrapper::onClosed(const Napi::CallbackInfo &info)
309
324
  // Callback
310
325
  mOnClosedCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
311
326
 
312
- mTrackPtr->onClosed([&]() {
327
+ mTrackPtr->onClosed([&]()
328
+ {
313
329
  if (mOnClosedCallback)
314
330
  mOnClosedCallback->call([this](Napi::Env env, std::vector<napi_value> &args) {
315
331
  // Do not check if the data channel has been closed here
@@ -317,6 +333,8 @@ void TrackWrapper::onClosed(const Napi::CallbackInfo &info)
317
333
  // This will run in main thread and needs to construct the
318
334
  // arguments for the call
319
335
  args = {};
336
+ },[this]{
337
+ doCleanup();
320
338
  }); });
321
339
  }
322
340
 
@@ -340,7 +358,8 @@ void TrackWrapper::onError(const Napi::CallbackInfo &info)
340
358
  // Callback
341
359
  mOnErrorCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
342
360
 
343
- mTrackPtr->onError([&](const std::string &error) {
361
+ mTrackPtr->onError([&](const std::string &error)
362
+ {
344
363
  if (mOnErrorCallback)
345
364
  mOnErrorCallback->call([this, error](Napi::Env env, std::vector<napi_value> &args) {
346
365
  // Check the track is not closed
@@ -373,7 +392,8 @@ void TrackWrapper::onMessage(const Napi::CallbackInfo &info)
373
392
  // Callback
374
393
  mOnMessageCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
375
394
 
376
- mTrackPtr->onMessage([&](std::variant<rtc::binary, std::string> message) {
395
+ mTrackPtr->onMessage([&](std::variant<rtc::binary, std::string> message)
396
+ {
377
397
  if (mOnMessageCallback)
378
398
  mOnMessageCallback->call([this, message = std::move(message)](Napi::Env env, std::vector<napi_value> &args) {
379
399
  // Check the track is not closed
@@ -39,21 +39,25 @@ public:
39
39
  void onError(const Napi::CallbackInfo &info);
40
40
  void onMessage(const Napi::CallbackInfo &info);
41
41
 
42
- // Close all existing track
42
+ // Close all existing tracks
43
43
  static void CloseAll();
44
44
 
45
+ // Reset all Callbacks for existing tracks
46
+ static void CleanupAll();
47
+
45
48
  private:
46
49
  static std::unordered_set<TrackWrapper *> instances;
47
50
 
48
51
  void doClose();
52
+ void doCleanup();
49
53
 
50
54
  std::shared_ptr<rtc::Track> mTrackPtr = nullptr;
51
55
 
52
- // Callback Ptrs
56
+ // Callback Ptrs
53
57
  std::unique_ptr<ThreadSafeCallback> mOnOpenCallback = nullptr;
54
58
  std::unique_ptr<ThreadSafeCallback> mOnClosedCallback = nullptr;
55
59
  std::unique_ptr<ThreadSafeCallback> mOnErrorCallback = nullptr;
56
60
  std::unique_ptr<ThreadSafeCallback> mOnMessageCallback = nullptr;
57
61
  };
58
62
 
59
- #endif // MEDIA_TRACK_WRAPPER_H
63
+ #endif // MEDIA_TRACK_WRAPPER_H
@@ -20,12 +20,12 @@ void PeerConnectionWrapper::CloseAll()
20
20
  inst->doClose();
21
21
  }
22
22
 
23
- void PeerConnectionWrapper::ResetCallbacksAll()
23
+ void PeerConnectionWrapper::CleanupAll()
24
24
  {
25
- PLOG_DEBUG << "ResetCallbacksAll() called";
25
+ PLOG_DEBUG << "CleanupAll() called";
26
26
  auto copy(instances);
27
27
  for (auto inst : copy)
28
- inst->doResetCallbacks();
28
+ inst->doCleanup();
29
29
  }
30
30
 
31
31
  Napi::Object PeerConnectionWrapper::Init(Napi::Env env, Napi::Object exports)
@@ -37,7 +37,6 @@ Napi::Object PeerConnectionWrapper::Init(Napi::Env env, Napi::Object exports)
37
37
  "PeerConnection",
38
38
  {
39
39
  InstanceMethod("close", &PeerConnectionWrapper::close),
40
- InstanceMethod("destroy", &PeerConnectionWrapper::destroy),
41
40
  InstanceMethod("setLocalDescription", &PeerConnectionWrapper::setLocalDescription),
42
41
  InstanceMethod("setRemoteDescription", &PeerConnectionWrapper::setRemoteDescription),
43
42
  InstanceMethod("localDescription", &PeerConnectionWrapper::localDescription),
@@ -253,7 +252,8 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N
253
252
  PeerConnectionWrapper::~PeerConnectionWrapper()
254
253
  {
255
254
  PLOG_DEBUG << "Destructor called";
256
- doDestroy();
255
+ doCleanup();
256
+ doClose();
257
257
  }
258
258
 
259
259
  void PeerConnectionWrapper::doClose()
@@ -273,40 +273,27 @@ void PeerConnectionWrapper::doClose()
273
273
  return;
274
274
  }
275
275
  }
276
- }
277
-
278
- void PeerConnectionWrapper::close(const Napi::CallbackInfo &info)
279
- {
280
- PLOG_DEBUG << "close() called";
281
- doClose();
282
- }
283
-
284
- void PeerConnectionWrapper::doDestroy()
285
- {
286
- PLOG_DEBUG << "doDestroy() called";
287
- doClose();
288
- doResetCallbacks();
289
- }
290
276
 
291
- void PeerConnectionWrapper::doResetCallbacks()
292
- {
293
- PLOG_DEBUG << "doResetCallbacks() called";
294
277
  mOnLocalDescriptionCallback.reset();
295
278
  mOnLocalCandidateCallback.reset();
296
- mOnStateChangeCallback.reset();
297
279
  mOnIceStateChangeCallback.reset();
298
280
  mOnSignalingStateChangeCallback.reset();
299
281
  mOnGatheringStateChangeCallback.reset();
300
282
  mOnDataChannelCallback.reset();
301
283
  mOnTrackCallback.reset();
284
+ }
302
285
 
303
- instances.erase(this);
286
+ void PeerConnectionWrapper::close(const Napi::CallbackInfo &info)
287
+ {
288
+ PLOG_DEBUG << "close() called";
289
+ doClose();
304
290
  }
305
291
 
306
- void PeerConnectionWrapper::destroy(const Napi::CallbackInfo &info)
292
+ void PeerConnectionWrapper::doCleanup()
307
293
  {
308
- PLOG_DEBUG << "destroy() called";
309
- doDestroy();
294
+ PLOG_DEBUG << "doCleanup() called";
295
+ mOnStateChangeCallback.reset();
296
+ instances.erase(this);
310
297
  }
311
298
 
312
299
  void PeerConnectionWrapper::setLocalDescription(const Napi::CallbackInfo &info)
@@ -738,6 +725,13 @@ void PeerConnectionWrapper::onStateChange(const Napi::CallbackInfo &info)
738
725
  stream << state;
739
726
  args = {Napi::String::New(env, stream.str())};
740
727
  PLOG_DEBUG << "mOnStateChangeCallback call(2)";
728
+ },[this,state](){
729
+ PLOG_DEBUG << "mOnStateChangeCallback cleanup";
730
+ // Special case for closed state, we need to reset all callbacks
731
+ if(state == rtc::PeerConnection::State::Closed)
732
+ {
733
+ doCleanup();
734
+ }
741
735
  }); });
742
736
  }
743
737
 
@@ -19,8 +19,6 @@ public:
19
19
  PeerConnectionWrapper(const Napi::CallbackInfo &info);
20
20
  ~PeerConnectionWrapper();
21
21
 
22
- void destroy(const Napi::CallbackInfo &info);
23
-
24
22
  // Functions
25
23
  void close(const Napi::CallbackInfo &info);
26
24
  void setLocalDescription(const Napi::CallbackInfo &info);
@@ -58,15 +56,14 @@ public:
58
56
  static void CloseAll();
59
57
 
60
58
  // Reset all Callbacks for existing Peer Connections
61
- static void ResetCallbacksAll();
59
+ static void CleanupAll();
62
60
 
63
61
  private:
64
62
  static Napi::FunctionReference constructor;
65
63
  static std::unordered_set<PeerConnectionWrapper *> instances;
66
64
 
67
65
  void doClose();
68
- void doDestroy();
69
- void doResetCallbacks();
66
+ void doCleanup();
70
67
 
71
68
  std::string mPeerName;
72
69
  std::unique_ptr<rtc::PeerConnection> mRtcPeerConnPtr = nullptr;
@@ -125,8 +125,11 @@ void RtcWrapper::cleanup(const Napi::CallbackInfo &info)
125
125
  if (rtc::Cleanup().wait_for(std::chrono::seconds(timeout)) == std::future_status::timeout)
126
126
  throw std::runtime_error("cleanup timeout (possible deadlock)");
127
127
 
128
- // Clear Callbacks
129
- PeerConnectionWrapper::ResetCallbacksAll();
128
+ // Cleanup the instances
129
+ PeerConnectionWrapper::CleanupAll();
130
+ DataChannelWrapper::CleanupAll();
131
+ TrackWrapper::CleanupAll();
132
+ WebSocketWrapper::CleanupAll();
130
133
 
131
134
  if (logCallback)
132
135
  logCallback.reset();
@@ -26,9 +26,9 @@ ThreadSafeCallback::~ThreadSafeCallback()
26
26
  tsfn.Abort();
27
27
  }
28
28
 
29
- void ThreadSafeCallback::call(arg_func_t argFunc)
29
+ void ThreadSafeCallback::call(arg_func_t argFunc, cleanup_func_t cleanupFunc)
30
30
  {
31
- CallbackData *data = new CallbackData{std::move(argFunc)};
31
+ CallbackData *data = new CallbackData{std::move(argFunc), std::move(cleanupFunc)};
32
32
  if (tsfn.BlockingCall(data) != napi_ok)
33
33
  {
34
34
  delete data;
@@ -47,6 +47,7 @@ void ThreadSafeCallback::callbackFunc(Napi::Env env,
47
47
 
48
48
  arg_vector_t args;
49
49
  arg_func_t argFunc(std::move(data->argFunc));
50
+ cleanup_func_t cleanup(std::move(data->cleanupFunc));
50
51
  delete data;
51
52
 
52
53
  try
@@ -59,5 +60,8 @@ void ThreadSafeCallback::callbackFunc(Napi::Env env,
59
60
  }
60
61
 
61
62
  if (env && callback)
63
+ {
62
64
  callback.Call(context->Value(), args);
65
+ cleanup();
66
+ }
63
67
  }
@@ -11,6 +11,7 @@ class ThreadSafeCallback
11
11
  public:
12
12
  using arg_vector_t = std::vector<napi_value>;
13
13
  using arg_func_t = std::function<void(napi_env, arg_vector_t &)>;
14
+ using cleanup_func_t = std::function<void()>;
14
15
 
15
16
  ThreadSafeCallback(Napi::Function callback);
16
17
  ~ThreadSafeCallback();
@@ -21,7 +22,7 @@ public:
21
22
  ThreadSafeCallback &operator=(const ThreadSafeCallback &) = delete;
22
23
  ThreadSafeCallback &operator=(ThreadSafeCallback &&) = delete;
23
24
 
24
- void call(arg_func_t argFunc);
25
+ void call(arg_func_t argFunc, cleanup_func_t cleanupFunc = []() {});
25
26
 
26
27
  class CancelException : public std::exception
27
28
  {
@@ -32,6 +33,7 @@ private:
32
33
  struct CallbackData
33
34
  {
34
35
  arg_func_t argFunc;
36
+ cleanup_func_t cleanupFunc;
35
37
  };
36
38
 
37
39
  static void callbackFunc(Napi::Env env,
@@ -13,6 +13,14 @@ void WebSocketWrapper::CloseAll()
13
13
  inst->doClose();
14
14
  }
15
15
 
16
+ void WebSocketWrapper::CleanupAll()
17
+ {
18
+ PLOG_DEBUG << "CleanupAll() called";
19
+ auto copy(instances);
20
+ for (auto inst : copy)
21
+ inst->doCleanup();
22
+ }
23
+
16
24
  Napi::Object WebSocketWrapper::Init(Napi::Env env, Napi::Object exports)
17
25
  {
18
26
  Napi::HandleScope scope(env);
@@ -255,11 +263,15 @@ void WebSocketWrapper::doClose()
255
263
  }
256
264
 
257
265
  mOnOpenCallback.reset();
258
- mOnClosedCallback.reset();
259
266
  mOnErrorCallback.reset();
260
267
  mOnBufferedAmountLowCallback.reset();
261
268
  mOnMessageCallback.reset();
269
+ }
262
270
 
271
+ void WebSocketWrapper::doCleanup()
272
+ {
273
+ PLOG_DEBUG << "doCleanup() called";
274
+ mOnClosedCallback.reset();
263
275
  instances.erase(this);
264
276
  }
265
277
 
@@ -488,8 +500,7 @@ void WebSocketWrapper::onOpen(const Napi::CallbackInfo &info)
488
500
  // This will run in main thread and needs to construct the
489
501
  // arguments for the call
490
502
  args = {};
491
- PLOG_DEBUG << "mOnOpenCallback call(2)"; });
492
- });
503
+ PLOG_DEBUG << "mOnOpenCallback call(2)"; }); });
493
504
  }
494
505
 
495
506
  void WebSocketWrapper::onClosed(const Napi::CallbackInfo &info)
@@ -524,6 +535,8 @@ void WebSocketWrapper::onClosed(const Napi::CallbackInfo &info)
524
535
  // This will run in main thread and needs to construct the
525
536
  // arguments for the call
526
537
  args = {};
538
+ },[this]{
539
+ doCleanup();
527
540
  }); });
528
541
  }
529
542
 
@@ -22,7 +22,7 @@ public:
22
22
 
23
23
  // Functions
24
24
  void open(const Napi::CallbackInfo &info);
25
- void close(const Napi::CallbackInfo &info);
25
+ void close(const Napi::CallbackInfo &info);
26
26
  Napi::Value sendMessage(const Napi::CallbackInfo &info);
27
27
  Napi::Value sendMessageBinary(const Napi::CallbackInfo &info);
28
28
  Napi::Value isOpen(const Napi::CallbackInfo &info);
@@ -40,10 +40,14 @@ public:
40
40
  // Close all existing WebSockets
41
41
  static void CloseAll();
42
42
 
43
+ // Reset all Callbacks for existing WebSockets
44
+ static void CleanupAll();
45
+
43
46
  private:
44
- static std::unordered_set<WebSocketWrapper*> instances;
47
+ static std::unordered_set<WebSocketWrapper *> instances;
45
48
 
46
49
  void doClose();
50
+ void doCleanup();
47
51
 
48
52
  std::shared_ptr<rtc::WebSocket> mWebSocketPtr = nullptr;
49
53
 
@@ -0,0 +1,37 @@
1
+ import * as nodeDataChannel from '../../lib';
2
+
3
+ describe('Module Definition', () => {
4
+ test('Module Defined', () => {
5
+ expect(nodeDataChannel).toBeDefined();
6
+ expect(nodeDataChannel.initLogger).toBeDefined();
7
+ expect(nodeDataChannel.PeerConnection).toBeDefined();
8
+ expect(typeof nodeDataChannel.PeerConnection).toBe('function');
9
+ expect(nodeDataChannel.DataChannel).toBeDefined();
10
+ expect(typeof nodeDataChannel.DataChannel).toBe('function');
11
+ });
12
+ });
13
+
14
+ describe('PeerConnection Classes', () => {
15
+ test('Create PeerConnection', () => {
16
+ let peer = new nodeDataChannel.PeerConnection('Peer', { iceServers: ['stun:stun.l.google.com:19302'] });
17
+ expect(peer).toBeDefined();
18
+ expect(peer.onStateChange).toBeDefined();
19
+ expect(peer.createDataChannel).toBeDefined();
20
+
21
+ peer.close();
22
+ });
23
+
24
+ test('Create Data Channel', () => {
25
+ let peer = new nodeDataChannel.PeerConnection('Peer', { iceServers: ['stun:stun.l.google.com:19302'] });
26
+ let dc = peer.createDataChannel('test', { protocol: 'test-protocol' });
27
+ expect(dc).toBeDefined();
28
+ expect(dc.getId()).toBeDefined();
29
+ expect(dc.getProtocol()).toBe('test-protocol');
30
+ expect(dc.getLabel()).toBe('test');
31
+ expect(dc.onOpen).toBeDefined();
32
+ expect(dc.onMessage).toBeDefined();
33
+
34
+ dc.close();
35
+ peer.close();
36
+ });
37
+ });
@@ -0,0 +1,73 @@
1
+ import { jest } from '@jest/globals';
2
+ import * as nodeDataChannel from '../../lib';
3
+
4
+ describe('P2P', () => {
5
+ // Default is 5000 ms but we need more
6
+ jest.setTimeout(12000);
7
+
8
+ test.each(Array(100).fill(0))('P2P Test-%p', (i) => {
9
+ return new Promise((done) => {
10
+ let peer1 = new nodeDataChannel.PeerConnection('Peer1', { iceServers: ['stun:stun.l.google.com:19302'] });
11
+ let peer2 = new nodeDataChannel.PeerConnection('Peer2', { iceServers: ['stun:stun.l.google.com:19302'] });
12
+ let dc1 = null;
13
+ let dc2 = null;
14
+
15
+ // Mocks
16
+ const p1DCMessageMock = jest.fn();
17
+ const p2DCMessageMock = jest.fn();
18
+
19
+ // Set Callbacks
20
+ peer1.onStateChange(() => {
21
+ /** */
22
+ });
23
+ peer1.onGatheringStateChange(() => {
24
+ /** */
25
+ });
26
+ peer1.onLocalDescription((sdp, type) => {
27
+ peer2.setRemoteDescription(sdp, type);
28
+ });
29
+ peer1.onLocalCandidate((candidate, mid) => {
30
+ peer2.addRemoteCandidate(candidate, mid);
31
+ });
32
+
33
+ // Set Callbacks
34
+ peer2.onStateChange(() => {
35
+ /** */
36
+ });
37
+ peer2.onGatheringStateChange(() => {
38
+ /** */
39
+ });
40
+ peer2.onLocalDescription((sdp, type) => {
41
+ peer1.setRemoteDescription(sdp, type);
42
+ });
43
+ peer2.onLocalCandidate((candidate, mid) => {
44
+ peer1.addRemoteCandidate(candidate, mid);
45
+ });
46
+ peer2.onDataChannel((dc) => {
47
+ dc2 = dc;
48
+ dc2.onMessage((msg) => {
49
+ p2DCMessageMock(msg);
50
+ dc2.sendMessage('Hello From Peer2');
51
+ });
52
+ });
53
+
54
+ dc1 = peer1.createDataChannel('test-p2p');
55
+ dc1.onOpen(() => {
56
+ dc1.sendMessage('Hello From Peer1');
57
+ });
58
+ dc1.onMessage((msg) => {
59
+ p1DCMessageMock(msg);
60
+ peer1.close();
61
+ peer2.close();
62
+
63
+ // DataChannel
64
+ expect(p1DCMessageMock.mock.calls.length).toBe(1);
65
+ expect(p1DCMessageMock.mock.calls[0][0]).toEqual('Hello From Peer2');
66
+ expect(p2DCMessageMock.mock.calls.length).toBe(1);
67
+ expect(p2DCMessageMock.mock.calls[0][0]).toEqual('Hello From Peer1');
68
+
69
+ done();
70
+ });
71
+ });
72
+ });
73
+ });
@@ -0,0 +1,99 @@
1
+ import { jest } from '@jest/globals';
2
+ import * as nodeDataChannel from '../../lib';
3
+
4
+ describe('P2P', () => {
5
+ // Default is 5000 ms but we need more
6
+ jest.setTimeout(30000);
7
+
8
+ test('P2P Test', () => {
9
+ return new Promise((done) => {
10
+ let peer1 = new nodeDataChannel.PeerConnection('Peer1', { iceServers: ['stun:stun.l.google.com:19302'] });
11
+ let peer2 = new nodeDataChannel.PeerConnection('Peer2', { iceServers: ['stun:stun.l.google.com:19302'] });
12
+ let dc1 = null;
13
+ let dc2 = null;
14
+
15
+ // Mocks
16
+ const p1StateMock = jest.fn();
17
+ const p1GatheringStateMock = jest.fn();
18
+ const p2StateMock = jest.fn();
19
+ const p2GatheringStateMock = jest.fn();
20
+ const p1SDPMock = jest.fn();
21
+ const p1CandidateMock = jest.fn();
22
+ const p2SDPMock = jest.fn();
23
+ const p2CandidateMock = jest.fn();
24
+
25
+ const p1DCMock = jest.fn();
26
+ const p1DCMessageMock = jest.fn();
27
+ const p2DCMock = jest.fn();
28
+ const p2DCMessageMock = jest.fn();
29
+
30
+ // Set Callbacks
31
+ peer1.onStateChange(p1StateMock);
32
+ peer1.onGatheringStateChange(p1GatheringStateMock);
33
+ peer1.onLocalDescription((sdp, type) => {
34
+ p1SDPMock();
35
+ peer2.setRemoteDescription(sdp, type);
36
+ });
37
+ peer1.onLocalCandidate((candidate, mid) => {
38
+ p1CandidateMock();
39
+ peer2.addRemoteCandidate(candidate, mid);
40
+ });
41
+
42
+ // Set Callbacks
43
+ peer2.onStateChange(p2StateMock);
44
+ peer2.onGatheringStateChange(p2GatheringStateMock);
45
+ peer2.onLocalDescription((sdp, type) => {
46
+ p2SDPMock();
47
+ peer1.setRemoteDescription(sdp, type);
48
+ });
49
+ peer2.onLocalCandidate((candidate, mid) => {
50
+ p2CandidateMock();
51
+ peer1.addRemoteCandidate(candidate, mid);
52
+ });
53
+ peer2.onDataChannel((dc) => {
54
+ p2DCMock();
55
+ dc2 = dc;
56
+ dc2.onMessage((msg) => {
57
+ p2DCMessageMock(msg);
58
+ dc2.sendMessage('Hello From Peer2');
59
+ });
60
+ });
61
+
62
+ dc1 = peer1.createDataChannel('test-p2p');
63
+ dc1.onOpen(() => {
64
+ p1DCMock();
65
+ dc1.sendMessage('Hello From Peer1');
66
+ });
67
+ dc1.onMessage((msg) => {
68
+ p1DCMessageMock(msg);
69
+ peer1.close();
70
+ peer2.close();
71
+
72
+ // State Callbacks
73
+ expect(p1StateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
74
+ expect(p1GatheringStateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
75
+ expect(p2StateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
76
+ expect(p2GatheringStateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
77
+
78
+ // SDP
79
+ expect(p1SDPMock.mock.calls.length).toBe(1);
80
+ expect(p2SDPMock.mock.calls.length).toBe(1);
81
+
82
+ // Candidates
83
+ expect(p1CandidateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
84
+ expect(p2CandidateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
85
+
86
+ // DataChannel
87
+ expect(p1DCMock.mock.calls.length).toBe(1);
88
+ expect(p1DCMessageMock.mock.calls.length).toBe(1);
89
+ expect(p1DCMessageMock.mock.calls[0][0]).toEqual('Hello From Peer2');
90
+ expect(p2DCMock.mock.calls.length).toBe(1);
91
+
92
+ expect(p2DCMessageMock.mock.calls.length).toBe(1);
93
+ expect(p2DCMessageMock.mock.calls[0][0]).toEqual('Hello From Peer1');
94
+
95
+ done();
96
+ });
97
+ });
98
+ });
99
+ });