node-datachannel 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.github/workflows/build-linux.yml +7 -2
  2. package/.github/workflows/build-win.yml +3 -3
  3. package/.gitmodules +3 -0
  4. package/CMakeLists.txt +1 -1
  5. package/README.md +18 -4
  6. package/cmake/toolchain/ci.cmake +1 -1
  7. package/examples/client-server/client-benchmark.js +7 -9
  8. package/examples/client-server/client-periodic.js +7 -9
  9. package/examples/client-server/client.js +7 -9
  10. package/examples/client-server/package-lock.json +0 -27
  11. package/examples/client-server/package.json +0 -1
  12. package/examples/client-server/signaling-server.js +21 -10
  13. package/examples/electron-demo/README.md +1 -1
  14. package/examples/electron-demo/package-lock.json +316 -372
  15. package/examples/electron-demo/package.json +2 -1
  16. package/examples/electron-demo/webpack.main.config.js +10 -0
  17. package/examples/websocket/websocket-client.js +20 -0
  18. package/examples/websocket/websocket-server.js +22 -0
  19. package/jest.config.cjs +1 -1
  20. package/lib/index.cjs +52 -9
  21. package/lib/index.js +22 -15
  22. package/lib/node-datachannel.js +7 -0
  23. package/lib/websocket-server.js +34 -0
  24. package/package.json +3 -3
  25. package/polyfill/Events.js +5 -3
  26. package/polyfill/Exception.js +23 -0
  27. package/polyfill/README.md +4 -0
  28. package/polyfill/RTCDataChannel.js +19 -11
  29. package/polyfill/RTCError.d.ts +11 -0
  30. package/polyfill/RTCError.js +65 -0
  31. package/polyfill/RTCIceCandidate.js +22 -23
  32. package/polyfill/RTCPeerConnection.js +157 -42
  33. package/polyfill/index.cjs +350 -82
  34. package/polyfill/index.js +3 -0
  35. package/src/peer-connection-wrapper.cpp +11 -3
  36. package/src/web-socket-server-wrapper.cpp +23 -23
  37. package/src/web-socket-server-wrapper.h +8 -6
  38. package/src/web-socket-wrapper.cpp +7 -1
  39. package/test/connectivity.js +1 -23
  40. package/test/wpt-tests/README.md +46 -0
  41. package/test/wpt-tests/chrome-failed-tests.js +42 -0
  42. package/test/wpt-tests/index.js +96 -0
  43. package/test/wpt-tests/last-test-results.md +301 -0
  44. package/test/wpt-tests/package-lock.json +1712 -0
  45. package/test/wpt-tests/package.json +19 -0
  46. package/test/wpt-tests/wpt-test-list.js +139 -0
  47. package/test/wpt-tests/wpt.js +131 -0
  48. package/test/wpt.js +0 -50
@@ -100,8 +100,17 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N
100
100
  Napi::Array iceServers = config.Get("iceServers").As<Napi::Array>();
101
101
  for (uint32_t i = 0; i < iceServers.Length(); i++)
