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 +63 -3
- package/bindPort.js +23 -2
- package/connection.js +158 -18
- package/examples/portForwardTest.js +2 -2
- package/package.json +1 -1
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: "
|
|
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]: {
|
|
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] = {
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
this.connectPromise =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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: "
|
|
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, "
|
|
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.
|
|
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": {
|