diodejs 0.3.0 → 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.
@@ -0,0 +1,435 @@
1
+ const EventEmitter = require('events');
2
+ const DiodeConnection = require('./connection');
3
+ const DiodeRPC = require('./rpc');
4
+ const logger = require('./logger');
5
+
6
+ const DEFAULT_DIODE_ADDRS = [
7
+ 'as1.prenet.diode.io:41046',
8
+ 'as2.prenet.diode.io:41046',
9
+ 'us1.prenet.diode.io:41046',
10
+ 'us2.prenet.diode.io:41046',
11
+ 'eu1.prenet.diode.io:41046',
12
+ 'eu2.prenet.diode.io:41046',
13
+ ];
14
+
15
+ function splitHostPort(input, defaultPort) {
16
+ if (!input || typeof input !== 'string') {
17
+ return { host: '', port: defaultPort };
18
+ }
19
+ const trimmed = input.trim();
20
+ if (!trimmed) {
21
+ return { host: '', port: defaultPort };
22
+ }
23
+
24
+ if (trimmed.startsWith('[')) {
25
+ const idx = trimmed.indexOf(']');
26
+ if (idx !== -1) {
27
+ const host = trimmed.slice(1, idx);
28
+ const rest = trimmed.slice(idx + 1);
29
+ if (rest.startsWith(':')) {
30
+ const port = parseInt(rest.slice(1), 10);
31
+ return { host, port: Number.isFinite(port) && port > 0 ? port : defaultPort };
32
+ }
33
+ return { host, port: defaultPort };
34
+ }
35
+ }
36
+
37
+ const lastColon = trimmed.lastIndexOf(':');
38
+ if (lastColon > -1 && trimmed.indexOf(':') === lastColon) {
39
+ const host = trimmed.slice(0, lastColon);
40
+ const portStr = trimmed.slice(lastColon + 1);
41
+ if (/^\d+$/.test(portStr)) {
42
+ const port = parseInt(portStr, 10);
43
+ return { host, port: Number.isFinite(port) && port > 0 ? port : defaultPort };
44
+ }
45
+ }
46
+
47
+ return { host: trimmed, port: defaultPort };
48
+ }
49
+
50
+ function joinHostPort(host, port) {
51
+ if (!host) return '';
52
+ if (host.includes(':') && !host.startsWith('[')) {
53
+ return `[${host}]:${port}`;
54
+ }
55
+ return `${host}:${port}`;
56
+ }
57
+
58
+ function normalizeAddress(address) {
59
+ if (!address) return null;
60
+ if (Buffer.isBuffer(address)) return address;
61
+ if (address instanceof Uint8Array) return Buffer.from(address);
62
+ if (typeof address === 'string') {
63
+ const hex = address.toLowerCase().startsWith('0x') ? address.slice(2) : address;
64
+ if (!hex) return Buffer.alloc(0);
65
+ return Buffer.from(hex, 'hex');
66
+ }
67
+ return null;
68
+ }
69
+
70
+ function normalizeServerIdHex(serverId) {
71
+ if (!serverId) return '';
72
+ if (Buffer.isBuffer(serverId) || serverId instanceof Uint8Array) {
73
+ return `0x${Buffer.from(serverId).toString('hex')}`.toLowerCase();
74
+ }
75
+ if (typeof serverId === 'string') {
76
+ return (serverId.startsWith('0x') ? serverId : `0x${serverId}`).toLowerCase();
77
+ }
78
+ return '';
79
+ }
80
+
81
+ function isConnected(connection) {
82
+ return connection && connection.socket && !connection.socket.destroyed;
83
+ }
84
+
85
+ class DiodeClientManager extends EventEmitter {
86
+ constructor(options = {}) {
87
+ super();
88
+
89
+ this.keyLocation = options.keyLocation || './db/keys.json';
90
+ this.defaultPort = Number.isFinite(options.port) && options.port > 0 ? options.port : 41046;
91
+ this.deviceCacheTtlMs = Number.isFinite(options.deviceCacheTtlMs)
92
+ ? options.deviceCacheTtlMs
93
+ : 30000;
94
+
95
+ this.connections = [];
96
+ this.connectionByHost = new Map();
97
+ this.serverIdToConnection = new Map();
98
+ this.pendingConnections = new Map();
99
+ this.deviceRelayCache = new Map();
100
+ this._rpcByConnection = new Map();
101
+ this._rrIndex = 0;
102
+
103
+ this.initialHosts = this._buildInitialHosts(options);
104
+ }
105
+
106
+ _buildInitialHosts(options) {
107
+ if (typeof options.host === 'string' && options.host.trim()) {
108
+ const { host, port } = splitHostPort(options.host, this.defaultPort);
109
+ return host ? [joinHostPort(host, port)] : [];
110
+ }
111
+
112
+ let hosts = [];
113
+ if (Array.isArray(options.hosts)) {
114
+ hosts = options.hosts;
115
+ } else if (typeof options.hosts === 'string') {
116
+ hosts = options.hosts.split(',').map((entry) => entry.trim());
117
+ }
118
+
119
+ if (hosts.length === 0) {
120
+ hosts = DEFAULT_DIODE_ADDRS.slice();
121
+ }
122
+
123
+ const seen = new Set();
124
+ const normalized = [];
125
+ for (const entry of hosts) {
126
+ if (!entry) continue;
127
+ const { host, port } = splitHostPort(entry, this.defaultPort);
128
+ if (!host) continue;
129
+ const key = joinHostPort(host, port);
130
+ const lower = key.toLowerCase();
131
+ if (!seen.has(lower)) {
132
+ seen.add(lower);
133
+ normalized.push(key);
134
+ }
135
+ }
136
+ return normalized;
137
+ }
138
+
139
+ _getRpcFor(connection) {
140
+ if (!connection) return null;
141
+ let rpc = this._rpcByConnection.get(connection);
142
+ if (!rpc) {
143
+ rpc = connection.RPC || new DiodeRPC(connection);
144
+ this._rpcByConnection.set(connection, rpc);
145
+ }
146
+ return rpc;
147
+ }
148
+
149
+ _updateServerIdMapping(connection) {
150
+ if (!connection) return;
151
+ try {
152
+ const serverId = connection.getServerEthereumAddress(true);
153
+ if (serverId) {
154
+ this.serverIdToConnection.set(serverId.toLowerCase(), connection);
155
+ }
156
+ } catch (error) {
157
+ logger.debug(() => `Failed to map server ID for ${connection.host}:${connection.port}: ${error}`);
158
+ }
159
+ }
160
+
161
+ _localAddressHintFor(connection) {
162
+ const connected = this._connectedConnections();
163
+ if (connected.length === 0) {
164
+ return Buffer.alloc(0);
165
+ }
166
+ const primary = connected[0];
167
+ const secondary = connected.length > 1 ? connected[1] : null;
168
+
169
+ let primaryId = '';
170
+ try {
171
+ primaryId = normalizeServerIdHex(primary.getServerEthereumAddress(true));
172
+ } catch (_) {
173
+ primaryId = '';
174
+ }
175
+ if (!primaryId) {
176
+ return Buffer.alloc(0);
177
+ }
178
+
179
+ if (primary === connection) {
180
+ if (!secondary) return Buffer.alloc(0);
181
+ let secondaryId = '';
182
+ try {
183
+ secondaryId = normalizeServerIdHex(secondary.getServerEthereumAddress(true));
184
+ } catch (_) {
185
+ secondaryId = '';
186
+ }
187
+ if (!secondaryId) return Buffer.alloc(0);
188
+ return Buffer.concat([
189
+ Buffer.from([1]),
190
+ Buffer.from(secondaryId.slice(2), 'hex'),
191
+ ]);
192
+ }
193
+
194
+ return Buffer.concat([
195
+ Buffer.from([0]),
196
+ Buffer.from(primaryId.slice(2), 'hex'),
197
+ ]);
198
+ }
199
+
200
+ _registerConnection(connection, hostKey) {
201
+ connection._managerHostKey = hostKey;
202
+ this.connections.push(connection);
203
+ this.connectionByHost.set(hostKey, connection);
204
+ if (typeof connection.setLocalAddressProvider === 'function') {
205
+ connection.setLocalAddressProvider(() => this._localAddressHintFor(connection));
206
+ }
207
+
208
+ connection.on('unsolicited', (message) => {
209
+ this.emit('unsolicited', message, connection);
210
+ });
211
+ connection.on('reconnected', () => {
212
+ this._updateServerIdMapping(connection);
213
+ this.emit('reconnected', connection);
214
+ });
215
+ connection.on('reconnecting', (info) => {
216
+ this.emit('reconnecting', connection, info);
217
+ });
218
+ connection.on('reconnect_failed', () => {
219
+ this.emit('reconnect_failed', connection);
220
+ });
221
+ }
222
+
223
+ _unregisterConnection(connection, hostKey) {
224
+ if (!connection) return;
225
+ if (hostKey) {
226
+ this.connectionByHost.delete(hostKey);
227
+ }
228
+ this.connections = this.connections.filter((item) => item !== connection);
229
+ for (const [serverId, conn] of this.serverIdToConnection.entries()) {
230
+ if (conn === connection) {
231
+ this.serverIdToConnection.delete(serverId);
232
+ }
233
+ }
234
+ this._rpcByConnection.delete(connection);
235
+ }
236
+
237
+ async _ensureConnection(hostEntry) {
238
+ const { host, port } = splitHostPort(hostEntry, this.defaultPort);
239
+ const hostKey = joinHostPort(host, port);
240
+ if (!host) {
241
+ throw new Error(`Invalid host entry: ${hostEntry}`);
242
+ }
243
+
244
+ if (this.connectionByHost.has(hostKey)) {
245
+ return this.connectionByHost.get(hostKey);
246
+ }
247
+
248
+ if (this.pendingConnections.has(hostKey)) {
249
+ return this.pendingConnections.get(hostKey);
250
+ }
251
+
252
+ const connection = new DiodeConnection(host, port, this.keyLocation);
253
+ this._registerConnection(connection, hostKey);
254
+
255
+ const promise = connection.connect()
256
+ .then(() => {
257
+ this._updateServerIdMapping(connection);
258
+ this.emit('connected', connection);
259
+ return connection;
260
+ })
261
+ .catch((error) => {
262
+ this._unregisterConnection(connection, hostKey);
263
+ throw error;
264
+ })
265
+ .finally(() => {
266
+ this.pendingConnections.delete(hostKey);
267
+ });
268
+
269
+ this.pendingConnections.set(hostKey, promise);
270
+ return promise;
271
+ }
272
+
273
+ _connectedConnections() {
274
+ return this.connections.filter((connection) => isConnected(connection));
275
+ }
276
+
277
+ getNearestConnection() {
278
+ const connected = this._connectedConnections();
279
+ if (connected.length === 0) {
280
+ return null;
281
+ }
282
+ this._rrIndex = (this._rrIndex + 1) % connected.length;
283
+ return connected[this._rrIndex];
284
+ }
285
+
286
+ async connect() {
287
+ if (!this.initialHosts || this.initialHosts.length === 0) {
288
+ throw new Error('No Diode hosts configured');
289
+ }
290
+
291
+ const results = await Promise.allSettled(
292
+ this.initialHosts.map((host) => this._ensureConnection(host))
293
+ );
294
+
295
+ const success = results.some((result) => result.status === 'fulfilled');
296
+ if (!success) {
297
+ const errorMessages = results
298
+ .filter((result) => result.status === 'rejected')
299
+ .map((result) => result.reason && result.reason.message ? result.reason.message : String(result.reason));
300
+ throw new Error(`Failed to connect to any Diode hosts. ${errorMessages.join('; ')}`);
301
+ }
302
+
303
+ return this;
304
+ }
305
+
306
+ async getConnectionForDevice(deviceId) {
307
+ const deviceIdBuffer = normalizeAddress(deviceId);
308
+ if (!deviceIdBuffer) {
309
+ throw new Error('Invalid device ID');
310
+ }
311
+ const deviceIdHex = deviceIdBuffer.toString('hex');
312
+
313
+ if (this.deviceCacheTtlMs > 0) {
314
+ const cached = this.deviceRelayCache.get(deviceIdHex);
315
+ if (cached && Date.now() - cached.ts < this.deviceCacheTtlMs) {
316
+ const cachedConn = this.serverIdToConnection.get(cached.serverIdHex) ||
317
+ this.connectionByHost.get(cached.hostKey);
318
+ if (cachedConn && isConnected(cachedConn)) {
319
+ return cachedConn;
320
+ }
321
+ }
322
+ }
323
+
324
+ const primary = this.getNearestConnection();
325
+ if (!primary) {
326
+ throw new Error('No connected relay available');
327
+ }
328
+
329
+ let ticket = null;
330
+ try {
331
+ ticket = await this._getRpcFor(primary).getObject(deviceIdBuffer);
332
+ } catch (error) {
333
+ logger.warn(() => `Failed to resolve device ticket: ${error}`);
334
+ return primary;
335
+ }
336
+
337
+ const serverIdHex = normalizeServerIdHex(ticket && (ticket.serverIdHex || ticket.serverId));
338
+ if (!serverIdHex) {
339
+ return primary;
340
+ }
341
+
342
+ const existing = this.serverIdToConnection.get(serverIdHex);
343
+ if (existing && isConnected(existing)) {
344
+ this.deviceRelayCache.set(deviceIdHex, {
345
+ serverIdHex,
346
+ hostKey: existing._managerHostKey || '',
347
+ ts: Date.now(),
348
+ });
349
+ return existing;
350
+ }
351
+
352
+ let nodeInfo = null;
353
+ try {
354
+ const nodeId = Buffer.from(serverIdHex.slice(2), 'hex');
355
+ nodeInfo = await this._getRpcFor(primary).getNode(nodeId);
356
+ } catch (error) {
357
+ logger.warn(() => `Failed to resolve relay node for ${serverIdHex}: ${error}`);
358
+ return primary;
359
+ }
360
+
361
+ if (!nodeInfo || !nodeInfo.host) {
362
+ return primary;
363
+ }
364
+
365
+ const relayPort = nodeInfo.edgePort || nodeInfo.serverPort;
366
+ if (!relayPort) {
367
+ return primary;
368
+ }
369
+
370
+ const hostKey = joinHostPort(nodeInfo.host, relayPort);
371
+ let relayConnection;
372
+ try {
373
+ relayConnection = await this._ensureConnection(hostKey);
374
+ } catch (error) {
375
+ logger.warn(() => `Failed to connect to relay ${hostKey}: ${error}`);
376
+ return primary;
377
+ }
378
+
379
+ this.deviceRelayCache.set(deviceIdHex, {
380
+ serverIdHex,
381
+ hostKey,
382
+ ts: Date.now(),
383
+ });
384
+
385
+ return relayConnection || primary;
386
+ }
387
+
388
+ async resolveRelayForDevice(deviceId) {
389
+ const deviceIdBuffer = normalizeAddress(deviceId);
390
+ if (!deviceIdBuffer) {
391
+ throw new Error('Invalid device ID');
392
+ }
393
+
394
+ const primary = this.getNearestConnection();
395
+ if (!primary) {
396
+ throw new Error('No connected relay available');
397
+ }
398
+
399
+ const ticket = await this._getRpcFor(primary).getObject(deviceIdBuffer);
400
+ const serverIdHex = normalizeServerIdHex(ticket && (ticket.serverIdHex || ticket.serverId));
401
+ if (!serverIdHex) {
402
+ throw new Error('Device ticket missing server ID');
403
+ }
404
+
405
+ const nodeId = Buffer.from(serverIdHex.slice(2), 'hex');
406
+ const nodeInfo = await this._getRpcFor(primary).getNode(nodeId);
407
+ if (!nodeInfo || !nodeInfo.host) {
408
+ throw new Error('Relay node info missing host');
409
+ }
410
+ const relayPort = nodeInfo.edgePort || nodeInfo.serverPort;
411
+ if (!relayPort) {
412
+ throw new Error('Relay node info missing port');
413
+ }
414
+
415
+ return {
416
+ serverId: serverIdHex,
417
+ host: nodeInfo.host,
418
+ port: relayPort,
419
+ };
420
+ }
421
+
422
+ getConnections() {
423
+ return this.connections.slice();
424
+ }
425
+
426
+ close() {
427
+ for (const connection of this.connections) {
428
+ try {
429
+ connection.close();
430
+ } catch (_) {}
431
+ }
432
+ }
433
+ }
434
+
435
+ module.exports = DiodeClientManager;
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');
@@ -47,6 +47,7 @@ class DiodeConnection extends EventEmitter {
47
47
  this.connections = new Map(); // For PublishPort
48
48
  this.certPem = null;
49
49
  this._serverEthAddress = null; // cache after first read
50
+ this.localAddressProvider = null;
50
51
  // Load or generate keypair
51
52
  this.keyPair = loadOrGenerateKeyPair(this.keyLocation);
52
53
 
@@ -82,6 +83,12 @@ class DiodeConnection extends EventEmitter {
82
83
 
83
84
  // Log the ticket batching settings
84
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);
85
92
  }