102
102
  {
103
- if (iceServers.Get(i).IsString())
104
- rtcConfig.iceServers.emplace_back(iceServers.Get(i).As<Napi::String>().ToString());
103
+ if (iceServers.Get(i).IsString()){
104
+ try
105
+ {
106
+ rtcConfig.iceServers.emplace_back(iceServers.Get(i).As<Napi::String>().ToString());
107
+ }
108
+ catch(std::exception &ex)
109
+ {
110
+ Napi::TypeError::New(env, "SyntaxError: IceServer config error: " + std::string(ex.what())).ThrowAsJavaScriptException();
111
+ return;
112
+ }
113
+ }
105
114
  else
106
115
  {
107
116
  if (!iceServers.Get(i).IsObject())
@@ -116,7 +125,6 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N
116
125
  Napi::TypeError::New(env, "IceServer config error (hostname OR/AND port is not suitable)").ThrowAsJavaScriptException();
117
126
  return;
118
127
  }
119
-
120
128
  if (iceServer.Get("relayType").IsString() &&
121
129
  (!iceServer.Get("username").IsString() || !iceServer.Get("password").IsString()))
122
130
  {
@@ -2,6 +2,9 @@
2
2
 
3
3
  #include "plog/Log.h"
4
4
 
5
+ Napi::FunctionReference WebSocketServerWrapper::constructor;
6
+ std::unordered_set<WebSocketServerWrapper *> WebSocketServerWrapper::instances;
7
+
5
8
  void WebSocketServerWrapper::StopAll()
6
9
  {
7
10
  PLOG_DEBUG << "StopAll() called";
@@ -10,9 +13,6 @@ void WebSocketServerWrapper::StopAll()
10
13
  inst->doStop();
11
14
  }
12
15
 
13
- Napi::FunctionReference WebSocketServerWrapper::constructor;
14
- std::unordered_set<WebSocketServerWrapper *> WebSocketServerWrapper::instances;
15
-
16
16
  Napi::Object WebSocketServerWrapper::Init(Napi::Env env, Napi::Object exports)
17
17
  {
18
18
  Napi::HandleScope scope(env);
@@ -36,7 +36,6 @@ Napi::Object WebSocketServerWrapper::Init(Napi::Env env, Napi::Object exports)
36
36
  WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap<WebSocketServerWrapper>(info)
37
37
  {
38
38
  PLOG_DEBUG << "Constructor called";
39
-
40
39
  Napi::Env env = info.Env();
41
40
 
42
41
  // Create WebSocketServer without config
@@ -52,7 +51,7 @@ WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) :
52
51
  Napi::Error::New(env, std::string("libdatachannel error while creating WebSocketServer without config: ") + ex.what()).ThrowAsJavaScriptException();
53
52
  return;
54
53
  }
55
-
54
+
56
55
  PLOG_DEBUG << "WebSocketServer created without config";
57
56
 
58
57
  instances.insert(this);
@@ -60,14 +59,14 @@ WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) :
60
59
  }
61
60
 
62
61
  // Create WebSocketServer with config
63
-
64
- Napi::Object config = info[0].As<Napi::Object>();
62
+
63
+ Napi::Object config = info[0].As<Napi::Object>();
65
64
  rtc::WebSocketServerConfiguration webSocketServerConfig;
66
65
 
67
66
  // Port
68
67
  if (config.Has("port"))
