diodejs 0.1.6 → 0.2.1

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/README.md CHANGED
@@ -25,6 +25,8 @@ Connection retry behavior can be configured via environment variables:
25
25
  | DIODE_RETRY_DELAY | Initial delay between retries (ms) | 1000 |
26
26
  | DIODE_MAX_RETRY_DELAY | Maximum delay between retries (ms) | 30000 |
27
27
  | DIODE_AUTO_RECONNECT | Whether to automatically reconnect | true |
28
+ | DIODE_TICKET_BYTES_THRESHOLD | Bytes threshold for ticket updates | 512000 (512KB) |
29
+ | DIODE_TICKET_UPDATE_INTERVAL | Time interval for ticket updates (ms) | 30000 (30s) |
28
30
 
29
31
  Example `.env` file:
30
32
  ```
@@ -32,6 +34,8 @@ DIODE_MAX_RETRIES=10
32
34
  DIODE_RETRY_DELAY=2000
33
35
  DIODE_MAX_RETRY_DELAY=20000
34
36
  DIODE_AUTO_RECONNECT=true
37
+ DIODE_TICKET_BYTES_THRESHOLD=512000
38
+ DIODE_TICKET_UPDATE_INTERVAL=30000
35
39
  ```
36
40
 
37
41
  These settings can also be configured programmatically:
@@ -40,7 +44,9 @@ connection.setReconnectOptions({
40
44
  maxRetries: 10,
41
45
  retryDelay: 2000,
42
46
  maxRetryDelay: 20000,
43
- autoReconnect: true
47
+ autoReconnect: true,
48
+ ticketBytesThreshold: 512000,
49
+ ticketUpdateInterval: 30000
44
50
  });
