diodejs 0.1.4 → 0.1.6

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
@@ -15,6 +15,35 @@ If you want to enable debug logs, set environment variable DEBUG to true.
15
15
 
16
16
  Can also use .env files
17
17
 
18
+ ### Connection Settings
19
+
20
+ Connection retry behavior can be configured via environment variables:
21
+
22
+ | Environment Variable | Description | Default |
23
+ |----------------------|-------------|---------|
24
+ | DIODE_MAX_RETRIES | Maximum number of reconnection attempts | Infinity |
25
+ | DIODE_RETRY_DELAY | Initial delay between retries (ms) | 1000 |
26
+ | DIODE_MAX_RETRY_DELAY | Maximum delay between retries (ms) | 30000 |
27
+ | DIODE_AUTO_RECONNECT | Whether to automatically reconnect | true |
28
+
29
+ Example `.env` file:
30
+ ```
31
+ DIODE_MAX_RETRIES=10
32
+ DIODE_RETRY_DELAY=2000
33
+ DIODE_MAX_RETRY_DELAY=20000
34
+ DIODE_AUTO_RECONNECT=true
35
+ ```
36
+
37
+ These settings can also be configured programmatically:
38
+ ```javascript
39
+ connection.setReconnectOptions({
40
+ maxRetries: 10,
41
+ retryDelay: 2000,
42
+ maxRetryDelay: 20000,
43
+ autoReconnect: true
44
+ });
45
+ ```
46
+
18
47
  ### Test RPC
19
48
 
20
49
  Here's a quick example to get you started with RPC functions using `DiodeRPC` Class
