hsync 0.18.3 → 0.20.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/lib/rtc-node.js CHANGED
@@ -16,95 +16,105 @@ const defaultOptions = { iceServers: ['stun:stun.l.google.com:19302'] };
16
16
 
17
17
  const GATHERING_TIMEOUT = 4000;
18
18
 
19
- async function offerPeer(peer) {
20
-
21
- if (!rtc.PeerConnection) {
22
- throw new Error('node-datachannel not installed');
23
- }
24
-
25
- const con = new rtc.PeerConnection('pc', defaultOptions);
26
- // window.rtc = rtc;
27
-
28
- peer.rtcCon = con;
29
- peer.rtcOfferer = true;
19
+ function offerPeer(peer) {
20
+ return new Promise(async (resolve, reject) => {
21
+ if (!rtc.PeerConnection) {
22
+ reject('node-datachannel not installed');
23
+ return;
24
+ }
30
25
 
31
- let gatheringComplete = false;
32
- let offerSent = false;
33
- const start = Date.now();
34
-
35
- function sendOffer() {
36
- const desc = con.localDescription();
37
- peer.methods.rtcSignal({type: desc.type, sdp: desc.sdp});
38
- }
39
-
40
- con.onGatheringStateChange = (state) => {
41
- debug('onGatheringStateChange', state);
42
- if (state === 'complete') {
43
- debug('icegathering done', Date.now() - start);
44
- gatheringComplete = true;
45
- // We only want to provide an answer once all of our candidates have been added to the SDP.
46
- sendOffer();
26
+ const con = new rtc.PeerConnection('pc', defaultOptions);
27
+ // window.rtc = rtc;
28
+
29
+ peer.rtcCon = con;
30
+ peer.rtcOfferer = true;
31
+
32
+ let gatheringComplete = false;
33
+ let offerSent = false;
34
+ const start = Date.now();
35
+
36
+ async function sendOffer() {
37
+ const desc = con.localDescription();
38
+ try {
39
+ const resp = await peer.methods.rtcSignal({type: desc.type, sdp: desc.sdp});
40
+ resolve(resp);
41
+ } catch (e) {
42
+ debugError('error sending offer', e);
43
+ reject(e);
44
+ }
45
+ // peer.methods.rtcSignal({type: desc.type, sdp: desc.sdp});
47
46
  }
48
- }
49
-
50
- con.onStateChange((state) => {
51
- debug('offerer onStateChange: ', state);
52
- if (state === 'connected') {
53
- peer.connected = true;
54
- peer.rtcEvents.emit('connected', con);
55
- } else if (state === 'disconnected') {
56
- peer.connected = false;
57
- peer.rtcEvents.emit('disconnected', con);
58
- peer.rtcCon = null;
59
- peer.dc = null;
60
- } else if (state === 'closed') {
61
- peer.connected = false;
62
- peer.rtcEvents.emit('closed', con);
63
- peer.rtcCon = null;
64
- peer.dc = null;
47
+
48
+ con.onGatheringStateChange = (state) => {
49
+ debug('onGatheringStateChange', state);
50
+ if (state === 'complete') {
51
+ debug('icegathering done', Date.now() - start);
52
+ gatheringComplete = true;
53
+ // We only want to provide an answer once all of our candidates have been added to the SDP.
54
+ sendOffer();
55
+ }
65
56
  }
66
- });
67
-
68
- con.onDataChannel((dc) => {
69
- debug('offerer onDataChannel', dc);
70
- peer.dc = dc;
71
- });
72
-
73
- const dc = con.createDataChannel('fromofferer');
74
- dc.onOpen(() => {
75
- peer.dc = dc;
76
- peer.dcOpen = true;
77
- peer.rtcSend = (packet) => {
78
- dc.sendMessageBinary(packet);
79
- };
80
- peer.rtcEvents.emit('dcOpen', dc);
81
- // dc.sendMessage("Hello from node from offerer");
82
- // peer.dc.onStateChange((state) => {
83
- // debug('dc state change', state);
84
- // });
85
- debug('keys', Object.keys(dc));
86
- });
87
-
88
- dc.onMessage((msg) => {
89
- debug('node offerer received msg:', msg.length);
90
- peer.rtcEvents.emit('packet', msg);
91
- });
92
-
93
- con.setLocalDescription();
94
-
95
- setTimeout(() => {
96
- if (!gatheringComplete) {
97
- debug('didnt finish gathering');
98
- sendOffer();
99
- offerSent = true;
57
+
58
+ con.onStateChange((state) => {
59
+ debug('offerer onStateChange: ', state);
60
+ if (state === 'connected') {
61
+ peer.connected = true;
62
+ peer.rtcEvents.emit('connected', con);
63
+ } else if (state === 'disconnected') {
64
+ peer.connected = false;
65
+ peer.rtcEvents.emit('disconnected', con);
66
+ peer.rtcCon = null;
67
+ peer.dc = null;
68
+ } else if (state === 'closed') {
69
+ peer.connected = false;
70
+ peer.rtcEvents.emit('closed', con);
71
+ peer.rtcCon = null;
72
+ peer.dc = null;
73
+ }
74
+ });
75
+
76
+ con.onDataChannel((dc) => {
77
+ debug('offerer onDataChannel', dc);
78
+ peer.dc = dc;
79
+ });
80
+
81
+ const dc = con.createDataChannel('fromofferer');
82
+ dc.onOpen(() => {
83
+ peer.dc = dc;
84
+ peer.dcOpen = true;
85
+ peer.rtcSend = (packet) => {
86
+ dc.sendMessageBinary(packet);
87
+ };
88
+ peer.rtcEvents.emit('dcOpen', dc);
89
+ // dc.sendMessage("Hello from node from offerer");
90
+ // peer.dc.onStateChange((state) => {
91
+ // debug('dc state change', state);
92
+ // });
93
+ debug('keys', Object.keys(dc));
94
+ });
95
+
96
+ dc.onMessage((msg) => {
97
+ debug('node offerer received msg:', msg.length);
98
+ peer.rtcEvents.emit('packet', msg);
99
+ });
100
+
101
+ con.setLocalDescription();
102
+
103
+ setTimeout(() => {
104
+ if (!gatheringComplete) {
105
+ debug('didnt finish gathering');
106
+ sendOffer();
107
+ offerSent = true;
108
+ }
109
+ }, GATHERING_TIMEOUT);
110
+
111
+ peer.handleRtcAnswer = (answer) => {
112
+ debug('node handleRtcAnswer', answer.sdp.length);
113
+ con.setRemoteDescription(answer.sdp, answer.type);
114
+ return 'node handleRtcAnswer ok';
100
115
  }
101
- }, GATHERING_TIMEOUT);
102
116
 
103
- peer.handleRtcAnswer = (answer) => {
104
- debug('node handleRtcAnswer', answer.sdp.length);
105
- con.setRemoteDescription(answer.sdp, answer.type);
106
- return 'node handleRtcAnswer ok';
107
- }
117
+ });
108
118
  }
109
119
 
110
120
  async function answerPeer(peer, offer) {
package/lib/rtc-web.js CHANGED
@@ -15,83 +15,90 @@ const defaultOptions = {
15
15
  const GATHERING_TIMEOUT = 4000;
16
16
 
17
17
  async function offerPeer(peer) {
18
+ return new Promise(async (resolve, reject) => {
19
+ const con = new RTCPeerConnection(defaultOptions);
20
+ // window.rtc = rtc;
21
+
22
+ peer.rtcCon = con;
23
+ peer.rtcOfferer = true;
24
+
25
+ let gatheringComplete = false;
26
+ let offerSent = false;
27
+ const start = Date.now();
28
+
29
+ async function sendOffer(alreadySent) {
30
+ debug('send offer', alreadySent);
31
+ const desc = con.localDescription;
32
+ try {
33
+ const resp = await peer.methods.rtcSignal({type: desc.type, sdp: desc.sdp, alreadySent});
34
+ resolve(resp);
35
+ } catch (e) {
36
+ debugError('error sending offer', e);
37
+ reject(e);
38
+ }
39
+ }
18
40
 
19
- const con = new RTCPeerConnection(defaultOptions);
20
- // window.rtc = rtc;
21
-
22
- peer.rtcCon = con;
23
- peer.rtcOfferer = true;
24
-
25
- let gatheringComplete = false;
26
- let offerSent = false;
27
- const start = Date.now();
41
+ con.onicegatheringstatechange = (state) => {
42
+ debug('state change', con.iceGatheringState);
43
+ if (con.iceGatheringState === 'complete') {
44
+ debug('icegathering done', Date.now() - start);
45
+ gatheringComplete = true;
46
+ // We only want to provide an answer once all of our candidates have been added to the SDP.
47
+ sendOffer(offerSent);
48
+ }
49
+ }
28
50
 
29
- function sendOffer(alreadySent) {
30
- debug('send offer', alreadySent);
31
- const desc = con.localDescription;
32
- peer.methods.rtcSignal({type: desc.type, sdp: desc.sdp, alreadySent});
33
- }
51
+ con.onicecandidate = (ice) => {
52
+ debug('ice candidate', ice);
53
+ };
34
54
 
35
- con.onicegatheringstatechange = (state) => {
36
- debug('state change', con.iceGatheringState);
37
- if (con.iceGatheringState === 'complete') {
38
- debug('icegathering done', Date.now() - start);
39
- gatheringComplete = true;
40
- // We only want to provide an answer once all of our candidates have been added to the SDP.
41
- sendOffer(offerSent);
55
+ con.onconnectionstatechange = (event) => {
56
+ debug('connection state', con.connectionState, event);
57
+ if(con.connectionState === 'connected') {
58
+ peer.connected = true;
59
+ peer.rtcEvents.emit('connected', con);
42
60
  }
43
- }
44
-
45
- con.onicecandidate = (ice) => {
46
- debug('ice candidate', ice);
47
- };
48
-
49
- con.onconnectionstatechange = (event) => {
50
- debug('connection state', con.connectionState, event);
51
- if(con.connectionState === 'connected') {
52
- peer.connected = true;
53
- peer.rtcEvents.emit('connected', con);
54
- }
55
- };
61
+ };
56
62
 
57
- con.ondatachannel = (event) => {
58
- debug('dc from answerer', event);
59
- peer.dc = event.channel;
60
- };
63
+ con.ondatachannel = (event) => {
64
+ debug('dc from answerer', event);
65
+ peer.dc = event.channel;
66
+ };
61
67
 
62
- const dc = con.createDataChannel('from web');
68
+ const dc = con.createDataChannel('from web');
63
69
 
64
- peer.dc = dc;
65
- dc.onmessage = (event) => {
66
- debug('dc.onmessage', event.data);
67
- peer.rtcEvents.emit('packet', event.data);
68
- };
69
- dc.onopen = (event) => {
70
- peer.dcOpen = true;
71
70
  peer.dc = dc;
72
- peer.rtcSend = (packet) => {
73
- dc.send(packet);
71
+ dc.onmessage = (event) => {
72
+ debug('dc.onmessage', event.data);
73
+ peer.rtcEvents.emit('packet', event.data);
74
+ };
75
+ dc.onopen = (event) => {
76
+ peer.dcOpen = true;
77
+ peer.dc = dc;
78
+ peer.rtcSend = (packet) => {
79
+ dc.send(packet);
80
+ };
81
+ peer.rtcEvents.emit('dcOpen', dc);
82
+ // dc.send('yo waddup from the browser');
74
83
  };
75
- peer.rtcEvents.emit('dcOpen', dc);
76
- // dc.send('yo waddup from the browser');
77
- };
78
84
 
79
- const offer = await con.createOffer({offerToReceiveAudio:true, offerToReceiveVideo:true});
80
- await con.setLocalDescription(offer);
85
+ const offer = await con.createOffer({offerToReceiveAudio:true, offerToReceiveVideo:true});
86
+ await con.setLocalDescription(offer);
81
87
 
82
- setTimeout(() => {
83
- if (!gatheringComplete) {
84
- debug('didnt finish gathering');
85
- sendOffer();
86
- offerSent = true;
87
- }
88
- }, GATHERING_TIMEOUT);
88
+ setTimeout(() => {
89
+ if (!gatheringComplete) {
90
+ debug('didnt finish gathering');
91
+ sendOffer();
92
+ offerSent = true;
93
+ }
94
+ }, GATHERING_TIMEOUT);
89
95
 
90
- peer.handleRtcAnswer = (answer) => {
91
- debug('node handleRtcAnswer', answer.sdp.length);
92
- con.setRemoteDescription(answer);
93
- return 'web handleRtcAnswer ok';
94
- }
96
+ peer.handleRtcAnswer = (answer) => {
97
+ debug('node handleRtcAnswer', answer.sdp.length);
98
+ con.setRemoteDescription(answer);
99
+ return 'web handleRtcAnswer ok';
100
+ }
101
+ });
95
102
  }
96
103
 
97
104
  async function answerPeer(peer, offer) {
@@ -0,0 +1,182 @@
1
+ const b64id = require('b64id');
2
+ const debug = require('debug')('hsync:listener');
3
+ const debugError = require('debug')('hsync:error');
4
+
5
+ const { sockets } = require('./socket-map');
6
+
7
+ let net;
8
+
9
+ function setNet(netImpl) {
10
+ net = netImpl;
11
+ }
12
+
13
+ debugError.color = 1;
14
+
15
+ function initListeners(hsyncClient) {
16
+ const socketListeners = {};
17
+
18
+ function getSocketListeners() {
19
+ const hKeys = Object.keys(socketListeners);
20
+ debug('getSocketListeners', hKeys);
21
+ let retVal = hKeys.map((hk) => {
22
+ const l = socketListeners[hk];
23
+ return {
24
+ port: l.port,
25
+ targetHost: l.targetHost,
26
+ targetPort: l.targetPort,
27
+ };
28
+ });
29
+ return retVal;
30
+ }
31
+
32
+ function addSocketListener(options = {}) {
33
+ const { port, targetPort, targetHost } = options;
34
+ debug('creating handler', port, targetHost);
35
+
36
+ const rpcPeer = hsyncClient.getRPCPeer({ hostName: targetHost });
37
+
38
+ const socketServer = net.createServer(async (socket) => {
39
+
40
+ if (!rpcPeer.rtcCon) {
41
+ try {
42
+ debug('initiating connectRTC from socket listener');
43
+ await rpcPeer.connectRTC();
44
+ } catch (e) {
45
+ debug('error connecting to rtc', e);
46
+ socket.end();
47
+ return;
48
+ }
49
+ }
50
+
51
+ rpcPeer.notifications.oncloseListenerSocket((remotePeer, { socketId }) => {
52
+ debug('closeListenerSocket', socketId, !!sockets[socketId]);
53
+ if (sockets[socketId]) {
54
+ sockets[socketId].end();
55
+ delete sockets[socketId];
56
+ return 'closeListenerSocket ok';
57
+ }
58
+ return `closeListenerSocket no matching socket for ${socketId}`;
59
+ });
60
+
61
+ socket.socketId = b64id.generateId();
62
+ sockets[socket.socketId] = socket;
63
+ rpcPeer.sockets[socket.socketId] = socket;
64
+ socket.listenerSocket = true;
65
+ debug('connected to local listener', port, socket.socketId);
66
+ socket.peerConnected = false;
67
+ // const pubTopic = `msg/${hostName}/${hsyncClient.myHostName}/socketData/${socket.socketId}`;
68
+ // const closeTopic = `msg/${hostName}/${hsyncClient.myHostName}/socketClose/${socket.socketId}`;
69
+ const dataQueue = [];
70
+
71
+ async function sendData(data) {
72
+ // TODO queue data if not connected
73
+ if (rpcPeer.packAndSend) {
74
+ debug('sending data via rtc', targetHost, socket.socketId, data.length);
75
+ rpcPeer.packAndSend(`socketData/${socket.socketId}`, data);
76
+ return;
77
+ }
78
+ // debug('sending data via rpc', targetHost, data.length);
79
+ // // hsyncClient.mqConn.publish(pubTopic, data);
80
+ // const result = await rpcPeer.methods.receiveListenerData({
81
+ // socketId: socket.socketId,
82
+ // data: Buffer.from(data).toString('base64'),
83
+ // });
84
+ // debug('sendData from Listener', result);
85
+ }
86
+
87
+ socket.on('data', async (data) => {
88
+ debug('socket data', data ? data.length : '');
89
+ // if (!socket.peerConnected) {
90
+ // dataQueue.push(data);
91
+ // return;
92
+ // }
93
+ sendData(data);
94
+ });
95
+
96
+ socket.on('close', (a, b, c) => {
97
+ debug('listener socket closed', port, socket.socketId, a, b, c);
98
+ if (sockets[socket.socketId]) {
99
+ delete sockets[socket.socketId];
100
+ try {
101
+ rpcPeer.notifiers.closeRelaySocket({
102
+ socketId: socket.socketId,
103
+ });
104
+ } catch (e) {
105
+ debug('error closing relay socket', e);
106
+ }
107
+ }
108
+ });
109
+
110
+ socket.on('error', (error) => {
111
+ debug('socket error', targetHost, socket.socketId, error);
112
+ if (sockets[socket.socketId]) {
113
+ delete sockets[socket.socketId];
114
+ }
115
+ });
116
+
117
+ try {
118
+ debug('connecting remotely', socket.socketId, targetPort, rpcPeer.hostName, targetHost);
119
+ const result = await rpcPeer.methods.connectSocket({
120
+ socketId: socket.socketId,
121
+ port: targetPort,
122
+ hostName: rpcPeer.hostName,
123
+ });
124
+ debug('connect result', result);
125
+ socket.peerConnected = true;
126
+ dataQueue.forEach(sendData);
127
+ } catch (e) {
128
+ debugError('cant connect remotely', targetHost, targetPort, e);
129
+ if (sockets[socket.socketId]) {
130
+ delete sockets[socket.socketId];
131
+ }
132
+ socket.end();
133
+ }
134
+ });
135
+
136
+ socketServer.listen(port);
137
+
138
+ function end() {
139
+ const sockKeys = Object.keys(sockets);
140
+ sockKeys.forEach((sk) => {
141
+ try {
142
+ if (sockets[sk].listenerSocket) {
143
+ sockets[sk].end();
144
+ delete sockets[sk];
145
+ }
146
+ }
147
+ catch(e) {
148
+ debug('error closing socket', e);
149
+ }
150
+ });
151
+ }
152
+
153
+ const listener = {
154
+ socketServer,
155
+ sockets,
156
+ end,
157
+ targetHost,
158
+ targetPort,
159
+ port,
160
+ };
161
+
162
+ socketListeners['p' + port] = listener;
163
+ return listener;
164
+ }
165
+
166
+ hsyncClient.socketListeners = socketListeners;
167
+ hsyncClient.addSocketListener = addSocketListener;
168
+ // hsyncClient.receiveRelayData = receiveRelayData;
169
+ hsyncClient.getSocketListeners = getSocketListeners;
170
+ // hsyncClient.closeListenerSocket = closeListenerSocket;
171
+
172
+ return {
173
+ addSocketListener,
174
+ // receiveRelayData,
175
+ getSocketListeners,
176
+ };
177
+ }
178
+
179
+ module.exports = {
180
+ initListeners,
181
+ setNet,
182
+ };
@@ -0,0 +1,123 @@
1
+ const debug = require('debug')('hsync:relay');
2
+ const debugError = require('debug')('hsync:error');
3
+ const { sockets } = require('./socket-map');
4
+
5
+ debugError.color = 1;
6
+
7
+ let net;
8
+
9
+ function setNet(netImpl) {
10
+ net = netImpl;
11
+ }
12
+
13
+ function initRelays(hsyncClient) {
14
+ const cachedRelays = {};
15
+
16
+ function getSocketRelays() {
17
+ const hKeys = Object.keys(cachedRelays);
18
+ debug('getSocketListeners', hKeys);
19
+ let retVal = hKeys.map((hk) => {
20
+ const l = cachedRelays[hk];
21
+ return {
22
+ port: l.port,
23
+ targetHost: l.targetHost,
24
+ targetPort: l.targetPort,
25
+ whitelist: l.whitelist || '',
26
+ blacklist: l.blacklist || '',
27
+ };
28
+ });
29
+ return retVal;
30
+ }
31
+
32
+ function connectSocket(remotePeer, { port, socketId }) {
33
+
34
+ remotePeer.notifications.oncloseRelaySocket((remotePeer, { socketId }) => {
35
+ debug('closeRelaySocket', socketId);
36
+ if (sockets[socketId]) {
37
+ sockets[socketId].end();
38
+ delete sockets[socketId];
39
+ return 'closeRelaySocket ok';
40
+ }
41
+ return `closeRelaySocket no matching socket for ${socketId}`;
42
+ });
43
+
44
+ const relay = cachedRelays['p' + port];
45
+ debug('connect relay', port, socketId, remotePeer.hostName);
46
+ if (!relay) {
47
+ throw new Error('no relay found for port: ' + port);
48
+ }
49
+
50
+ // TODO: check white and black lists on remotePeer
51
+
52
+ // const relayDataTopic = `msg/${hostName}/${hsyncClient.myHostName}/relayData/${socketId}`;
53
+ return new Promise((resolve, reject) => {
54
+ const socket = new net.Socket();
55
+ socket.socketId = socketId;
56
+ sockets[socketId] = socket;
57
+ socket.connect(relay.targetPort, relay.targetHost, () => {
58
+ debug(`CONNECTED TO LOCAL SERVER`, socket.socketId, socket.hostName, port);
59
+ resolve({socketId, targetHost: relay.targetHost, targetPort: relay.targetPort});
60
+ });
61
+
62
+ socket.on('data', async (data) => {
63
+ debug(`data in ${socket.socketId}`, relay.targetPort, relay.targetHost, data.length);
64
+ // TODO: queue data if remotePeer is not ready
65
+ if (remotePeer.packAndSend) {
66
+ debug('sending relay data via rtc', socket.socketId, data.length);
67
+ remotePeer.packAndSend(`socketData/${socket.socketId}`, Buffer.from(data));
68
+ return;
69
+ }
70
+ });
71
+ socket.on('close', async () => {
72
+ debug(`LOCAL CONNECTION CLOSED`, socket.socketId);
73
+ if (sockets[socket.socketId]) {
74
+ try {
75
+ await remotePeer.notifiers.closeListenerSocket({socketId});
76
+ } catch (e) {
77
+ debug('error closing socket', e);
78
+ }
79
+ delete sockets[socket.socketId];
80
+ }
81
+ });
82
+
83
+ socket.on('error', (e) => {
84
+ debugError(`LOCAL CONNECTION ERROR`, socket.socketId, e);
85
+ delete sockets[socket.socketId];
86
+ reject(e);
87
+ });
88
+
89
+ });
90
+
91
+ }
92
+
93
+ function addSocketRelay({whitelist, blacklist, port, targetPort, targetHost = 'localhost'}) {
94
+ targetPort = targetPort || port;
95
+ debug('creating relay', whitelist, blacklist, port, targetPort, targetHost);
96
+ const newRelay = {
97
+ whitelist,
98
+ blacklist,
99
+ port,
100
+ targetPort,
101
+ targetHost,
102
+ };
103
+ cachedRelays['p' + port] = newRelay;
104
+ return newRelay;
105
+ }
106
+
107
+ hsyncClient.cachedRelays = cachedRelays;
108
+ hsyncClient.addSocketRelay = addSocketRelay;
109
+ hsyncClient.getSocketRelays = getSocketRelays;
110
+ hsyncClient.connectSocket = connectSocket;
111
+
112
+ return {
113
+ // receiveListenerData,
114
+ getSocketRelays,
115
+ connectSocket,
116
+ addSocketRelay,
117
+ };
118
+ }
119
+
120
+ module.exports = {
121
+ initRelays,
122
+ setNet,
123
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hsync",
3
- "version": "0.18.3",
3
+ "version": "0.20.0",
4
4
  "description": "client for hsync-server",
5
5
  "main": "hsync.js",
6
6
  "scripts": {
@@ -24,7 +24,8 @@
24
24
  "./hsync": "./hsync-web"
25
25
  },
26
26
  "bin": {
27
- "hsync": "./cli.js"
27
+ "hsync": "./cli.js",
28
+ "sh-hsync": "./shell.js"
28
29
  },
29
30
  "author": "Luis Montes",
30
31
  "license": "ISC",
@@ -38,14 +39,14 @@
38
39
  "mqtt-packet-web": "^8.2.0",
39
40
  "net-web": "^0.2.0",
40
41
  "precompiled-mqtt": "^4.3.14-beta",
41
- "rawr": "^0.15.1",
42
- "webpack": "^5.88.2",
43
- "webpack-cli": "^4.10.0"
42
+ "rawr": "^0.15.1"
44
43
  },
45
44
  "optionalDependencies": {
46
45
  "node-datachannel": "^0.4.3"
47
46
  },
48
47
  "devDependencies": {
49
- "nodemon": "^3.0.1"
48
+ "nodemon": "^3.0.1",
49
+ "webpack": "^5.88.2",
50
+ "webpack-cli": "^4.10.0"
50
51
  }
51
52
  }