@celerispay/hazelcast-client 3.12.5 → 3.12.7

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,67 @@ 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
+ * Handles authentication errors by clearing failed connections and allowing retry
480
+ * @param address The address that had authentication issues
481
+ */
482
+ ClientConnectionManager.prototype.handleAuthenticationError = function (address) {
483
+ var addressStr = address.toString();
484
+ this.logger.warn('ClientConnectionManager', "Handling authentication error for " + addressStr);
485
+ // Clear from failed connections to allow retry with fresh credentials
486
+ this.failedConnections.delete(addressStr);
487
+ // Also clear any established connections to this address
488
+ if (this.establishedConnections.hasOwnProperty(addressStr)) {
489
+ this.logger.info('ClientConnectionManager', "Clearing established connection to " + addressStr + " due to auth error");
490
+ this.destroyConnection(address);
491
+ }
492
+ // Clear any pending connections
493
+ if (this.pendingConnections.hasOwnProperty(addressStr)) {
494
+ this.logger.info('ClientConnectionManager', "Clearing pending connection to " + addressStr + " due to auth error");
495
+ this.pendingConnections[addressStr].reject(new Error('Authentication error, connection cleared'));
496
+ delete this.pendingConnections[addressStr];
497
+ }
196
498
  };
197
499
  ClientConnectionManager.prototype.shutdown = function () {
198
500
  if (this.connectionHealthCheckInterval) {
199
501
  clearInterval(this.connectionHealthCheckInterval);
200
502
  }
503
+ if (this.connectionCleanupTask) {
504
+ clearInterval(this.connectionCleanupTask);
505
+ }
201
506
  for (var pending in this.pendingConnections) {
202
507
  this.pendingConnections[pending].reject(new HazelcastError_1.ClientNotActiveError('Client is shutting down!'));
203
508
  }
@@ -290,8 +595,65 @@ var ClientConnectionManager = /** @class */ (function (_super) {
290
595
  this.emit(EMIT_CONNECTION_OPENED, connection);
291
596
  };
292
597
  ClientConnectionManager.prototype.authenticate = function (connection, ownerConnection) {
598
+ var _this = this;
599
+ var address = connection.getAddress();
600
+ var addressStr = address.toString();
601
+ this.logger.info('ClientConnectionManager', "\uD83D\uDD10 Starting authentication for " + addressStr + " (owner=" + ownerConnection + ")");
602
+ // Check if we have stored credentials for this address
603
+ var storedCredentials = this.credentialPreservationService.restoreCredentials(address);
604
+ var hasMemberAddedEvent = this.hasMemberAddedEvent(address);
605
+ if (storedCredentials) {
606
+ this.logger.info('ClientConnectionManager', "\uD83D\uDCCB Using STORED credentials for " + addressStr + ":");
607
+ this.logger.info('ClientConnectionManager', " - UUID: " + storedCredentials.uuid);
608
+ this.logger.info('ClientConnectionManager', " - Owner UUID: " + storedCredentials.ownerUuid);
609
+ this.logger.info('ClientConnectionManager', " - Group Name: " + storedCredentials.groupName);
610
+ this.logger.info('ClientConnectionManager', " - Member Added Event: " + (hasMemberAddedEvent ? 'YES' : 'NO'));
611
+ }
612
+ else {
613
+ this.logger.info('ClientConnectionManager', "\uD83D\uDCCB No stored credentials found for " + addressStr + ", using fresh authentication");
614
+ }
615
+ // Log current cluster state for debugging
616
+ var clusterService = this.client.getClusterService();
617
+ this.logger.info('ClientConnectionManager', "\uD83D\uDD0D Current cluster state for " + addressStr + ":");
618
+ this.logger.info('ClientConnectionManager', " - Client UUID: " + (clusterService.uuid || 'NOT SET'));
619
+ this.logger.info('ClientConnectionManager', " - Client Owner UUID: " + (clusterService.ownerUuid || 'NOT SET'));
620
+ this.logger.info('ClientConnectionManager', " - Active Members: " + clusterService.getMembers().length);
621
+ // Log what we're about to send
622
+ this.logger.info('ClientConnectionManager', "\uD83D\uDCE4 Sending authentication request to " + addressStr + " with:");
623
+ this.logger.info('ClientConnectionManager', " - Owner Connection: " + ownerConnection);
624
+ this.logger.info('ClientConnectionManager', " - Using Stored Credentials: " + (storedCredentials ? 'YES' : 'NO'));
293
625
  var authenticator = new ConnectionAuthenticator_1.ConnectionAuthenticator(connection, this.client);
294
- return authenticator.authenticate(ownerConnection);
626
+ // Use normal authentication flow - server handles everything
627
+ return authenticator.authenticate(ownerConnection)
628
+ .then(function () {
629
+ _this.logger.info('ClientConnectionManager', "\u2705 Authentication successful for " + addressStr);
630
+ // After successful authentication, store the new credentials from server
631
+ if (ownerConnection) {
632
+ var newUuid = clusterService.uuid;
633
+ var newOwnerUuid = clusterService.ownerUuid;
634
+ _this.logger.info('ClientConnectionManager', "\uD83D\uDCBE Storing new credentials from server for " + addressStr + ":");
635
+ _this.logger.info('ClientConnectionManager', " - New UUID: " + newUuid);
636
+ _this.logger.info('ClientConnectionManager', " - New Owner UUID: " + newOwnerUuid);
637
+ // Store the new credentials
638
+ _this.credentialPreservationService.updateCredentials(address, newUuid, newOwnerUuid);
639
+ }
640
+ })
641
+ .catch(function (error) {
642
+ _this.logger.error('ClientConnectionManager', "\u274C Authentication FAILED for " + addressStr + ":");
643
+ _this.logger.error('ClientConnectionManager', " - Error: " + error.message);
644
+ _this.logger.error('ClientConnectionManager', " - Used Stored Credentials: " + (storedCredentials ? 'YES' : 'NO'));
645
+ if (storedCredentials) {
646
+ _this.logger.error('ClientConnectionManager', " - Failed Credentials: uuid=" + storedCredentials.uuid + ", ownerUuid=" + storedCredentials.ownerUuid);
647
+ }
648
+ _this.logger.error('ClientConnectionManager', " - Current Client UUID: " + (clusterService.uuid || 'NOT SET'));
649
+ _this.logger.error('ClientConnectionManager', " - Current Client Owner UUID: " + (clusterService.ownerUuid || 'NOT SET'));
650
+ // Clear failed credentials
651
+ if (storedCredentials) {
652
+ _this.logger.info('ClientConnectionManager', "\uD83D\uDDD1\uFE0F Clearing failed credentials for " + addressStr);
653
+ _this.credentialPreservationService.clearCredentialsForAddress(address);
654
+ }
655
+ throw error;
656
+ });
295
657
  };
296
658
  return ClientConnectionManager;
297
659
  }(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,10 @@ 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;
48
57
  /**
49
58
  * Returns the list of members in the cluster.
50
59
  * @returns
@@ -61,11 +70,6 @@ export declare class ClusterService {
61
70
  * @returns {ClientInfo}
62
71
  */
63
72
  getClientInfo(): ClientInfo;
64
- /**
65
- * Returns the connection associated with owner node of this client.
66
- * @returns {ClientConnection}
67
- */
68
- getOwnerConnection(): ClientConnection;
69
73
  /**
70
74
  * Adds MembershipListener to listen for membership updates. There is no check for duplicate registrations,
71
75
  * so if you register the listener twice, it will get events twice.
@@ -85,6 +89,10 @@ export declare class ClusterService {
85
89
  private onConnectionClosed(connection);
86
90
  private onHeartbeatStopped(connection);
87
91
  private triggerFailover();
92
+ /**
93
+ * Attempts emergency recovery when failover fails
94
+ */
95
+ private attemptEmergencyRecovery();
88
96
  private isAddressKnownDown(address);
89
97
  private markAddressAsDown(address);
90
98
  private getDownAddressesInfo();
@@ -96,4 +104,66 @@ export declare class ClusterService {
96
104
  private handleMemberAttributeChange(uuid, key, operationType, value);
97
105
  private memberAdded(member);
98
106
  private memberRemoved(member);
107
+ /**
108
+ * Performs comprehensive credential cleanup when cluster membership changes
109
+ * This ensures ALL credentials are consistent with the current cluster owner UUID
110
+ * @param connectionManager The connection manager instance
111
+ * @param currentClusterOwnerUuid The current cluster owner UUID
112
+ */
113
+ /**
114
+ * Handles member added event - SERVER-FIRST APPROACH
115
+ * We trust what the server tells us and store it as credentials
116
+ * @param member The member that was added
117
+ */
118
+ private handleMemberAdded(member);
119
+ /**
120
+ * Finds the current owner from the cluster state
121
+ * @returns The current owner member or null if not found
122
+ */
123
+ private findCurrentOwner();
124
+ /**
125
+ * Logs the current state for debugging purposes
126
+ */
127
+ private logCurrentState();
128
+ private startReconnectionTask();
129
+ /**
130
+ * Starts a periodic task to log the current state for debugging
131
+ */
132
+ private startStateLoggingTask();
133
+ private attemptReconnectionToFailedNodes();
134
+ /**
135
+ * Attempts to establish a connection to a specific address
136
+ * @param address The address to reconnect to
137
+ */
138
+ private attemptReconnectionToAddress(address);
139
+ /**
140
+ * Evaluates whether we should switch ownership to a reconnected node
141
+ * @param address The address of the reconnected node
142
+ * @param connection The connection to the reconnected node
143
+ */
144
+ private evaluateOwnershipChange(address, connection);
145
+ /**
146
+ * Promotes a connection to owner status
147
+ * @param connection The connection to promote
148
+ * @param address The address of the promoted connection
149
+ */
150
+ private promoteToOwner(connection, address);
151
+ /**
152
+ * Marks an address as down with a custom block duration
153
+ * @param address The address to mark as down
154
+ * @param blockDuration The duration to block the address (in milliseconds)
155
+ */
156
+ private markAddressAsDownWithDuration(address, blockDuration);
157
+ /**
158
+ * Handles ownership change when failover occurs
159
+ * @param newOwnerAddress The address of the new owner
160
+ * @param newOwnerConnection The connection to the new owner
161
+ */
162
+ handleOwnershipChange(newOwnerAddress: Address, newOwnerConnection: ClientConnection): void;
163
+ /**
164
+ * Gets the failover manager for external access
165
+ * @returns The failover manager instance
166
+ */
167
+ getFailoverManager(): HazelcastFailoverManager;
168
+ shutdown(): void;
99
169
  }