69
68
  {
70
- if (!config.Get("port").IsNumber())
69
+ if (!config.Get("port").IsNumber())
71
70
  {
72
71
  Napi::TypeError::New(info.Env(), "port must be a number").ThrowAsJavaScriptException();
73
72
  return;
@@ -78,7 +77,7 @@ WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) :
78
77
  // Enable TLS
79
78
  if (config.Has("enableTls"))
80
79
  {
81
- if (!config.Get("enableTls").IsBoolean())
80
+ if (!config.Get("enableTls").IsBoolean())
82
81
  {
83
82
  Napi::TypeError::New(info.Env(), "enableTls must be boolean").ThrowAsJavaScriptException();
84
83
  return;
@@ -89,7 +88,7 @@ WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) :
89
88
  // Certificate PEM File
90
89
  if (config.Has("certificatePemFile"))
91
90
  {
92
- if (!config.Get("certificatePemFile").IsString())
91
+ if (!config.Get("certificatePemFile").IsString())
93
92
  {
94
93
  Napi::TypeError::New(info.Env(), "certificatePemFile must be a string").ThrowAsJavaScriptException();
95
94
  return;
@@ -100,7 +99,7 @@ WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) :
100
99
  // Key PEM File
101
100
  if (config.Has("keyPemFile"))
102
101
  {
103
- if (!config.Get("keyPemFile").IsString())
102
+ if (!config.Get("keyPemFile").IsString())
104
103
  {
105
104
  Napi::TypeError::New(info.Env(), "keyPemFile must be a string").ThrowAsJavaScriptException();
106
105
  return;
@@ -111,7 +110,7 @@ WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) :
111
110
  // Key PEM Pass
112
111
  if (config.Has("keyPemPass"))
113
112
  {
114
- if (!config.Get("keyPemPass").IsString())
113
+ if (!config.Get("keyPemPass").IsString())
115
114
  {
116
115
  Napi::TypeError::New(info.Env(), "keyPemPass must be a string").ThrowAsJavaScriptException();
117
116
  return;
@@ -122,7 +121,7 @@ WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) :
122
121
  // Bind Address
123
122
  if (config.Has("bindAddress"))
124
123
  {
125
- if (!config.Get("bindAddress").IsString())
124
+ if (!config.Get("bindAddress").IsString())
126
125
  {
127
126
  Napi::TypeError::New(info.Env(), "bindAddress must be a string").ThrowAsJavaScriptException();
128
127
  return;
@@ -133,7 +132,7 @@ WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) :
133
132
  // Connection Timeout
134
133
  if (config.Has("connectionTimeout"))
135
134
  {
136
- if (!config.Get("connectionTimeout").IsNumber())
135
+ if (!config.Get("connectionTimeout").IsNumber())
137
136
  {
138
137
  Napi::TypeError::New(info.Env(), "connectionTimeout must be a number").ThrowAsJavaScriptException();
139
138
  return;
@@ -144,7 +143,7 @@ WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) :
144
143
  // Max Message Size
145
144
  if (config.Has("maxMessageSize"))
146
145
  {
147
- if (!config.Get("maxMessageSize").IsNumber())
146
+ if (!config.Get("maxMessageSize").IsNumber())
148
147
  {
149
148
  Napi::TypeError::New(info.Env(), "maxMessageSize must be a number").ThrowAsJavaScriptException();
150
149
  return;
@@ -163,7 +162,7 @@ WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) :
163
162
  Napi::Error::New(env, std::string("libdatachannel error while creating WebSocketServer: ") + ex.what()).ThrowAsJavaScriptException();
164
163
  return;
165
164
  }
166
-
165
+
167
166
  PLOG_DEBUG << "WebSocketServer created";
168
167
  instances.insert(this);
169
168
  }
@@ -179,7 +178,7 @@ void WebSocketServerWrapper::doStop()
179
178
  PLOG_DEBUG << "doStop() called";
180
179
  if (mWebSocketServerPtr)
181
180
  {
182
- PLOG_DEBUG << "Closing...";
181
+ PLOG_DEBUG << "Stopping...";
183
182
  try
184
183
  {
185
184
  mWebSocketServerPtr->stop();
@@ -187,13 +186,12 @@ void WebSocketServerWrapper::doStop()
187
186
  }
188
187
  catch (std::exception &ex)
189
188
  {
190
- std::cerr << std::string("libWebRtc error while closing WebSocketServer: ") + ex.what() << std::endl;
189
+ std::cerr << std::string("libdatachannel error while closing WebSocketServer: ") + ex.what() << std::endl;
191
190
  return;
192
191
  }
193
192
  }
194
193
 
195
194
  mOnClientCallback.reset();
196
-
197
195
  instances.erase(this);
198
196
  }
199
197
 
@@ -238,7 +236,7 @@ void WebSocketServerWrapper::onClient(const Napi::CallbackInfo &info)
238
236
 
239
237
  if (length < 1 || !info[0].IsFunction())
240
238
  {
241
- Napi::TypeError::New(env, "Function expected as onClient calback").ThrowAsJavaScriptException();
239
+ Napi::TypeError::New(env, "Function expected as onClient callback").ThrowAsJavaScriptException();
242
240
  return;
243
241
  }
244
242
 
@@ -246,7 +244,7 @@ void WebSocketServerWrapper::onClient(const Napi::CallbackInfo &info)
246
244
  mOnClientCallback = std::make_unique<ThreadSafeCallback>(info[0].As<Napi::Function>());
247
245
 
248
246
  mWebSocketServerPtr->onClient([&](std::shared_ptr<rtc::WebSocket> ws)
249
- {
247
+ {
250
248
  PLOG_DEBUG << "onClient ws received from WebSocketServer";
251
249
  if (mOnClientCallback)
252
250
  mOnClientCallback->call([this, ws](Napi::Env env, std::vector<napi_value> &args) {
@@ -258,8 +256,10 @@ void WebSocketServerWrapper::onClient(const Napi::CallbackInfo &info)
258
256
  // This will run in main thread and needs to construct the
259
257
  // arguments for the call
260
258
  std::shared_ptr<rtc::WebSocket> webSocket = ws;
261
- auto instance = WebSocketWrapper::constructor.New({Napi::External<std::shared_ptr<rtc::WebSocket>>::New(env, nullptr), Napi::External<std::shared_ptr<rtc::WebSocket>>::New(env, &webSocket)});
259
+ // First argument is just a placeholder
260
+ auto instance = WebSocketWrapper::constructor.New({Napi::Boolean::New(env, false), Napi::External<std::shared_ptr<rtc::WebSocket>>::New(env, &webSocket)});
262
261
  args = {instance};
263
262
  PLOG_DEBUG << "mOnClientCallback call(2)";
264
- }); });
263
+ });
264
+ });
265
265
  }
@@ -16,13 +16,11 @@
16
16
  class WebSocketServerWrapper : public Napi::ObjectWrap<WebSocketServerWrapper>
17
17
  {
18
18
  public:
19
- static Napi::FunctionReference constructor;
20
19
  static Napi::Object Init(Napi::Env env, Napi::Object exports);
21
20
  WebSocketServerWrapper(const Napi::CallbackInfo &info);
22
21
  ~WebSocketServerWrapper();
23
22
 
24
23
  // Functions
25
-
26
24
  void stop(const Napi::CallbackInfo &info);
27
25
  Napi::Value port(const Napi::CallbackInfo &info);
28
26
 
@@ -31,13 +29,17 @@ public:
31
29
 
32
30
  // Close all existing WebSocketServers
33
31
  static void StopAll();
34
-
32
+
35
33
  private:
36
- static std::unordered_set<WebSocketServerWrapper*> instances;
37
- std::unique_ptr<rtc::WebSocketServer> mWebSocketServerPtr = nullptr;
38
- std::unique_ptr<ThreadSafeCallback> mOnClientCallback = nullptr;
34
+ static Napi::FunctionReference constructor;
35
+ static std::unordered_set<WebSocketServerWrapper *> instances;
39
36
 
40
37
  void doStop();
38
+
39
+ std::unique_ptr<rtc::WebSocketServer> mWebSocketServerPtr = nullptr;
40
+
41
+ // Callback Ptrs
42
+ std::unique_ptr<ThreadSafeCallback> mOnClientCallback = nullptr;
41
43
  };
42
44
 
43
45
  #endif // WEB_SOCKET_SERVER_WRAPPER_H
@@ -65,6 +65,9 @@ WebSocketWrapper::WebSocketWrapper(const Napi::CallbackInfo &info) : Napi::Objec
65
65
  mWebSocketPtr = *(info[1].As<Napi::External<std::shared_ptr<rtc::WebSocket>>>().Data());
66
66
  PLOG_DEBUG << "Using WebSocket got from WebSocketServer";
67
67
  instances.insert(this);
68
+
69
+ // Closed callback must be set to trigger cleanup
70
+ mOnClosedCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {}));
68
71
  return;
