@celerispay/hazelcast-client 3.12.5 → 3.12.7-3

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.
@@ -35,6 +35,7 @@ var net = require("net");
35
35
  var tls = require("tls");
36
36
  var Util_1 = require("../Util");
37
37
  var BasicSSLOptionsFactory_1 = require("../connection/BasicSSLOptionsFactory");
38
+ var CredentialPreservationService_1 = require("./CredentialPreservationService");
38
39
  var EMIT_CONNECTION_CLOSED = 'connectionClosed';
39
40
  var EMIT_CONNECTION_OPENED = 'connectionOpened';
40
41
  /**
@@ -49,11 +50,17 @@ var ClientConnectionManager = /** @class */ (function (_super) {
49
50
  _this.failedConnections = new Set();
50
51
  _this.maxConnectionRetries = 3;
51
52
  _this.connectionRetryDelay = 1000;
53
+ _this.connectionCleanupTask = null;
54
+ _this.connectionCleanupInterval = 15000; // 15 seconds between cleanup checks
55
+ _this.memberAddedEvents = new Set();
52
56
  _this.client = client;
53
57
  _this.logger = _this.client.getLoggingService().getLogger();
54
58
  _this.addressTranslator = addressTranslator;
55
59
  _this.addressProviders = addressProviders;
60
+ // Initialize credential preservation service for server data storage
61
+ _this.credentialPreservationService = new CredentialPreservationService_1.CredentialPreservationService(_this.logger);
56
62
  _this.startConnectionHealthCheck();
63
+ _this.startConnectionCleanupTask();
57
64
  return _this;
58
65
  }
59
66
  ClientConnectionManager.prototype.startConnectionHealthCheck = function () {
@@ -63,6 +70,50 @@ var ClientConnectionManager = /** @class */ (function (_super) {
63
70
  _this.checkConnectionHealth();
64
71
  }, 5000);
65
72
  };
73
+ ClientConnectionManager.prototype.startConnectionCleanupTask = function () {
74
+ var _this = this;
75
+ // Periodically clean up stale connections and failed connection tracking
76
+ this.connectionCleanupTask = setInterval(function () {
77
+ _this.cleanupStaleConnections();
78
+ }, this.connectionCleanupInterval);
79
+ };
80
+ ClientConnectionManager.prototype.cleanupStaleConnections = function () {
81
+ var _this = this;
82
+ var now = Date.now();
83
+ var staleThreshold = 60000; // 1 minute threshold for stale connections
84
+ // Clean up failed connections that are older than threshold
85
+ var addressesToRemove = [];
86
+ this.failedConnections.forEach(function (address) {
87
+ // For now, we'll use a simple cleanup strategy
88
+ // In a more sophisticated implementation, we could track timestamps
89
+ addressesToRemove.push(address);
90
+ });
91
+ if (addressesToRemove.length > 0) {
92
+ this.logger.debug('ClientConnectionManager', "Cleaning up " + addressesToRemove.length + " stale failed connections");
93
+ addressesToRemove.forEach(function (address) {
94
+ _this.failedConnections.delete(address);
95
+ });
96
+ }
97
+ // Clean up any connections that are not responding
98
+ Object.keys(this.establishedConnections).forEach(function (addressStr) {
99
+ var connection = _this.establishedConnections[addressStr];
100
+ if (connection && !connection.isAlive()) {
101
+ _this.logger.warn('ClientConnectionManager', "Cleaning up stale connection to " + addressStr);
102
+ _this.destroyConnection(connection.getAddress());
103
+ }
104
+ });
105
+ // Log current connection state
106
+ var activeConnections = Object.keys(this.establishedConnections).length;
107
+ var pendingConnections = Object.keys(this.pendingConnections).length;
108
+ var failedConnections = this.failedConnections.size;
109
+ this.logger.debug('ClientConnectionManager', "Connection State - Active: " + activeConnections + ", Pending: " + pendingConnections + ", Failed: " + failedConnections);
110
+ if (activeConnections > 0) {
111
+ Object.keys(this.establishedConnections).forEach(function (addressStr) {
112
+ var connection = _this.establishedConnections[addressStr];
113
+ _this.logger.debug('ClientConnectionManager', "Connection to " + addressStr + ": Alive=" + connection.isAlive() + ", Owner=" + connection.isAuthenticatedAsOwner());
114
+ });
115
+ }
116
+ };
66
117
  ClientConnectionManager.prototype.checkConnectionHealth = function () {
67
118
  // Use Object.keys() instead of Object.values() for compatibility
68
119
  var connectionKeys = Object.keys(this.establishedConnections);
@@ -76,8 +127,8 @@ var ClientConnectionManager = /** @class */ (function (_super) {
76
127
  }
77
128
  };
78
129
  ClientConnectionManager.prototype.isConnectionHealthy = function (connection) {
79
- // Only check if connection is alive - isAuthenticated() method doesn't exist
80
- return connection.isAlive();
130
+ // Use the improved health check method
131
+ return connection.isHealthy();
81
132
  };
82
133
  ClientConnectionManager.prototype.retryConnection = function (address, asOwner, retryCount) {
83
134
  var _this = this;
@@ -86,11 +137,21 @@ var ClientConnectionManager = /** @class */ (function (_super) {
86
137
  _this.failedConnections.delete(address.toString());
87
138
  return connection;
88
139
  }).catch(function (error) {
140
+ // Check if it's an authentication error
141
+ var isAuthError = error.message && (error.message.includes('Invalid Credentials') ||
142
+ error.message.includes('authentication') ||
143
+ error.message.includes('credentials'));
144
+ if (isAuthError) {
145
+ _this.logger.error('ClientConnectionManager', "Authentication failed for " + address.toString() + ", not retrying: " + error.message);
146
+ // Handle authentication errors specially
147
+ _this.handleAuthenticationError(address);
148
+ throw error;
149
+ }
89
150
  if (retryCount < _this.maxConnectionRetries) {
90
151
  _this.logger.warn('ClientConnectionManager', "Connection attempt " + (retryCount + 1) + " failed for " + address.toString() + ", retrying in " + _this.connectionRetryDelay + "ms");
91
- return new Promise(function (resolve) {
152
+ return new Promise(function (resolve, reject) {
92
153
  setTimeout(function () {
93
- _this.retryConnection(address, asOwner, retryCount + 1).then(resolve).catch(resolve);
154
+ _this.retryConnection(address, asOwner, retryCount + 1).then(resolve).catch(reject);
94
155
  }, _this.connectionRetryDelay);
95
156
  });
96
157
  }
@@ -124,6 +185,196 @@ var ClientConnectionManager = /** @class */ (function (_super) {
124
185
  ClientConnectionManager.prototype.getActiveConnections = function () {
125
186
  return this.establishedConnections;
126
187
  };
188
+ /**
189
+ * Checks if we already have a connection to the given address
190
+ * @param address The address to check
191
+ * @returns true if connection exists and is healthy, false otherwise
192
+ */
193
+ ClientConnectionManager.prototype.hasConnection = function (address) {
194
+ var addressStr = address.toString();
195
+ var connection = this.establishedConnections[addressStr];
196
+ return connection && connection.isAlive();
197
+ };
198
+ /**
199
+ * Forces cleanup of all dead connections
200
+ * This is useful during failover to prevent connection leakage
201
+ */
202
+ ClientConnectionManager.prototype.forceCleanupDeadConnections = function () {
203
+ var _this = this;
204
+ this.logger.info('ClientConnectionManager', 'Forcing cleanup of all dead connections');
205
+ var addressesToRemove = [];
206
+ // Check all established connections
207
+ Object.keys(this.establishedConnections).forEach(function (addressStr) {
208
+ var connection = _this.establishedConnections[addressStr];
209
+ if (connection && !connection.isHealthy()) {
210
+ _this.logger.warn('ClientConnectionManager', "Found dead connection to " + addressStr + ", marking for cleanup");
211
+ addressesToRemove.push(addressStr);
212
+ }
213
+ });
214
+ // Remove dead connections
215
+ addressesToRemove.forEach(function (addressStr) {
216
+ var connection = _this.establishedConnections[addressStr];
217
+ if (connection) {
218
+ _this.destroyConnection(connection.getAddress());
219
+ }
220
+ });
221
+ // Also clean up any pending connections that might be stuck
222
+ Object.keys(this.pendingConnections).forEach(function (addressStr) {
223
+ var pendingConnection = _this.pendingConnections[addressStr];
224
+ if (pendingConnection) {
225
+ _this.logger.warn('ClientConnectionManager', "Cleaning up stuck pending connection to " + addressStr);
226
+ pendingConnection.reject(new Error('Connection cleanup forced'));
227
+ delete _this.pendingConnections[addressStr];
228
+ }
229
+ });
230
+ this.logger.info('ClientConnectionManager', "Cleanup completed. Removed " + addressesToRemove.length + " dead connections");
231
+ };
232
+ /**
233
+ * Gets an existing connection to a specific address if it exists
234
+ * @param address The address to check for existing connection
235
+ * @returns The existing connection or undefined if none exists
236
+ */
237
+ ClientConnectionManager.prototype.getConnection = function (address) {
238
+ var addressStr = address.toString();
239
+ return this.establishedConnections[addressStr];
240
+ };
241
+ /**
242
+ * Gets all established connections
243
+ * @returns Object containing all established connections
244
+ */
245
+ ClientConnectionManager.prototype.getEstablishedConnections = function () {
246
+ return this.establishedConnections;
247
+ };
248
+ /**
249
+ * Gets all pending connections
250
+ * @returns Object containing all pending connections
251
+ */
252
+ ClientConnectionManager.prototype.getPendingConnections = function () {
253
+ return this.pendingConnections;
254
+ };
255
+ /**
256
+ * Updates preserved credentials with server-provided data
257
+ * @param address The address to update credentials for
258
+ * @param newUuid The new UUID from server member event
259
+ */
260
+ ClientConnectionManager.prototype.updatePreservedCredentials = function (address, newUuid) {
261
+ var addressStr = address.toString();
262
+ this.logger.info('ClientConnectionManager', "\uD83D\uDD04 SERVER-FIRST: Updating credentials for " + addressStr + " with server UUID: " + newUuid);
263
+ // Get the current owner UUID from cluster service
264
+ var clusterService = this.client.getClusterService();
265
+ var currentOwnerUuid = clusterService.ownerUuid;
266
+ // Get group config from client
267
+ var groupConfig = this.client.getConfig().groupConfig;
268
+ // Store the server-provided UUID as the authoritative credential
269
+ // Use preserveCredentials to create new credentials if they don't exist
270
+ this.credentialPreservationService.preserveCredentials(address, newUuid, currentOwnerUuid || newUuid, // Use current owner UUID or fallback to member UUID
271
+ groupConfig.name, // Group name from config
272
+ groupConfig.password || '', // Group password from config
273
+ false // Not owner connection
274
+ );
275
+ // Mark that we received a member added event for this address
276
+ this.memberAddedEvents.add(addressStr);
277
+ this.logger.info('ClientConnectionManager', "\uD83D\uDCBE SERVER-FIRST: Stored server credential for " + addressStr + ": uuid=" + newUuid + ", ownerUuid=" + (currentOwnerUuid || newUuid));
278
+ };
279
+ /**
280
+ * Records that a member added event was received from server
281
+ * @param address The address that had a server member added event
282
+ */
283
+ ClientConnectionManager.prototype.recordMemberAddedEvent = function (address) {
284
+ var addressStr = address.toString();
285
+ this.memberAddedEvents.add(addressStr);
286
+ this.logger.debug('ClientConnectionManager', "\uD83D\uDCDD SERVER-FIRST: Recorded server member added event for " + addressStr);
287
+ };
288
+ /**
289
+ * Checks if we received a server member added event for an address
290
+ * @param address The address to check
291
+ * @returns True if server member added event was received
292
+ */
293
+ ClientConnectionManager.prototype.hasMemberAddedEvent = function (address) {
294
+ var addressStr = address.toString();
295
+ return this.memberAddedEvents.has(addressStr);
296
+ };
297
+ /**
298
+ * Clears member added events for an address (useful during failover)
299
+ * @param address The address to clear events for
300
+ */
301
+ ClientConnectionManager.prototype.clearMemberAddedEvents = function (address) {
302
+ var addressStr = address.toString();
303
+ this.memberAddedEvents.delete(addressStr);
304
+ this.logger.debug('ClientConnectionManager', "\uD83D\uDDD1\uFE0F SERVER-FIRST: Cleared member events for " + addressStr);
305
+ };
306
+ /**
307
+ * Updates ALL credentials with server-provided owner UUID
308
+ * @param newOwnerUuid The new owner UUID from server cluster state
309
+ */
310
+ ClientConnectionManager.prototype.updateAllCredentialsWithNewOwnerUuid = function (newOwnerUuid) {
311
+ this.logger.info('ClientConnectionManager', "\uD83D\uDD04 SERVER-FIRST: Updating ALL credentials with server owner UUID: " + newOwnerUuid);
312
+ // Update all preserved credentials with server data
313
+ this.credentialPreservationService.updateAllOwnerUuids(newOwnerUuid);
314
+ // Validate consistency
315
+ var isConsistent = this.credentialPreservationService.validateOwnerUuidConsistency();
316
+ if (isConsistent) {
317
+ this.logger.info('ClientConnectionManager', "\u2705 SERVER-FIRST: All credentials now consistent with server owner UUID: " + newOwnerUuid);
318
+ }
319
+ else {
320
+ this.logger.warn('ClientConnectionManager', "\u26A0\uFE0F SERVER-FIRST: Credential consistency validation failed after server update");
321
+ }
322
+ };
323
+ /**
324
+ * Gets the current owner UUID from server-stored credentials
325
+ * @returns The current owner UUID or null if not found
326
+ */
327
+ ClientConnectionManager.prototype.getCurrentOwnerUuid = function () {
328
+ return this.credentialPreservationService.getCurrentOwnerUuid();
329
+ };
330
+ /**
331
+ * Validates that all stored credentials are consistent with server data
332
+ * @returns True if all credentials are consistent, false otherwise
333
+ */
334
+ ClientConnectionManager.prototype.validateCredentialConsistency = function () {
335
+ return this.credentialPreservationService.validateOwnerUuidConsistency();
336
+ };
337
+ /**
338
+ * Requests current cluster state from server when reconnecting
339
+ * This ensures we have the most up-to-date member information
340
+ * @returns Server cluster state
341
+ */
342
+ ClientConnectionManager.prototype.requestServerClusterState = function () {
343
+ this.logger.info('ClientConnectionManager', "\uD83D\uDD0D SERVER-FIRST: Requesting current cluster state from server...");
344
+ try {
345
+ // Get the current owner connection
346
+ var ownerConnection = this.getOwnerConnection();
347
+ if (!ownerConnection || !ownerConnection.isAlive()) {
348
+ this.logger.warn('ClientConnectionManager', "\u26A0\uFE0F SERVER-FIRST: No active owner connection available for cluster state request");
349
+ return null;
350
+ }
351
+ // Request cluster state from the server
352
+ // This will give us the authoritative member list
353
+ this.logger.info('ClientConnectionManager', "\uD83D\uDCE4 SERVER-FIRST: Sending cluster state request to server...");
354
+ // For now, we'll use the existing cluster service to get member info
355
+ // In a full implementation, we'd send a specific protocol message
356
+ var clusterService = this.client.getClusterService();
357
+ var members = clusterService.getMembers();
358
+ this.logger.info('ClientConnectionManager', "\uD83D\uDCE5 SERVER-FIRST: Received cluster state from server: " + members.length + " members");
359
+ return {
360
+ members: members,
361
+ timestamp: Date.now(),
362
+ source: 'server'
363
+ };
364
+ }
365
+ catch (error) {
366
+ this.logger.error('ClientConnectionManager', "\u274C SERVER-FIRST: Failed to request cluster state from server: " + error.message);
367
+ return null;
368
+ }
369
+ };
370
+ /**
371
+ * Gets the current owner connection for server requests
372
+ * @returns The owner connection or null if not available
373
+ */
374
+ ClientConnectionManager.prototype.getOwnerConnection = function () {
375
+ var clusterService = this.client.getClusterService();
376
+ return clusterService.getOwnerConnection();
377
+ };
127
378
  /**
128
379
  * Returns the {@link ClientConnection} with given {@link Address}. If there is no such connection established,
129
380
  * it first connects to the address and then return the {@link ClientConnection}.
@@ -159,6 +410,13 @@ var ClientConnectionManager = /** @class */ (function (_super) {
159
410
  this.pendingConnections[addressIndex] = connectionResolver;
160
411
  this.retryConnection(address, asOwner)
161
412
  .then(function (clientConnection) {
413
+ // Safety check: ensure we got a proper ClientConnection
414
+ if (!clientConnection || typeof clientConnection.getAddress !== 'function') {
415
+ var error = new Error("Invalid connection object returned: " + typeof clientConnection);
416
+ _this.logger.error('ClientConnectionManager', error.message);
417
+ connectionResolver.reject(error);
418
+ return;
419
+ }
162
420
  _this.establishedConnections[clientConnection.getAddress().toString()] = clientConnection;
163
421
  _this.onConnectionOpened(clientConnection);
164
422
  connectionResolver.resolve(clientConnection);
@@ -184,20 +442,78 @@ var ClientConnectionManager = /** @class */ (function (_super) {
184
442
  */
185
443
  ClientConnectionManager.prototype.destroyConnection = function (address) {
186
444
  var addressStr = address.toString();
445
+ // Clean up pending connections
187
446
  if (this.pendingConnections.hasOwnProperty(addressStr)) {
188
- this.pendingConnections[addressStr].reject(null);
447
+ this.pendingConnections[addressStr].reject(new Error('Connection destroyed'));
448
+ delete this.pendingConnections[addressStr];
189
449
  }
450
+ // Clean up established connections
190
451
  if (this.establishedConnections.hasOwnProperty(addressStr)) {
191
452
  var conn = this.establishedConnections[addressStr];
192
453
  delete this.establishedConnections[addressStr];
193
- conn.close();
454
+ try {
455
+ conn.close();
456
+ }
457
+ catch (error) {
458
+ this.logger.warn('ClientConnectionManager', "Error closing connection to " + addressStr + ":", error);
459
+ }
194
460
  this.onConnectionClosed(conn);
195
461
  }
462
+ // Mark as failed to prevent immediate reconnection attempts
463
+ this.failedConnections.add(addressStr);
464
+ this.logger.debug('ClientConnectionManager', "Connection to " + addressStr + " destroyed and marked as failed");
465
+ };
466
+ /**
467
+ * Cleans up all connections to a specific address during failover
468
+ * @param address The address to cleanup
469
+ */
470
+ ClientConnectionManager.prototype.cleanupConnectionsForFailover = function (address) {
471
+ var addressStr = address.toString();
472
+ this.logger.info('ClientConnectionManager', "Cleaning up all connections to " + addressStr + " for failover");
473
+ // Force cleanup of all connection types
474
+ this.destroyConnection(address);
475
+ // Remove from failed connections to allow reconnection after failover
476
+ this.failedConnections.delete(addressStr);
477
+ };
478
+ /**
479
+ * Clears all stored credentials - used for single-node cluster reset
480
+ */
481
+ ClientConnectionManager.prototype.clearAllCredentials = function () {
482
+ this.logger.info('ClientConnectionManager', '🧹 Clearing all stored credentials for cluster reset');
483
+ // Clear all stored credentials
484
+ this.credentialPreservationService.clearAllCredentials();
485
+ // Clear all failed connections to allow fresh reconnection attempts
486
+ this.failedConnections.clear();
487
+ this.logger.info('ClientConnectionManager', '✅ All credentials cleared, ready for fresh authentication');
488
+ };
489
+ /**
490
+ * Handles authentication errors by clearing failed connections and allowing retry
491
+ * @param address The address that had authentication issues
492
+ */
493
+ ClientConnectionManager.prototype.handleAuthenticationError = function (address) {
494
+ var addressStr = address.toString();
495
+ this.logger.warn('ClientConnectionManager', "Handling authentication error for " + addressStr);
496
+ // Clear from failed connections to allow retry with fresh credentials
497
+ this.failedConnections.delete(addressStr);
498
+ // Also clear any established connections to this address
499
+ if (this.establishedConnections.hasOwnProperty(addressStr)) {
500
+ this.logger.info('ClientConnectionManager', "Clearing established connection to " + addressStr + " due to auth error");
501
+ this.destroyConnection(address);
502
+ }
503
+ // Clear any pending connections
504
+ if (this.pendingConnections.hasOwnProperty(addressStr)) {
505
+ this.logger.info('ClientConnectionManager', "Clearing pending connection to " + addressStr + " due to auth error");
506
+ this.pendingConnections[addressStr].reject(new Error('Authentication error, connection cleared'));
507
+ delete this.pendingConnections[addressStr];
508
+ }
196
509
  };
197
510
  ClientConnectionManager.prototype.shutdown = function () {
198
511
  if (this.connectionHealthCheckInterval) {
199
512
  clearInterval(this.connectionHealthCheckInterval);
200
513
  }
514
+ if (this.connectionCleanupTask) {
515
+ clearInterval(this.connectionCleanupTask);
516
+ }
201
517
  for (var pending in this.pendingConnections) {
202
518
  this.pendingConnections[pending].reject(new HazelcastError_1.ClientNotActiveError('Client is shutting down!'));
203
519
  }
@@ -209,9 +525,20 @@ var ClientConnectionManager = /** @class */ (function (_super) {
209
525
  ClientConnectionManager.prototype.triggerConnect = function (address, asOwner) {
210
526
  var _this = this;
211
527
  if (!asOwner) {
212
- if (this.client.getClusterService().getOwnerConnection() == null) {
213
- var error = new HazelcastError_1.IllegalStateError('Owner connection is not available!');
214
- return Promise.reject(error);
528
+ var ownerConnection = this.client.getClusterService().getOwnerConnection();
529
+ if (ownerConnection == null) {
530
+ // Check if this is a single-node scenario
531
+ var knownAddresses = this.client.getClusterService().getKnownAddresses();
532
+ var isSingleNode = knownAddresses.length === 1;
533
+ if (isSingleNode) {
534
+ // Allow connection in single-node scenario
535
+ this.logger.info('ClientConnectionManager', "\uD83D\uDD17 SINGLE-NODE: Allowing connection to " + address.toString() + " (only node in cluster)");
536
+ }
537
+ else {
538
+ // Multi-node scenario - require owner connection (preserve existing logic)
539
+ var error = new HazelcastError_1.IllegalStateError('Owner connection is not available!');
540
+ return Promise.reject(error);
541
+ }
215
542
  }
216
543
  }
217
544
  if (this.client.getConfig().networkConfig.sslConfig.enabled) {
@@ -290,8 +617,65 @@ var ClientConnectionManager = /** @class */ (function (_super) {
290
617
  this.emit(EMIT_CONNECTION_OPENED, connection);
291
618
  };
292
619
  ClientConnectionManager.prototype.authenticate = function (connection, ownerConnection) {
620
+ var _this = this;
621
+ var address = connection.getAddress();
622
+ var addressStr = address.toString();
623
+ this.logger.info('ClientConnectionManager', "\uD83D\uDD10 Starting authentication for " + addressStr + " (owner=" + ownerConnection + ")");
624
+ // Check if we have stored credentials for this address
625
+ var storedCredentials = this.credentialPreservationService.restoreCredentials(address);
626
+ var hasMemberAddedEvent = this.hasMemberAddedEvent(address);
627
+ if (storedCredentials) {
628
+ this.logger.info('ClientConnectionManager', "\uD83D\uDCCB Using STORED credentials for " + addressStr + ":");
629
+ this.logger.info('ClientConnectionManager', " - UUID: " + storedCredentials.uuid);
630
+ this.logger.info('ClientConnectionManager', " - Owner UUID: " + storedCredentials.ownerUuid);
631
+ this.logger.info('ClientConnectionManager', " - Group Name: " + storedCredentials.groupName);
632
+ this.logger.info('ClientConnectionManager', " - Member Added Event: " + (hasMemberAddedEvent ? 'YES' : 'NO'));
633
+ }
634
+ else {
635
+ this.logger.info('ClientConnectionManager', "\uD83D\uDCCB No stored credentials found for " + addressStr + ", using fresh authentication");
636
+ }
637
+ // Log current cluster state for debugging
638
+ var clusterService = this.client.getClusterService();
639
+ this.logger.info('ClientConnectionManager', "\uD83D\uDD0D Current cluster state for " + addressStr + ":");
640
+ this.logger.info('ClientConnectionManager', " - Client UUID: " + (clusterService.uuid || 'NOT SET'));
641
+ this.logger.info('ClientConnectionManager', " - Client Owner UUID: " + (clusterService.ownerUuid || 'NOT SET'));
642
+ this.logger.info('ClientConnectionManager', " - Active Members: " + clusterService.getMembers().length);
643
+ // Log what we're about to send
644
+ this.logger.info('ClientConnectionManager', "\uD83D\uDCE4 Sending authentication request to " + addressStr + " with:");
645
+ this.logger.info('ClientConnectionManager', " - Owner Connection: " + ownerConnection);
646
+ this.logger.info('ClientConnectionManager', " - Using Stored Credentials: " + (storedCredentials ? 'YES' : 'NO'));
293
647
  var authenticator = new ConnectionAuthenticator_1.ConnectionAuthenticator(connection, this.client);
294
- return authenticator.authenticate(ownerConnection);
648
+ // Use normal authentication flow - server handles everything
649
+ return authenticator.authenticate(ownerConnection)
650
+ .then(function () {
651
+ _this.logger.info('ClientConnectionManager', "\u2705 Authentication successful for " + addressStr);
652
+ // After successful authentication, store the new credentials from server
653
+ if (ownerConnection) {
654
+ var newUuid = clusterService.uuid;
655
+ var newOwnerUuid = clusterService.ownerUuid;
656
+ _this.logger.info('ClientConnectionManager', "\uD83D\uDCBE Storing new credentials from server for " + addressStr + ":");
657
+ _this.logger.info('ClientConnectionManager', " - New UUID: " + newUuid);
658
+ _this.logger.info('ClientConnectionManager', " - New Owner UUID: " + newOwnerUuid);
659
+ // Store the new credentials
660
+ _this.credentialPreservationService.updateCredentials(address, newUuid, newOwnerUuid);
661
+ }
662
+ })
663
+ .catch(function (error) {
664
+ _this.logger.error('ClientConnectionManager', "\u274C Authentication FAILED for " + addressStr + ":");
665
+ _this.logger.error('ClientConnectionManager', " - Error: " + error.message);
666
+ _this.logger.error('ClientConnectionManager', " - Used Stored Credentials: " + (storedCredentials ? 'YES' : 'NO'));
667
+ if (storedCredentials) {
668
+ _this.logger.error('ClientConnectionManager', " - Failed Credentials: uuid=" + storedCredentials.uuid + ", ownerUuid=" + storedCredentials.ownerUuid);
669
+ }
670
+ _this.logger.error('ClientConnectionManager', " - Current Client UUID: " + (clusterService.uuid || 'NOT SET'));
671
+ _this.logger.error('ClientConnectionManager', " - Current Client Owner UUID: " + (clusterService.ownerUuid || 'NOT SET'));
672
+ // Clear failed credentials
673
+ if (storedCredentials) {
674
+ _this.logger.info('ClientConnectionManager', "\uD83D\uDDD1\uFE0F Clearing failed credentials for " + addressStr);
675
+ _this.credentialPreservationService.clearCredentialsForAddress(address);
676
+ }
677
+ throw error;
678
+ });
295
679
  };
296
680
  return ClientConnectionManager;
297
681
  }(events_1.EventEmitter));
@@ -6,6 +6,8 @@ import { ClientInfo } from '../ClientInfo';
6
6
  import HazelcastClient from '../HazelcastClient';
7
7
  import { MemberSelector } from '../core/MemberSelector';
8
8
  import { MembershipListener } from '../core/MembershipListener';
9
+ import Address = require('../Address');
10
+ import { HazelcastFailoverManager } from './HazelcastFailoverManager';
9
11
  export declare enum MemberEvent {
10
12
  ADDED = 1,
11
13
  REMOVED = 2,
@@ -33,6 +35,9 @@ export declare class ClusterService {
33
35
  private readonly failoverCooldown;
34
36
  private downAddresses;
35
37
  private readonly addressBlockDuration;
38
+ private reconnectionTask;
39
+ private readonly reconnectionInterval;
40
+ private failoverManager;
36
41
  constructor(client: HazelcastClient);
37
42
  /**
38
43
  * Starts cluster service.
@@ -45,6 +50,18 @@ export declare class ClusterService {
45
50
  */
46
51
  connectToCluster(): Promise<void>;
47
52
  getPossibleMemberAddresses(): Promise<string[]>;
53
+ /**
54
+ * Returns the owner connection if available
55
+ */
56
+ getOwnerConnection(): ClientConnection | null;
57
+ /**
58
+ * Returns whether failover is currently in progress
59
+ */
60
+ isFailoverInProgress(): boolean;
61
+ /**
62
+ * Returns the list of known addresses in the cluster
63
+ */
64
+ getKnownAddresses(): Address[];
48
65
  /**
49
66
  * Returns the list of members in the cluster.
50
67
  * @returns
@@ -61,11 +78,6 @@ export declare class ClusterService {
61
78
  * @returns {ClientInfo}
62
79
  */
63
80
  getClientInfo(): ClientInfo;
64
- /**
65
- * Returns the connection associated with owner node of this client.
66
- * @returns {ClientConnection}
67
- */
68
- getOwnerConnection(): ClientConnection;
69
81
  /**
70
82
  * Adds MembershipListener to listen for membership updates. There is no check for duplicate registrations,
71
83
  * so if you register the listener twice, it will get events twice.
@@ -85,15 +97,96 @@ export declare class ClusterService {
85
97
  private onConnectionClosed(connection);
86
98
  private onHeartbeatStopped(connection);
87
99
  private triggerFailover();
100
+ /**
101
+ * Handles single-node cluster reset - treats node restart as fresh cluster
102
+ */
103
+ private handleSingleNodeClusterReset();
104
+ /**
105
+ * Handles multi-node failover - preserves existing logic
106
+ */
107
+ private handleMultiNodeFailover();
108
+ /**
109
+ * Attempts emergency recovery when failover fails
110
+ */
111
+ private attemptEmergencyRecovery();
88
112
  private isAddressKnownDown(address);
89
113
  private markAddressAsDown(address);
90
114
  private getDownAddressesInfo();
91
115
  private tryConnectingToAddresses(index, remainingAttemptLimit, attemptPeriod, cause?);
92
116
  private handleMember(member, eventType);
93
117
  private handleMemberList(members);
118
+ /**
119
+ * Proactively opens connections to all non-owner cluster members.
120
+ * Required for smart routing: the client needs a live connection to every
121
+ * node so it can route operations to the correct partition owner.
122
+ * Failures are silently ignored — the periodic reconnection task will retry.
123
+ */
124
+ private connectToNonOwnerMembers(members);
94
125
  private detectMembershipEvents(prevMembers);
95
126
  private fireMembershipEvent(membershipEvent);
96
127
  private handleMemberAttributeChange(uuid, key, operationType, value);
97
128
  private memberAdded(member);
98
129
  private memberRemoved(member);
130
+ /**
131
+ * Performs comprehensive credential cleanup when cluster membership changes
132
+ * This ensures ALL credentials are consistent with the current cluster owner UUID
133
+ * @param connectionManager The connection manager instance
134
+ * @param currentClusterOwnerUuid The current cluster owner UUID
135
+ */
136
+ /**
137
+ * Handles member added event - SERVER-FIRST APPROACH
138
+ * We trust what the server tells us and store it as credentials
139
+ * @param member The member that was added
140
+ */
141
+ private handleMemberAdded(member);
142
+ /**
143
+ * Finds the current owner from the cluster state
144
+ * @returns The current owner member or null if not found
145
+ */
146
+ private findCurrentOwner();
147
+ /**
148
+ * Logs the current state for debugging purposes
149
+ */
150
+ private logCurrentState();
151
+ private startReconnectionTask();
152
+ /**
153
+ * Starts a periodic task to log the current state for debugging
154
+ */
155
+ private startStateLoggingTask();
156
+ private attemptReconnectionToFailedNodes();
157
+ /**
158
+ * Attempts to establish a connection to a specific address
159
+ * @param address The address to reconnect to
160
+ */
161
+ private attemptReconnectionToAddress(address);
162
+ /**
163
+ * Evaluates whether we should switch ownership to a reconnected node
164
+ * @param address The address of the reconnected node
165
+ * @param connection The connection to the reconnected node
166
+ */
167
+ private evaluateOwnershipChange(address, connection);
168
+ /**
169
+ * Promotes a connection to owner status
170
+ * @param connection The connection to promote
171
+ * @param address The address of the promoted connection
172
+ */
173
+ private promoteToOwner(connection, address);
174
+ /**
175
+ * Marks an address as down with a custom block duration
176
+ * @param address The address to mark as down
177
+ * @param blockDuration The duration to block the address (in milliseconds)
178
+ */
179
+ private markAddressAsDownWithDuration(address, blockDuration);
180
+ /**
181
+ * Handles ownership change when failover occurs
182
+ * @param newOwnerAddress The address of the new owner
183
+ * @param newOwnerConnection The connection to the new owner
184
+ */
185
+ handleOwnershipChange(newOwnerAddress: Address, newOwnerConnection: ClientConnection): void;
186
+ /**
187
+ * Gets the failover manager for external access
188
+ * @returns The failover manager instance
189
+ */
190
+ getFailoverManager(): HazelcastFailoverManager;
191
+ shutdown(): void;
99
192
  }