@celerispay/hazelcast-client 3.12.5-8 → 3.12.7-2

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.
@@ -23,9 +23,6 @@ var PartitionService = /** @class */ (function () {
23
23
  this.partitionMap = {};
24
24
  this.lastRefreshTime = 0;
25
25
  this.minRefreshInterval = 2000; // Minimum 2 seconds between refreshes
26
- this.refreshInProgress = false;
27
- this.maxRefreshRetries = 3;
28
- this.refreshRetryCount = 0;
29
26
  this.client = client;
30
27
  this.logger = client.getLoggingService().getLogger();
31
28
  this.isShutdown = false;
@@ -37,7 +34,6 @@ var PartitionService = /** @class */ (function () {
37
34
  PartitionService.prototype.shutdown = function () {
38
35
  clearInterval(this.partitionRefreshTask);
39
36
  this.isShutdown = true;
40
- this.refreshInProgress = false;
41
37
  };
42
38
  /**
43
39
  * Clears the partition table, forcing a refresh on next operation
@@ -47,7 +43,6 @@ var PartitionService = /** @class */ (function () {
47
43
  this.partitionMap = {};
48
44
  this.partitionCount = 0;
49
45
  this.lastRefreshTime = 0;
50
- this.refreshRetryCount = 0;
51
46
  };
52
47
  /**
53
48
  * Refreshes the internal partition table.
@@ -62,50 +57,26 @@ var PartitionService = /** @class */ (function () {
62
57
  this.logger.debug('PartitionService', 'Skipping refresh, too soon since last refresh');
63
58
  return Promise.resolve();
64
59
  }
65
- if (this.refreshInProgress) {
66
- this.logger.debug('PartitionService', 'Refresh already in progress, skipping');
67
- return Promise.resolve();
68
- }
69
60
  var ownerConnection = this.client.getClusterService().getOwnerConnection();
70
61
  if (ownerConnection == null) {
71
62
  this.logger.warn('PartitionService', 'Cannot refresh partitions, no owner connection available');
72
- // Return a rejected promise instead of resolved to indicate failure
73
- return Promise.reject(new Error('No owner connection available for partition refresh'));
63
+ return Promise.resolve();
74
64
  }
75
- this.refreshInProgress = true;
76
65
  var clientMessage = GetPartitionsCodec.encodeRequest();
77
- // Add timeout to prevent hanging
78
- var refreshPromise = this.client.getInvocationService()
66
+ return this.client.getInvocationService()
79
67
  .invokeOnConnection(ownerConnection, clientMessage)
80
68
  .then(function (response) {
81
69
  var receivedPartitionMap = GetPartitionsCodec.decodeResponse(response);
82
70
  _this.partitionMap = receivedPartitionMap;
83
71
  _this.partitionCount = Object.keys(_this.partitionMap).length;
84
72
  _this.lastRefreshTime = now;
85
- _this.refreshRetryCount = 0; // Reset retry count on success
86
73
  _this.logger.debug('PartitionService', "Refreshed partition table with " + _this.partitionCount + " partitions");
87
74
  }).catch(function (e) {
88
75
  if (_this.client.getLifecycleService().isRunning()) {
89
76
  _this.logger.warn('PartitionService', 'Error while fetching cluster partition table from '
90
- + _this.client.getClusterService().ownerUuid, e);
91
- // Increment retry count and clear table if too many failures
92
- _this.refreshRetryCount++;
93
- if (_this.refreshRetryCount >= _this.maxRefreshRetries) {
94
- _this.logger.error('PartitionService', "Max refresh retries (" + _this.maxRefreshRetries + ") exceeded, clearing partition table");
95
- _this.clearPartitionTable();
96
- }
77
+ + _this.client.getClusterService().ownerUuid || 'unknown', e);
97
78
  }
98
- throw e; // Re-throw the error to propagate it
99
- }).finally(function () {
100
- _this.refreshInProgress = false;
101
- });
102
- // Add timeout to prevent hanging
103
- var timeoutPromise = new Promise(function (_, reject) {
104
- setTimeout(function () {
105
- reject(new Error('Partition refresh timed out'));
106
- }, 10000); // 10 second timeout
107
79
  });
108
- return Promise.race([refreshPromise, timeoutPromise]);
109
80
  };
110
81
  /**
111
82
  * Returns the {@link Address} of the node which owns given partition id.
@@ -231,20 +231,28 @@ var ClientConnection = /** @class */ (function () {
231
231
  _this.closedTime = Date.now();
232
232
  _this.logger.debug('ClientConnection', "Socket closed for " + _this.address.toString());
233
233
  });
234
- // Handle socket end events
234
+ // Handle socket end events - be more resilient to temporary disconnections
235
235
  this.socket.on('end', function () {
236
- _this.closedTime = Date.now();
237
236
  _this.logger.debug('ClientConnection', "Socket ended by remote for " + _this.address.toString());
237
+ // Don't immediately mark as closed - this could be a temporary network hiccup
238
+ // Only mark as closed if we're certain it's a real disconnection
239
+ // The health check will handle this more gracefully
238
240
  });
239
- // Handle socket errors
241
+ // Handle socket errors - be more resilient
240
242
  this.socket.on('error', function (error) {
241
243
  _this.logger.warn('ClientConnection', "Socket error for " + _this.address.toString() + ":", error);
242
- // Don't set closedTime here as the socket might still be usable
244
+ // Only mark as closed for specific error types that indicate real disconnection
245
+ if (error.code === 'ECONNRESET' || error.code === 'EPIPE') {
246
+ _this.logger.warn('ClientConnection', "Critical socket error for " + _this.address.toString() + ", marking as closed");
247
+ _this.closedTime = Date.now();
248
+ }
249
+ // For other errors, don't immediately close - let health check handle it
243
250
  });
244
- // Handle socket timeout
251
+ // Handle socket timeout - be more resilient
245
252
  this.socket.on('timeout', function () {
246
253
  _this.logger.warn('ClientConnection', "Socket timeout for " + _this.address.toString());
247
254
  // Don't set closedTime here as the socket might still be usable
255
+ // Let the health check handle this more gracefully
248
256
  });
249
257
  };
250
258
  /**
@@ -280,8 +288,27 @@ var ClientConnection = /** @class */ (function () {
280
288
  * Closes this connection.
281
289
  */
282
290
  ClientConnection.prototype.close = function () {
291
+ var addressStr = this.address.toString();
292
+ // Log who is initiating the disconnection
293
+ this.logger.info('ClientConnection', "\uD83D\uDD0C CLIENT INITIATED DISCONNECTION from " + addressStr);
294
+ // Log the call stack to understand why this is being called
295
+ this.logger.info('ClientConnection', "\uD83D\uDD0D Close Connection Call Stack for " + addressStr + ":");
296
+ var stack = new Error().stack;
297
+ if (stack) {
298
+ this.logger.info('ClientConnection', " - Called from: " + stack.split('\n').slice(1, 4).join(' | '));
299
+ }
300
+ // Log connection state at closure
301
+ this.logger.info('ClientConnection', "\uD83D\uDCCA Connection State at Closure for " + addressStr + ":");
302
+ this.logger.info('ClientConnection', " - Is Alive: " + this.isAlive());
303
+ this.logger.info('ClientConnection', " - Is Healthy: " + this.isHealthy());
304
+ this.logger.info('ClientConnection', " - Is Heartbeating: " + this.isHeartbeating());
305
+ this.logger.info('ClientConnection', " - Is Owner: " + this.isAuthenticatedAsOwner());
306
+ this.logger.info('ClientConnection', " - Last Read: " + (this.lastReadTimeMillis > 0 ? Math.round((Date.now() - this.lastReadTimeMillis) / 1000) + 's ago' : 'never'));
307
+ this.logger.info('ClientConnection', " - Last Write: " + (this.lastWriteTimeMillis > 0 ? Math.round((Date.now() - this.lastWriteTimeMillis) / 1000) + 's ago' : 'never'));
308
+ this.logger.info('ClientConnection', " - Connection Age: " + Math.round((Date.now() - this.startTime) / 1000) + "s");
283
309
  this.socket.end();
284
310
  this.closedTime = Date.now();
311
+ this.logger.info('ClientConnection', "\u2705 Disconnection completed for " + addressStr);
285
312
  };
286
313
  /**
287
314
  * Checks if the connection is alive and healthy
@@ -300,11 +327,12 @@ var ClientConnection = /** @class */ (function () {
300
327
  if (!this.socket.writable) {
301
328
  return false;
302
329
  }
303
- // Check if we've received data recently (within last 30 seconds)
330
+ // More resilient data freshness check - only fail if REALLY stale
304
331
  var now = Date.now();
305
- var lastReadThreshold = 30000; // 30 seconds
332
+ var lastReadThreshold = 120000; // Increased from 30s to 2 minutes for production resilience
306
333
  if (this.lastReadTimeMillis > 0 && (now - this.lastReadTimeMillis) > lastReadThreshold) {
307
- // If we haven't received data for a while, the connection might be stale
334
+ // Only mark as dead if we haven't received data for 2 minutes
335
+ // This prevents false drops due to temporary network hiccups
308
336
  return false;
309
337
  }
310
338
  return true;
@@ -314,12 +342,14 @@ var ClientConnection = /** @class */ (function () {
314
342
  * @returns true if connection is healthy, false otherwise
315
343
  */
316
344
  ClientConnection.prototype.isHealthy = function () {
345
+ var addressStr = this.address.toString();
317
346
  if (!this.isAlive()) {
318
347
  return false;
319
348
  }
320
- // Additional health checks can be added here
321
- // For now, just check if we're still heartbeating
322
- return this.isHeartbeating();
349
+ if (!this.isHeartbeating()) {
350
+ return false;
351
+ }
352
+ return true;
323
353
  };
324
354
  ClientConnection.prototype.isHeartbeating = function () {
325
355
  return this.heartbeating;
@@ -67,6 +67,56 @@ export declare class ClientConnectionManager extends EventEmitter {
67
67
  getPendingConnections(): {
68
68
  [address: string]: Promise.Resolver<ClientConnection>;
69
69
  };
70
+ private credentialPreservationService;
71
+ private memberAddedEvents;
72
+ /**
73
+ * Updates preserved credentials with server-provided data
74
+ * @param address The address to update credentials for
75
+ * @param newUuid The new UUID from server member event
76
+ */
77
+ updatePreservedCredentials(address: Address, newUuid: string): void;
78
+ /**
79
+ * Records that a member added event was received from server
80
+ * @param address The address that had a server member added event
81
+ */
82
+ recordMemberAddedEvent(address: Address): void;
83
+ /**
84
+ * Checks if we received a server member added event for an address
85
+ * @param address The address to check
86
+ * @returns True if server member added event was received
87
+ */
88
+ hasMemberAddedEvent(address: Address): boolean;
89
+ /**
90
+ * Clears member added events for an address (useful during failover)
91
+ * @param address The address to clear events for
92
+ */
93
+ clearMemberAddedEvents(address: Address): void;
94
+ /**
95
+ * Updates ALL credentials with server-provided owner UUID
96
+ * @param newOwnerUuid The new owner UUID from server cluster state
97
+ */
98
+ updateAllCredentialsWithNewOwnerUuid(newOwnerUuid: string): void;
99
+ /**
100
+ * Gets the current owner UUID from server-stored credentials
101
+ * @returns The current owner UUID or null if not found
102
+ */
103
+ getCurrentOwnerUuid(): string | null;
104
+ /**
105
+ * Validates that all stored credentials are consistent with server data
106
+ * @returns True if all credentials are consistent, false otherwise
107
+ */
108
+ validateCredentialConsistency(): boolean;
109
+ /**
110
+ * Requests current cluster state from server when reconnecting
111
+ * This ensures we have the most up-to-date member information
112
+ * @returns Server cluster state
113
+ */
114
+ requestServerClusterState(): any;
115
+ /**
116
+ * Gets the current owner connection for server requests
117
+ * @returns The owner connection or null if not available
118
+ */
119
+ private getOwnerConnection();
70
120
  /**
71
121
  * Returns the {@link ClientConnection} with given {@link Address}. If there is no such connection established,
72
122
  * it first connects to the address and then return the {@link ClientConnection}.
@@ -85,6 +135,10 @@ export declare class ClientConnectionManager extends EventEmitter {
85
135
  * @param address The address to cleanup
86
136
  */
87
137
  cleanupConnectionsForFailover(address: Address): void;
138
+ /**
139
+ * Clears all stored credentials - used for single-node cluster reset
140
+ */
141
+ clearAllCredentials(): void;
88
142
  /**
89
143
  * Handles authentication errors by clearing failed connections and allowing retry
90
144
  * @param address The address that had authentication issues
@@ -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
  /**
@@ -51,10 +52,13 @@ var ClientConnectionManager = /** @class */ (function (_super) {
51
52
  _this.connectionRetryDelay = 1000;
52
53
  _this.connectionCleanupTask = null;
53
54
  _this.connectionCleanupInterval = 15000; // 15 seconds between cleanup checks
55
+ _this.memberAddedEvents = new Set();
54
56
  _this.client = client;
55
57
  _this.logger = _this.client.getLoggingService().getLogger();
56
58
  _this.addressTranslator = addressTranslator;
57
59
  _this.addressProviders = addressProviders;
60
+ // Initialize credential preservation service for server data storage
61
+ _this.credentialPreservationService = new CredentialPreservationService_1.CredentialPreservationService(_this.logger);
58
62
  _this.startConnectionHealthCheck();
59
63
  _this.startConnectionCleanupTask();
60
64
  return _this;
@@ -248,6 +252,129 @@ var ClientConnectionManager = /** @class */ (function (_super) {
248
252
  ClientConnectionManager.prototype.getPendingConnections = function () {
249
253
  return this.pendingConnections;
250
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
+ };
251
378
  /**
252
379
  * Returns the {@link ClientConnection} with given {@link Address}. If there is no such connection established,
253
380
  * it first connects to the address and then return the {@link ClientConnection}.
@@ -348,6 +475,17 @@ var ClientConnectionManager = /** @class */ (function (_super) {
348
475
  // Remove from failed connections to allow reconnection after failover
349
476
  this.failedConnections.delete(addressStr);
350
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
+ };
351
489
  /**
352
490
  * Handles authentication errors by clearing failed connections and allowing retry
353
491
  * @param address The address that had authentication issues
@@ -387,9 +525,20 @@ var ClientConnectionManager = /** @class */ (function (_super) {
387
525
  ClientConnectionManager.prototype.triggerConnect = function (address, asOwner) {
388
526
  var _this = this;
389
527
  if (!asOwner) {
390
- if (this.client.getClusterService().getOwnerConnection() == null) {
391
- var error = new HazelcastError_1.IllegalStateError('Owner connection is not available!');
392
- 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
+ }
393
542
  }
394
543
  }
395
544
  if (this.client.getConfig().networkConfig.sslConfig.enabled) {
@@ -468,8 +617,65 @@ var ClientConnectionManager = /** @class */ (function (_super) {
468
617
  this.emit(EMIT_CONNECTION_OPENED, connection);
469
618
  };
470
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'));
471
647
  var authenticator = new ConnectionAuthenticator_1.ConnectionAuthenticator(connection, this.client);
472
- 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
+ });
473
679
  };
474
680
  return ClientConnectionManager;
475
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,
@@ -35,6 +37,7 @@ export declare class ClusterService {
35
37
  private readonly addressBlockDuration;
36
38
  private reconnectionTask;
37
39
  private readonly reconnectionInterval;
40
+ private failoverManager;
38
41
  constructor(client: HazelcastClient);
39
42
  /**
40
43
  * Starts cluster service.
@@ -51,6 +54,14 @@ export declare class ClusterService {
51
54
  * Returns the owner connection if available
52
55
  */
53
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[];
54
65
  /**
55
66
  * Returns the list of members in the cluster.
56
67
  * @returns
@@ -86,6 +97,14 @@ export declare class ClusterService {
86
97
  private onConnectionClosed(connection);
87
98
  private onHeartbeatStopped(connection);
88
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();
89
108
  /**
90
109
  * Attempts emergency recovery when failover fails
91
110
  */
@@ -101,6 +120,23 @@ export declare class ClusterService {
101
120
  private handleMemberAttributeChange(uuid, key, operationType, value);
102
121
  private memberAdded(member);
103
122
  private memberRemoved(member);
123
+ /**
124
+ * Performs comprehensive credential cleanup when cluster membership changes
125
+ * This ensures ALL credentials are consistent with the current cluster owner UUID
126
+ * @param connectionManager The connection manager instance
127
+ * @param currentClusterOwnerUuid The current cluster owner UUID
128
+ */
129
+ /**
130
+ * Handles member added event - SERVER-FIRST APPROACH
131
+ * We trust what the server tells us and store it as credentials
132
+ * @param member The member that was added
133
+ */
134
+ private handleMemberAdded(member);
135
+ /**
136
+ * Finds the current owner from the cluster state
137
+ * @returns The current owner member or null if not found
138
+ */
139
+ private findCurrentOwner();
104
140
  /**
105
141
  * Logs the current state for debugging purposes
106
142
  */
@@ -134,5 +170,16 @@ export declare class ClusterService {
134
170
  * @param blockDuration The duration to block the address (in milliseconds)
135
171
  */
136
172
  private markAddressAsDownWithDuration(address, blockDuration);
173
+ /**
174
+ * Handles ownership change when failover occurs
175
+ * @param newOwnerAddress The address of the new owner
176
+ * @param newOwnerConnection The connection to the new owner
177
+ */
178
+ handleOwnershipChange(newOwnerAddress: Address, newOwnerConnection: ClientConnection): void;
179
+ /**
180
+ * Gets the failover manager for external access
181
+ * @returns The failover manager instance
182
+ */
183
+ getFailoverManager(): HazelcastFailoverManager;
137
184
  shutdown(): void;
138
185
  }