45
51
  ```
46
52
 
@@ -63,7 +69,9 @@ async function main() {
63
69
  maxRetries: Infinity, // Unlimited reconnection attempts
64
70
  retryDelay: 1000, // Initial delay of 1 second
65
71
  maxRetryDelay: 30000, // Maximum delay of 30 seconds
66
- autoReconnect: true // Automatically reconnect on disconnection
72
+ autoReconnect: true, // Automatically reconnect on disconnection
73
+ ticketBytesThreshold: 512000, // Bytes threshold for ticket updates
74
+ ticketUpdateInterval: 30000 // Time interval for ticket updates
67
75
  });
68
76
 
69
77
  // Listen for reconnection events (optional)
@@ -117,15 +125,23 @@ async function main() {
117
125
 
118
126
  // Multiple or single port binding with configuration object
119
127
  const portsConfig = {
120
- 3002: { targetPort: 80, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" },
121
- 3003: { targetPort: 443, deviceIdHex: "0x5365baf29cb7ab58de588dfc448913cb609283e2" } // Works with or without 0x prefix
128
+ 3002: {
129
+ targetPort: 80,
130
+ deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2",
131
+ protocol: "tls" // Optional - defaults to TLS if not specified
132
+ },
133
+ 3003: {
134
+ targetPort: 443,
135
+ deviceIdHex: "0x5365baf29cb7ab58de588dfc448913cb609283e2",
136
+ protocol: "tcp" // Can be "tls", "tcp", or "udp"
137
+ }
122
138
  };
123
139
 
124
140
  const portForward = new BindPort(connection, portsConfig);
125
141
  portForward.bind();
126
142
 
127
- // You can also dynamically add and remove ports
128
- portForward.addPort(3004, 8080, "5365baf29cb7ab58de588dfc448913cb609283e2");
143
+ // You can also dynamically add ports with protocol specification
144
+ portForward.addPort(3004, 8080, "5365baf29cb7ab58de588dfc448913cb609283e2", "udp");
129
145
  portForward.removePort(3003);
130
146
  }
131
147
 
@@ -144,7 +160,7 @@ async function main() {
144
160
  const connection = new DiodeConnection(host, port, keyLocation);
145
161
  await connection.connect();
146
162
 
147
- // Legacy method - single port binding
163
+ // Legacy method - single port binding (defaults to TLS protocol)
148
164
  const portForward = new BindPort(connection, 3002, 80, "5365baf29cb7ab58de588dfc448913cb609283e2");
149
165
  portForward.bind();
150
166
  }
@@ -211,6 +227,8 @@ main();
211
227
  - `retryDelay` (number): Initial delay between retries in ms (default: 1000)
212
228
  - `maxRetryDelay` (number): Maximum delay between retries in ms (default: 30000)
213
229
  - `autoReconnect` (boolean): Whether to automatically reconnect on disconnection (default: true)
230
+ - `ticketBytesThreshold` (number): Bytes threshold for ticket updates (default: 512000)
231
+ - `ticketUpdateInterval` (number): Time interval for ticket updates in ms (default: 30000)
214
232
 
215
233
  - **Events**:
216
234
  - `reconnecting`: Emitted when a reconnection attempt is about to start, with `attempt` and `delay` information
@@ -249,13 +267,15 @@ main();
249
267
  New Constructor:
250
268
  - `new BindPort(connection, portsConfig)`
251
269
  - `connection` (DiodeConnection): An instance of `DiodeConnection`.
252
- - `portsConfig` (object): A configuration object where keys are local ports and values are objects with `targetPort` and `deviceIdHex`.
253
- Example: `{ 3002: { targetPort: 80, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" } }`
254
- Note: deviceIdHex can be provided with or without the '0x' prefix.
270
+ - `portsConfig` (object): A configuration object where keys are local ports and values are objects with:
271
+ - `targetPort` (number): The target port on the device.
272
+ - `deviceIdHex` (string): The device ID in hexadecimal format (with or without '0x' prefix).
273
+ - `protocol` (string, optional): The protocol to use ("tls", "tcp", or "udp"). Defaults to "tls".
255
274
 
256
275
  - **Methods**:
257
276
  - `bind()`: Binds all configured local ports to their target ports on the devices.
258
- - `addPort(localPort, targetPort, deviceIdHex)`: Adds a new port binding configuration. deviceIdHex can include '0x' prefix.
277
+ - `addPort(localPort, targetPort, deviceIdHex, protocol)`: Adds a new port binding configuration.
278
+ - `protocol` (string, optional): The protocol to use. Can be "tls", "tcp", or "udp". Defaults to "tls".
259
279
  - `removePort(localPort)`: Removes a port binding configuration.
260
280
  - `bindSinglePort(localPort)`: Binds a single local port to its target.
261
281
  - `closeAllServers()`: Closes all active server instances.
package/bindPort.js CHANGED
@@ -1,8 +1,45 @@
1
1
  const net = require('net');
2
+ const tls = require('tls');
3
+ const dgram = require('dgram');
2
4
  const { Buffer } = require('buffer');
5
+ const { Duplex } = require('stream');
3
6
  const DiodeRPC = require('./rpc');
4
7
  const logger = require('./logger');
5
8
 
9
+ // Custom Duplex stream to handle the Diode connection
10
+ class DiodeSocket extends Duplex {
11
+ constructor(ref, rpc) {
12
+ super();
13
+ this.ref = ref;
14
+ this.rpc = rpc;
15
+ this.destroyed = false;
16
+ }
17
+
18
+ _write(chunk, encoding, callback) {
19
+ // Send data to the remote device via portSend
20
+ this.rpc.portSend(this.ref, chunk)
21
+ .then(() => callback())
22
+ .catch((err) => callback(err));
23
+ }
24
+
25
+ _read(size) {
26
+ // No need to implement this method for our use case
27
+ }
28
+
29
+ // Method to push data received from the remote device
30
+ pushData(data) {
31
+ if (!this.destroyed) {
32
+ this.push(data);
33
+ }
34
+ }
35
+
36
+ _destroy(err, callback) {
37
+ this.destroyed = true;
38
+ this.push(null);
39
+ callback(err);
40
+ }
41
+ }
42
+
6
43
  class BindPort {
7
44
  constructor(connection, localPortOrPortsConfig, targetPort, deviceIdHex) {
8
45
  this.connection = connection;
@@ -12,7 +49,8 @@ class BindPort {
12
49
  this.portsConfig = {
13
50
  [localPortOrPortsConfig]: {
14
51
  targetPort,
15
- deviceIdHex: this._stripHexPrefix(deviceIdHex)
52
+ deviceIdHex: this._stripHexPrefix(deviceIdHex),
53
+ protocol: 'tls' // Default protocol is tls
16
54
  }
17
55
  };
18
56
  } else {
@@ -20,10 +58,17 @@ class BindPort {
20
58
  this.portsConfig = localPortOrPortsConfig || {};
21
59
 
22
60
  // Strip 0x prefix from all deviceIdHex values in portsConfig
61
+ // And ensure protocol is specified (default to tls)
23
62
  for (const port in this.portsConfig) {
24
63
  if (this.portsConfig[port].deviceIdHex) {
25
64
  this.portsConfig[port].deviceIdHex = this._stripHexPrefix(this.portsConfig[port].deviceIdHex);
26
65
  }
66
+ // Set default protocol if not provided
67
+ if (!this.portsConfig[port].protocol) {
68
+ this.portsConfig[port].protocol = 'tls';
69
+ }
70
+ // Ensure protocol is uppercase
71
+ this.portsConfig[port].protocol = this.portsConfig[port].protocol.toLowerCase();
27
72
  }
28
73
  }
29
74
 
@@ -45,7 +90,6 @@ class BindPort {
45
90
  _setupMessageListener() {
46
91
  // Listen for data events from the device
47
92
  this.connection.on('unsolicited', (message) => {
48
- // message is [messageId, [messageType, ...]]
49
93
  const [messageIdRaw, messageContent] = message;
50
94
  const messageTypeRaw = messageContent[0];
51
95
  const messageType = Buffer.from(messageTypeRaw).toString('utf8');
@@ -60,7 +104,13 @@ class BindPort {
60
104
  // Find the associated client socket from connection
61
105
  const clientSocket = this.connection.getClientSocket(dataRef);
62
106
  if (clientSocket) {
63
- clientSocket.write(data);
107
+ if (clientSocket.diodeSocket) {
108
+ // If it's a DiodeSocket, push data to it so tls can process
109
+ clientSocket.diodeSocket.pushData(data);
110
+ } else {
111
+ // Otherwise write directly to the socket
112
+ clientSocket.write(data);
113
+ }
64
114
  } else {
65
115
  const connectionInfo = this.connection.getConnection(dataRef);
66
116
  if (connectionInfo) {
@@ -76,6 +126,9 @@ class BindPort {
76
126
  // Close the associated client socket
77
127
  const clientSocket = this.connection.getClientSocket(dataRef);
78
128
  if (clientSocket) {
129
+ if (clientSocket.diodeSocket) {
130
+ clientSocket.diodeSocket._destroy(null, () => {});
131
+ }
79
132
  clientSocket.end();
80
133
  this.connection.deleteClientSocket(dataRef);
81
134
  logger.info(`Port closed for ref: ${dataRef.toString('hex')}`);
@@ -100,7 +153,7 @@ class BindPort {
100
153
  });
101
154
  }
102
155
 
103
- addPort(localPort, targetPort, deviceIdHex) {
156
+ addPort(localPort, targetPort, deviceIdHex, protocol = 'tls') {
104
157
  if (this.servers.has(localPort)) {
105
158
  logger.warn(`Port ${localPort} is already bound`);
106
159
  return false;
@@ -108,7 +161,8 @@ class BindPort {
108
161
 
109
162
  this.portsConfig[localPort] = {
110
163
  targetPort,
111
- deviceIdHex: this._stripHexPrefix(deviceIdHex)
164
+ deviceIdHex: this._stripHexPrefix(deviceIdHex),
165
+ protocol: protocol.toLowerCase()
112
166
  };
113
167
 
114
168
  this.bindSinglePort(localPort);
@@ -151,70 +205,191 @@ class BindPort {
151
205
  return false;
152
206
  }
153
207
 
154
- const { targetPort, deviceIdHex } = config;
208
+ const { targetPort, deviceIdHex, protocol = 'tls' } = config;
155
209
  const deviceId = Buffer.from(deviceIdHex, 'hex');
156
210
 
157
- // Set up local server
158
- const server = net.createServer(async (clientSocket) => {
159
- logger.info(`Client connected to local server on port ${localPort}`);
160
-
161
- // Open a new port on the device for this client
162
- let ref;
163
- try {
164
- ref = await this.rpc.portOpen(deviceId, targetPort, 'rw');
211
+ // Format the target port with protocol prefix for the remote connection
212
+ const formattedTargetPort = `${protocol}:${targetPort}`;
213
+ logger.info(`Binding local port ${localPort} to remote ${formattedTargetPort}`);
214
+
215
+ // For udp protocol, use udp server
216
+ if (protocol === 'udp') {
217
+ const server = dgram.createSocket('udp4');
218
+
219
+ server.on('listening', () => {
220
+ logger.info(`udp server listening on port ${localPort} forwarding to device port ${targetPort}`);
221
+ });
222
+
223
+ server.on('message', async (data, rinfo) => {
224
+ // Open a new port on the device if this is a new client
225
+ const clientKey = `${rinfo.address}:${rinfo.port}`;
226
+ let ref = server.clientRefs && server.clientRefs[clientKey];
227
+
165
228
  if (!ref) {
166
- logger.error(`Error opening port ${targetPort} on deviceId: ${deviceIdHex}`);
167
- clientSocket.destroy();
168
- return;
169
- } else {
170
- logger.info(`Port ${targetPort} opened on device with ref: ${ref.toString('hex')} for client`);
229
+ try {
230
+ ref = await this.rpc.portOpen(deviceId, formattedTargetPort, 'rw');
231
+ if (!ref) {
232
+ logger.error(`Error opening port ${formattedTargetPort} on deviceId: ${deviceIdHex}`);
233
+ return;
234
+ } else {
235
+ logger.info(`Port ${formattedTargetPort} opened on device with ref: ${ref.toString('hex')} for udp client ${clientKey}`);
236
+ if (!server.clientRefs) server.clientRefs = {};
237
+ server.clientRefs[clientKey] = ref;
238
+
239
+ // Store the client info
240
+ this.connection.addClientSocket(ref, {
241
+ address: rinfo.address,
242
+ port: rinfo.port,
243
+ protocol: 'udp',
244
+ write: (data) => {
245
+ server.send(data, rinfo.port, rinfo.address);
246
+ }
247
+ });
248
+ }
249
+ } catch (error) {
250
+ logger.error(`Error opening port ${formattedTargetPort} on device: ${error}`);
251
+ return;
252
+ }
171
253
  }
172
- } catch (error) {
173
- logger.error(`Error opening port ${targetPort} on device: ${error}`);
174
- clientSocket.destroy();
175
- return;
176
- }
177
-
178
- // Store the client socket with the ref using connection's method
179
- this.connection.addClientSocket(ref, clientSocket);
180
-
181
- // When data is received from the client, send it to the device
182
- clientSocket.on('data', async (data) => {
254
+
255
+ // Send data to the device
183
256
  try {
184
257
  await this.rpc.portSend(ref, data);
185
258
  } catch (error) {
186
- logger.error(`Error sending data to device: ${error}`);
187
- clientSocket.destroy();
259
+ logger.error(`Error sending udp data to device: ${error}`);
188
260
  }
189
261
  });
262
+
263
+ server.on('error', (err) => {
264
+ logger.error(`udp Server error: ${err}`);
265
+ });
266
+
267
+ server.bind(localPort);
268
+ this.servers.set(parseInt(localPort), server);
269
+ } else {
270
+ // For TCP and tls protocols, use TCP server locally
271
+ const server = net.createServer(async (clientSocket) => {
272
+ logger.info(`Client connected to local server on port ${localPort}`);
190
273
 
191
- // Handle client socket closure
192
- clientSocket.on('end', async () => {
193
- logger.info('Client disconnected');
194
- if (ref && this.connection.hasClientSocket(ref)) {
274
+ // Open a new port on the device for this client
275
+ let ref;
276
+ try {
277
+ ref = await this.rpc.portOpen(deviceId, formattedTargetPort, 'rw');
278
+ if (!ref) {
279
+ logger.error(`Error opening port ${formattedTargetPort} on deviceId: ${deviceIdHex}`);
280
+ clientSocket.destroy();
281
+ return;
282
+ } else {
283
+ logger.info(`Port ${formattedTargetPort} opened on device with ref: ${ref.toString('hex')} for client`);
284
+ }
285
+ } catch (error) {
286
+ logger.error(`Error opening port ${formattedTargetPort} on device: ${error}`);
287
+ clientSocket.destroy();
288
+ return;
289
+ }
290
+
291
+ if (protocol === 'tls') {
292
+ // For tls protocol, create a proper tls connection
195
293
  try {
196
- await this.rpc.portClose(ref);
197
- logger.info(`Port closed on device for ref: ${ref.toString('hex')}`);
198
- this.connection.deleteClientSocket(ref);
294
+ // Create a DiodeSocket to handle communication with the device
295
+ const diodeSocket = new DiodeSocket(ref, this.rpc);
296
+
297
+ // Get the device certificate for tls
298
+ const certPem = this.connection.getDeviceCertificate();
299
+ if (!certPem) {
300
+ throw new Error('No device certificate available');
301
+ }
302
+
303
+ // Setup tls options for client mode
304
+ const tlsOptions = {
305
+ cert: certPem,
306
+ key: certPem,
307
+ rejectUnauthorized: false,
308
+ ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
309
+ ecdhCurve: 'secp256k1',
310
+ minVersion: 'TLSv1.2',
311
+ maxVersion: 'TLSv1.2',
312
+ };
313
+
314
+ // Create tls socket as client (not server)
315
+ const tlsSocket = tls.connect({
316
+ socket: diodeSocket,
317
+ ...tlsOptions
318
+ }, () => {
319
+ logger.info(`tls connection established to device ${deviceIdHex}`);
320
+ });
321
+
322
+ // Pipe data between the client socket and the tls socket
323
+ tlsSocket.pipe(clientSocket).pipe(tlsSocket);
324
+
325
+ // Handle tls socket errors
326
+ tlsSocket.on('error', (err) => {
327
+ logger.error(`tls Socket error: ${err}`);
328
+ clientSocket.destroy();
329
+ });
330
+
331
+ // Store reference to the diodeSocket so we can push data to it
332
+ const socketWrapper = {
333
+ diodeSocket,
334
+ tlsSocket,
335
+ end: () => {
336
+ tlsSocket.end();
337
+ diodeSocket._destroy(null, () => {});
338
+ }
339
+ };
340
+
341
+ // Store the socket wrapper
342
+ this.connection.addClientSocket(ref, socketWrapper);
343
+
199
344
  } catch (error) {
200
- logger.error(`Error closing port on device: ${error}`);
345
+ logger.error(`Error setting up tls connection: ${error}`);
346
+ clientSocket.destroy();
347
+ return;
201
348
  }
202
349
  } else {
203
- logger.warn('Ref is invalid or no longer in clientSockets.');
350
+ // For TCP protocol, just use the raw socket
351
+ this.connection.addClientSocket(ref, clientSocket);
352
+
353
+ // Handle data from client to device
354
+ clientSocket.on('data', async (data) => {
355
+ try {
356
+ await this.rpc.portSend(ref, data);
357
+ } catch (error) {
358
+ logger.error(`Error sending data to device: ${error}`);
359
+ clientSocket.destroy();
360
+ }
361
+ });
204
362
  }
205
- });
206
363
 
207
- // Handle client socket errors
208
- clientSocket.on('error', (err) => {
209
- logger.error('Client socket error:', err);
364
+ // Handle client socket closure (common for all protocols)
365
+ clientSocket.on('end', async () => {
366
+ logger.info('Client disconnected');
367
+ if (ref && this.connection.hasClientSocket(ref)) {
368
+ try {
369
+ await this.rpc.portClose(ref);
370
+ logger.info(`Port closed on device for ref: ${ref.toString('hex')}`);
371
+ this.connection.deleteClientSocket(ref);
372
+ } catch (error) {
373
+ logger.error(`Error closing port on device: ${error}`);
374
+ }
375
+ } else {
376
+ logger.warn('Ref is invalid or no longer in clientSockets.');
377
+ }
378
+ });
379
+
380
+ // Handle client socket errors
381
+ clientSocket.on('error', (err) => {
382
+ logger.error(`Client socket error: ${err}`);
383
+ });
210
384
  });
211
- });
212
385
 
213
- server.listen(localPort, () => {
214
- logger.info(`Local server listening on port ${localPort} forwarding to device port ${targetPort}`);
215
- });
386
+ server.listen(localPort, () => {
387
+ logger.info(`Local server listening on port ${localPort} forwarding to device ${protocol} port ${targetPort}`);
388
+ });
389
+
390
+ this.servers.set(parseInt(localPort), server);
391
+ }
216
392
 
217
- this.servers.set(parseInt(localPort), server);
218
393
  return true;
219
394
  }
220
395
 
package/connection.js CHANGED
@@ -62,6 +62,16 @@ class DiodeConnection extends EventEmitter {
62
62
  logger.info(`Connection settings - Auto Reconnect: ${this.autoReconnect}, Max Retries: ${
63
63
  this.maxRetries === Infinity ? 'Infinity' : this.maxRetries
64
64
  }, Retry Delay: ${this.retryDelay}ms, Max Retry Delay: ${this.maxRetryDelay}ms`);
65
+
66
+ // Add ticket batching configuration
67
+ this.lastTicketUpdate = Date.now();
68
+ this.accumulatedBytes = 0;
69
+ this.ticketUpdateThreshold = parseInt(process.env.DIODE_TICKET_BYTES_THRESHOLD, 10) || 512000; // 512KB default
70
+ this.ticketUpdateInterval = parseInt(process.env.DIODE_TICKET_UPDATE_INTERVAL, 10) || 30000; // 30 seconds default
71
+ this.ticketUpdateTimer = null;
72
+
73
+ // Log the ticket batching settings
74
+ logger.info(`Ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
65
75
  }
66
76
 
67
77
  connect() {
@@ -96,7 +106,6 @@ class DiodeConnection extends EventEmitter {
96
106
  try {
97
107
  const ticketCommand = await this.createTicketCommand();
98
108
  const response = await this.sendCommand(ticketCommand).catch(reject);
99
- logger.info(`Ticket accepted: ${makeReadable(response)}`);
100
109
  resolve();
101
110
  } catch (error) {
102
111
  logger.error(`Error sending ticket: ${error}`);
@@ -113,6 +122,11 @@ class DiodeConnection extends EventEmitter {
113
122
  }
114
123
  });
115
124
 
125
+ // Start the periodic ticket update timer after successful connection
126
+ this.socket.on('connect', () => {
127
+ this._startTicketUpdateTimer();
128
+ });
129
+
116
130
  this.socket.on('error', (err) => {
117
131
  logger.error(`Connection error: ${err}`);
118
132
  reject(err);
@@ -242,6 +256,11 @@ class DiodeConnection extends EventEmitter {
242
256
 
243
257
  // Update close method to prevent reconnection when intentionally closing
244
258
  close() {
259
+ if (this.ticketUpdateTimer) {
260
+ clearTimeout(this.ticketUpdateTimer);
261
+ this.ticketUpdateTimer = null;
262
+ }
263
+
245
264
  this.autoReconnect = false;
246
265
  if (this.retryTimeoutId) {
247
266
  clearTimeout(this.retryTimeoutId);
@@ -431,7 +450,6 @@ class DiodeConnection extends EventEmitter {
431
450
  const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
432
451
  const address = '0x' + addressBuffer.toString('hex');
433
452
 
434
- logger.info(`Ethereum address: ${address}`);
435
453
  return address;
436
454
  } catch (error) {
437
455
  logger.error(`Error extracting Ethereum address: ${error}`);
@@ -455,7 +473,6 @@ class DiodeConnection extends EventEmitter {
455
473
  const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
456
474
  const address = '0x' + addressBuffer.toString('hex');
457
475
 
458
- logger.info(`Server Ethereum address: ${address}`);
459
476
  return address;
460
477
  } catch (error) {
461
478
  logger.error(`Error extracting server Ethereum address: ${error}`);
@@ -605,6 +622,79 @@ class DiodeConnection extends EventEmitter {
605
622
  hasConnection(ref) {
606
623
  return this.connections.has(ref.toString('hex'));
607
624
  }
625
+
626
+ // Start timer for periodic ticket updates
627
+ _startTicketUpdateTimer() {
628
+ if (this.ticketUpdateTimer) {
629
+ clearTimeout(this.ticketUpdateTimer);
630
+ }
631
+
632
+ this.ticketUpdateTimer = setTimeout(() => {
633
+ this._updateTicketIfNeeded(true);
634
+ }, this.ticketUpdateInterval);
635
+ }
636
+
637
+ // Method to check if ticket update is needed and perform it
638
+ async _updateTicketIfNeeded(force = false) {
639
+ // If socket is not connected, don't try to update
640
+ if (!this.socket || this.socket.destroyed) {
641
+ return;
642
+ }
643
+
644
+ const timeSinceLastUpdate = Date.now() - this.lastTicketUpdate;
645
+
646
+ if (force ||
647
+ this.accumulatedBytes >= this.ticketUpdateThreshold ||
648
+ timeSinceLastUpdate >= this.ticketUpdateInterval) {
649
+
650
+ try {
651
+ if (this.accumulatedBytes > 0 || force) {
652
+ logger.debug(`Updating ticket: accumulated ${this.accumulatedBytes} bytes, ${timeSinceLastUpdate}ms since last update`);
653
+ const ticketCommand = await this.createTicketCommand();
654
+ await this.sendCommand(ticketCommand);
655
+
656
+ // Reset counters
657
+ this.accumulatedBytes = 0;
658
+ this.lastTicketUpdate = Date.now();
659
+ }
660
+ } catch (error) {
661
+ logger.error(`Error updating ticket: ${error}`);
662
+ }
663
+ }
664
+
665
+ // Restart the timer
666
+ this._startTicketUpdateTimer();
667
+ }
668
+
669
+ // Add method to track bytes without immediate ticket update
670
+ addBytes(bytesCount) {
671
+ this.totalBytes += bytesCount;
672
+ this.accumulatedBytes += bytesCount;
673
+
674
+ // Optionally check if we should update ticket now
675
+ if (this.accumulatedBytes >= this.ticketUpdateThreshold) {
676
+ this._updateTicketIfNeeded();
677
+ }
678
+ }
679
+
680
+ // Method to set ticket batching options
681
+ setTicketBatchingOptions(options = {}) {
682
+ if (typeof options.threshold === 'number') {
683
+ this.ticketUpdateThreshold = options.threshold;
684
+ }
685
+ if (typeof options.interval === 'number') {
686
+ this.ticketUpdateInterval = options.interval;
687
+ }
688
+
689
+ logger.info(`Updated ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
690
+
691
+ // Reset the timer with new interval
692
+ if (this.socket && !this.socket.destroyed) {
693
+ this._startTicketUpdateTimer();
694
+ }
695
+
696
+ return this;
697
+ }
608
698
  }
609
699
 
610
700
 
@@ -9,8 +9,8 @@ async function main() {
9
9
  await connection.connect();
10
10
 
11
11
  const portForward = new BindPort(connection, {
12
- 3003: { targetPort: 8080, deviceIdHex: "0xca1e71d8105a598810578fb6042fa8cbc1e7f039" },
13
- 3004: { targetPort: 443, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" }
12
+ 3003: { targetPort: 8080, deviceIdHex: "0xca1e71d8105a598810578fb6042fa8cbc1e7f039", protocol: "tcp" },
13
+ 3004: { targetPort: 8081, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2", protocol: "tls" }
14
14
  });
15
15
  portForward.bind();
16
16
 
@@ -23,7 +23,7 @@ async function main() {
23
23
  // after 10 seconds, add port 3003 back
24
24
  setTimeout(() => {
25
25
  console.log("Adding port 3003 back");
26
- portForward.addPort(3003, 8080, "0xca1e71d8105a598810578fb6042fa8cbc1e7f039");
26
+ portForward.addPort(3003, 8080, "0xca1e71d8105a598810578fb6042fa8cbc1e7f039", "tcp");
27
27
  }, 10000);
28
28
 
29
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diodejs",
3
- "version": "0.1.6",
3
+ "version": "0.2.1",
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/publishPort.js CHANGED
@@ -254,7 +254,7 @@ class PublishPort extends EventEmitter {
254
254
  // Create a DiodeSocket instance
255
255
  const diodeSocket = new DiodeSocket(ref, this.rpc);
256
256
 
257
- certPem = this.connection.getDeviceCertificate();
257
+ const certPem = this.connection.getDeviceCertificate();
258
258
 
259
259
  // TLS options with your server's certificate and key
260
260
  const tlsOptions = {
package/rpc.js CHANGED
@@ -98,9 +98,9 @@ class DiodeRPC {
98
98
  }
99
99
 
100
100
  async portSend(ref, data) {
101
- // Update totalBytes
101
+ // Update bytes count but don't update ticket yet
102
102
  const bytesToSend = data.length;
103
- this.connection.totalBytes += bytesToSend;
103
+ this.connection.addBytes(bytesToSend);
104
104
 
105
105
  // Now send the data
106
106
  return this.connection.sendCommand(['portsend', ref, data]).then(async (responseData) => {
@@ -109,17 +109,7 @@ class DiodeRPC {
109
109
  const status = parseResponseType(statusRaw);
110
110
 
111
111
  if (status === 'ok') {
112
- try {
113
- const ticketCommand = await this.connection.createTicketCommand();
114
- const ticketResponse = await this.connection.sendCommand(ticketCommand).catch((error) => {
115
- logger.error(`Error during ticket command: ${error}`);
116
- throw error;
117
- });
118
- logger.debug(`Ticket updated: ${makeReadable(ticketResponse)}`);
119
- } catch (error) {
120
- logger.error(`Error updating ticket: ${error}`);
121
- throw error;
122
- }
112
+ // No ticket update here anymore - handled by batching mechanism
123
113
  return;
124
114
  } else if (status === 'error') {
125
115
  throw new Error('Error during port send');
@@ -192,17 +182,40 @@ class DiodeRPC {
192
182
  return epoch;
193
183
  }
194
184
 
195
- parseTimestamp(blockHeader) {
196
- // Implement parsing of timestamp from blockHeader
197
- const timestampRaw = blockHeader[0][1]; // Adjust index based on actual structure
198
- //Timestamp Raw: [ 'timestamp', 1726689425 ]
199
- if (timestampRaw instanceof Uint8Array || Buffer.isBuffer(timestampRaw)) {
200
- return Buffer.from(timestampRaw).readUIntBE(0, timestampRaw.length);
201
- } else if (typeof timestampRaw === 'number') {
202
- return timestampRaw;
203
- } else {
204
- throw new Error('Invalid timestamp format in block header');
185
+ parseTimestamp(blockHeader) {
186
+ // Search for the timestamp field by name
187
+ if (Array.isArray(blockHeader)) {
188
+ for (const field of blockHeader) {
189
+ if (Array.isArray(field) && field.length >= 2 && field[0] === 'timestamp') {
190
+ const timestampValue = field[1];
191
+
192
+ // Handle different timestamp value types
193
+ if (typeof timestampValue === 'number') {
194
+ return timestampValue;
195
+ } else if (typeof timestampValue === 'string' && timestampValue.startsWith('0x')) {
196
+ // Handle hex string
197
+ return parseInt(timestampValue.slice(2), 16);
198
+ } else if (typeof timestampValue === 'string') {
199
+ // Handle decimal string
200
+ return parseInt(timestampValue, 10);
201
+ } else if (timestampValue instanceof Uint8Array || Buffer.isBuffer(timestampValue)) {
202
+ // Handle buffer - carefully determine the byte length
203
+ const buf = Buffer.from(timestampValue);
204
+ // Use a safe approach to read the value based on buffer length
205
+ if (buf.length <= 6) {
206
+ return buf.readUIntBE(0, buf.length);
207
+ } else {
208
+ // For larger buffers, convert to hex string first
209
+ return parseInt(buf.toString('hex'), 16);
210
+ }
211
+ }
212
+ }
213
+ }
205
214
  }
215
+
216
+ // Fallback: if we couldn't find the timestamp or parse it correctly
217
+ logger.warn('Could not find or parse timestamp in block header, using current time');
218
+ return Math.floor(Date.now() / 1000);
206
219
  }
207
220
  }
208
221