69
72
  }
70
73
 
@@ -82,6 +85,9 @@ WebSocketWrapper::WebSocketWrapper(const Napi::CallbackInfo &info) : Napi::Objec
82
85
  return;
83
86
  }
84
87
  instances.insert(this);
88
+
89
+ // Closed callback must be set to trigger cleanup
90
+ mOnClosedCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {}));
85
91
  return;
86
92
  }
87
93
 
@@ -240,7 +246,7 @@ WebSocketWrapper::WebSocketWrapper(const Napi::CallbackInfo &info) : Napi::Objec
240
246
  PLOG_DEBUG << "WebSocket created";
241
247
 
242
248
  // Closed callback must set to trigger cleanup
243
- mOnClosedCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo&){}));
249
+ mOnClosedCallback = std::make_unique<ThreadSafeCallback>(Napi::Function::New(info.Env(), [](const Napi::CallbackInfo &) {}));
244
250
 
245
251
  instances.insert(this);
246
252
  }
@@ -1,30 +1,16 @@
1
1
  import nodeDataChannel from '../lib/index.js';
2
2
 
3
3
  nodeDataChannel.initLogger('Debug');
4
- nodeDataChannel.preload();
5
4
 
6
5
  let dc1 = null;
7
6
  let dc2 = null;
8
7
 
9
- // Config options
10
- // export interface RtcConfig {
11
- // iceServers: string[];
12
- // proxyServer?: ProxyServer;
13
- // bindAddress?: string;
14
- // enableIceTcp?: boolean;
15
- // portRangeBegin?: number;
16
- // portRangeEnd?: number;
17
- // maxMessageSize?: number;
18
- // iceTransportPolicy?: TransportPolicy;
19
- // }
20
- //
21
8
  // "iceServers" option is an array of stun/turn server urls
