diodejs 0.4.0 → 0.4.2

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/connection.js CHANGED
@@ -3,7 +3,19 @@ const tls = require('tls');
3
3
  const fs = require('fs');
4
4
  const { RLP } = require('@ethereumjs/rlp');
5
5
  const EventEmitter = require('events');
6
- const { makeReadable, parseRequestId, parseResponseType, parseReason, parseUInt, generateCert, ensureDirectoryExistence, loadOrGenerateKeyPair, toBufferView } = require('./utils');
6
+ const {
7
+ makeReadable,
8
+ parseRequestId,
9
+ parseResponseType,
10
+ parseReason,
11
+ parseUInt,
12
+ generateCert,
13
+ ensureDirectoryExistence,
14
+ loadOrGenerateKeyPair,
15
+ toBufferView,
16
+ DEFAULT_FLEET_CONTRACT,
17
+ normalizeFleetContractAddress,
18
+ } = require('./utils');
7
19
  const { Buffer } = require('buffer'); // Import Buffer
8
20
  const asn1 = require('asn1.js');
9
21
  const secp256k1 = require('secp256k1');
@@ -48,6 +60,8 @@ class DiodeConnection extends EventEmitter {
48
60
  this.certPem = null;
49
61
  this._serverEthAddress = null; // cache after first read
50
62
  this.localAddressProvider = null;
63
+ this.fleetContractHex = DEFAULT_FLEET_CONTRACT;
64
+ this.fleetContract = Buffer.from(DEFAULT_FLEET_CONTRACT.slice(2), 'hex');
51
65
  // Load or generate keypair
52
66
  this.keyPair = loadOrGenerateKeyPair(this.keyLocation);
53
67
 
@@ -283,6 +297,13 @@ class DiodeConnection extends EventEmitter {
283
297
  return this;
284
298
  }
285
299
 
300
+ setFleetContract(fleetContract) {
301
+ const normalizedFleetContract = normalizeFleetContractAddress(fleetContract);
302
+ this.fleetContractHex = normalizedFleetContract;
303
+ this.fleetContract = Buffer.from(normalizedFleetContract.slice(2), 'hex');
304
+ return this;
305
+ }
306
+
286
307
  // Update close method to prevent reconnection when intentionally closing
287
308
  close() {
288
309
  if (this.ticketUpdateTimer) {
@@ -592,7 +613,7 @@ class DiodeConnection extends EventEmitter {
592
613
 
593
614
  async createTicketSignature(serverIdBuffer, totalConnections, totalBytes, localAddress, epoch) {
594
615
  const chainId = 1284;
595
- const fleetContractBuffer = ethUtil.toBuffer('0x6000000000000000000000000000000000000000'); // 20-byte Buffer
616
+ const fleetContractBuffer = this.fleetContract;
596
617
 
597
618
  const localAddressBytes = Buffer.isBuffer(localAddress) || localAddress instanceof Uint8Array
598
619
  ? toBufferView(localAddress)
@@ -637,7 +658,7 @@ class DiodeConnection extends EventEmitter {
637
658
 
638
659
  async createTicketCommand() {
639
660
  const chainId = 1284;
640
- const fleetContract = ethUtil.toBuffer('0x6000000000000000000000000000000000000000')
661
+ const fleetContract = this.fleetContract;
641
662
  let localAddress = '';
642
663
  if (typeof this.localAddressProvider === 'function') {
643
664
  try {
@@ -3,8 +3,13 @@ const { makeReadable } = require('../utils');
3
3
 
4
4
  async function main() {
5
5
  const keyLocation = './db/keys.json';
6
+ const fleetContract = process.env.DIODE_FLEET_CONTRACT;
7
+ const nextFleetContract = process.env.DIODE_NEXT_FLEET_CONTRACT;
6
8
 
7
- const client = new DiodeClientManager({ keyLocation });
9
+ const client = new DiodeClientManager({
10
+ keyLocation,
11
+ ...(fleetContract ? { fleetContract } : {}),
12
+ });
8
13
  await client.connect();
9
14
  const [connection] = client.getConnections();
10
15
  if (!connection) {
@@ -15,6 +20,11 @@ async function main() {
15
20
  try {
16
21
  const address = connection.getEthereumAddress();
17
22
  console.log('Address:', address);
23
+ console.log('Current fleet contract:', connection.fleetContractHex);
24
+ if (nextFleetContract) {
25
+ client.setFleetContract(nextFleetContract);
26
+ console.log('Updated fleet contract for future tickets:', connection.fleetContractHex);
27
+ }
18
28
  const ping = await rpc.ping();
19
29
  console.log('Ping:', ping);
20
30
  const blockPeak = await rpc.getBlockPeak();
@@ -0,0 +1,45 @@
1
+ const { DiodeClientManager } = require('../index');
2
+
3
+ const keyLocation = './db/keys.json';
4
+ const initialFleetContract = process.env.DIODE_FLEET_CONTRACT || '0x1111111111111111111111111111111111111111';
5
+ const nextFleetContract = process.env.DIODE_NEXT_FLEET_CONTRACT || '0x2222222222222222222222222222222222222222';
6
+ const shouldConnect = process.env.DIODE_CONNECT === 'true';
7
+
8
+ async function main() {
9
+ const client = new DiodeClientManager({
10
+ keyLocation,
11
+ fleetContract: initialFleetContract,
12
+ });
13
+
14
+ console.log('Initial manager fleet contract:', client.fleetContract);
15
+
16
+ if (!shouldConnect) {
17
+ client.setFleetContract(nextFleetContract);
18
+ console.log('Updated manager fleet contract:', client.fleetContract);
19
+ console.log('Set DIODE_CONNECT=true to connect and observe the updated contract on active relay connections.');
20
+ return;
21
+ }
22
+
23
+ try {
24
+ await client.connect();
25
+ console.log(`Connected relay count: ${client.getConnections().length}`);
26
+ for (const connection of client.getConnections()) {
27
+ console.log(`Before update ${connection.host}:${connection.port} fleet contract: ${connection.fleetContractHex}`);
28
+ }
29
+
30
+ client.setFleetContract(nextFleetContract);
31
+ console.log('Updated manager fleet contract:', client.fleetContract);
32
+ for (const connection of client.getConnections()) {
33
+ console.log(`After update ${connection.host}:${connection.port} fleet contract: ${connection.fleetContractHex}`);
34
+ }
35
+
36
+ console.log('The new fleet contract will be used on the next ticket generated by each connection.');
37
+ } finally {
38
+ client.close();
39
+ }
40
+ }
41
+
42
+ main().catch((error) => {
43
+ console.error('Fleet contract example failed:', error);
44
+ process.exitCode = 1;
45
+ });
@@ -10,9 +10,10 @@ async function startPublishing() {
10
10
 
11
11
  // Create a PublishPort instance with initial ports
12
12
  const publishPort = new PublishPort(client, {
13
- 3000: { mode: 'public' },
13
+ 3000: { mode: 'public', host: '192.168.1.10' },
14
14
  8080: {
15
15
  mode: 'private',
16
+ host: 'backend.internal',
16
17
  whitelist: ['0xca1e71d8105a598810578fb6042fa8cbc1e7f039'] // Replace with actual addresses
17
18
  }
18
19
  }, keyLocation);
@@ -29,7 +30,7 @@ async function startPublishing() {
29
30
  // After 15 seconds, add multiple ports
30
31
  setTimeout(() => {
31
32
  console.log("Adding multiple ports");
32
- publishPort.addPort(3000,{ mode: 'public' });
33
+ publishPort.addPort(3000,{ mode: 'public', host: '127.0.0.1' });
33
34
  console.log('Updated published ports:', publishPort.getPublishedPorts());
34
35
  }, 15000);
35
36
 
@@ -0,0 +1,89 @@
1
+ const WebSocket = require('ws');
2
+
3
+ function fetchNetworkDirectory(options = {}) {
4
+ const endpoint = typeof options.endpoint === 'string' && options.endpoint.trim()
5
+ ? options.endpoint
6
+ : 'wss://prenet.diode.io:8443/ws';
7
+ const method = typeof options.method === 'string' && options.method.trim()
8
+ ? options.method
9
+ : 'dio_network';
10
+ const timeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs > 0
11
+ ? Math.floor(options.timeoutMs)
12
+ : 1500;
13
+
14
+ return new Promise((resolve, reject) => {
15
+ let settled = false;
16
+ let socket = null;
17
+ let timer = null;
18
+
19
+ const cleanup = () => {
20
+ if (timer) {
21
+ clearTimeout(timer);
22
+ timer = null;
23
+ }
24
+ if (socket) {
25
+ socket.removeAllListeners();
26
+ try {
27
+ socket.close();
28
+ } catch (_) {}
29
+ socket = null;
30
+ }
31
+ };
32
+
33
+ const finish = (fn, value) => {
34
+ if (settled) {
35
+ return;
36
+ }
37
+ settled = true;
38
+ cleanup();
39
+ fn(value);
40
+ };
41
+
42
+ socket = new WebSocket(endpoint, {
43
+ handshakeTimeout: timeoutMs,
44
+ origin: 'https://diode.io',
45
+ });
46
+
47
+ socket.on('open', () => {
48
+ socket.send(JSON.stringify({
49
+ jsonrpc: '2.0',
50
+ id: 1,
51
+ method,
52
+ params: [],
53
+ }));
54
+ });
55
+
56
+ socket.on('message', (raw) => {
57
+ try {
58
+ const parsed = JSON.parse(typeof raw === 'string' ? raw : raw.toString('utf8'));
59
+ if (parsed && parsed.id === 1) {
60
+ if (parsed.error) {
61
+ finish(reject, new Error(parsed.error.message || 'Network discovery request failed'));
62
+ return;
63
+ }
64
+ finish(resolve, Array.isArray(parsed.result) ? parsed.result : []);
65
+ }
66
+ } catch (error) {
67
+ finish(reject, error);
68
+ }
69
+ });
70
+
71
+ socket.on('error', (error) => {
72
+ finish(reject, error);
73
+ });
74
+
75
+ socket.on('close', () => {
76
+ if (!settled) {
77
+ finish(reject, new Error('Network discovery socket closed before a response was received'));
78
+ }
79
+ });
80
+
81
+ timer = setTimeout(() => {
82
+ finish(reject, new Error(`Network discovery timed out after ${timeoutMs}ms`));
83
+ }, timeoutMs);
84
+ });
85
+ }
86
+
87
+ module.exports = {
88
+ fetchNetworkDirectory,
89
+ };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "diodejs",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
7
+ "test": "node --test"
8
8
  },
9
9
  "author": "",
10
10
  "license": "ISC",
@@ -20,7 +20,8 @@
20
20
  "keccak": "^3.0.4",
21
21
  "node-fetch": "^2.7.0",
22
22
  "rlp": "^3.0.0",
23
- "secp256k1": "^5.0.1"
23
+ "secp256k1": "^5.0.1",
24
+ "ws": "^8.19.0"
24
25
  },
25
26
  "engines": {
26
27
  "node": ">=18"
package/publishPort.js CHANGED
@@ -38,6 +38,25 @@ function normalizeDeviceId(raw) {
38
38
  return '';
39
39
  }
40
40
 
41
+ function normalizePublishedPortConfig(config = {}) {
42
+ const hasHost = Object.prototype.hasOwnProperty.call(config, 'host');
43
+ const rawHost = hasHost ? config.host : '127.0.0.1';
44
+ if (typeof rawHost !== 'string') {
45
+ throw new TypeError('PublishPort config.host must be a non-empty string');
46
+ }
47
+
48
+ const host = rawHost.trim();
49
+ if (!host) {
50
+ throw new TypeError('PublishPort config.host must be a non-empty string');
51
+ }
52
+
53
+ return {
54
+ mode: config.mode || 'public',
55
+ whitelist: Array.isArray(config.whitelist) ? config.whitelist : [],
56
+ host,
57
+ };
58
+ }
59
+
41
60
  class DiodeSocket extends Duplex {
42
61
  constructor(ref, rpc) {
43
62
  super({ readableHighWaterMark: 256 * 1024, writableHighWaterMark: 256 * 1024, allowHalfOpen: false });
@@ -93,14 +112,11 @@ class PublishPort extends EventEmitter {
93
112
  const portNum = parseInt(port, 10);
94
113
 
95
114
  // Normalize the configuration
96
- const portConfig = {
97
- mode: config.mode || 'public',
98
- whitelist: Array.isArray(config.whitelist) ? config.whitelist : []
99
- };
115
+ const portConfig = normalizePublishedPortConfig(config);
100
116
 
101
117
  // Add to map
102
118
  this.publishedPorts.set(portNum, portConfig);
103
- logger.info(() => `Added published port ${portNum} with mode: ${portConfig.mode}`);
119
+ logger.info(() => `Added published port ${portNum} with mode: ${portConfig.mode}, host: ${portConfig.host}`);
104
120
 
105
121
  return true;
106
122
  }
@@ -186,6 +202,10 @@ class PublishPort extends EventEmitter {
186
202
  return rpc;
187
203
  }
188
204
 
205
+ _getPublishedPortConfig(port) {
206
+ return this.publishedPorts.get(port);
207
+ }
208
+
189
209
  startListening() {
190
210
  if (this._listening) return this; // idempotent
191
211
  // Listen for unsolicited messages from the connection
@@ -269,7 +289,7 @@ class PublishPort extends EventEmitter {
269
289
  }
270
290
 
271
291
  // Get port configuration and check whitelist if in private mode
272
- const portConfig = this.publishedPorts.get(port);
292
+ const portConfig = this._getPublishedPortConfig(port);
273
293
  if (portConfig.mode === 'private' && Array.isArray(portConfig.whitelist)) {
274
294
  if (!portConfig.whitelist.includes(deviceId)) {
275
295
  logger.warn(() => `Device ${deviceId} is not whitelisted for port ${port}. Rejecting request.`);
@@ -281,15 +301,15 @@ class PublishPort extends EventEmitter {
281
301
 
282
302
  // Handle based on protocol
283
303
  if (protocol === 'tcp') {
284
- this.handleTCPConnection(sessionId, ref, port, deviceId, connection);
304
+ this.handleTCPConnection(sessionId, ref, port, deviceId, portConfig, connection);
285
305
  } else if (protocol === 'tls') {
286
306
  if (isHandshake) {
287
307
  this.handleTLSHandshake(sessionId, ref, port, deviceId, connection);
288
308
  } else {
289
- this.handleTLSConnection(sessionId, ref, port, deviceId, connection);
309
+ this.handleTLSConnection(sessionId, ref, port, deviceId, portConfig, connection);
290
310
  }
291
311
  } else if (protocol === 'udp') {
292
- this.handleUDPConnection(sessionId, ref, port, deviceId, connection);
312
+ this.handleUDPConnection(sessionId, ref, port, deviceId, portConfig, connection);
293
313
  } else {
294
314
  logger.warn(() => `Unsupported protocol: ${protocol}`);
295
315
  rpc.sendError(sessionId, ref, `Unsupported protocol: ${protocol}`);
@@ -449,7 +469,7 @@ class PublishPort extends EventEmitter {
449
469
  }
450
470
 
451
471
  // Get port configuration and check whitelist if in private mode
452
- const portConfig = this.publishedPorts.get(port);
472
+ const portConfig = this._getPublishedPortConfig(port);
453
473
  if (portConfig.mode === 'private' && Array.isArray(portConfig.whitelist)) {
454
474
  if (!portConfig.whitelist.includes(deviceId)) {
455
475
  logger.warn(() => `Device ${deviceId} is not whitelisted for port ${port}. Rejecting request.`);
@@ -473,6 +493,7 @@ class PublishPort extends EventEmitter {
473
493
  const session = {
474
494
  physicalPort,
475
495
  port,
496
+ host: portConfig.host,
476
497
  protocol,
477
498
  deviceId: deviceId.toLowerCase(),
478
499
  connection,
@@ -527,7 +548,7 @@ class PublishPort extends EventEmitter {
527
548
 
528
549
  handleNativeTCPRelay(sessionId, physicalPortRef, session, connection) {
529
550
  const rpc = this._getRpcFor(connection);
530
- const { physicalPort, port, deviceId } = session;
551
+ const { physicalPort, port, host, deviceId } = session;
531
552
  let responded = false;
532
553
  const sendOk = () => {
533
554
  if (responded) return;
@@ -544,7 +565,7 @@ class PublishPort extends EventEmitter {
544
565
  const relaySocket = net.connect({ host: relayHost, port: physicalPort }, () => {
545
566
  relaySocket.setNoDelay(true);
546
567
  });
547
- const localSocket = net.connect({ port }, () => {
568
+ const localSocket = net.connect({ port, host }, () => {
548
569
  localSocket.setNoDelay(true);
549
570
  });
550
571
  localSocket.pause();
@@ -616,7 +637,7 @@ class PublishPort extends EventEmitter {
616
637
 
617
638
  handleNativeUDPRelay(sessionId, physicalPortRef, session, connection) {
618
639
  const rpc = this._getRpcFor(connection);
619
- const { physicalPort, port, deviceId } = session;
640
+ const { physicalPort, port, host, deviceId } = session;
620
641
  let responded = false;
621
642
  const sendOk = () => {
622
643
  if (responded) return;
@@ -672,7 +693,7 @@ class PublishPort extends EventEmitter {
672
693
  relayReady = true;
673
694
  maybeReady();
674
695
  });
675
- localSocket.connect(port, '127.0.0.1', () => {
696
+ localSocket.connect(port, host, () => {
676
697
  localReady = true;
677
698
  maybeReady();
678
699
  });
@@ -706,12 +727,12 @@ class PublishPort extends EventEmitter {
706
727
  }
707
728
  }
708
729
 
709
- handleTCPConnection(sessionId, ref, port, deviceId, connection) {
730
+ handleTCPConnection(sessionId, ref, port, deviceId, portConfig, connection) {
710
731
  const rpc = this._getRpcFor(connection);
711
732
  // Create a TCP connection to the local service on the specified port
712
- const localSocket = net.connect({ port: port }, () => {
733
+ const localSocket = net.connect({ port, host: portConfig.host }, () => {
713
734
  localSocket.setNoDelay(true);
714
- logger.info(() => `Connected to local TCP service on port ${port}`);
735
+ logger.info(() => `Connected to local TCP service on ${portConfig.host}:${port}`);
715
736
  // Send success response
716
737
  rpc.sendResponse(sessionId, ref, 'ok');
717
738
  });
@@ -720,10 +741,10 @@ class PublishPort extends EventEmitter {
720
741
  this.setupLocalSocketHandlers(localSocket, ref, 'tcp', rpc, connection);
721
742
 
722
743
  // Store the local socket with the ref using connection's method
723
- connection.addConnection(ref, { socket: localSocket, protocol: 'tcp', port, deviceId });
744
+ connection.addConnection(ref, { socket: localSocket, protocol: 'tcp', port, host: portConfig.host, deviceId });
724
745
  }
725
746
 
726
- handleTLSConnection(sessionId, ref, port, deviceId, connection) {
747
+ handleTLSConnection(sessionId, ref, port, deviceId, portConfig, connection) {
727
748
  const rpc = this._getRpcFor(connection);
728
749
  // Create a DiodeSocket instance
729
750
  const diodeSocket = new DiodeSocket(ref, rpc);
@@ -748,8 +769,8 @@ class PublishPort extends EventEmitter {
748
769
  });
749
770
  tlsSocket.setNoDelay(true);
750
771
  // Connect to the local service (TCP or TLS as needed)
751
- const localSocket = net.connect({ port: port }, () => {
752
- logger.info(() => `Connected to local TCP service on port ${port}`);
772
+ const localSocket = net.connect({ port, host: portConfig.host }, () => {
773
+ logger.info(() => `Connected to local TCP service on ${portConfig.host}:${port}`);
753
774
  // Send success response
754
775
  rpc.sendResponse(sessionId, ref, 'ok');
755
776
  });
@@ -775,11 +796,12 @@ class PublishPort extends EventEmitter {
775
796
  localSocket,
776
797
  protocol: 'tls',
777
798
  port,
799
+ host: portConfig.host,
778
800
  deviceId,
779
801
  });
780
802
  }
781
803
 
782
- handleUDPConnection(sessionId, ref, port, deviceId, connection) {
804
+ handleUDPConnection(sessionId, ref, port, deviceId, portConfig, connection) {
783
805
  const rpc = this._getRpcFor(connection);
784
806
  // Create a UDP socket
785
807
  const localSocket = dgram.createSocket('udp4');
@@ -795,7 +817,7 @@ class PublishPort extends EventEmitter {
795
817
  });
796
818
 
797
819
  // Store the remote address and port from the Diode client
798
- const remoteInfo = {port, address: '127.0.0.1'};
820
+ const remoteInfo = { port, address: portConfig.host };
799
821
 
800
822
  // Send success response
801
823
  rpc.sendResponse(sessionId, ref, 'ok');
@@ -806,10 +828,11 @@ class PublishPort extends EventEmitter {
806
828
  protocol: 'udp',
807
829
  remoteInfo,
808
830
  port,
831
+ host: portConfig.host,
809
832
  deviceId
810
833
  });
811
834
 
812
- logger.info(() => `UDP connection set up on port ${port}`);
835
+ logger.info(() => `UDP connection set up for ${portConfig.host}:${port}`);
813
836
 
814
837
  // Handle messages from the local UDP service
815
838
  localSocket.on('message', (msg, rinfo) => {
@@ -835,7 +858,7 @@ class PublishPort extends EventEmitter {
835
858
  const connectionInfo = connection.getConnection(ref);
836
859
  // Check if the port is still open and address is still in whitelist
837
860
  if (connectionInfo) {
838
- const { socket: localSocket, protocol, remoteInfo, port, deviceId } = connectionInfo;
861
+ const { socket: localSocket, protocol, remoteInfo, port, host, deviceId } = connectionInfo;
839
862
 
840
863
  if (!this.publishedPorts.has(port)) {
841
864
  logger.warn(() => `Port ${port} is not published. Sending portclose.`);
@@ -844,7 +867,7 @@ class PublishPort extends EventEmitter {
844
867
  return;
845
868
  }
846
869
 
847
- const portConfig = this.publishedPorts.get(port);
870
+ const portConfig = this._getPublishedPortConfig(port);
848
871
  if (portConfig.mode === 'private' && Array.isArray(portConfig.whitelist)) {
849
872
  if (!portConfig.whitelist.includes(deviceId)) {
850
873
  logger.warn(() => `Device ${deviceId} is not whitelisted for port ${port}. Sending portclose.`);
@@ -865,7 +888,7 @@ class PublishPort extends EventEmitter {
865
888
 
866
889
  // Update remoteInfo if not set
867
890
  if (!localSocket.remoteAddress) {
868
- localSocket.remoteAddress = '127.0.0.1'; // Assuming local service is on localhost
891
+ localSocket.remoteAddress = host || (remoteInfo && remoteInfo.address) || portConfig.host;
869
892
  localSocket.remotePort = port;
870
893
  }
871
894
  } else if (protocol === 'tcp') {