diodejs 0.4.3 → 0.4.4
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/bindPort.js +61 -19
- package/package.json +1 -1
- package/test/bindPort.test.js +118 -1
package/bindPort.js
CHANGED
|
@@ -660,6 +660,46 @@ class BindPort {
|
|
|
660
660
|
let rpc = null;
|
|
661
661
|
|
|
662
662
|
let ref;
|
|
663
|
+
let clientClosed = false;
|
|
664
|
+
let remoteCleanupStarted = false;
|
|
665
|
+
let tlsSocketWrapper = null;
|
|
666
|
+
|
|
667
|
+
const closeRemoteRef = async () => {
|
|
668
|
+
if (remoteCleanupStarted) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (!ref || !connection || !rpc) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
remoteCleanupStarted = true;
|
|
676
|
+
|
|
677
|
+
const storedSocket = typeof connection.getClientSocket === 'function'
|
|
678
|
+
? connection.getClientSocket(ref)
|
|
679
|
+
: null;
|
|
680
|
+
if (storedSocket && storedSocket !== clientSocket && typeof storedSocket.end === 'function') {
|
|
681
|
+
try { storedSocket.end(); } catch {}
|
|
682
|
+
}
|
|
683
|
+
if (typeof connection.deleteClientSocket === 'function') {
|
|
684
|
+
try { connection.deleteClientSocket(ref); } catch {}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
try {
|
|
688
|
+
await rpc.portClose(ref);
|
|
689
|
+
logger.info(() => `Port closed on device for ref: ${ref.toString('hex')}`);
|
|
690
|
+
} catch (error) {
|
|
691
|
+
logger.error(() => `Error closing port on device: ${error}`);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
clientSocket.once('close', () => {
|
|
696
|
+
clientClosed = true;
|
|
697
|
+
closeRemoteRef();
|
|
698
|
+
});
|
|
699
|
+
clientSocket.once('error', (err) => {
|
|
700
|
+
logger.error(() => `Client socket error: ${err}`);
|
|
701
|
+
});
|
|
702
|
+
|
|
663
703
|
if (useNative) {
|
|
664
704
|
try {
|
|
665
705
|
connection = await this._resolveConnectionForDevice(deviceId);
|
|
@@ -783,6 +823,12 @@ class BindPort {
|
|
|
783
823
|
return;
|
|
784
824
|
}
|
|
785
825
|
|
|
826
|
+
if (clientClosed || clientSocket.destroyed) {
|
|
827
|
+
logger.warn(() => `Local client disconnected before port ${formattedTargetPort} opened; closing ref ${ref.toString('hex')}`);
|
|
828
|
+
await closeRemoteRef();
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
786
832
|
if (protocol === 'tls') {
|
|
787
833
|
// For tls protocol, create a proper tls connection
|
|
788
834
|
try {
|
|
@@ -829,16 +875,27 @@ class BindPort {
|
|
|
829
875
|
diodeSocket,
|
|
830
876
|
tlsSocket,
|
|
831
877
|
end: () => {
|
|
832
|
-
tlsSocket.end();
|
|
833
|
-
diodeSocket._destroy(null, () => {});
|
|
878
|
+
try { tlsSocket.end(); } catch {}
|
|
879
|
+
try { diodeSocket._destroy(null, () => {}); } catch {}
|
|
834
880
|
}
|
|
835
881
|
};
|
|
882
|
+
tlsSocketWrapper = socketWrapper;
|
|
836
883
|
|
|
837
884
|
// Store the socket wrapper
|
|
838
885
|
connection.addClientSocket(ref, socketWrapper);
|
|
886
|
+
tlsSocket.once('close', () => {
|
|
887
|
+
if (!clientSocket.destroyed) {
|
|
888
|
+
clientSocket.end();
|
|
889
|
+
}
|
|
890
|
+
closeRemoteRef();
|
|
891
|
+
});
|
|
839
892
|
|
|
840
893
|
} catch (error) {
|
|
841
894
|
logger.error(() => `Error setting up tls connection: ${error}`);
|
|
895
|
+
if (tlsSocketWrapper && typeof tlsSocketWrapper.end === 'function') {
|
|
896
|
+
tlsSocketWrapper.end();
|
|
897
|
+
}
|
|
898
|
+
await closeRemoteRef();
|
|
842
899
|
clientSocket.destroy();
|
|
843
900
|
return;
|
|
844
901
|
}
|
|
@@ -858,24 +915,9 @@ class BindPort {
|
|
|
858
915
|
}
|
|
859
916
|
|
|
860
917
|
// Handle client socket closure (common for all protocols)
|
|
861
|
-
clientSocket.
|
|
918
|
+
clientSocket.once('end', () => {
|
|
862
919
|
logger.info(() => 'Client disconnected');
|
|
863
|
-
|
|
864
|
-
try {
|
|
865
|
-
await rpc.portClose(ref);
|
|
866
|
-
logger.info(() => `Port closed on device for ref: ${ref.toString('hex')}`);
|
|
867
|
-
connection.deleteClientSocket(ref);
|
|
868
|
-
} catch (error) {
|
|
869
|
-
logger.error(() => `Error closing port on device: ${error}`);
|
|
870
|
-
}
|
|
871
|
-
} else {
|
|
872
|
-
logger.warn(() => 'Ref is invalid or no longer in clientSockets.');
|
|
873
|
-
}
|
|
874
|
-
});
|
|
875
|
-
|
|
876
|
-
// Handle client socket errors
|
|
877
|
-
clientSocket.on('error', (err) => {
|
|
878
|
-
logger.error(() => `Client socket error: ${err}`);
|
|
920
|
+
closeRemoteRef();
|
|
879
921
|
});
|
|
880
922
|
});
|
|
881
923
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "diodejs",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "A JavaScript client for interacting with the Diode network. It provides functionalities to bind and publish ports, send RPC commands, and handle responses.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
package/test/bindPort.test.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const test = require('node:test');
|
|
2
2
|
const assert = require('node:assert/strict');
|
|
3
3
|
const EventEmitter = require('events');
|
|
4
|
+
const net = require('net');
|
|
5
|
+
const { once } = require('events');
|
|
4
6
|
|
|
5
7
|
const BindPort = require('../bindPort');
|
|
6
8
|
|
|
@@ -9,21 +11,61 @@ function makeRef(hex) {
|
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
function makeRelay(hostKey, result, calls) {
|
|
14
|
+
const clientSockets = new Map();
|
|
15
|
+
const portCloseCalls = [];
|
|
12
16
|
return {
|
|
13
17
|
_managerHostKey: hostKey,
|
|
14
18
|
socket: { destroyed: false },
|
|
19
|
+
clientSockets,
|
|
20
|
+
portCloseCalls,
|
|
15
21
|
RPC: {
|
|
16
22
|
portOpen: async (_deviceId, port, flags) => {
|
|
17
23
|
calls.push({ hostKey, port, flags });
|
|
18
24
|
if (result instanceof Error) {
|
|
19
25
|
throw result;
|
|
20
26
|
}
|
|
21
|
-
return result;
|
|
27
|
+
return typeof result === 'function' ? result() : result;
|
|
22
28
|
},
|
|
29
|
+
portClose: async (ref) => {
|
|
30
|
+
portCloseCalls.push(ref);
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
addClientSocket(ref, socket) {
|
|
34
|
+
clientSockets.set(ref.toString('hex'), socket);
|
|
35
|
+
},
|
|
36
|
+
getClientSocket(ref) {
|
|
37
|
+
return clientSockets.get(ref.toString('hex'));
|
|
38
|
+
},
|
|
39
|
+
deleteClientSocket(ref) {
|
|
40
|
+
return clientSockets.delete(ref.toString('hex'));
|
|
41
|
+
},
|
|
42
|
+
hasClientSocket(ref) {
|
|
43
|
+
return clientSockets.has(ref.toString('hex'));
|
|
23
44
|
},
|
|
24
45
|
};
|
|
25
46
|
}
|
|
26
47
|
|
|
48
|
+
function deferred() {
|
|
49
|
+
let resolve;
|
|
50
|
+
let reject;
|
|
51
|
+
const promise = new Promise((res, rej) => {
|
|
52
|
+
resolve = res;
|
|
53
|
+
reject = rej;
|
|
54
|
+
});
|
|
55
|
+
return { promise, resolve, reject };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function waitFor(predicate, timeoutMs = 1000) {
|
|
59
|
+
const startedAt = Date.now();
|
|
60
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
61
|
+
if (predicate()) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
65
|
+
}
|
|
66
|
+
assert.equal(predicate(), true);
|
|
67
|
+
}
|
|
68
|
+
|
|
27
69
|
class FakeManager extends EventEmitter {
|
|
28
70
|
constructor({ relays, resolvedRelay, nearestRelay, resolveError = null }) {
|
|
29
71
|
super();
|
|
@@ -123,3 +165,78 @@ test('multiple BindPort instances share one manager listener set', () => {
|
|
|
123
165
|
assert.equal(manager.listenerCount('end'), 1);
|
|
124
166
|
assert.equal(manager.listenerCount('error'), 1);
|
|
125
167
|
});
|
|
168
|
+
|
|
169
|
+
test('API bind closes ref when local TCP client disconnects before portopen returns', async () => {
|
|
170
|
+
const calls = [];
|
|
171
|
+
const opened = deferred();
|
|
172
|
+
const ref = makeRef('0a0b0c0d');
|
|
173
|
+
const relay = makeRelay('relay.example:41046', () => opened.promise, calls);
|
|
174
|
+
const manager = new FakeManager({
|
|
175
|
+
relays: [relay],
|
|
176
|
+
resolvedRelay: relay,
|
|
177
|
+
nearestRelay: relay,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const bind = new BindPort(manager, {
|
|
181
|
+
0: {
|
|
182
|
+
targetPort: 8088,
|
|
183
|
+
deviceIdHex: '8a72468957504d50247a260deb0218d504dd091b',
|
|
184
|
+
protocol: 'tcp',
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
bind.addPort(0, 8088, '8a72468957504d50247a260deb0218d504dd091b', 'tcp');
|
|
188
|
+
const server = bind.servers.get(0);
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
await once(server, 'listening');
|
|
192
|
+
const client = net.connect(server.address().port, '127.0.0.1');
|
|
193
|
+
await once(client, 'connect');
|
|
194
|
+
client.destroy();
|
|
195
|
+
await once(client, 'close');
|
|
196
|
+
|
|
197
|
+
opened.resolve(ref);
|
|
198
|
+
await waitFor(() => relay.portCloseCalls.length === 1);
|
|
199
|
+
|
|
200
|
+
assert.equal(relay.portCloseCalls[0].toString('hex'), ref.toString('hex'));
|
|
201
|
+
assert.equal(relay.hasClientSocket(ref), false);
|
|
202
|
+
} finally {
|
|
203
|
+
server.close();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('API bind closes ref when local TCP client disconnects after portopen returns', async () => {
|
|
208
|
+
const calls = [];
|
|
209
|
+
const ref = makeRef('0e0f1011');
|
|
210
|
+
const relay = makeRelay('relay.example:41046', ref, calls);
|
|
211
|
+
const manager = new FakeManager({
|
|
212
|
+
relays: [relay],
|
|
213
|
+
resolvedRelay: relay,
|
|
214
|
+
nearestRelay: relay,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const bind = new BindPort(manager, {
|
|
218
|
+
0: {
|
|
219
|
+
targetPort: 8088,
|
|
220
|
+
deviceIdHex: '8a72468957504d50247a260deb0218d504dd091b',
|
|
221
|
+
protocol: 'tcp',
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
bind.addPort(0, 8088, '8a72468957504d50247a260deb0218d504dd091b', 'tcp');
|
|
225
|
+
const server = bind.servers.get(0);
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
await once(server, 'listening');
|
|
229
|
+
const client = net.connect(server.address().port, '127.0.0.1');
|
|
230
|
+
await once(client, 'connect');
|
|
231
|
+
await waitFor(() => relay.hasClientSocket(ref));
|
|
232
|
+
|
|
233
|
+
client.end();
|
|
234
|
+
await once(client, 'close');
|
|
235
|
+
await waitFor(() => relay.portCloseCalls.length === 1);
|
|
236
|
+
|
|
237
|
+
assert.equal(relay.portCloseCalls[0].toString('hex'), ref.toString('hex'));
|
|
238
|
+
assert.equal(relay.hasClientSocket(ref), false);
|
|
239
|
+
} finally {
|
|
240
|
+
server.close();
|
|
241
|
+
}
|
|
242
|
+
});
|