22
9
  // Examples;
23
10
  // STUN Server Example : stun:stun.l.google.com:19302
24
11
  // TURN Server Example : turn:USERNAME:PASSWORD@TURN_IP_OR_ADDRESS:PORT
25
12
  // TURN Server Example (TCP) : turn:USERNAME:PASSWORD@TURN_IP_OR_ADDRESS:PORT?transport=tcp
26
13
  // TURN Server Example (TLS) : turns:USERNAME:PASSWORD@TURN_IP_OR_ADDRESS:PORT
27
-
28
14
  let peer1 = new nodeDataChannel.PeerConnection('Peer1', { iceServers: ['stun:stun.l.google.com:19302'] });
29
15
 
30
16
  // Set Callbacks
@@ -78,15 +64,7 @@ peer2.onDataChannel((dc) => {
78
64
  });
79
65
  });
80
66
 
81
- // DataChannel Options
82
- // export interface DataChannelInitConfig {
83
- // protocol?: string;
84
- // negotiated?: boolean;
85
- // id?: number;
86
- // ordered?: boolean;
87
- // maxPacketLifeTime?: number;
88
- // maxRetransmits?: number;
89
- // }
67
+ // Create DataChannel
90
68
  dc1 = peer1.createDataChannel('test');
91
69
  dc1.onOpen(() => {
92
70
  dc1.sendMessage('Hello from Peer1');
@@ -0,0 +1,46 @@
1
+ # Setup (First time usage)
2
+
3
+ > cd test/wpt-tests
4
+
5
+ > npm i
6
+
7
+ > git submodule update --init --recursive --depth 1
8
+
9
+ > cd wpt
10
+
11
+ > ./wpt make-hosts-file | sudo tee -a /etc/hosts
12
+
13
+ After your hosts file is configured, the servers will be locally accessible at:
14
+
15
+ http://web-platform.test:8000/
16
+
17
+ https://web-platform.test:8443/ \*
18
+
19
+ To use the web-based runner point your browser to:
20
+
21
+ http://web-platform.test:8000/tools/runner/index.html
22
+
23
+ https://web-platform.test:8443/tools/runner/index.html \*
24
+
25
+ # Running Tests
26
+
27
+ > npm run test
28
+
29
+ OR
30
+
31
+ > npm run:wpt (Let this run)
32
+
33
+ > npm run:test (In an other console)
34
+
35
+ The files that tested are listed in `wpt-test-list.js`. The commented lines need still to be one-by-one tested and uncommented.
36
+
37
+ Contributions are welcome!
38
+
39
+ # Run a test for Chrome
40
+
41
+ ./wpt run chrome /webrtc/RTCPeerConnection-addIceCandidate.html
42
+
43
+ # Latest Test Results
44
+
45
+ Please check [last-test-results.md](./last-test-results.md) page
46
+
@@ -0,0 +1,42 @@
1
+ import { runWptTests } from './wpt.js';
2
+
3
+ // Some tests also fail in Chrome
4
+ // We don't also care of them
5
+ let chromeFailedTests = [];
6
+ let totalNumberOfTests = 0;
7
+
8
+ export async function runChromeTests(wptTestList) {
9
+ chromeFailedTests = [];
10
+ totalNumberOfTests = 0;
11
+ let results = await runWptTests(wptTestList, true);
12
+ for (let i = 0; i < results.length; i++) {
13
+ totalNumberOfTests += results[i].result?.length || 0;
14
+ if (results[i].result && results[i].result.some((test) => test.status === 1)) {
15
+ chromeFailedTests.push({
16
+ test: results[i].test,
17
+ result: results[i].result.filter((test) => test.status === 1),
18
+ });
19
+ }
20
+ }
21
+ }
22
+
23
+ export function getChromeFailedTests() {
24
+ return chromeFailedTests;
25
+ }
26
+
27
+ export function isTestForChromeFailed(testPath, testName) {
28
+ return chromeFailedTests.some(
29
+ (test) =>
30
+ test.test === testPath && test.result.some((result) => result.name === testName && result.status === 1),
31
+ );
32
+ }
33
+
34
+ export function getTotalNumberOfTests() {
35
+ return totalNumberOfTests;
36
+ }
37
+
38
+ // Test
39
+ // (async () => {
40
+ // await runChromeTests();
41
+ // console.log(getChromeFailedTests());
42
+ // })();
@@ -0,0 +1,96 @@
1
+ import ndc from '../../lib/index.js';
2
+ import wptTestList from './wpt-test-list.js';
3
+ import { runWptTests } from './wpt.js';
4
+ import { runChromeTests, isTestForChromeFailed, getTotalNumberOfTests } from './chrome-failed-tests.js';
5
+
6
+ // Set the log level, for debugging purposes
7
+ // ndc.initLogger('Debug');
8
+
9
+ // Catch unhandled exceptions
10
+ process.on('unhandledRejection', (reason, promise) => {
11
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
12
+ process.exit(1);
13
+ });
14
+
15
+ // Run tests for Chrome
16
+ console.log('# Running tests for Chrome...');
17
+ await runChromeTests(wptTestList);
18
+ //console.log(JSON.stringify(getChromeFailedTests(), null, 2));
19
+
20
+ // Run tests for node-datachannel
21
+ console.log('');
22
+ console.log('# Running tests for node-datachannel...');
23
+ let results = await runWptTests(wptTestList);
24
+
25
+ // Calc total number of tests
26
+ let totalTests = 0;
27
+ results.forEach((result) => {
28
+ totalTests += result.result.length;
29
+ });
30
+
31
+ // Compare results
32
+ // Filter failed tests for node-datachannel
33
+ let failedTestsLibrary = [];
34
+ results.forEach((result) => {
35
+ if (result.result.some((test) => test.status === 1)) {
36
+ failedTestsLibrary.push({
37
+ test: result.test,
38
+ result: result.result.filter((test) => test.status === 1),
39
+ });
40
+ }
41
+ });
42
+ let totalFailedTestsLibrary = 0;
43
+ failedTestsLibrary.forEach((result) => {
44
+ totalFailedTestsLibrary += result.result.length;
45
+ });
46
+ // console.log(JSON.stringify(failedTestsLibrary, null, 2));
47
+
48
+ // Filter out any failed tests that also failed in Chrome
49
+ let failedTests = [];
50
+ failedTestsLibrary.forEach((result) => {
51
+ if (result.result.some((test) => !isTestForChromeFailed(result.test, test.name))) {
52
+ failedTests.push({
53
+ test: result.test,
54
+ result: result.result.filter((test) => !isTestForChromeFailed(result.test, test.name)),
55
+ });
56
+ }
57
+ });
58
+ let totalFailedTests = 0;
59
+ failedTests.forEach((result) => {
60
+ totalFailedTests += result.result.length;
61
+ });
62
+ // console.log(JSON.stringify(failedTests, null, 2));
63
+
64
+ // Print Report
65
+ // Print Test Names
66
+ console.log('');
67
+ console.log('# Tests Report');
68
+ // Total number of tests
69
+ console.log('Total Tests [Chrome]: ', getTotalNumberOfTests(), ' ');
70
+ console.log('Total Tests [Library]: ', totalTests, ' (We expect this to be equal to Total Tests [Chrome]) ');
71
+ // Number of passed tests
72
+ console.log('Passed Tests: ', totalTests - totalFailedTestsLibrary, ' ');
73
+ // Number of failed tests for chrome + node-datachannel
74
+ console.log(
75
+ 'Failed Tests (Chrome + Library): ',
76
+ totalFailedTestsLibrary - totalFailedTests,
77
+ " (We don't care about these tests) ",
78
+ );
79
+ // Number of failed tests
80
+ console.log('Failed Tests: ', totalFailedTests, ' ');
81
+
82
+ // Print Failed Tests
83
+ console.log('');
84
+ console.log('## Failed Tests');
85
+ for (let i = 0; i < failedTests.length; i++) {
86
+ console.log(`### ${failedTests[i].test}`);
87
+ for (let j = 0; j < failedTests[i].result.length; j++) {
88
+ console.log(`- name: ${failedTests[i].result[j].name} `);
89
+ console.log(` message: ${failedTests[i].result[j].message} `);
90
+ }
91
+ }
92
+
93
+ // Sometimes failed tests are not cleaned up
94
+ // This can prevent the process from exiting
95
+ ndc.cleanup();
96
+ console.log('End of tests');