@@ -28,6 +57,26 @@ async function main() {
28
57
  const keyLocation = './db/keys.json'; // Optional, defaults to './db/keys.json'
29
58
 
30
59
  const connection = new DiodeConnection(host, port, keyLocation);
60
+
61
+ // Configure reconnection (optional - overrides environment variables)
62
+ connection.setReconnectOptions({
63
+ maxRetries: Infinity, // Unlimited reconnection attempts
64
+ retryDelay: 1000, // Initial delay of 1 second
65
+ maxRetryDelay: 30000, // Maximum delay of 30 seconds
66
+ autoReconnect: true // Automatically reconnect on disconnection
67
+ });
68
+
69
+ // Listen for reconnection events (optional)
70
+ connection.on('reconnecting', (info) => {
71
+ console.log(`Reconnecting... Attempt #${info.attempt} in ${info.delay}ms`);
72
+ });
73
+ connection.on('reconnected', () => {
74
+ console.log('Successfully reconnected!');
75
+ });
76
+ connection.on('reconnect_failed', () => {
77
+ console.log('Failed to reconnect after maximum attempts');
78
+ });
79
+
31
80
  await connection.connect();
32
81
 
33
82
  const rpc = new DiodeRPC(connection);
@@ -69,7 +118,7 @@ async function main() {
69
118
  // Multiple or single port binding with configuration object
70
119
  const portsConfig = {
71
120
  3002: { targetPort: 80, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" },
72
- 3003: { targetPort: 443, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" }
121
+ 3003: { targetPort: 443, deviceIdHex: "0x5365baf29cb7ab58de588dfc448913cb609283e2" } // Works with or without 0x prefix
73
122
  };
74
123
 
75
124
  const portForward = new BindPort(connection, portsConfig);
@@ -157,6 +206,16 @@ main();
157
206
  - `createTicketCommand()`: Creates a ticket command for authentication. Returns a promise.
158
207
  - `close()`: Closes the connection to the Diode server.
159
208
  - `getDeviceCertificate()`: Returns the generated certificate PEM.
209
+ - `setReconnectOptions(options)`: Configures reconnection behavior with the following options:
210
+ - `maxRetries` (number): Maximum reconnection attempts (default: Infinity)
211
+ - `retryDelay` (number): Initial delay between retries in ms (default: 1000)
212
+ - `maxRetryDelay` (number): Maximum delay between retries in ms (default: 30000)
213
+ - `autoReconnect` (boolean): Whether to automatically reconnect on disconnection (default: true)
214
+
215
+ - **Events**:
216
+ - `reconnecting`: Emitted when a reconnection attempt is about to start, with `attempt` and `delay` information
217
+ - `reconnected`: Emitted when reconnection is successful
218
+ - `reconnect_failed`: Emitted when all reconnection attempts have failed
160
219
 
161
220
  #### `DiodeRPC`
162
221
 
@@ -185,17 +244,18 @@ main();
185
244
  - `connection` (DiodeConnection): An instance of `DiodeConnection`.
186
245
  - `localPort` (number): The local port to bind.
187
246
  - `targetPort` (number): The target port on the device.
188
- - `deviceIdHex` (string): The device ID in hexadecimal format.
247
+ - `deviceIdHex` (string): The device ID in hexadecimal format (with or without '0x' prefix).
189
248
 
190
249
  New Constructor:
191
250
  - `new BindPort(connection, portsConfig)`
192
251
  - `connection` (DiodeConnection): An instance of `DiodeConnection`.
193
252
  - `portsConfig` (object): A configuration object where keys are local ports and values are objects with `targetPort` and `deviceIdHex`.
194
253
  Example: `{ 3002: { targetPort: 80, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" } }`
254
+ Note: deviceIdHex can be provided with or without the '0x' prefix.
195
255
 
196
256
  - **Methods**:
197
257
  - `bind()`: Binds all configured local ports to their target ports on the devices.
198
- - `addPort(localPort, targetPort, deviceIdHex)`: Adds a new port binding configuration.
258
+ - `addPort(localPort, targetPort, deviceIdHex)`: Adds a new port binding configuration. deviceIdHex can include '0x' prefix.
199
259
  - `removePort(localPort)`: Removes a port binding configuration.
200
260
  - `bindSinglePort(localPort)`: Binds a single local port to its target.
201
261
  - `closeAllServers()`: Closes all active server instances.
package/bindPort.js CHANGED
@@ -10,11 +10,21 @@ class BindPort {
10
10
  // Handle legacy constructor (connection, localPort, targetPort, deviceIdHex)
11
11
  if (typeof localPortOrPortsConfig === 'number' && targetPort !== undefined && deviceIdHex !== undefined) {
12
12
  this.portsConfig = {
13
- [localPortOrPortsConfig]: { targetPort, deviceIdHex }
13
+ [localPortOrPortsConfig]: {
14
+ targetPort,
15
+ deviceIdHex: this._stripHexPrefix(deviceIdHex)
16
+ }
14
17
  };
15
18
  } else {
16
19
  // New constructor (connection, portsConfig)
17
20
  this.portsConfig = localPortOrPortsConfig || {};
21
+
22
+ // Strip 0x prefix from all deviceIdHex values in portsConfig
23
+ for (const port in this.portsConfig) {
24
+ if (this.portsConfig[port].deviceIdHex) {
25
+ this.portsConfig[port].deviceIdHex = this._stripHexPrefix(this.portsConfig[port].deviceIdHex);
26
+ }
27
+ }
18
28
  }
19
29
 
20
30
  this.servers = new Map(); // Track server instances by localPort
@@ -24,6 +34,14 @@ class BindPort {
24
34
  this._setupMessageListener();
25
35
  }
26
36
 
37
+ // Helper method to strip 0x prefix from hex strings
38
+ _stripHexPrefix(hexString) {
39
+ if (typeof hexString === 'string' && hexString.toLowerCase().startsWith('0x')) {
40
+ return hexString.slice(2);
41
+ }
42
+ return hexString;
43
+ }
44
+
27
45
  _setupMessageListener() {
28
46
  // Listen for data events from the device
29
47
  this.connection.on('unsolicited', (message) => {
@@ -88,7 +106,10 @@ class BindPort {
88
106
  return false;
89
107
  }
90
108
 
91
- this.portsConfig[localPort] = { targetPort, deviceIdHex };
109
+ this.portsConfig[localPort] = {
110
+ targetPort,
111
+ deviceIdHex: this._stripHexPrefix(deviceIdHex)
112
+ };
92
113
 
93
114
  this.bindSinglePort(localPort);
94
115
 
package/connection.js CHANGED
@@ -13,6 +13,8 @@ const DiodeRPC = require('./rpc');
13
13
  const abi = require('ethereumjs-abi');
14
14
  const logger = require('./logger');
15
15
  const path = require('path');
16
+ // Add dotenv for environment variables
17
+ require('dotenv').config();
16
18
 
17
19
  class DiodeConnection extends EventEmitter {
18
20
  constructor(host, port, keyLocation = './db/keys.json') {
@@ -37,9 +39,38 @@ class DiodeConnection extends EventEmitter {
37
39
  this.certPem = null;
38
40
  // Load or generate keypair
39
41
  this.keyPair = loadOrGenerateKeyPair(this.keyLocation);
42
+
43
+ // Load reconnection properties from environment variables with defaults
44
+ const envMaxRetries = process.env.DIODE_MAX_RETRIES;
45
+ this.maxRetries = envMaxRetries !== undefined ?
46
+ (envMaxRetries.toLowerCase() === 'infinity' ? Infinity : parseInt(envMaxRetries, 10)) :
47
+ Infinity;
48
+
49
+ this.retryDelay = parseInt(process.env.DIODE_RETRY_DELAY, 10) || 1000; // Default: 1 second
50
+ this.maxRetryDelay = parseInt(process.env.DIODE_MAX_RETRY_DELAY, 10) || 30000; // Default: 30 seconds
51
+
52
+ // Parse boolean from string ('true'/'false')
53
+ const envAutoReconnect = process.env.DIODE_AUTO_RECONNECT;
54
+ this.autoReconnect = envAutoReconnect !== undefined ?
55
+ (envAutoReconnect.toLowerCase() === 'true') :
56
+ true;
57
+
58
+ this.retryCount = 0;
59
+ this.retryTimeoutId = null;
60
+
61
+ // Log the reconnection settings
62
+ logger.info(`Connection settings - Auto Reconnect: ${this.autoReconnect}, Max Retries: ${
63
+ this.maxRetries === Infinity ? 'Infinity' : this.maxRetries
64
+ }, Retry Delay: ${this.retryDelay}ms, Max Retry Delay: ${this.maxRetryDelay}ms`);
40
65
  }
41
66
 
42
67
  connect() {
68
+ // Clear any existing retry timeout
69
+ if (this.retryTimeoutId) {
70
+ clearTimeout(this.retryTimeoutId);
71
+ this.retryTimeoutId = null;
72
+ }
73
+
43
74
  return new Promise((resolve, reject) => {
44
75
  // Generate a temporary certificate valid for 1 month
45
76
  this.certPem = generateCert(this.keyPair.prvKeyObj, this.keyPair.pubKeyObj);
@@ -56,8 +87,10 @@ class DiodeConnection extends EventEmitter {
56
87
 
57
88
  this.socket = tls.connect(this.port, this.host, options, async () => {
58
89
  logger.info('Connected to Diode.io server');
90
+ // Reset retry counter on successful connection
91
+ this.retryCount = 0;
59
92
  // Set keep-alive to prevent connection timeout forever
60
- this.socket.setKeepAlive(true, 0);
93
+ this.socket.setKeepAlive(true, 1500);
61
94
 
62
95
  // Send the ticketv2 command
63
96
  try {
@@ -79,14 +112,87 @@ class DiodeConnection extends EventEmitter {
79
112
  logger.error(`Error handling data: ${error}`);
80
113
  }
81
114
  });
115
+
82
116
  this.socket.on('error', (err) => {
83
117
  logger.error(`Connection error: ${err}`);
84
- reject(err);
118
+ reject(err);
119
+ });
120
+
121
+ this.socket.on('end', () => {
122
+ logger.info('Disconnected from server');
123
+ this._handleDisconnect();
124
+ });
125
+
126
+ this.socket.on('close', (hadError) => {
127
+ logger.info(`Connection closed${hadError ? ' due to error' : ''}`);
128
+ this._handleDisconnect();
129
+ });
130
+
131
+ this.socket.on('timeout', () => {
132
+ logger.warn('Connection timeout');
133
+ this._handleDisconnect();
85
134
  });
86
- this.socket.on('end', () => logger.info('Disconnected from server'));
87
135
  });
88
136
  }
89
137
 
138
+ // New method to handle reconnection with exponential backoff
139
+ _reconnect() {
140
+ if (!this.autoReconnect || this.isReconnecting) return;
141
+
142
+ this.retryCount++;
143
+
144
+ if (this.maxRetries !== Infinity && this.retryCount > this.maxRetries) {
145
+ logger.error(`Maximum reconnection attempts (${this.maxRetries}) reached. Giving up.`);
146
+ this.emit('reconnect_failed');
147
+ return;
148
+ }
149
+
150
+ // Calculate delay with exponential backoff
151
+ const delay = Math.min(this.retryDelay * Math.pow(1.5, this.retryCount - 1), this.maxRetryDelay);
152
+
153
+ logger.info(`Reconnecting in ${delay}ms... (Attempt ${this.retryCount})`);
154
+ this.emit('reconnecting', { attempt: this.retryCount, delay });
155
+
156
+ this.retryTimeoutId = setTimeout(() => {
157
+ this.isReconnecting = true;
158
+
159
+ // Clear existing socket if any
160
+ if (this.socket) {
161
+ this.socket.removeAllListeners();
162
+ if (!this.socket.destroyed) {
163
+ this.socket.destroy();
164
+ }
165
+ this.socket = null;
166
+ }
167
+
168
+ // Connect again
169
+ this.connect()
170
+ .then(() => {
171
+ this.isReconnecting = false;
172
+ this.emit('reconnected');
173
+ logger.info('Successfully reconnected to Diode.io server');
174
+ })
175
+ .catch((err) => {
176
+ this.isReconnecting = false;
177
+ logger.error(`Reconnection attempt failed: ${err}`);
178
+ });
179
+ }, delay);
180
+ }
181
+
182
+ // Helper method to handle disconnection events
183
+ _handleDisconnect() {
184
+ // Reset socket to null to ensure we don't try to use it
185
+ if (this.socket && !this.socket.destroyed) {
186
+ this.socket.destroy();
187
+ }
188
+ this.socket = null;
189
+
190
+ // Don't try to reconnect if we're intentionally closing
191
+ if (this.autoReconnect && !this.isReconnecting) {
192
+ this._reconnect();
193
+ }
194
+ }
195
+
90
196
  _ensureConnected() {
91
197
  if (this.socket && !this.socket.destroyed) {
92
198
  return Promise.resolve();
@@ -94,20 +200,58 @@ class DiodeConnection extends EventEmitter {
94
200
  if (this.connectPromise) {
95
201
  return this.connectPromise;
96
202
  }
97
- this.isReconnecting = true;
98
- this.connectPromise = this.connect()
99
- .then(() => {
100
- this.isReconnecting = false;
101
- this.connectPromise = null;
102
- })
103
- .catch((err) => {
104
- this.isReconnecting = false;
105
- this.connectPromise = null;
106
- throw err;
107
- });
203
+
204
+ this.connectPromise = new Promise((resolve, reject) => {
205
+
206
+ if (this.isReconnecting) {
207
+ // If we're already reconnecting, wait for the reconnection to complete
208
+ this.once('reconnected', resolve);
209
+ //wait for max retry delay
210
+ setTimeout(() => {
211
+ reject(new Error('Reconnection timed out'));
212
+ }, this.maxRetryDelay);
213
+ } else {
214
+ this._reconnect();
215
+ this.once('reconnected', resolve);
216
+ //wait for max retry delay
217
+ setTimeout(() => {
218
+ reject(new Error('Reconnection timed out'));
219
+ }, this.maxRetryDelay);
220
+ }
221
+ });
222
+
108
223
  return this.connectPromise;
109
224
  }
110
225
 
226
+ // Method to set reconnection options
227
+ setReconnectOptions(options = {}) {
228
+ if (typeof options.maxRetries === 'number') {
229
+ this.maxRetries = options.maxRetries;
230
+ }
231
+ if (typeof options.retryDelay === 'number') {
232
+ this.retryDelay = options.retryDelay;
233
+ }
234
+ if (typeof options.maxRetryDelay === 'number') {
235
+ this.maxRetryDelay = options.maxRetryDelay;
236
+ }
237
+ if (typeof options.autoReconnect === 'boolean') {
238
+ this.autoReconnect = options.autoReconnect;
239
+ }
240
+ return this;
241
+ }
242
+
243
+ // Update close method to prevent reconnection when intentionally closing
244
+ close() {
245
+ this.autoReconnect = false;
246
+ if (this.retryTimeoutId) {
247
+ clearTimeout(this.retryTimeoutId);
248
+ this.retryTimeoutId = null;
249
+ }
250
+ if (this.socket) {
251
+ this.socket.end();
252
+ }
253
+ }
254
+
111
255
  _handleData(data) {
112
256
  // Append new data to the receive buffer
113
257
  this.receiveBuffer = Buffer.concat([this.receiveBuffer, data]);
@@ -428,10 +572,6 @@ class DiodeConnection extends EventEmitter {
428
572
  return this.requestId;
429
573
  }
430
574
 
431
- close() {
432
- this.socket.end();
433
- }
434
-
435
575
  // Client sockets management methods (for BindPort)
436
576
  addClientSocket(ref, socket) {
437
577
  this.clientSockets.set(ref.toString('hex'), socket);
@@ -9,7 +9,7 @@ async function main() {
9
9
  await connection.connect();
10
10
 
11
11
  const portForward = new BindPort(connection, {
12
- 3003: { targetPort: 8080, deviceIdHex: "ca1e71d8105a598810578fb6042fa8cbc1e7f039" },
12
+ 3003: { targetPort: 8080, deviceIdHex: "0xca1e71d8105a598810578fb6042fa8cbc1e7f039" },
13
13
  3004: { targetPort: 443, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" }
14
14
  });
15
15
  portForward.bind();
@@ -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, "ca1e71d8105a598810578fb6042fa8cbc1e7f039");
26
+ portForward.addPort(3003, 8080, "0xca1e71d8105a598810578fb6042fa8cbc1e7f039");
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.4",
3
+ "version": "0.1.6",
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": {