86
93
 
87
94
  connect() {
@@ -106,17 +113,18 @@ class DiodeConnection extends EventEmitter {
106
113
  };
107
114
 
108
115
  this.socket = tls.connect(this.port, this.host, options, async () => {
109
- 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}`);
110
119
  // Reset retry counter on successful connection
111
120
  this.retryCount = 0;
112
121
  // Set keep-alive to prevent connection timeout forever
113
122
  this.socket.setKeepAlive(true, 1500);
114
123
  this.socket.setNoDelay(true);
115
124
  // Cache server address after handshake
116
- try {
117
- this._serverEthAddress = this.getServerEthereumAddress();
118
- } catch (e) {
119
- logger.warn(() => `Failed caching server address: ${e}`);
125
+ const cachedServerAddress = await this._waitForServerEthereumAddress();
126
+ if (cachedServerAddress) {
127
+ this._serverEthAddress = cachedServerAddress;
120
128
  }
121
129
  // Start periodic ticket updates now that we are fully connected
122
130
  this._startTicketUpdateTimer();
@@ -269,6 +277,12 @@ class DiodeConnection extends EventEmitter {
269
277
  return this;
270
278
  }
271
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
+
272
286
  // Update close method to prevent reconnection when intentionally closing
273
287
  close() {
274
288
  if (this.ticketUpdateTimer) {
@@ -382,6 +396,34 @@ class DiodeConnection extends EventEmitter {
382
396
  this.receiveBuffer = this.receiveBuffer.slice(offset);
383
397
  }
384
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
+
385
427
  fixResponse(response) {
386
428
  /* response is :
387
429
  [
@@ -474,14 +516,18 @@ class DiodeConnection extends EventEmitter {
474
516
  }
475
517
  }
476
518
 
477
- getServerEthereumAddress() {
519
+ getServerEthereumAddress(quiet = false) {
478
520
  try {
479
521
  if (this._serverEthAddress) {
480
522
  return this._serverEthAddress;
481
523
  }
482
524
  const serverCert = this.socket.getPeerCertificate(true);
483
525
  if (!serverCert.raw) {
484
- 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;
485
531
  }
486
532
 
487
533
  const publicKeyBuffer = Buffer.isBuffer(serverCert.pubkey)
@@ -495,9 +541,40 @@ class DiodeConnection extends EventEmitter {
495
541
  this._serverEthAddress = address;
496
542
  return this._serverEthAddress;
497
543
  } catch (error) {
498
- logger.error(() => `Error extracting server Ethereum address: ${error}`);
499
- throw error;
544
+ if (!quiet) {
545
+ logger.error(() => `Error extracting server Ethereum address: ${error}`);
546
+ throw error;
547
+ }
548
+ return null;
549
+ }
550
+ }
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
+ }
561
+ return address;
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;
500
576
  }
577
+ return null;
501
578
  }
502
579
 
503
580
  // Method to extract private key bytes from keyPair
@@ -517,8 +594,10 @@ class DiodeConnection extends EventEmitter {
517
594
  const chainId = 1284;
518
595
  const fleetContractBuffer = ethUtil.toBuffer('0x6000000000000000000000000000000000000000'); // 20-byte Buffer
519
596
 
520
- // Hash of localAddress (empty string)
521
- 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();
522
601
 
523
602
  // Data to sign
524
603
  const dataToSign = [
@@ -559,7 +638,18 @@ class DiodeConnection extends EventEmitter {
559
638
  async createTicketCommand() {
560
639
  const chainId = 1284;
561
640
  const fleetContract = ethUtil.toBuffer('0x6000000000000000000000000000000000000000')
562
- 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
+ }
563
653
 
564
654
  // Increment totalConnections
565
655
  this.totalConnections += 1;
@@ -569,7 +659,10 @@ class DiodeConnection extends EventEmitter {
569
659
  const totalBytes = this.totalBytes;
570
660
 
571
661
  // Get server Ethereum address as Buffer
572
- const serverIdBuffer = this.getServerEthereumAddress();
662
+ const serverIdBuffer = await this._waitForServerEthereumAddress();
663
+ if (!serverIdBuffer) {
664
+ throw new Error('Failed to get server certificate.');
665
+ }
573
666
 
574
667
  // Get epoch
575
668
  const epoch = await this.RPC.getEpoch();
@@ -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