node-datachannel 0.5.5 → 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.
- package/.github/workflows/build-linux.yml +2 -2
- package/CMakeLists.txt +6 -10
- package/examples/electron-demo/package-lock.json +6 -6
- package/examples/media/README.md +12 -0
- package/jest.config.cjs +7 -0
- package/lib/index.cjs +142 -0
- package/lib/index.d.ts +66 -2
- package/lib/index.js +4 -0
- package/package.json +15 -6
- package/polyfill/RTCPeerConnection.js +0 -1
- package/polyfill/polyfill.cjs +1069 -0
- package/src/data-channel-wrapper.cpp +15 -1
- package/src/data-channel-wrapper.h +5 -1
- package/src/main.cpp +4 -0
- package/src/media-track-wrapper.cpp +25 -5
- package/src/media-track-wrapper.h +7 -3
- package/src/peer-connection-wrapper.cpp +21 -27
- package/src/peer-connection-wrapper.h +2 -5
- package/src/rtc-wrapper.cpp +9 -2
- package/src/thread-safe-callback.cpp +6 -2
- package/src/thread-safe-callback.h +3 -1
- package/src/web-socket-server-wrapper.cpp +265 -0
- package/src/web-socket-server-wrapper.h +43 -0
- package/src/web-socket-wrapper.cpp +664 -0
- package/src/web-socket-wrapper.h +62 -0
- package/test/jest-tests/basic.test.js +37 -0
- package/test/jest-tests/multiple-run.test.js +73 -0
- package/test/jest-tests/p2p.test.js +99 -0
- package/test/jest-tests/streams.test.js +48 -0
- package/test/jest-tests/websocket.test.js +69 -0
- package/test/websockets.js +48 -0
- package/test/test.js +0 -185
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#ifndef WEB_SOCKET_WRAPPER_H
|
|
2
|
+
#define WEB_SOCKET_WRAPPER_H
|
|
3
|
+
|
|
4
|
+
#include <iostream>
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <variant>
|
|
7
|
+
#include <memory>
|
|
8
|
+
#include <unordered_set>
|
|
9
|
+
|
|
10
|
+
#include <napi.h>
|
|
11
|
+
#include <rtc/rtc.hpp>
|
|
12
|
+
|
|
13
|
+
#include "thread-safe-callback.h"
|
|
14
|
+
|
|
15
|
+
class WebSocketWrapper : public Napi::ObjectWrap<WebSocketWrapper>
|
|
16
|
+
{
|
|
17
|
+
public:
|
|
18
|
+
static Napi::FunctionReference constructor;
|
|
19
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
20
|
+
WebSocketWrapper(const Napi::CallbackInfo &info);
|
|
21
|
+
~WebSocketWrapper();
|
|
22
|
+
|
|
23
|
+
// Functions
|
|
24
|
+
void open(const Napi::CallbackInfo &info);
|
|
25
|
+
void close(const Napi::CallbackInfo &info);
|
|
26
|
+
Napi::Value sendMessage(const Napi::CallbackInfo &info);
|
|
27
|
+
Napi::Value sendMessageBinary(const Napi::CallbackInfo &info);
|
|
28
|
+
Napi::Value isOpen(const Napi::CallbackInfo &info);
|
|
29
|
+
Napi::Value bufferedAmount(const Napi::CallbackInfo &info);
|
|
30
|
+
Napi::Value maxMessageSize(const Napi::CallbackInfo &info);
|
|
31
|
+
void setBufferedAmountLowThreshold(const Napi::CallbackInfo &info);
|
|
32
|
+
|
|
33
|
+
// Callbacks
|
|
34
|
+
void onOpen(const Napi::CallbackInfo &info);
|
|
35
|
+
void onClosed(const Napi::CallbackInfo &info);
|
|
36
|
+
void onError(const Napi::CallbackInfo &info);
|
|
37
|
+
void onBufferedAmountLow(const Napi::CallbackInfo &info);
|
|
38
|
+
void onMessage(const Napi::CallbackInfo &info);
|
|
39
|
+
|
|
40
|
+
// Close all existing WebSockets
|
|
41
|
+
static void CloseAll();
|
|
42
|
+
|
|
43
|
+
// Reset all Callbacks for existing WebSockets
|
|
44
|
+
static void CleanupAll();
|
|
45
|
+
|
|
46
|
+
private:
|
|
47
|
+
static std::unordered_set<WebSocketWrapper *> instances;
|
|
48
|
+
|
|
49
|
+
void doClose();
|
|
50
|
+
void doCleanup();
|
|
51
|
+
|
|
52
|
+
std::shared_ptr<rtc::WebSocket> mWebSocketPtr = nullptr;
|
|
53
|
+
|
|
54
|
+
// Callback Ptrs
|
|
55
|
+
std::unique_ptr<ThreadSafeCallback> mOnOpenCallback = nullptr;
|
|
56
|
+
std::unique_ptr<ThreadSafeCallback> mOnClosedCallback = nullptr;
|
|
57
|
+
std::unique_ptr<ThreadSafeCallback> mOnErrorCallback = nullptr;
|
|
58
|
+
std::unique_ptr<ThreadSafeCallback> mOnBufferedAmountLowCallback = nullptr;
|
|
59
|
+
std::unique_ptr<ThreadSafeCallback> mOnMessageCallback = nullptr;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
#endif // WEB_SOCKET_WRAPPER_H
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as nodeDataChannel from '../../lib';
|
|
2
|
+
|
|
3
|
+
function waitForGathering(peer) {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
peer.onGatheringStateChange((state) => {
|
|
6
|
+
if (state === 'complete') resolve();
|
|
7
|
+
});
|
|
8
|
+
// Handle race conditions where gathering has already completed
|
|
9
|
+
if (peer.gatheringState() === 'complete') resolve();
|
|
10
|
+
|
|
11
|
+
resolve();
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('DataChannel streams', () => {
|
|
16
|
+
test('can build an echo pipeline', async () => {
|
|
17
|
+
let clientPeer = new nodeDataChannel.PeerConnection('Client', { iceServers: [] });
|
|
18
|
+
let echoPeer = new nodeDataChannel.PeerConnection('Client', { iceServers: [] });
|
|
19
|
+
|
|
20
|
+
const echoStream = new nodeDataChannel.DataChannelStream(echoPeer.createDataChannel('echo-channel'));
|
|
21
|
+
echoStream.pipe(echoStream); // Echo all received data back to the client
|
|
22
|
+
|
|
23
|
+
await waitForGathering(echoPeer);
|
|
24
|
+
|
|
25
|
+
const { sdp: echoDescSdp, type: echoDescType } = echoPeer.localDescription();
|
|
26
|
+
clientPeer.setRemoteDescription(echoDescSdp, echoDescType);
|
|
27
|
+
await waitForGathering(clientPeer);
|
|
28
|
+
|
|
29
|
+
const { sdp: clientDescSdp, type: clientDescType } = clientPeer.localDescription();
|
|
30
|
+
echoPeer.setRemoteDescription(clientDescSdp, clientDescType);
|
|
31
|
+
|
|
32
|
+
const clientChannel = await new Promise((resolve) => clientPeer.onDataChannel(resolve));
|
|
33
|
+
|
|
34
|
+
const clientResponsePromise = new Promise((resolve) => clientChannel.onMessage(resolve));
|
|
35
|
+
clientChannel.sendMessage('test message');
|
|
36
|
+
|
|
37
|
+
expect(await clientResponsePromise).toBe('test message');
|
|
38
|
+
|
|
39
|
+
clientChannel.close();
|
|
40
|
+
clientPeer.close();
|
|
41
|
+
echoPeer.close();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterAll(() => {
|
|
46
|
+
// Properly cleanup so Jest does not complain about asynchronous operations that weren't stopped.
|
|
47
|
+
nodeDataChannel.cleanup();
|
|
48
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import * as nodeDataChannel from '../../lib';
|
|
3
|
+
|
|
4
|
+
describe('Websocket', () => {
|
|
5
|
+
const webSocketServer = new nodeDataChannel.WebSocketServer({ bindAddress: '127.0.0.1', port: 1987 });
|
|
6
|
+
const clientSocket = new nodeDataChannel.WebSocket();
|
|
7
|
+
|
|
8
|
+
// Mocks
|
|
9
|
+
const clientOnOpenMock = jest.fn();
|
|
10
|
+
const clientOnMessageMock = jest.fn();
|
|
11
|
+
const clientOnCLosedMock = jest.fn();
|
|
12
|
+
|
|
13
|
+
const webServerOnClientMock = jest.fn();
|
|
14
|
+
const webServerOnOpenMock = jest.fn();
|
|
15
|
+
const webServerOnMessageMock = jest.fn();
|
|
16
|
+
const webServerOnClosedMock = jest.fn();
|
|
17
|
+
|
|
18
|
+
webSocketServer.onClient((serverSocket) => {
|
|
19
|
+
webServerOnClientMock();
|
|
20
|
+
|
|
21
|
+
serverSocket.onOpen(() => {
|
|
22
|
+
webServerOnOpenMock();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
serverSocket.onMessage((message) => {
|
|
26
|
+
webServerOnMessageMock(message);
|
|
27
|
+
serverSocket.sendMessage('reply to ' + message);
|
|
28
|
+
serverSocket.close();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
serverSocket.onClosed(() => {
|
|
32
|
+
webServerOnClosedMock();
|
|
33
|
+
serverSocket.close();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('can create a websocket client and connect to a server', (done) => {
|
|
38
|
+
clientSocket.open('ws://127.0.0.1:1987');
|
|
39
|
+
clientSocket.onOpen(() => {
|
|
40
|
+
clientOnOpenMock();
|
|
41
|
+
clientSocket.sendMessage('Hello');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
clientSocket.onMessage((message) => {
|
|
45
|
+
clientOnMessageMock(message);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
clientSocket.onClosed(() => {
|
|
49
|
+
clientOnCLosedMock();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
expect(clientOnMessageMock.mock.calls[0][0]).toEqual('reply to Hello');
|
|
54
|
+
expect(clientOnOpenMock.mock.calls.length).toBe(1);
|
|
55
|
+
expect(clientOnMessageMock.mock.calls.length).toBe(1);
|
|
56
|
+
expect(clientOnCLosedMock.mock.calls.length).toBe(1);
|
|
57
|
+
|
|
58
|
+
expect(webServerOnMessageMock.mock.calls[0][0]).toEqual('Hello');
|
|
59
|
+
expect(webServerOnOpenMock.mock.calls.length).toBe(1);
|
|
60
|
+
expect(webServerOnMessageMock.mock.calls.length).toBe(1);
|
|
61
|
+
expect(webServerOnClosedMock.mock.calls.length).toBe(1);
|
|
62
|
+
|
|
63
|
+
clientSocket.close();
|
|
64
|
+
webSocketServer.stop();
|
|
65
|
+
|
|
66
|
+
done();
|
|
67
|
+
}, 3000);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import nodeDataChannel from '../lib/index.js';
|
|
2
|
+
|
|
3
|
+
nodeDataChannel.initLogger('Debug');
|
|
4
|
+
nodeDataChannel.preload();
|
|
5
|
+
|
|
6
|
+
const webSocketServer = new nodeDataChannel.WebSocketServer({ bindAddress: '127.0.0.1', port: 1987 });
|
|
7
|
+
|
|
8
|
+
webSocketServer.onClient((serverSocket) => {
|
|
9
|
+
console.log('webSocketServer.onClient()');
|
|
10
|
+
|
|
11
|
+
serverSocket.onOpen(() => {
|
|
12
|
+
console.log('serverSocket.onOpen()');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
serverSocket.onMessage((message) => {
|
|
16
|
+
console.log('serverSocket.onMessage():', message);
|
|
17
|
+
serverSocket.sendMessage('reply to ' + message);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
serverSocket.onClosed(() => {
|
|
21
|
+
console.log('serverSocket.onClosed()');
|
|
22
|
+
serverSocket.close();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const clientSocket = new nodeDataChannel.WebSocket();
|
|
27
|
+
|
|
28
|
+
clientSocket.onOpen(() => {
|
|
29
|
+
console.log('clientSocket.onOpen()');
|
|
30
|
+
clientSocket.sendMessage('Hello');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
clientSocket.onMessage((message) => {
|
|
34
|
+
console.log('clientSocket.onMessage():', message);
|
|
35
|
+
clientSocket.close();
|
|
36
|
+
webSocketServer.stop();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
clientSocket.onClosed(() => {
|
|
40
|
+
console.log('clientSocket.onClosed()');
|
|
41
|
+
clientSocket.close();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
clientSocket.open('ws://127.0.0.1:1987');
|
|
45
|
+
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
nodeDataChannel.cleanup();
|
|
48
|
+
}, 1000);
|
package/test/test.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { jest } from '@jest/globals';
|
|
2
|
-
import * as nodeDataChannel from '../lib';
|
|
3
|
-
|
|
4
|
-
describe('Module Definition', () => {
|
|
5
|
-
test('Module Defined', () => {
|
|
6
|
-
expect(nodeDataChannel).toBeDefined();
|
|
7
|
-
expect(nodeDataChannel.initLogger).toBeDefined();
|
|
8
|
-
expect(nodeDataChannel.PeerConnection).toBeDefined();
|
|
9
|
-
expect(typeof nodeDataChannel.PeerConnection).toBe('function');
|
|
10
|
-
expect(nodeDataChannel.DataChannel).toBeDefined();
|
|
11
|
-
expect(typeof nodeDataChannel.DataChannel).toBe('function');
|
|
12
|
-
});
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
describe('PeerConnection Classes', () => {
|
|
16
|
-
test('Create PeerConnection', () => {
|
|
17
|
-
let peer = new nodeDataChannel.PeerConnection('Peer', { iceServers: ['stun:stun.l.google.com:19302'] });
|
|
18
|
-
expect(peer).toBeDefined();
|
|
19
|
-
expect(peer.onStateChange).toBeDefined();
|
|
20
|
-
expect(peer.createDataChannel).toBeDefined();
|
|
21
|
-
|
|
22
|
-
peer.close();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('Create Data Channel', () => {
|
|
26
|
-
let peer = new nodeDataChannel.PeerConnection('Peer', { iceServers: ['stun:stun.l.google.com:19302'] });
|
|
27
|
-
let dc = peer.createDataChannel('test', { protocol: 'test-protocol' });
|
|
28
|
-
expect(dc).toBeDefined();
|
|
29
|
-
expect(dc.getId()).toBeDefined();
|
|
30
|
-
expect(dc.getProtocol()).toBe('test-protocol');
|
|
31
|
-
expect(dc.getLabel()).toBe('test');
|
|
32
|
-
expect(dc.onOpen).toBeDefined();
|
|
33
|
-
expect(dc.onMessage).toBeDefined();
|
|
34
|
-
|
|
35
|
-
dc.close();
|
|
36
|
-
peer.close();
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe('P2P', () => {
|
|
41
|
-
// Default is 5000 ms but we need more
|
|
42
|
-
jest.setTimeout(30000);
|
|
43
|
-
|
|
44
|
-
let peer1 = new nodeDataChannel.PeerConnection('Peer1', { iceServers: ['stun:stun.l.google.com:19302'] });
|
|
45
|
-
let peer2 = new nodeDataChannel.PeerConnection('Peer2', { iceServers: ['stun:stun.l.google.com:19302'] });
|
|
46
|
-
let dc1 = null;
|
|
47
|
-
let dc2 = null;
|
|
48
|
-
|
|
49
|
-
// Mocks
|
|
50
|
-
const p1StateMock = jest.fn();
|
|
51
|
-
const p1GatheringStateMock = jest.fn();
|
|
52
|
-
const p2StateMock = jest.fn();
|
|
53
|
-
const p2GatheringStateMock = jest.fn();
|
|
54
|
-
const p1SDPMock = jest.fn();
|
|
55
|
-
const p1CandidateMock = jest.fn();
|
|
56
|
-
const p2SDPMock = jest.fn();
|
|
57
|
-
const p2CandidateMock = jest.fn();
|
|
58
|
-
|
|
59
|
-
const p1DCMock = jest.fn();
|
|
60
|
-
const p1DCMessageMock = jest.fn();
|
|
61
|
-
const p2DCMock = jest.fn();
|
|
62
|
-
const p2DCMessageMock = jest.fn();
|
|
63
|
-
|
|
64
|
-
// Set Callbacks
|
|
65
|
-
peer1.onStateChange(p1StateMock);
|
|
66
|
-
peer1.onGatheringStateChange(p1GatheringStateMock);
|
|
67
|
-
peer1.onLocalDescription((sdp, type) => {
|
|
68
|
-
p1SDPMock();
|
|
69
|
-
peer2.setRemoteDescription(sdp, type);
|
|
70
|
-
});
|
|
71
|
-
peer1.onLocalCandidate((candidate, mid) => {
|
|
72
|
-
p1CandidateMock();
|
|
73
|
-
peer2.addRemoteCandidate(candidate, mid);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Set Callbacks
|
|
77
|
-
peer2.onStateChange(p2StateMock);
|
|
78
|
-
peer2.onGatheringStateChange(p2GatheringStateMock);
|
|
79
|
-
peer2.onLocalDescription((sdp, type) => {
|
|
80
|
-
p2SDPMock();
|
|
81
|
-
peer1.setRemoteDescription(sdp, type);
|
|
82
|
-
});
|
|
83
|
-
peer2.onLocalCandidate((candidate, mid) => {
|
|
84
|
-
p2CandidateMock();
|
|
85
|
-
peer1.addRemoteCandidate(candidate, mid);
|
|
86
|
-
});
|
|
87
|
-
peer2.onDataChannel((dc) => {
|
|
88
|
-
p2DCMock();
|
|
89
|
-
dc2 = dc;
|
|
90
|
-
dc2.onMessage((msg) => {
|
|
91
|
-
p2DCMessageMock(msg);
|
|
92
|
-
});
|
|
93
|
-
dc2.sendMessage('Hello From Peer2');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
dc1 = peer1.createDataChannel('test-p2p');
|
|
97
|
-
dc1.onOpen(() => {
|
|
98
|
-
p1DCMock();
|
|
99
|
-
dc1.sendMessage('Hello From Peer1');
|
|
100
|
-
});
|
|
101
|
-
dc1.onMessage((msg) => {
|
|
102
|
-
p1DCMessageMock(msg);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('P2P', (done) => {
|
|
106
|
-
setTimeout(() => {
|
|
107
|
-
dc1.close();
|
|
108
|
-
dc2.close();
|
|
109
|
-
peer1.close();
|
|
110
|
-
peer2.close();
|
|
111
|
-
}, 10 * 1000);
|
|
112
|
-
|
|
113
|
-
setTimeout(() => {
|
|
114
|
-
// State Callbacks
|
|
115
|
-
expect(p1StateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
|
|
116
|
-
expect(p1GatheringStateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
|
|
117
|
-
expect(p2StateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
|
|
118
|
-
expect(p2GatheringStateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
|
|
119
|
-
|
|
120
|
-
// SDP
|
|
121
|
-
expect(p1SDPMock.mock.calls.length).toBe(1);
|
|
122
|
-
expect(p2SDPMock.mock.calls.length).toBe(1);
|
|
123
|
-
|
|
124
|
-
// Candidates
|
|
125
|
-
expect(p1CandidateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
|
|
126
|
-
expect(p2CandidateMock.mock.calls.length).toBeGreaterThanOrEqual(1);
|
|
127
|
-
|
|
128
|
-
// DataChannel
|
|
129
|
-
expect(p1DCMock.mock.calls.length).toBe(1);
|
|
130
|
-
expect(p1DCMessageMock.mock.calls.length).toBe(1);
|
|
131
|
-
expect(p1DCMessageMock.mock.calls[0][0]).toEqual('Hello From Peer2');
|
|
132
|
-
expect(p2DCMock.mock.calls.length).toBe(1);
|
|
133
|
-
|
|
134
|
-
expect(p2DCMessageMock.mock.calls.length).toBe(1);
|
|
135
|
-
expect(p2DCMessageMock.mock.calls[0][0]).toEqual('Hello From Peer1');
|
|
136
|
-
|
|
137
|
-
done();
|
|
138
|
-
}, 12 * 1000);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
function waitForGathering(peer) {
|
|
143
|
-
return new Promise((resolve) => {
|
|
144
|
-
peer.onGatheringStateChange((state) => {
|
|
145
|
-
if (state === 'complete') resolve();
|
|
146
|
-
});
|
|
147
|
-
// Handle race conditions where gathering has already completed
|
|
148
|
-
if (peer.gatheringState() === 'complete') resolve();
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
describe('DataChannel streams', () => {
|
|
153
|
-
test('can build an echo pipeline', async () => {
|
|
154
|
-
let clientPeer = new nodeDataChannel.PeerConnection('Client', { iceServers: [] });
|
|
155
|
-
let echoPeer = new nodeDataChannel.PeerConnection('Client', { iceServers: [] });
|
|
156
|
-
|
|
157
|
-
const echoStream = new nodeDataChannel.DataChannelStream(echoPeer.createDataChannel('echo-channel'));
|
|
158
|
-
echoStream.pipe(echoStream); // Echo all received data back to the client
|
|
159
|
-
|
|
160
|
-
await waitForGathering(echoPeer);
|
|
161
|
-
|
|
162
|
-
const { sdp: echoDescSdp, type: echoDescType } = echoPeer.localDescription();
|
|
163
|
-
clientPeer.setRemoteDescription(echoDescSdp, echoDescType);
|
|
164
|
-
await waitForGathering(clientPeer);
|
|
165
|
-
|
|
166
|
-
const { sdp: clientDescSdp, type: clientDescType } = clientPeer.localDescription();
|
|
167
|
-
echoPeer.setRemoteDescription(clientDescSdp, clientDescType);
|
|
168
|
-
|
|
169
|
-
const clientChannel = await new Promise((resolve) => clientPeer.onDataChannel(resolve));
|
|
170
|
-
|
|
171
|
-
const clientResponsePromise = new Promise((resolve) => clientChannel.onMessage(resolve));
|
|
172
|
-
clientChannel.sendMessage('test message');
|
|
173
|
-
|
|
174
|
-
expect(await clientResponsePromise).toBe('test message');
|
|
175
|
-
|
|
176
|
-
clientChannel.close();
|
|
177
|
-
clientPeer.close();
|
|
178
|
-
echoPeer.close();
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
afterAll(() => {
|
|
183
|
-
// Properly cleanup so Jest does not complain about asynchronous operations that weren't stopped.
|
|
184
|
-
nodeDataChannel.cleanup();
|
|
185
|
-
});
|