diff --git a/examples/client-server/client-benchmark.js b/examples/client-server/client-benchmark.js index cbc17fb..8d9a2be 100644 --- a/examples/client-server/client-benchmark.js +++ b/examples/client-server/client-benchmark.js @@ -6,7 +6,6 @@ const require = createRequire(import.meta.url); const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); -import WebSocket from 'ws'; import readline from 'readline'; import nodeDataChannel from '../../lib/index.js'; @@ -60,23 +59,22 @@ const rl = readline.createInterface({ output: process.stdout, }); -const ws = new WebSocket(wsUrl + '/' + id, { - perMessageDeflate: false, -}); +const ws = new nodeDataChannel.WebSocket(); +ws.open(wsUrl + '/' + id); console.log(`The local ID is: ${id}`); console.log(`Waiting for signaling to be connected...`); -ws.on('open', () => { +ws.onOpen(() => { console.log('WebSocket connected, signaling ready'); readUserInput(); }); -ws.on('error', (err) => { +ws.onError((err) => { console.log('WebSocket Error: ', err); }); -ws.on('message', (msgStr) => { +ws.onMessage((msgStr) => { let msg = JSON.parse(msgStr); switch (msg.type) { case 'offer': @@ -184,10 +182,10 @@ function createPeerConnection(peerId) { console.log('GatheringState: ', state); }); peerConnection.onLocalDescription((description, type) => { - ws.send(JSON.stringify({ id: peerId, type, description })); + ws.sendMessage(JSON.stringify({ id: peerId, type, description })); }); peerConnection.onLocalCandidate((candidate, mid) => { - ws.send(JSON.stringify({ id: peerId, type: 'candidate', candidate, mid })); + ws.sendMessage(JSON.stringify({ id: peerId, type: 'candidate', candidate, mid })); }); peerConnection.onDataChannel((dc) => { rl.close(); diff --git a/examples/client-server/client-periodic.js b/examples/client-server/client-periodic.js index 95bf602..c97a92b 100644 --- a/examples/client-server/client-periodic.js +++ b/examples/client-server/client-periodic.js @@ -1,4 +1,3 @@ -import WebSocket from 'ws'; import readline from 'readline'; import nodeDataChannel from '../../lib/index.js'; @@ -25,23 +24,22 @@ const rl = readline.createInterface({ // Signaling Server const WS_URL = process.env.WS_URL || 'ws://localhost:8000'; -const ws = new WebSocket(WS_URL + '/' + id, { - perMessageDeflate: false, -}); +const ws = new nodeDataChannel.WebSocket(); +ws.open(WS_URL + '/' + id); console.log(`The local ID is: ${id}`); console.log(`Waiting for signaling to be connected...`); -ws.on('open', () => { +ws.onOpen(() => { console.log('WebSocket connected, signaling ready'); readUserInput(); }); -ws.on('error', (err) => { +ws.onError((err) => { console.log('WebSocket Error: ', err); }); -ws.on('message', (msgStr) => { +ws.onMessage((msgStr) => { let msg = JSON.parse(msgStr); switch (msg.type) { case 'offer': @@ -122,10 +120,10 @@ function createPeerConnection(peerId) { console.log('GatheringState: ', state); }); peerConnection.onLocalDescription((description, type) => { - ws.send(JSON.stringify({ id: peerId, type, description })); + ws.sendMessage(JSON.stringify({ id: peerId, type, description })); }); peerConnection.onLocalCandidate((candidate, mid) => { - ws.send(JSON.stringify({ id: peerId, type: 'candidate', candidate, mid })); + ws.sendMessage(JSON.stringify({ id: peerId, type: 'candidate', candidate, mid })); }); peerConnection.onDataChannel((dc) => { rl.close(); diff --git a/examples/client-server/client.js b/examples/client-server/client.js index 95a4841..2046519 100644 --- a/examples/client-server/client.js +++ b/examples/client-server/client.js @@ -1,4 +1,3 @@ -import WebSocket from 'ws'; import readline from 'readline'; import nodeDataChannel from '../../lib/index.js'; @@ -13,23 +12,22 @@ const id = randomId(4); // Signaling Server const WS_URL = process.env.WS_URL || 'ws://localhost:8000'; -const ws = new WebSocket(WS_URL + '/' + id, { - perMessageDeflate: false, -}); +const ws = new nodeDataChannel.WebSocket(); +ws.open(WS_URL + '/' + id); console.log(`The local ID is: ${id}`); console.log(`Waiting for signaling to be connected...`); -ws.on('open', () => { +ws.onOpen(() => { console.log('WebSocket connected, signaling ready'); readUserInput(); }); -ws.on('error', (err) => { +ws.onError((err) => { console.log('WebSocket Error: ', err); }); -ws.on('message', (msgStr) => { +ws.onMessage((msgStr) => { let msg = JSON.parse(msgStr); switch (msg.type) { case 'offer': @@ -86,10 +84,10 @@ function createPeerConnection(peerId) { console.log('GatheringState: ', state); }); peerConnection.onLocalDescription((description, type) => { - ws.send(JSON.stringify({ id: peerId, type, description })); + ws.sendMessage(JSON.stringify({ id: peerId, type, description })); }); peerConnection.onLocalCandidate((candidate, mid) => { - ws.send(JSON.stringify({ id: peerId, type: 'candidate', candidate, mid })); + ws.sendMessage(JSON.stringify({ id: peerId, type: 'candidate', candidate, mid })); }); peerConnection.onDataChannel((dc) => { console.log('DataChannel from ' + peerId + ' received with label "', dc.getLabel() + '"'); diff --git a/examples/client-server/package-lock.json b/examples/client-server/package-lock.json index 9f13652..ef520ef 100644 --- a/examples/client-server/package-lock.json +++ b/examples/client-server/package-lock.json @@ -9,7 +9,6 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "ws": "^7.5.3", "yargs": "^16.2.0" } }, @@ -138,26 +137,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -282,12 +261,6 @@ "strip-ansi": "^6.0.0" } }, - "ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "requires": {} - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/examples/client-server/package.json b/examples/client-server/package.json index 63861d6..99e665f 100644 --- a/examples/client-server/package.json +++ b/examples/client-server/package.json @@ -11,7 +11,6 @@ "author": "", "license": "ISC", "dependencies": { - "ws": "^7.5.3", "yargs": "^16.2.0" } } diff --git a/examples/client-server/signaling-server.js b/examples/client-server/signaling-server.js index b5145ed..c64bcd5 100644 --- a/examples/client-server/signaling-server.js +++ b/examples/client-server/signaling-server.js @@ -1,15 +1,22 @@ -import WebSocket from 'ws'; +import nodeDataChannel from '../../lib/index.js'; + +// Init Logger +nodeDataChannel.initLogger('Debug'); const clients = {}; -const wss = new WebSocket.Server({ port: 8000 }); +const wsServer = new nodeDataChannel.WebSocketServer({ bindAddress: '127.0.0.1', port: 8000 }); + +wsServer.onClient((ws) => { + let id = ''; -wss.on('connection', (ws, req) => { - const id = req.url.replace('/', ''); - console.log(`New Connection from ${id}`); + ws.onOpen(() => { + id = ws.path().replace('/', ''); + console.log(`New Connection from ${id}`); + clients[id] = ws; + }); - clients[id] = ws; - ws.on('message', (buffer) => { + ws.onMessage((buffer) => { let msg = JSON.parse(buffer); let peerId = msg.id; let peerWs = clients[peerId]; @@ -18,11 +25,15 @@ wss.on('connection', (ws, req) => { if (!peerWs) return console.error(`Can not find peer with ID ${peerId}`); msg.id = id; - peerWs.send(JSON.stringify(msg)); + peerWs.sendMessage(JSON.stringify(msg)); }); - ws.on('close', () => { - console.log(`${id} disconected`); + ws.onClosed(() => { + console.log(`${id} disconnected`); delete clients[id]; }); + + ws.onError((err) => { + console.error(err); + }); }); diff --git a/lib/index.js b/lib/index.js index 3035b61..3019cc0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,9 +1,6 @@ -// createRequire is native in node version >= 12 -import { createRequire } from 'module'; -const require = createRequire(import.meta.url); - -const nodeDataChannel = require('../build/Release/node_datachannel.node'); +import nodeDataChannel from './node-datachannel.js'; import DataChannelStream from './datachannel-stream.js'; +import WebSocketServer from './websocket-server.js'; const { initLogger, @@ -17,9 +14,16 @@ const { DataChannel, PeerConnection, WebSocket, - WebSocketServer, } = nodeDataChannel; +export const DescriptionType = { + Unspec: 'unspec', + Offer: 'offer', + Answer: 'answer', + Pranswer: 'pranswer', + Rollback: 'rollback', +}; + export { initLogger, cleanup, @@ -38,14 +42,17 @@ export { }; export default { - ...nodeDataChannel, + initLogger, + cleanup, + preload, + setSctpSettings, + RtcpReceivingSession, + Track, + Video, + Audio, + DataChannel, + PeerConnection, + WebSocket, + WebSocketServer, DataChannelStream, }; - -export const DescriptionType = { - Unspec: 'unspec', - Offer: 'offer', - Answer: 'answer', - Pranswer: 'pranswer', - Rollback: 'rollback', -}; diff --git a/lib/node-datachannel.js b/lib/node-datachannel.js new file mode 100644 index 0000000..62435f1 --- /dev/null +++ b/lib/node-datachannel.js @@ -0,0 +1,7 @@ +// createRequire is native in node version >= 12 +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); + +const nodeDataChannel = require('../build/Release/node_datachannel.node'); + +export default nodeDataChannel; diff --git a/lib/websocket-server.js b/lib/websocket-server.js new file mode 100644 index 0000000..70f93ca --- /dev/null +++ b/lib/websocket-server.js @@ -0,0 +1,34 @@ +import { EventEmitter } from 'events'; +import nodeDataChannel from './node-datachannel.js'; + +export default class WebSocketServer extends EventEmitter { + #server; + #clients = []; + + constructor(options) { + super(); + this.#server = new nodeDataChannel.WebSocketServer(options); + + this.#server.onClient((client) => { + this.emit('client', client); + this.#clients.push(client); + }); + } + + port() { + return this.#server?.port() || 0; + } + + stop() { + this.#clients.forEach((client) => { + client?.close(); + }); + this.#server?.stop(); + this.#server = null; + this.removeAllListeners(); + } + + onClient(cb) { + if (this.#server) this.on('client', cb); + } +} diff --git a/src/web-socket-server-wrapper.cpp b/src/web-socket-server-wrapper.cpp index 74abcb0..09ff7eb 100644 --- a/src/web-socket-server-wrapper.cpp +++ b/src/web-socket-server-wrapper.cpp @@ -2,6 +2,9 @@ #include "plog/Log.h" +Napi::FunctionReference WebSocketServerWrapper::constructor; +std::unordered_set WebSocketServerWrapper::instances; + void WebSocketServerWrapper::StopAll() { PLOG_DEBUG << "StopAll() called"; @@ -10,9 +13,6 @@ void WebSocketServerWrapper::StopAll() inst->doStop(); } -Napi::FunctionReference WebSocketServerWrapper::constructor; -std::unordered_set WebSocketServerWrapper::instances; - Napi::Object WebSocketServerWrapper::Init(Napi::Env env, Napi::Object exports) { Napi::HandleScope scope(env); @@ -20,9 +20,11 @@ Napi::Object WebSocketServerWrapper::Init(Napi::Env env, Napi::Object exports) Napi::Function func = DefineClass( env, "WebSocketServer", - {InstanceMethod("stop", &WebSocketServerWrapper::stop), - InstanceMethod("port", &WebSocketServerWrapper::port), - InstanceMethod("onClient", &WebSocketServerWrapper::onClient)}); + { + InstanceMethod("stop", &WebSocketServerWrapper::stop), + InstanceMethod("port", &WebSocketServerWrapper::port), + InstanceMethod("onClient", &WebSocketServerWrapper::onClient) + }); constructor = Napi::Persistent(func); constructor.SuppressDestruct(); @@ -34,7 +36,6 @@ Napi::Object WebSocketServerWrapper::Init(Napi::Env env, Napi::Object exports) WebSocketServerWrapper::WebSocketServerWrapper(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) { PLOG_DEBUG << "Constructor called"; - Napi::Env env = info.Env(); // Create WebSocketServer without config @@ -177,7 +178,7 @@ void WebSocketServerWrapper::doStop() PLOG_DEBUG << "doStop() called"; if (mWebSocketServerPtr) { - PLOG_DEBUG << "Closing..."; + PLOG_DEBUG << "Stopping..."; try { mWebSocketServerPtr->stop(); @@ -191,7 +192,6 @@ void WebSocketServerWrapper::doStop() } mOnClientCallback.reset(); - instances.erase(this); } @@ -256,8 +256,10 @@ void WebSocketServerWrapper::onClient(const Napi::CallbackInfo &info) // This will run in main thread and needs to construct the // arguments for the call std::shared_ptr webSocket = ws; - auto instance = WebSocketWrapper::constructor.New({Napi::External>::New(env, nullptr), Napi::External>::New(env, &webSocket)}); + // First argument is just a placeholder + auto instance = WebSocketWrapper::constructor.New({Napi::Boolean::New(env, false), Napi::External>::New(env, &webSocket)}); args = {instance}; PLOG_DEBUG << "mOnClientCallback call(2)"; - }); }); + }); + }); } diff --git a/src/web-socket-server-wrapper.h b/src/web-socket-server-wrapper.h index d64dc67..0ac7f10 100644 --- a/src/web-socket-server-wrapper.h +++ b/src/web-socket-server-wrapper.h @@ -16,13 +16,11 @@ class WebSocketServerWrapper : public Napi::ObjectWrap { public: - static Napi::FunctionReference constructor; static Napi::Object Init(Napi::Env env, Napi::Object exports); WebSocketServerWrapper(const Napi::CallbackInfo &info); ~WebSocketServerWrapper(); // Functions - void stop(const Napi::CallbackInfo &info); Napi::Value port(const Napi::CallbackInfo &info); @@ -31,13 +29,17 @@ class WebSocketServerWrapper : public Napi::ObjectWrap // Close all existing WebSocketServers static void StopAll(); - + private: - static std::unordered_set instances; - std::unique_ptr mWebSocketServerPtr = nullptr; - std::unique_ptr mOnClientCallback = nullptr; + static Napi::FunctionReference constructor; + static std::unordered_set instances; void doStop(); + + std::unique_ptr mWebSocketServerPtr = nullptr; + + // Callback Ptrs + std::unique_ptr mOnClientCallback = nullptr; }; #endif // WEB_SOCKET_SERVER_WRAPPER_H diff --git a/test/connectivity.js b/test/connectivity.js index e9b9eed..1639e8e 100644 --- a/test/connectivity.js +++ b/test/connectivity.js @@ -1,30 +1,16 @@ import nodeDataChannel from '../lib/index.js'; nodeDataChannel.initLogger('Debug'); -nodeDataChannel.preload(); let dc1 = null; let dc2 = null; -// Config options -// export interface RtcConfig { -// iceServers: string[]; -// proxyServer?: ProxyServer; -// bindAddress?: string; -// enableIceTcp?: boolean; -// portRangeBegin?: number; -// portRangeEnd?: number; -// maxMessageSize?: number; -// iceTransportPolicy?: TransportPolicy; -// } -// // "iceServers" option is an array of stun/turn server urls // Examples; // STUN Server Example : stun:stun.l.google.com:19302 // TURN Server Example : turn:USERNAME:PASSWORD@TURN_IP_OR_ADDRESS:PORT // TURN Server Example (TCP) : turn:USERNAME:PASSWORD@TURN_IP_OR_ADDRESS:PORT?transport=tcp // TURN Server Example (TLS) : turns:USERNAME:PASSWORD@TURN_IP_OR_ADDRESS:PORT - let peer1 = new nodeDataChannel.PeerConnection('Peer1', { iceServers: ['stun:stun.l.google.com:19302'] }); // Set Callbacks @@ -78,15 +64,7 @@ peer2.onDataChannel((dc) => { }); }); -// DataChannel Options -// export interface DataChannelInitConfig { -// protocol?: string; -// negotiated?: boolean; -// id?: number; -// ordered?: boolean; -// maxPacketLifeTime?: number; -// maxRetransmits?: number; -// } +// Create DataChannel dc1 = peer1.createDataChannel('test'); dc1.onOpen(() => { dc1.sendMessage('Hello from Peer1');