diodejs 0.2.2 → 0.4.0

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,7 @@ 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, generateCert, ensureDirectoryExistence, loadOrGenerateKeyPair } = require('./utils');
6
+ const { makeReadable, parseRequestId, parseResponseType, parseReason, parseUInt, generateCert, ensureDirectoryExistence, loadOrGenerateKeyPair, toBufferView } = require('./utils');
7
7
  const { Buffer } = require('buffer'); // Import Buffer
8
8
  const asn1 = require('asn1.js');
9
9
  const secp256k1 = require('secp256k1');
@@ -16,6 +16,15 @@ const path = require('path');
16
16
  // Add dotenv for environment variables
17
17
  require('dotenv').config();
18
18
 
19
+ // Try to use native keccak if available (optional perf boost)
20
+ let nativeKeccak = null;
21
+ try {
22
+ // eslint-disable-next-line import/no-extraneous-dependencies
23
+ nativeKeccak = require('keccak');
24
+ } catch (_) {
25
+ // optional dependency; fallback to ethereumjs-util.keccak256
26
+ }
27
+
19
28
  class DiodeConnection extends EventEmitter {
20
29
  constructor(host, port, keyLocation = './db/keys.json') {
21
30
  super();
@@ -37,6 +46,8 @@ class DiodeConnection extends EventEmitter {
37
46
  this.clientSockets = new Map(); // For BindPort
38
47
  this.connections = new Map(); // For PublishPort
39
48
  this.certPem = null;
49
+ this._serverEthAddress = null; // cache after first read
50
+ this.localAddressProvider = null;
40
51
  // Load or generate keypair
41
52
  this.keyPair = loadOrGenerateKeyPair(this.keyLocation);
42
53
 
@@ -59,7 +70,7 @@ class DiodeConnection extends EventEmitter {
59
70
  this.retryTimeoutId = null;
60
71
 
61
72
  // Log the reconnection settings
62
- logger.info(`Connection settings - Auto Reconnect: ${this.autoReconnect}, Max Retries: ${
73
+ logger.info(() => `Connection settings - Auto Reconnect: ${this.autoReconnect}, Max Retries: ${
63
74
  this.maxRetries === Infinity ? 'Infinity' : this.maxRetries
64
75
  }, Retry Delay: ${this.retryDelay}ms, Max Retry Delay: ${this.maxRetryDelay}ms`);
65
76
 
@@ -71,7 +82,13 @@ class DiodeConnection extends EventEmitter {
71
82
  this.ticketUpdateTimer = null;
72
83
 
73
84
  // Log the ticket batching settings
74
- logger.info(`Ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
85
+ logger.info(() => `Ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
86
+
87
+ // Handle server ticket requests on the API socket
88
+ this._onUnsolicited = (message) => {
89
+ this._handleUnsolicitedMessage(message);
90
+ };
91
+ this.on('unsolicited', this._onUnsolicited);
75
92
  }
76
93
 
77
94
  connect() {
@@ -96,54 +113,60 @@ class DiodeConnection extends EventEmitter {
96
113
  };
97
114
 
98
115
  this.socket = tls.connect(this.port, this.host, options, async () => {
99
- logger.info('Connected to Diode.io server');
116
+ const relayHost = this.getServerRelayHost();
117
+ const relayPort = (this.socket && this.socket.remotePort) ? this.socket.remotePort : this.port;
118
+ logger.info(() => `Connected to Diode relay ${relayHost}:${relayPort}`);
100
119
  // Reset retry counter on successful connection
101
120
  this.retryCount = 0;
102
121
  // Set keep-alive to prevent connection timeout forever
103
122
  this.socket.setKeepAlive(true, 1500);
104
-
123
+ this.socket.setNoDelay(true);
124
+ // Cache server address after handshake
125
+ const cachedServerAddress = await this._waitForServerEthereumAddress();
126
+ if (cachedServerAddress) {
127
+ this._serverEthAddress = cachedServerAddress;
128
+ }
129
+ // Start periodic ticket updates now that we are fully connected
130
+ this._startTicketUpdateTimer();
105
131
  // Send the ticketv2 command
106
132
  try {
107
133
  const ticketCommand = await this.createTicketCommand();
108
134
  const response = await this.sendCommand(ticketCommand).catch(reject);
109
135
  resolve();
110
136
  } catch (error) {
111
- logger.error(`Error sending ticket: ${error}`);
137
+ logger.error(() => `Error sending ticket: ${error}`);
112
138
  reject(error);
113
139
  }
114
140
  });
115
141
 
116
142
  this.socket.on('data', (data) => {
117
- // logger.debug(`Received data: ${data.toString('hex')}`);
143
+ // logger.debug(() => `Received data: ${data.toString('hex')}`);
118
144
  try {
119
145
  this._handleData(data);
120
146
  } catch (error) {
121
- logger.error(`Error handling data: ${error}`);
147
+ logger.error(() => `Error handling data: ${error}`);
122
148
  }
123
149
  });
124
150
 
125
- // Start the periodic ticket update timer after successful connection
126
- this.socket.on('connect', () => {
127
- this._startTicketUpdateTimer();
128
- });
151
+ // No-op: rely on secure handshake callback above for timers/caching
129
152
 
130
153
  this.socket.on('error', (err) => {
131
- logger.error(`Connection error: ${err}`);
154
+ logger.error(() => `Connection error: ${err}`);
132
155
  reject(err);
133
156
  });
134
157
 
135
158
  this.socket.on('end', () => {
136
- logger.info('Disconnected from server');
159
+ logger.info(() => 'Disconnected from server');
137
160
  this._handleDisconnect();
138
161
  });
139
162
 
140
163
  this.socket.on('close', (hadError) => {
141
- logger.info(`Connection closed${hadError ? ' due to error' : ''}`);
164
+ logger.info(() => `Connection closed${hadError ? ' due to error' : ''}`);
142
165
  this._handleDisconnect();
143
166
  });
144
167
 
145
168
  this.socket.on('timeout', () => {
146
- logger.warn('Connection timeout');
169
+ logger.warn(() => 'Connection timeout');
147
170
  this._handleDisconnect();
148
171
  });
149
172
  });
@@ -156,7 +179,7 @@ class DiodeConnection extends EventEmitter {
156
179
  this.retryCount++;
157
180
 
158
181
  if (this.maxRetries !== Infinity && this.retryCount > this.maxRetries) {
159
- logger.error(`Maximum reconnection attempts (${this.maxRetries}) reached. Giving up.`);
182
+ logger.error(() => `Maximum reconnection attempts (${this.maxRetries}) reached. Giving up.`);
160
183
  this.emit('reconnect_failed');
161
184
  return;
162
185
  }
@@ -164,7 +187,7 @@ class DiodeConnection extends EventEmitter {
164
187
  // Calculate delay with exponential backoff
165
188
  const delay = Math.min(this.retryDelay * Math.pow(1.5, this.retryCount - 1), this.maxRetryDelay);
166
189
 
167
- logger.info(`Reconnecting in ${delay}ms... (Attempt ${this.retryCount})`);
190
+ logger.info(() => `Reconnecting in ${delay}ms... (Attempt ${this.retryCount})`);
168
191
  this.emit('reconnecting', { attempt: this.retryCount, delay });
169
192
 
170
193
  this.retryTimeoutId = setTimeout(() => {
@@ -184,11 +207,11 @@ class DiodeConnection extends EventEmitter {
184
207
  .then(() => {
185
208
  this.isReconnecting = false;
186
209
  this.emit('reconnected');
187
- logger.info('Successfully reconnected to Diode.io server');
210
+ logger.info(() => 'Successfully reconnected to Diode.io server');
188
211
  })
189
212
  .catch((err) => {
190
213
  this.isReconnecting = false;
191
- logger.error(`Reconnection attempt failed: ${err}`);
214
+ logger.error(() => `Reconnection attempt failed: ${err}`);
192
215
  });
193
216
  }, delay);
194
217
  }
@@ -254,6 +277,12 @@ class DiodeConnection extends EventEmitter {
254
277
  return this;
255
278
  }
256
279
 
280
+ // Optional provider for LocalAddr ticket hint (Buffer or string)
281
+ setLocalAddressProvider(provider) {
282
+ this.localAddressProvider = typeof provider === 'function' ? provider : null;
283
+ return this;
284
+ }
285
+
257
286
  // Update close method to prevent reconnection when intentionally closing
258
287
  close() {
259
288
  if (this.ticketUpdateTimer) {
@@ -274,7 +303,7 @@ class DiodeConnection extends EventEmitter {
274
303
  _handleData(data) {
275
304
  // Append new data to the receive buffer
276
305
  this.receiveBuffer = Buffer.concat([this.receiveBuffer, data]);
277
- // logger.debug(`Received data: ${data.toString('hex')}`);
306
+ // logger.debug(() => `Received data: ${data.toString('hex')}`);
278
307
 
279
308
  let offset = 0;
280
309
  while (offset + 2 <= this.receiveBuffer.length) {
@@ -291,8 +320,9 @@ class DiodeConnection extends EventEmitter {
291
320
  offset += 2 + length;
292
321
 
293
322
  try {
294
- const decodedMessage = RLP.decode(Uint8Array.from(messageBuffer));
295
- // logger.debug(`Decoded message: ${makeReadable(decodedMessage)}`);
323
+ // Avoid copying: pass Buffer directly to RLP.decode
324
+ const decodedMessage = RLP.decode(messageBuffer);
325
+ // logger.debug(() => `Decoded message: ${makeReadable(decodedMessage)}`);
296
326
 
297
327
  if (Array.isArray(decodedMessage) && decodedMessage.length > 1) {
298
328
  const requestIdRaw = decodedMessage[0];
@@ -302,8 +332,8 @@ class DiodeConnection extends EventEmitter {
302
332
  const requestId = parseRequestId(requestIdRaw);
303
333
 
304
334
  // Debug statements
305
- logger.debug(`requestIdRaw: ${requestIdRaw}`);
306
- logger.debug(`Parsed requestId: ${requestId}`);
335
+ logger.debug(() => `requestIdRaw: ${requestIdRaw}`);
336
+ logger.debug(() => `Parsed requestId: ${requestId}`);
307
337
 
308
338
  if (requestId !== null && this.pendingRequests.has(requestId)) {
309
339
  // This is a response to a pending request
@@ -311,14 +341,14 @@ class DiodeConnection extends EventEmitter {
311
341
  const responseRaw = responseData[0];
312
342
 
313
343
  // Debug statements
314
- logger.debug(`responseTypeRaw: ${responseTypeRaw}`);
315
- logger.debug(`Type of responseTypeRaw: ${typeof responseTypeRaw}`);
344
+ logger.debug(() => `responseTypeRaw: ${responseTypeRaw}`);
345
+ logger.debug(() => `Type of responseTypeRaw: ${typeof responseTypeRaw}`);
316
346
 
317
347
  // Parse responseType
318
348
  const responseType = parseResponseType(responseTypeRaw);
319
349
 
320
- logger.debug(`Received response for requestId: ${requestId}`);
321
- logger.debug(`Response Type: '${responseType}'`);
350
+ logger.debug(() => `Received response for requestId: ${requestId}`);
351
+ logger.debug(() => `Response Type: '${responseType}'`);
322
352
 
323
353
  const { resolve, reject } = this.pendingRequests.get(requestId);
324
354
  try{
@@ -344,20 +374,20 @@ class DiodeConnection extends EventEmitter {
344
374
  resolve(responseData);
345
375
  }
346
376
  } catch (error) {
347
- logger.error(`Error handling response: ${error}`);
377
+ logger.error(() => `Error handling response: ${error}`);
348
378
  }
349
379
  this.pendingRequests.delete(requestId);
350
380
  } else {
351
381
  // This is an unsolicited message
352
- logger.debug(`Received unsolicited message`);
382
+ logger.debug(() => `Received unsolicited message`);
353
383
  this.emit('unsolicited', decodedMessage);
354
384
  }
355
385
  } else {
356
386
  // Invalid message format
357
- logger.error(`Invalid message format: ${makeReadable(decodedMessage)}`);
387
+ logger.error(() => `Invalid message format: ${makeReadable(decodedMessage)}`);
358
388
  }
359
389
  } catch (error) {
360
- logger.error(`Error decoding message: ${error}`);
390
+ logger.error(() => `Error decoding message: ${error}`);
361
391
  }
362
392
  }
363
393
 
@@ -366,6 +396,34 @@ class DiodeConnection extends EventEmitter {
366
396
  this.receiveBuffer = this.receiveBuffer.slice(offset);
367
397
  }
368
398
 
399
+ _handleUnsolicitedMessage(message) {
400
+ if (!Array.isArray(message) || message.length < 2) return;
401
+ const messageContent = message[1];
402
+ if (!Array.isArray(messageContent) || messageContent.length < 1) return;
403
+
404
+ const messageTypeRaw = messageContent[0];
405
+ const messageType = toBufferView(messageTypeRaw).toString('utf8');
406
+
407
+ if (messageType === 'ticket_request') {
408
+ const deviceUsageRaw = messageContent[1];
409
+ const deviceUsage = parseUInt(deviceUsageRaw);
410
+ if (typeof deviceUsage === 'number' && deviceUsage > this.totalBytes) {
411
+ this.totalBytes = deviceUsage;
412
+ }
413
+
414
+ // Send a fresh ticket promptly to avoid disconnect
415
+ this.createTicketCommand()
416
+ .then((ticketCommand) => this.sendCommand(ticketCommand))
417
+ .then(() => {
418
+ this.accumulatedBytes = 0;
419
+ this.lastTicketUpdate = Date.now();
420
+ })
421
+ .catch((error) => {
422
+ logger.error(() => `Error handling ticket_request: ${error}`);
423
+ });
424
+ }
425
+ }
426
+
369
427
  fixResponse(response) {
370
428
  /* response is :
371
429
  [
@@ -392,21 +450,21 @@ class DiodeConnection extends EventEmitter {
392
450
  const requestId = this._getNextRequestId();
393
451
  // Build the message as [requestId, [commandArray]]
394
452
  const commandWithId = [requestId, commandArray];
395
-
453
+
396
454
  // Store the promise callbacks to resolve/reject later
397
455
  this.pendingRequests.set(requestId, { resolve, reject });
398
-
456
+
399
457
  const commandBuffer = RLP.encode(commandWithId);
400
- const byteLength = Buffer.byteLength(commandBuffer);
401
-
458
+ const byteLength = commandBuffer.length; // Buffer/Uint8Array length is bytes
459
+
402
460
  // Create a 2-byte length buffer
403
461
  const lengthBuffer = Buffer.alloc(2);
404
462
  lengthBuffer.writeUInt16BE(byteLength, 0);
405
-
463
+
406
464
  const message = Buffer.concat([lengthBuffer, commandBuffer]);
407
465
 
408
- logger.debug(`Sending command with requestId ${requestId}: ${commandArray}`);
409
- // logger.debug(`Command buffer: ${message.toString('hex')}`);
466
+ logger.debug(() => `Sending command with requestId ${requestId}: ${commandArray}`);
467
+ // logger.debug(() => `Command buffer: ${message.toString('hex')}`);
410
468
 
411
469
  this.socket.write(message);
412
470
  }).catch(reject);
@@ -419,23 +477,24 @@ class DiodeConnection extends EventEmitter {
419
477
  const requestId = sessionId;
420
478
  // Build the message as [requestId, [commandArray]]
421
479
  const commandWithId = [requestId, commandArray];
422
-
480
+
423
481
  // Store the promise callbacks to resolve/reject later
424
482
  this.pendingRequests.set(requestId, { resolve, reject });
425
-
483
+
426
484
  const commandBuffer = RLP.encode(commandWithId);
427
- const byteLength = Buffer.byteLength(commandBuffer);
428
-
485
+ const byteLength = commandBuffer.length; // Buffer/Uint8Array length is bytes
486
+
429
487
  // Create a 2-byte length buffer
430
488
  const lengthBuffer = Buffer.alloc(2);
431
489
  lengthBuffer.writeUInt16BE(byteLength, 0);
432
-
490
+
433
491
  const message = Buffer.concat([lengthBuffer, commandBuffer]);
434
492
 
435
- logger.debug(`Sending command with requestId ${requestId}: ${commandArray}`);
436
- // logger.debug(`Command buffer: ${message.toString('hex')}`);
493
+ logger.debug(() => `Sending command with requestId ${requestId}: ${commandArray}`);
494
+ // logger.debug(() => `Command buffer: ${message.toString('hex')}`);
437
495
 
438
496
  this.socket.write(message);
497
+ resolve();
439
498
  }).catch(reject);
440
499
  });
441
500
  }
@@ -452,32 +511,70 @@ class DiodeConnection extends EventEmitter {
452
511
 
453
512
  return address;
454
513
  } catch (error) {
455
- logger.error(`Error extracting Ethereum address: ${error}`);
514
+ logger.error(() => `Error extracting Ethereum address: ${error}`);
456
515
  throw error;
457
516
  }
458
517
  }
459
518
 
460
- getServerEthereumAddress() {
519
+ getServerEthereumAddress(quiet = false) {
461
520
  try {
521
+ if (this._serverEthAddress) {
522
+ return this._serverEthAddress;
523
+ }
462
524
  const serverCert = this.socket.getPeerCertificate(true);
463
525
  if (!serverCert.raw) {
464
- throw new Error('Failed to get server certificate.');
526
+ const err = new Error('Failed to get server certificate.');
527
+ if (!quiet) {
528
+ throw err;
529
+ }
530
+ return null;
465
531
  }
466
532
 
467
533
  const publicKeyBuffer = Buffer.isBuffer(serverCert.pubkey)
468
534
  ? serverCert.pubkey
469
535
  : Buffer.from(serverCert.pubkey);
470
536
 
471
- logger.debug(`Public key Server: ${publicKeyBuffer.toString('hex')}`);
537
+ logger.debug(() => `Public key Server: ${publicKeyBuffer.toString('hex')}`);
472
538
 
473
539
  const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
474
540
  const address = '0x' + addressBuffer.toString('hex');
541
+ this._serverEthAddress = address;
542
+ return this._serverEthAddress;
543
+ } catch (error) {
544
+ if (!quiet) {
545
+ logger.error(() => `Error extracting server Ethereum address: ${error}`);
546
+ throw error;
547
+ }
548
+ return null;
549
+ }
550
+ }
475
551
 
552
+ getServerRelayHost() {
553
+ if (this.socket && this.socket.remoteAddress) {
554
+ const address = this.socket.remoteAddress;
555
+ if (address.startsWith('::ffff:')) {
556
+ return address.slice(7);
557
+ }
558
+ if (address.includes(':')) {
559
+ return this.host;
560
+ }
476
561
  return address;
477
- } catch (error) {
478
- logger.error(`Error extracting server Ethereum address: ${error}`);
479
- throw error;
480
562
  }
563
+ return this.host;
564
+ }
565
+
566
+ async _waitForServerEthereumAddress(options = {}) {
567
+ const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 2000;
568
+ const intervalMs = Number.isFinite(options.intervalMs) ? options.intervalMs : 50;
569
+ const start = Date.now();
570
+ let address = this.getServerEthereumAddress(true);
571
+ if (address) return address;
572
+ while (Date.now() - start < timeoutMs) {
573
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
574
+ address = this.getServerEthereumAddress(true);
575
+ if (address) return address;
576
+ }
577
+ return null;
481
578
  }
482
579
 
483
580
  // Method to extract private key bytes from keyPair
@@ -488,18 +585,19 @@ class DiodeConnection extends EventEmitter {
488
585
  const privateKeyBytes = Buffer.from(privateKeyHex, 'hex');
489
586
  return privateKeyBytes;
490
587
  } catch (error) {
491
- logger.error(`Error extracting private key: ${error}`);
588
+ logger.error(() => `Error extracting private key: ${error}`);
492
589
  throw error;
493
590
  }
494
591
  }
495
592
 
496
593
  async createTicketSignature(serverIdBuffer, totalConnections, totalBytes, localAddress, epoch) {
497
- this.getEthereumAddress()
498
594
  const chainId = 1284;
499
595
  const fleetContractBuffer = ethUtil.toBuffer('0x6000000000000000000000000000000000000000'); // 20-byte Buffer
500
596
 
501
- // Hash of localAddress (empty string)
502
- const localAddressHash = crypto.createHash('sha256').update(Buffer.from(localAddress, 'utf8')).digest();
597
+ const localAddressBytes = Buffer.isBuffer(localAddress) || localAddress instanceof Uint8Array
598
+ ? toBufferView(localAddress)
599
+ : Buffer.from(localAddress || '', 'utf8');
600
+ const localAddressHash = crypto.createHash('sha256').update(localAddressBytes).digest();
503
601
 
504
602
  // Data to sign
505
603
  const dataToSign = [
@@ -512,23 +610,25 @@ class DiodeConnection extends EventEmitter {
512
610
  ethUtil.setLengthLeft(localAddressHash, 32),
513
611
  ];
514
612
 
515
- // Convert each element in dataToSign to bytes32 and concatenate them
516
- const encodedData = Buffer.concat(dataToSign.map(item => abi.rawEncode(['bytes32'], [item])));
613
+ // Elements are already bytes32; concatenate directly to avoid ABI overhead
614
+ const encodedData = Buffer.concat(dataToSign);
517
615
 
518
- logger.debug(`Encoded data: ${encodedData.toString('hex')}`);
616
+ logger.debug(() => `Encoded data: ${encodedData.toString('hex')}`);
519
617
 
520
- logger.debug(`Data to sign: ${makeReadable(dataToSign)}`);
618
+ logger.debug(() => `Data to sign: ${makeReadable(dataToSign)}`);
521
619
 
522
620
 
523
621
  // Sign the data
524
622
  const privateKey = this.getPrivateKey();
525
- const msgHash = ethUtil.keccak256(encodedData);
526
- logger.debug(`Message hash: ${msgHash.toString('hex')}`);
623
+ const msgHash = nativeKeccak
624
+ ? nativeKeccak('keccak256').update(encodedData).digest()
625
+ : ethUtil.keccak256(encodedData);
626
+ logger.debug(() => `Message hash: ${msgHash.toString('hex')}`);
527
627
  const signature = secp256k1.ecdsaSign(msgHash, privateKey);
528
- logger.debug(`Signature: ${signature.signature.toString('hex')}`);
628
+ logger.debug(() => `Signature: ${signature.signature.toString('hex')}`);
529
629
 
530
630
  const signatureBuffer = Buffer.concat([
531
- ethUtil.toBuffer([signature.recid]),
631
+ Buffer.from([signature.recid]),
532
632
  signature.signature
533
633
  ]);
534
634
 
@@ -538,7 +638,18 @@ class DiodeConnection extends EventEmitter {
538
638
  async createTicketCommand() {
539
639
  const chainId = 1284;
540
640
  const fleetContract = ethUtil.toBuffer('0x6000000000000000000000000000000000000000')
541
- const localAddress = 'test2'; // Always empty string
641
+ let localAddress = '';
642
+ if (typeof this.localAddressProvider === 'function') {
643
+ try {
644
+ localAddress = this.localAddressProvider();
645
+ } catch (error) {
646
+ logger.warn(() => `Failed to get local address hint: ${error}`);
647
+ localAddress = '';
648
+ }
649
+ }
650
+ if (localAddress === null || localAddress === undefined) {
651
+ localAddress = '';
652
+ }
542
653
 
543
654
  // Increment totalConnections
544
655
  this.totalConnections += 1;
@@ -548,7 +659,10 @@ class DiodeConnection extends EventEmitter {
548
659
  const totalBytes = this.totalBytes;
549
660
 
550
661
  // Get server Ethereum address as Buffer
551
- const serverIdBuffer = this.getServerEthereumAddress();
662
+ const serverIdBuffer = await this._waitForServerEthereumAddress();
663
+ if (!serverIdBuffer) {
664
+ throw new Error('Failed to get server certificate.');
665
+ }
552
666
 
553
667
  // Get epoch
554
668
  const epoch = await this.RPC.getEpoch();
@@ -559,7 +673,7 @@ class DiodeConnection extends EventEmitter {
559
673
  localAddress,
560
674
  epoch
561
675
  );
562
- logger.debug(`Signature hex: ${signature.toString('hex')}`);
676
+ logger.debug(() => `Signature hex: ${signature.toString('hex')}`);
563
677
 
564
678
 
565
679
  // Construct the ticket command
@@ -650,7 +764,7 @@ class DiodeConnection extends EventEmitter {
650
764
 
651
765
  try {
652
766
  if (this.accumulatedBytes > 0 || force) {
653
- logger.debug(`Updating ticket: accumulated ${this.accumulatedBytes} bytes, ${timeSinceLastUpdate}ms since last update`);
767
+ logger.debug(() => `Updating ticket: accumulated ${this.accumulatedBytes} bytes, ${timeSinceLastUpdate}ms since last update`);
654
768
  const ticketCommand = await this.createTicketCommand();
655
769
  await this.sendCommand(ticketCommand);
656
770
 
@@ -659,7 +773,7 @@ class DiodeConnection extends EventEmitter {
659
773
  this.lastTicketUpdate = Date.now();
660
774
  }
661
775
  } catch (error) {
662
- logger.error(`Error updating ticket: ${error}`);
776
+ logger.error(() => `Error updating ticket: ${error}`);
663
777
  }
664
778
  }
665
779
 
@@ -687,7 +801,7 @@ class DiodeConnection extends EventEmitter {
687
801
  this.ticketUpdateInterval = options.interval;
688
802
  }
689
803
 
690
- logger.info(`Updated ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
804
+ logger.info(() => `Updated ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
691
805
 
692
806
  // Reset the timer with new interval
693
807
  if (this.socket && !this.socket.destroyed) {
@@ -1,14 +1,16 @@
1
- const { DiodeConnection, DiodeRPC } = require('../index');
1
+ const { DiodeClientManager, DiodeRPC } = require('../index');
2
2
  const { makeReadable } = require('../utils');
3
3
 
4
4
  async function main() {
5
- const host = 'us2.prenet.diode.io';
6
- const port = 41046;
7
5
  const keyLocation = './db/keys.json';
8
6
 
9
- const connection = new DiodeConnection(host, port, keyLocation);
10
- await connection.connect();
11
- const rpc = connection.RPC;
7
+ const client = new DiodeClientManager({ keyLocation });
8
+ await client.connect();
9
+ const [connection] = client.getConnections();
10
+ if (!connection) {
11
+ throw new Error('No relay connection available');
12
+ }
13
+ const rpc = connection.RPC || new DiodeRPC(connection);
12
14
 
13
15
  try {
14
16
  const address = connection.getEthereumAddress();
@@ -22,7 +24,7 @@ async function main() {
22
24
  } catch (error) {
23
25
  console.error('RPC Error:', error);
24
26
  } finally {
25
- connection.close();
27
+ client.close();
26
28
  }
27
29
  }
28
30
 
@@ -0,0 +1,55 @@
1
+ const { KEYUTIL } = require('jsrsasign');
2
+ const { DiodeClientManager, BindPort } = require('../index');
3
+
4
+ function printPublicKey(connection) {
5
+ try {
6
+ const pem = KEYUTIL.getPEM(connection.keyPair.pubKeyObj, 'PKCS8PUB');
7
+ console.log('Public key (PEM):');
8
+ console.log(pem);
9
+ } catch (error) {
10
+ console.error('Failed to print public key:', error);
11
+ }
12
+ try {
13
+ console.log('Ethereum address:', connection.getEthereumAddress());
14
+ } catch (error) {
15
+ console.error('Failed to print address:', error);
16
+ }
17
+ }
18
+
19
+ async function main() {
20
+ const keyLocation = './db/keys2.json';
21
+
22
+ const deviceIdHex = process.env.DIODE_TARGET_DEVICE || process.argv[2] || '0x4632c04cf8c44a586554951e7de03ca2bd3e8f1c'; // Replace with actual device
23
+
24
+ const client = new DiodeClientManager({keyLocation });
25
+ await client.connect();
26
+ const [connection] = client.getConnections();
27
+ if (!connection) {
28
+ throw new Error('No relay connection available');
29
+ }
30
+
31
+ printPublicKey(connection);
32
+
33
+ const portsConfig = {
34
+ 3005: {
35
+ targetPort: 8089,
36
+ deviceIdHex,
37
+ protocol: 'tcp',
38
+ transport: 'native'
39
+ },
40
+ 3006: {
41
+ targetPort: 8090,
42
+ deviceIdHex,
43
+ protocol: 'udp',
44
+ transport: 'native',
45
+ flags: 'rwu'
46
+ }
47
+ };
48
+
49
+ const portForward = new BindPort(client, portsConfig);
50
+ portForward.bind();
51
+
52
+ console.log('Native bind ports active:', Object.keys(portsConfig).join(', '));
53
+ }
54
+
55
+ main().catch(console.error);