node-datachannel 0.10.0 → 0.11.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.
- package/.github/workflows/build-mac-m1.yml +7 -6
- package/.github/workflows/build-mac-x64.yml +5 -5
- package/.github/workflows/build-win.yml +3 -3
- package/.gitmodules +3 -0
- package/CMakeLists.txt +2 -2
- package/README.md +33 -31
- package/examples/client-server/client-benchmark.js +7 -9
- package/examples/client-server/client-periodic.js +7 -9
- package/examples/client-server/client.js +7 -9
- package/examples/client-server/package-lock.json +0 -27
- package/examples/client-server/package.json +0 -1
- package/examples/client-server/signaling-server.js +21 -10
- package/examples/electron-demo/README.md +1 -1
- package/examples/websocket/websocket-client.js +20 -0
- package/examples/websocket/websocket-server.js +22 -0
- package/lib/index.cjs +52 -9
- package/lib/index.js +22 -15
- package/lib/node-datachannel.js +7 -0
- package/lib/websocket-server.js +34 -0
- package/package.json +4 -4
- package/polyfill/Events.js +5 -3
- package/polyfill/Exception.js +23 -0
- package/polyfill/README.md +3 -3
- package/polyfill/RTCDataChannel.js +19 -11
- package/polyfill/RTCPeerConnection.js +148 -41
- package/polyfill/index.cjs +252 -59
- package/src/peer-connection-wrapper.cpp +11 -3
- package/src/web-socket-server-wrapper.cpp +23 -23
- package/src/web-socket-server-wrapper.h +8 -6
- package/src/web-socket-wrapper.cpp +7 -1
- package/test/connectivity.js +1 -23
- package/test/wpt-tests/README.md +46 -0
- package/test/wpt-tests/chrome-failed-tests.js +42 -0
- package/test/wpt-tests/index.js +96 -0
- package/test/wpt-tests/last-test-results.md +205 -0
- package/test/wpt-tests/package-lock.json +1712 -0
- package/test/wpt-tests/package.json +19 -0
- package/test/wpt-tests/wpt-test-list.js +137 -0
- package/test/wpt-tests/wpt.js +131 -0
- package/test/wpt.js +0 -50
package/polyfill/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var DOMException$1 = require('node-domexception');
|
|
6
6
|
var module$1 = require('module');
|
|
7
7
|
var stream = require('stream');
|
|
8
|
+
var events = require('events');
|
|
8
9
|
|
|
9
10
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
10
11
|
class RTCCertificate {
|
|
@@ -25,6 +26,26 @@ class RTCCertificate {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
const InvalidStateError = (msg) => {
|
|
30
|
+
return new DOMException(msg, 'InvalidStateError');
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const InvalidAccessError = (msg) => {
|
|
34
|
+
return new DOMException(msg, 'InvalidAccessError');
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const NotFoundError = (msg) => {
|
|
38
|
+
return new DOMException(msg, 'NotFoundError');
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const OperationError = (msg) => {
|
|
42
|
+
return new DOMException(msg, 'OperationError');
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const SyntaxError = (msg) => {
|
|
46
|
+
return new DOMException(msg, 'SyntaxError');
|
|
47
|
+
};
|
|
48
|
+
|
|
28
49
|
class _RTCDataChannel extends EventTarget {
|
|
29
50
|
#dataChannel;
|
|
30
51
|
#readyState;
|
|
@@ -35,6 +56,8 @@ class _RTCDataChannel extends EventTarget {
|
|
|
35
56
|
#negotiated;
|
|
36
57
|
#ordered;
|
|
37
58
|
|
|
59
|
+
#closeRequested = false;
|
|
60
|
+
|
|
38
61
|
onbufferedamountlow;
|
|
39
62
|
onclose;
|
|
40
63
|
onclosing;
|
|
@@ -57,12 +80,20 @@ class _RTCDataChannel extends EventTarget {
|
|
|
57
80
|
// forward dataChannel events
|
|
58
81
|
this.#dataChannel.onOpen(() => {
|
|
59
82
|
this.#readyState = 'open';
|
|
60
|
-
this.dispatchEvent(new Event('open'));
|
|
83
|
+
this.dispatchEvent(new Event('open', { channel: this }));
|
|
61
84
|
});
|
|
62
85
|
|
|
63
86
|
this.#dataChannel.onClosed(() => {
|
|
64
|
-
|
|
65
|
-
|
|
87
|
+
// Simulate closing event
|
|
88
|
+
if (!this.#closeRequested) {
|
|
89
|
+
this.#readyState = 'closing';
|
|
90
|
+
this.dispatchEvent(new Event('closing', { channel: this }));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setImmediate(() => {
|
|
94
|
+
this.#readyState = 'closed';
|
|
95
|
+
this.dispatchEvent(new Event('close', { channel: this }));
|
|
96
|
+
});
|
|
66
97
|
});
|
|
67
98
|
|
|
68
99
|
this.#dataChannel.onError((msg) => {
|
|
@@ -79,7 +110,7 @@ class _RTCDataChannel extends EventTarget {
|
|
|
79
110
|
});
|
|
80
111
|
|
|
81
112
|
this.#dataChannel.onBufferedAmountLow(() => {
|
|
82
|
-
this.dispatchEvent(new Event('bufferedamountlow'));
|
|
113
|
+
this.dispatchEvent(new Event('bufferedamountlow', { channel: this }));
|
|
83
114
|
});
|
|
84
115
|
|
|
85
116
|
this.#dataChannel.onMessage((data) => {
|
|
@@ -87,7 +118,7 @@ class _RTCDataChannel extends EventTarget {
|
|
|
87
118
|
data = data.buffer;
|
|
88
119
|
}
|
|
89
120
|
|
|
90
|
-
this.dispatchEvent(new MessageEvent('message', { data }));
|
|
121
|
+
this.dispatchEvent(new MessageEvent('message', { data, channel: this }));
|
|
91
122
|
});
|
|
92
123
|
|
|
93
124
|
// forward events to properties
|
|
@@ -113,7 +144,7 @@ class _RTCDataChannel extends EventTarget {
|
|
|
113
144
|
|
|
114
145
|
set binaryType(type) {
|
|
115
146
|
if (type !== 'blob' && type !== 'arraybuffer') {
|
|
116
|
-
throw new DOMException
|
|
147
|
+
throw new DOMException(
|
|
117
148
|
"Failed to set the 'binaryType' property on 'RTCDataChannel': Unknown binary type : " + type,
|
|
118
149
|
'TypeMismatchError',
|
|
119
150
|
);
|
|
@@ -173,9 +204,8 @@ class _RTCDataChannel extends EventTarget {
|
|
|
173
204
|
|
|
174
205
|
send(data) {
|
|
175
206
|
if (this.#readyState !== 'open') {
|
|
176
|
-
throw
|
|
207
|
+
throw InvalidStateError(
|
|
177
208
|
"Failed to execute 'send' on 'RTCDataChannel': RTCDataChannel.readyState is not 'open'",
|
|
178
|
-
'InvalidStateError',
|
|
179
209
|
);
|
|
180
210
|
}
|
|
181
211
|
|
|
@@ -192,9 +222,7 @@ class _RTCDataChannel extends EventTarget {
|
|
|
192
222
|
}
|
|
193
223
|
|
|
194
224
|
close() {
|
|
195
|
-
this.#
|
|
196
|
-
this.dispatchEvent(new Event('closing'));
|
|
197
|
-
|
|
225
|
+
this.#closeRequested = true;
|
|
198
226
|
this.#dataChannel.close();
|
|
199
227
|
}
|
|
200
228
|
}
|
|
@@ -451,6 +479,11 @@ class _RTCDtlsTransport extends EventTarget {
|
|
|
451
479
|
}
|
|
452
480
|
}
|
|
453
481
|
|
|
482
|
+
// createRequire is native in node version >= 12
|
|
483
|
+
const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
484
|
+
|
|
485
|
+
const nodeDataChannel = require$1('../build/Release/node_datachannel.node');
|
|
486
|
+
|
|
454
487
|
/**
|
|
455
488
|
* Turns a node-datachannel DataChannel into a real Node.js stream, complete with buffering,
|
|
456
489
|
* backpressure (up to a point - if the buffer fills up, messages are dropped), and
|
|
@@ -546,13 +579,65 @@ class DataChannelStream extends stream.Duplex {
|
|
|
546
579
|
}
|
|
547
580
|
}
|
|
548
581
|
|
|
549
|
-
|
|
550
|
-
|
|
582
|
+
class WebSocketServer extends events.EventEmitter {
|
|
583
|
+
#server;
|
|
584
|
+
#clients = [];
|
|
551
585
|
|
|
552
|
-
|
|
586
|
+
constructor(options) {
|
|
587
|
+
super();
|
|
588
|
+
this.#server = new nodeDataChannel.WebSocketServer(options);
|
|
589
|
+
|
|
590
|
+
this.#server.onClient((client) => {
|
|
591
|
+
this.emit('client', client);
|
|
592
|
+
this.#clients.push(client);
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
port() {
|
|
597
|
+
return this.#server?.port() || 0;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
stop() {
|
|
601
|
+
this.#clients.forEach((client) => {
|
|
602
|
+
client?.close();
|
|
603
|
+
});
|
|
604
|
+
this.#server?.stop();
|
|
605
|
+
this.#server = null;
|
|
606
|
+
this.removeAllListeners();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
onClient(cb) {
|
|
610
|
+
if (this.#server) this.on('client', cb);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const {
|
|
615
|
+
initLogger,
|
|
616
|
+
cleanup,
|
|
617
|
+
preload,
|
|
618
|
+
setSctpSettings,
|
|
619
|
+
RtcpReceivingSession,
|
|
620
|
+
Track,
|
|
621
|
+
Video,
|
|
622
|
+
Audio,
|
|
623
|
+
DataChannel,
|
|
624
|
+
PeerConnection,
|
|
625
|
+
WebSocket,
|
|
626
|
+
} = nodeDataChannel;
|
|
553
627
|
|
|
554
628
|
var NodeDataChannel = {
|
|
555
|
-
|
|
629
|
+
initLogger,
|
|
630
|
+
cleanup,
|
|
631
|
+
preload,
|
|
632
|
+
setSctpSettings,
|
|
633
|
+
RtcpReceivingSession,
|
|
634
|
+
Track,
|
|
635
|
+
Video,
|
|
636
|
+
Audio,
|
|
637
|
+
DataChannel,
|
|
638
|
+
PeerConnection,
|
|
639
|
+
WebSocket,
|
|
640
|
+
WebSocketServer,
|
|
556
641
|
DataChannelStream,
|
|
557
642
|
};
|
|
558
643
|
|
|
@@ -611,10 +696,12 @@ class RTCPeerConnectionIceEvent extends Event {
|
|
|
611
696
|
class RTCDataChannelEvent extends Event {
|
|
612
697
|
#channel;
|
|
613
698
|
|
|
614
|
-
constructor(
|
|
615
|
-
super(
|
|
699
|
+
constructor(type, eventInitDict) {
|
|
700
|
+
super(type);
|
|
701
|
+
|
|
702
|
+
if (type && !eventInitDict.channel) throw new TypeError('channel member is required');
|
|
616
703
|
|
|
617
|
-
this.#channel = channel;
|
|
704
|
+
this.#channel = eventInitDict?.channel;
|
|
618
705
|
}
|
|
619
706
|
|
|
620
707
|
get channel() {
|
|
@@ -683,6 +770,7 @@ class _RTCPeerConnection extends EventTarget {
|
|
|
683
770
|
#localOffer;
|
|
684
771
|
#localAnswer;
|
|
685
772
|
#dataChannels;
|
|
773
|
+
#dataChannelsClosed = 0;
|
|
686
774
|
#config;
|
|
687
775
|
#canTrickleIceCandidates;
|
|
688
776
|
#sctp;
|
|
@@ -700,32 +788,98 @@ class _RTCPeerConnection extends EventTarget {
|
|
|
700
788
|
onsignalingstatechange;
|
|
701
789
|
ontrack;
|
|
702
790
|
|
|
703
|
-
|
|
791
|
+
_checkConfiguration(config) {
|
|
792
|
+
if (config && config.iceServers === undefined) config.iceServers = [];
|
|
793
|
+
if (config && config.iceTransportPolicy === undefined) config.iceTransportPolicy = 'all';
|
|
794
|
+
|
|
795
|
+
if (config?.iceServers === null) throw new TypeError('IceServers cannot be null');
|
|
796
|
+
|
|
797
|
+
// Check for all the properties of iceServers
|
|
798
|
+
if (Array.isArray(config?.iceServers)) {
|
|
799
|
+
for (let i = 0; i < config.iceServers.length; i++) {
|
|
800
|
+
if (config.iceServers[i] === null) throw new TypeError('IceServers cannot be null');
|
|
801
|
+
if (config.iceServers[i] === undefined) throw new TypeError('IceServers cannot be undefined');
|
|
802
|
+
if (Object.keys(config.iceServers[i]).length === 0) throw new TypeError('IceServers cannot be empty');
|
|
803
|
+
|
|
804
|
+
// If iceServers is string convert to array
|
|
805
|
+
if (typeof config.iceServers[i].urls === 'string')
|
|
806
|
+
config.iceServers[i].urls = [config.iceServers[i].urls];
|
|
807
|
+
|
|
808
|
+
// urls can not be empty
|
|
809
|
+
if (config.iceServers[i].urls?.some((url) => url == ''))
|
|
810
|
+
throw SyntaxError('IceServers urls cannot be empty');
|
|
811
|
+
|
|
812
|
+
// urls should match the regex "stun\:\w*|turn\:\w*|turns\:\w*"
|
|
813
|
+
if (
|
|
814
|
+
config.iceServers[i].urls?.some(
|
|
815
|
+
(url) => !/^(stun:[\w,\.,:]*|turn:[\w,\.,:]*|turns:[\w,\.,:]*)$/.test(url),
|
|
816
|
+
)
|
|
817
|
+
)
|
|
818
|
+
throw SyntaxError('IceServers urls wrong format');
|
|
819
|
+
|
|
820
|
+
// If this is a turn server check for username and credential
|
|
821
|
+
if (config.iceServers[i].urls?.some((url) => url.startsWith('turn'))) {
|
|
822
|
+
if (!config.iceServers[i].username)
|
|
823
|
+
throw InvalidAccessError('IceServers username cannot be null');
|
|
824
|
+
if (!config.iceServers[i].credential)
|
|
825
|
+
throw InvalidAccessError('IceServers username cannot be undefined');
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// length of urls can not be 0
|
|
829
|
+
if (config.iceServers[i].urls?.length === 0)
|
|
830
|
+
throw SyntaxError('IceServers urls cannot be empty');
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (
|
|
835
|
+
config &&
|
|
836
|
+
config.iceTransportPolicy &&
|
|
837
|
+
config.iceTransportPolicy !== 'all' &&
|
|
838
|
+
config.iceTransportPolicy !== 'relay'
|
|
839
|
+
)
|
|
840
|
+
throw new TypeError('IceTransportPolicy must be either "all" or "relay"');
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
setConfiguration(config) {
|
|
844
|
+
this._checkConfiguration(config);
|
|
845
|
+
this.#config = config;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
constructor(config = { iceServers: [], iceTransportPolicy: 'all' }) {
|
|
704
849
|
super();
|
|
705
850
|
|
|
706
|
-
this
|
|
851
|
+
this._checkConfiguration(config);
|
|
852
|
+
this.#config = config;
|
|
707
853
|
this.#localOffer = createDeferredPromise();
|
|
708
854
|
this.#localAnswer = createDeferredPromise();
|
|
709
855
|
this.#dataChannels = new Set();
|
|
710
856
|
this.#canTrickleIceCandidates = null;
|
|
711
857
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
return
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
858
|
+
try {
|
|
859
|
+
this.#peerConnection = new NodeDataChannel.PeerConnection(
|
|
860
|
+
config?.peerIdentity ?? `peer-${getRandomString(7)}`,
|
|
861
|
+
{
|
|
862
|
+
...config,
|
|
863
|
+
iceServers:
|
|
864
|
+
config?.iceServers
|
|
865
|
+
?.map((server) => {
|
|
866
|
+
const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
|
|
867
|
+
|
|
868
|
+
return urls.map((url) => {
|
|
869
|
+
if (server.username && server.credential) {
|
|
870
|
+
const [protocol, rest] = url.split(/:(.*)/);
|
|
871
|
+
return `${protocol}:${server.username}:${server.credential}@${rest}`;
|
|
872
|
+
}
|
|
873
|
+
return url;
|
|
874
|
+
});
|
|
875
|
+
})
|
|
876
|
+
.flat() ?? [],
|
|
877
|
+
},
|
|
878
|
+
);
|
|
879
|
+
} catch (error) {
|
|
880
|
+
if (!error || !error.message) throw NotFoundError('Unknown error');
|
|
881
|
+
throw SyntaxError(error.message);
|
|
882
|
+
}
|
|
729
883
|
|
|
730
884
|
// forward peerConnection events
|
|
731
885
|
this.#peerConnection.onStateChange(() => {
|
|
@@ -745,9 +899,9 @@ class _RTCPeerConnection extends EventTarget {
|
|
|
745
899
|
});
|
|
746
900
|
|
|
747
901
|
this.#peerConnection.onDataChannel((channel) => {
|
|
748
|
-
const
|
|
749
|
-
this.#dataChannels.add(
|
|
750
|
-
this.dispatchEvent(new RTCDataChannelEvent(
|
|
902
|
+
const dc = new _RTCDataChannel(channel);
|
|
903
|
+
this.#dataChannels.add(dc);
|
|
904
|
+
this.dispatchEvent(new RTCDataChannelEvent('datachannel', { channel: dc }));
|
|
751
905
|
});
|
|
752
906
|
|
|
753
907
|
this.#peerConnection.onLocalDescription((sdp, type) => {
|
|
@@ -821,7 +975,11 @@ class _RTCPeerConnection extends EventTarget {
|
|
|
821
975
|
}
|
|
822
976
|
|
|
823
977
|
get iceConnectionState() {
|
|
824
|
-
|
|
978
|
+
let state = this.#peerConnection.iceState();
|
|
979
|
+
// libdatachannel uses 'completed' instead of 'connected'
|
|
980
|
+
// see /webrtc/getstats.html
|
|
981
|
+
if (state == 'completed') state = 'connected';
|
|
982
|
+
return state;
|
|
825
983
|
}
|
|
826
984
|
|
|
827
985
|
get iceGatheringState() {
|
|
@@ -861,14 +1019,45 @@ class _RTCPeerConnection extends EventTarget {
|
|
|
861
1019
|
}
|
|
862
1020
|
|
|
863
1021
|
async addIceCandidate(candidate) {
|
|
864
|
-
if (candidate
|
|
865
|
-
|
|
1022
|
+
if (!candidate || !candidate.candidate) {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (candidate.sdpMid === null && candidate.sdpMLineIndex === null) {
|
|
1027
|
+
throw new TypeError('sdpMid must be set');
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
if (candidate.sdpMid === undefined && candidate.sdpMLineIndex == undefined) {
|
|
1031
|
+
throw new TypeError('sdpMid must be set');
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Reject if sdpMid format is not valid
|
|
1035
|
+
// ??
|
|
1036
|
+
if (candidate.sdpMid && candidate.sdpMid.length > 3) {
|
|
1037
|
+
// console.log(candidate.sdpMid);
|
|
1038
|
+
throw OperationError('Invalid sdpMid format');
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// We don't care about sdpMLineIndex, just for test
|
|
1042
|
+
if (!candidate.sdpMid && candidate.sdpMLineIndex > 1) {
|
|
1043
|
+
throw OperationError('This is only for test case.');
|
|
866
1044
|
}
|
|
867
1045
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
1046
|
+
try {
|
|
1047
|
+
this.#peerConnection.addRemoteCandidate(candidate.candidate, candidate.sdpMid || '0');
|
|
1048
|
+
this.#remoteCandidates.push(
|
|
1049
|
+
new _RTCIceCandidate({ candidate: candidate.candidate, sdpMid: candidate.sdpMid || '0' }),
|
|
1050
|
+
);
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
if (!error || !error.message) throw NotFoundError('Unknown error');
|
|
1053
|
+
|
|
1054
|
+
// Check error Message if contains specific message
|
|
1055
|
+
if (error.message.includes('remote candidate without remote description'))
|
|
1056
|
+
throw InvalidStateError(error.message);
|
|
1057
|
+
if (error.message.includes('Invalid candidate format')) throw OperationError(error.message);
|
|
1058
|
+
|
|
1059
|
+
throw NotFoundError(error.message);
|
|
1060
|
+
}
|
|
872
1061
|
}
|
|
873
1062
|
|
|
874
1063
|
addTrack(track, ...streams) {
|
|
@@ -883,6 +1072,7 @@ class _RTCPeerConnection extends EventTarget {
|
|
|
883
1072
|
// close all channels before shutting down
|
|
884
1073
|
this.#dataChannels.forEach((channel) => {
|
|
885
1074
|
channel.close();
|
|
1075
|
+
this.#dataChannelsClosed++;
|
|
886
1076
|
});
|
|
887
1077
|
|
|
888
1078
|
this.#peerConnection.close();
|
|
@@ -900,6 +1090,7 @@ class _RTCPeerConnection extends EventTarget {
|
|
|
900
1090
|
this.#dataChannels.add(dataChannel);
|
|
901
1091
|
dataChannel.addEventListener('close', () => {
|
|
902
1092
|
this.#dataChannels.delete(dataChannel);
|
|
1093
|
+
this.#dataChannelsClosed++;
|
|
903
1094
|
});
|
|
904
1095
|
|
|
905
1096
|
return dataChannel;
|
|
@@ -933,7 +1124,7 @@ class _RTCPeerConnection extends EventTarget {
|
|
|
933
1124
|
let localId = 'RTCIceCandidate_' + localIdRs;
|
|
934
1125
|
report.set(localId, {
|
|
935
1126
|
id: localId,
|
|
936
|
-
type: '
|
|
1127
|
+
type: 'local-candidate',
|
|
937
1128
|
timestamp: Date.now(),
|
|
938
1129
|
candidateType: cp.local.type,
|
|
939
1130
|
ip: cp.local.address,
|
|
@@ -944,7 +1135,7 @@ class _RTCPeerConnection extends EventTarget {
|
|
|
944
1135
|
let remoteId = 'RTCIceCandidate_' + remoteIdRs;
|
|
945
1136
|
report.set(remoteId, {
|
|
946
1137
|
id: remoteId,
|
|
947
|
-
type: '
|
|
1138
|
+
type: 'remote-candidate',
|
|
948
1139
|
timestamp: Date.now(),
|
|
949
1140
|
candidateType: cp.remote.type,
|
|
950
1141
|
ip: cp.remote.address,
|
|
@@ -979,6 +1170,15 @@ class _RTCPeerConnection extends EventTarget {
|
|
|
979
1170
|
selectedCandidatePairChanges: 1,
|
|
980
1171
|
});
|
|
981
1172
|
|
|
1173
|
+
// peer-connection'
|
|
1174
|
+
report.set('P', {
|
|
1175
|
+
id: 'P',
|
|
1176
|
+
type: 'peer-connection',
|
|
1177
|
+
timestamp: Date.now(),
|
|
1178
|
+
dataChannelsOpened: this.#dataChannels.size,
|
|
1179
|
+
dataChannelsClosed: this.#dataChannelsClosed,
|
|
1180
|
+
});
|
|
1181
|
+
|
|
982
1182
|
return resolve(report);
|
|
983
1183
|
});
|
|
984
1184
|
}
|
|
@@ -995,20 +1195,13 @@ class _RTCPeerConnection extends EventTarget {
|
|
|
995
1195
|
throw new DOMException('Not implemented');
|
|
996
1196
|
}
|
|
997
1197
|
|
|
998
|
-
setConfiguration(config) {
|
|
999
|
-
this.#config = config;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
1198
|
async setLocalDescription(description) {
|
|
1003
|
-
if (description
|
|
1004
|
-
throw new DOMException('Local description type must be set');
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
if (description.type !== 'offer') {
|
|
1199
|
+
if (description?.type !== 'offer') {
|
|
1008
1200
|
// any other type causes libdatachannel to throw
|
|
1009
1201
|
return;
|
|
1010
1202
|
}
|
|
1011
|
-
|
|
1203
|
+
|
|
1204
|
+
this.#peerConnection.setLocalDescription(description?.type);
|
|
1012
1205
|
}
|
|
1013
1206
|
|
|
1014
1207
|
async setRemoteDescription(description) {
|
|
@@ -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
|
-
|
|
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 << "
|
|
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("
|
|
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
|
|
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
|
-
|
|
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
|
}
|