common-tg-service 1.0.92 → 1.0.94

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.
@@ -1,8 +1,28 @@
1
1
  import TelegramManager from '../TelegramManager';
2
2
  import { UsersService } from '../../../components/users/users.service';
3
+ interface ClientInfo {
4
+ client: TelegramManager;
5
+ lastUsed: number;
6
+ autoDisconnect: boolean;
7
+ connectionAttempts: number;
8
+ isConnecting: boolean;
9
+ }
3
10
  interface GetClientOptions {
4
11
  autoDisconnect?: boolean;
5
12
  handler?: boolean;
13
+ maxRetries?: number;
14
+ }
15
+ interface ConnectionStats {
16
+ activeConnections: number;
17
+ totalConnections: number;
18
+ failedConnections: number;
19
+ cleanupCount: number;
20
+ }
21
+ declare class ConnectionManagerError extends Error {
22
+ readonly mobile: string;
23
+ readonly operation: string;
24
+ readonly originalError?: Error;
25
+ constructor(message: string, mobile: string, operation: string, originalError?: Error);
6
26
  }
7
27
  declare class ConnectionManager {
8
28
  private static instance;
@@ -10,19 +30,38 @@ declare class ConnectionManager {
10
30
  private readonly logger;
11
31
  private cleanupInterval;
12
32
  private usersService;
33
+ private readonly maxRetries;
34
+ private readonly connectionTimeout;
35
+ private stats;
13
36
  private constructor();
14
37
  setUsersService(usersService: UsersService): void;
15
38
  static getInstance(): ConnectionManager;
16
39
  private cleanupInactiveConnections;
17
40
  private updateLastUsed;
18
- getClient(mobile: string, options?: GetClientOptions): Promise<TelegramManager | undefined>;
19
- hasClient(number: string): boolean;
20
- disconnectAll(): Promise<void>;
41
+ private validateMobile;
42
+ private getUserByMobile;
43
+ getClient(mobile: string, options?: GetClientOptions): Promise<TelegramManager>;
44
+ private tryGetExistingClient;
45
+ private waitForConnection;
46
+ private createNewClientWithRetries;
47
+ private createNewClient;
48
+ private handleFinalError;
49
+ private markUserAsExpired;
50
+ hasClient(mobile: string): boolean;
51
+ disconnectAll(): Promise<number>;
21
52
  private registerClient;
22
- unregisterClient(mobile: string): Promise<void>;
53
+ unregisterClient(mobile: string): Promise<boolean>;
23
54
  getActiveConnectionCount(): number;
55
+ getConnectionStats(): ConnectionStats;
56
+ getClientInfo(mobile: string): Omit<ClientInfo, 'client'> | null;
24
57
  startCleanupInterval(intervalMs?: number): NodeJS.Timeout;
25
58
  stopCleanupInterval(): void;
59
+ healthCheck(): Promise<{
60
+ status: 'healthy' | 'degraded' | 'unhealthy';
61
+ activeConnections: number;
62
+ stats: ConnectionStats;
63
+ issues: string[];
64
+ }>;
26
65
  }
27
66
  export declare const connectionManager: ConnectionManager;
28
- export {};
67
+ export { ConnectionManager, ConnectionManagerError };
@@ -3,21 +3,45 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.connectionManager = void 0;
6
+ exports.ConnectionManagerError = exports.ConnectionManager = exports.connectionManager = void 0;
7
7
  const TelegramManager_1 = __importDefault(require("../TelegramManager"));
8
8
  const parseError_1 = require("../../../utils/parseError");
9
9
  const telegram_logger_1 = require("./telegram-logger");
10
10
  const common_1 = require("@nestjs/common");
11
11
  const utils_1 = require("../../../utils");
12
12
  const TelegramBots_config_1 = require("../../../utils/TelegramBots.config");
13
+ class ConnectionManagerError extends Error {
14
+ constructor(message, mobile, operation, originalError) {
15
+ super(message);
16
+ this.mobile = mobile;
17
+ this.operation = operation;
18
+ this.originalError = originalError;
19
+ this.name = 'ConnectionManagerError';
20
+ }
21
+ }
22
+ exports.ConnectionManagerError = ConnectionManagerError;
13
23
  class ConnectionManager {
14
24
  constructor() {
15
- this.cleanupInterval = null;
16
25
  this.clients = new Map();
26
+ this.cleanupInterval = null;
27
+ this.usersService = null;
28
+ this.maxRetries = 3;
29
+ this.connectionTimeout = 30000;
30
+ this.stats = {
31
+ activeConnections: 0,
32
+ totalConnections: 0,
33
+ failedConnections: 0,
34
+ cleanupCount: 0
35
+ };
17
36
  this.logger = telegram_logger_1.TelegramLogger.getInstance();
37
+ this.logger.logOperation('system', 'ConnectionManager initialized');
18
38
  }
19
39
  setUsersService(usersService) {
40
+ if (!usersService) {
41
+ throw new Error('UsersService cannot be null or undefined');
42
+ }
20
43
  this.usersService = usersService;
44
+ this.logger.logOperation('system', 'UsersService registered successfully');
21
45
  }
22
46
  static getInstance() {
23
47
  if (!ConnectionManager.instance) {
@@ -26,131 +50,393 @@ class ConnectionManager {
26
50
  return ConnectionManager.instance;
27
51
  }
28
52
  async cleanupInactiveConnections(maxIdleTime = 180000) {
29
- const now = Date.now();
30
- for (const [mobile, connection] of this.clients.entries()) {
31
- if (!connection.autoDisconnect) {
32
- continue;
53
+ const startTime = Date.now();
54
+ let cleanedCount = 0;
55
+ try {
56
+ this.logger.logOperation('system', 'Starting cleanup of inactive connections', { maxIdleTime });
57
+ const now = Date.now();
58
+ const clientsToCleanup = [];
59
+ for (const [mobile, connection] of this.clients.entries()) {
60
+ if (!connection.autoDisconnect) {
61
+ continue;
62
+ }
63
+ if (now - connection.lastUsed > maxIdleTime) {
64
+ clientsToCleanup.push(mobile);
65
+ }
33
66
  }
34
- if (now - connection.lastUsed > maxIdleTime) {
35
- this.logger.logOperation(mobile, 'Releasing inactive connection');
36
- await this.unregisterClient(mobile);
67
+ for (const mobile of clientsToCleanup) {
68
+ try {
69
+ this.logger.logOperation(mobile, 'Cleaning up inactive connection');
70
+ await this.unregisterClient(mobile);
71
+ cleanedCount++;
72
+ }
73
+ catch (error) {
74
+ this.logger.logError(mobile, 'Failed to cleanup inactive connection', error);
75
+ }
37
76
  }
77
+ this.stats.cleanupCount += cleanedCount;
78
+ const duration = Date.now() - startTime;
79
+ this.logger.logOperation('system', 'Cleanup completed', {
80
+ cleanedCount,
81
+ totalChecked: this.clients.size + cleanedCount,
82
+ duration: `${duration}ms`
83
+ });
84
+ return cleanedCount;
85
+ }
86
+ catch (error) {
87
+ this.logger.logError('system', 'Error during cleanup operation', error);
88
+ throw new ConnectionManagerError('Cleanup operation failed', 'system', 'cleanupInactiveConnections', error);
38
89
  }
39
90
  }
40
91
  updateLastUsed(mobile) {
41
- const connection = this.clients.get(mobile);
42
- if (connection) {
43
- connection.lastUsed = Date.now();
44
- this.clients.set(mobile, connection);
92
+ try {
93
+ const connection = this.clients.get(mobile);
94
+ if (connection) {
95
+ connection.lastUsed = Date.now();
96
+ this.clients.set(mobile, connection);
97
+ return true;
98
+ }
99
+ return false;
100
+ }
101
+ catch (error) {
102
+ this.logger.logError(mobile, 'Failed to update last used timestamp', error);
103
+ return false;
104
+ }
105
+ }
106
+ async validateMobile(mobile) {
107
+ if (!mobile || typeof mobile !== 'string' || mobile.trim().length === 0) {
108
+ throw new common_1.BadRequestException('Mobile number is required and must be a non-empty string');
109
+ }
110
+ }
111
+ async getUserByMobile(mobile) {
112
+ if (!this.usersService) {
113
+ throw new common_1.InternalServerErrorException('UsersService not initialized');
114
+ }
115
+ try {
116
+ const users = await this.usersService.search({ mobile });
117
+ if (!users || users.length === 0) {
118
+ throw new common_1.BadRequestException(`User not found for mobile: ${mobile}`);
119
+ }
120
+ const user = users[0];
121
+ if (!user.session) {
122
+ throw new common_1.BadRequestException(`User session not found for mobile: ${mobile}`);
123
+ }
124
+ return user;
125
+ }
126
+ catch (error) {
127
+ if (error instanceof common_1.BadRequestException) {
128
+ throw error;
129
+ }
130
+ this.logger.logError(mobile, 'Failed to fetch user from database', error);
131
+ throw new common_1.InternalServerErrorException('Failed to retrieve user information');
45
132
  }
46
133
  }
47
134
  async getClient(mobile, options = {}) {
48
- if (!mobile) {
49
- this.logger.logDebug('system', 'getClient called with empty mobile number');
50
- return undefined;
135
+ const startTime = Date.now();
136
+ const { autoDisconnect = true, handler = true, maxRetries = this.maxRetries } = options;
137
+ try {
138
+ await this.validateMobile(mobile);
139
+ this.logger.logOperation(mobile, 'Getting/Creating client', {
140
+ autoDisconnect,
141
+ handler,
142
+ maxRetries
143
+ });
144
+ const existingClient = await this.tryGetExistingClient(mobile);
145
+ if (existingClient) {
146
+ const duration = Date.now() - startTime;
147
+ this.logger.logOperation(mobile, 'Client retrieved successfully', {
148
+ source: 'existing',
149
+ duration: `${duration}ms`
150
+ });
151
+ return existingClient;
152
+ }
153
+ const newClient = await this.createNewClientWithRetries(mobile, { autoDisconnect, handler }, maxRetries);
154
+ const duration = Date.now() - startTime;
155
+ this.logger.logOperation(mobile, 'Client created successfully', {
156
+ source: 'new',
157
+ duration: `${duration}ms`
158
+ });
159
+ return newClient;
51
160
  }
52
- const { autoDisconnect = true, handler = true, } = options;
53
- this.logger.logOperation(mobile, 'Getting/Creating client', { autoDisconnect, handler });
161
+ catch (error) {
162
+ const duration = Date.now() - startTime;
163
+ this.stats.failedConnections++;
164
+ this.logger.logError(mobile, 'Failed to get client', error);
165
+ if (error instanceof common_1.BadRequestException || error instanceof common_1.InternalServerErrorException) {
166
+ throw error;
167
+ }
168
+ throw new common_1.InternalServerErrorException('Failed to establish Telegram connection');
169
+ }
170
+ }
171
+ async tryGetExistingClient(mobile) {
54
172
  const clientInfo = this.clients.get(mobile);
55
- if (clientInfo?.client) {
56
- this.updateLastUsed(mobile);
57
- if (clientInfo.client.connected()) {
58
- this.logger.logOperation(mobile, 'Reusing existing connected client');
59
- return clientInfo.client;
173
+ if (!clientInfo?.client) {
174
+ return null;
175
+ }
176
+ if (clientInfo.isConnecting) {
177
+ this.logger.logOperation(mobile, 'Another connection attempt in progress, waiting...');
178
+ await this.waitForConnection(mobile);
179
+ return this.clients.get(mobile)?.client || null;
180
+ }
181
+ this.updateLastUsed(mobile);
182
+ if (clientInfo.client.connected()) {
183
+ this.logger.logOperation(mobile, 'Reusing existing connected client');
184
+ return clientInfo.client;
185
+ }
186
+ try {
187
+ clientInfo.isConnecting = true;
188
+ this.logger.logOperation(mobile, 'Reconnecting existing client');
189
+ await Promise.race([
190
+ clientInfo.client.connect(),
191
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Connection timeout')), this.connectionTimeout))
192
+ ]);
193
+ clientInfo.isConnecting = false;
194
+ return clientInfo.client;
195
+ }
196
+ catch (error) {
197
+ clientInfo.isConnecting = false;
198
+ this.logger.logError(mobile, 'Failed to reconnect existing client', error);
199
+ await this.unregisterClient(mobile);
200
+ return null;
201
+ }
202
+ }
203
+ async waitForConnection(mobile, maxWaitTime = 60000) {
204
+ const startTime = Date.now();
205
+ const checkInterval = 1000;
206
+ while (Date.now() - startTime < maxWaitTime) {
207
+ const clientInfo = this.clients.get(mobile);
208
+ if (!clientInfo?.isConnecting) {
209
+ return;
60
210
  }
61
- else {
62
- try {
63
- this.logger.logOperation(mobile, 'Reconnecting existing client');
64
- await clientInfo.client.connect();
65
- return clientInfo.client;
211
+ await new Promise(resolve => setTimeout(resolve, checkInterval));
212
+ }
213
+ throw new Error('Timeout waiting for connection to complete');
214
+ }
215
+ async createNewClientWithRetries(mobile, options, maxRetries) {
216
+ let lastError = null;
217
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
218
+ try {
219
+ this.logger.logOperation(mobile, `Creating client (attempt ${attempt}/${maxRetries})`);
220
+ const client = await this.createNewClient(mobile, options);
221
+ this.stats.totalConnections++;
222
+ return client;
223
+ }
224
+ catch (error) {
225
+ lastError = error;
226
+ this.logger.logError(mobile, `Client creation attempt ${attempt} failed`, error);
227
+ if (attempt < maxRetries) {
228
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
229
+ this.logger.logOperation(mobile, `Retrying in ${delay}ms...`);
230
+ await new Promise(resolve => setTimeout(resolve, delay));
66
231
  }
67
- catch (error) {
68
- this.logger.logError(mobile, 'Failed to reconnect client', error);
69
- await this.unregisterClient(mobile);
232
+ else {
233
+ await this.handleFinalError(mobile, error);
70
234
  }
71
235
  }
72
236
  }
73
- if (!this.usersService) {
74
- throw new Error('UsersService not initialized');
237
+ throw lastError || new Error('All retry attempts failed');
238
+ }
239
+ async createNewClient(mobile, options) {
240
+ const tempClientInfo = {
241
+ client: null,
242
+ lastUsed: Date.now(),
243
+ autoDisconnect: options.autoDisconnect,
244
+ connectionAttempts: 0,
245
+ isConnecting: true
246
+ };
247
+ this.clients.set(mobile, tempClientInfo);
248
+ try {
249
+ const user = await this.getUserByMobile(mobile);
250
+ const telegramManager = new TelegramManager_1.default(user.session, user.mobile);
251
+ const client = await Promise.race([
252
+ telegramManager.createClient(options.handler),
253
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Client creation timeout')), this.connectionTimeout))
254
+ ]);
255
+ await Promise.race([
256
+ client.getMe(),
257
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Client verification timeout')), this.connectionTimeout))
258
+ ]);
259
+ await this.registerClient(mobile, telegramManager, { autoDisconnect: options.autoDisconnect });
260
+ return telegramManager;
75
261
  }
76
- const user = (await this.usersService.search({ mobile }))[0];
77
- if (!user) {
78
- throw new common_1.BadRequestException('user not found');
262
+ catch (error) {
263
+ this.clients.delete(mobile);
264
+ throw error;
79
265
  }
80
- const telegramManager = new TelegramManager_1.default(user.session, user.mobile);
81
- let client;
266
+ }
267
+ async handleFinalError(mobile, error) {
82
268
  try {
83
- client = await telegramManager.createClient(handler);
84
- await client.getMe();
85
- if (client) {
86
- await this.registerClient(mobile, telegramManager, { autoDisconnect });
87
- this.logger.logOperation(mobile, 'Client created successfully');
88
- return telegramManager;
269
+ this.logger.logDebug(mobile, 'Parsing final error details...');
270
+ const errorDetails = (0, parseError_1.parseError)(error, mobile, false);
271
+ try {
272
+ await TelegramBots_config_1.BotConfig.getInstance().sendMessage(TelegramBots_config_1.ChannelCategory.ACCOUNT_LOGIN_FAILURES, `${process.env.clientId}::${mobile}\n\n${errorDetails.message}`);
89
273
  }
90
- else {
91
- throw new common_1.BadRequestException('Client Expired');
274
+ catch (notificationError) {
275
+ this.logger.logError(mobile, 'Failed to send error notification', notificationError);
92
276
  }
277
+ const lowerCaseMessage = errorDetails.message.toLowerCase();
278
+ const expiredKeywords = ['expired', 'unregistered', 'deactivated', 'revoked', 'user_deactivated_ban'];
279
+ if ((0, utils_1.contains)(lowerCaseMessage, expiredKeywords)) {
280
+ await this.markUserAsExpired(mobile);
281
+ }
282
+ throw new common_1.BadRequestException(errorDetails.message);
93
283
  }
94
284
  catch (error) {
95
- this.logger.logError(mobile, 'Client creation failed', error);
96
- this.logger.logDebug(mobile, 'Parsing error details...');
97
- await this.unregisterClient(mobile);
98
- const errorDetails = (0, parseError_1.parseError)(error, mobile, false);
99
- await TelegramBots_config_1.BotConfig.getInstance().sendMessage(TelegramBots_config_1.ChannelCategory.ACCOUNT_LOGIN_FAILURES, `${process.env.clientId}::${mobile}\n\n${errorDetails.message}`);
100
- if ((0, utils_1.contains)(errorDetails.message.toLowerCase(), ['expired', 'unregistered', 'deactivated', "revoked", "user_deactivated_ban"])) {
101
- this.logger.logOperation(mobile, 'Marking user as expired');
102
- await this.usersService.updateByFilter({ $or: [{ tgId: user.tgId }, { mobile: mobile }] }, { expired: true });
285
+ if (error instanceof common_1.BadRequestException) {
286
+ throw error;
103
287
  }
104
- throw new common_1.BadRequestException(errorDetails.message);
288
+ this.logger.logError(mobile, 'Error handling final error', error);
289
+ throw new common_1.InternalServerErrorException('Client creation failed with unhandled error');
290
+ }
291
+ }
292
+ async markUserAsExpired(mobile) {
293
+ try {
294
+ if (!this.usersService) {
295
+ throw new Error('UsersService not available');
296
+ }
297
+ this.logger.logOperation(mobile, 'Marking user as expired');
298
+ const users = await this.usersService.search({ mobile });
299
+ const user = users?.[0];
300
+ const filter = user?.tgId
301
+ ? { $or: [{ tgId: user.tgId }, { mobile: mobile }] }
302
+ : { mobile: mobile };
303
+ await this.usersService.updateByFilter(filter, { expired: true });
304
+ this.logger.logOperation(mobile, 'User marked as expired successfully');
305
+ }
306
+ catch (error) {
307
+ this.logger.logError(mobile, 'Failed to mark user as expired', error);
105
308
  }
106
309
  }
107
- hasClient(number) {
108
- return this.clients.has(number);
310
+ hasClient(mobile) {
311
+ try {
312
+ if (!mobile)
313
+ return false;
314
+ return this.clients.has(mobile);
315
+ }
316
+ catch (error) {
317
+ this.logger.logError(mobile || 'unknown', 'Error checking client existence', error);
318
+ return false;
319
+ }
109
320
  }
110
321
  async disconnectAll() {
111
- this.logger.logOperation('system', 'Disconnecting all clients');
112
- const clientMobiles = Array.from(this.clients.keys());
113
- await Promise.all(clientMobiles.map(mobile => {
114
- this.logger.logOperation(mobile, 'Disconnecting client');
115
- return this.unregisterClient(mobile);
116
- }));
117
- this.clients.clear();
118
- this.logger.logOperation('system', 'All clients disconnected');
322
+ const startTime = Date.now();
323
+ let disconnectedCount = 0;
324
+ try {
325
+ this.logger.logOperation('system', 'Starting disconnection of all clients');
326
+ const clientMobiles = Array.from(this.clients.keys());
327
+ const results = await Promise.allSettled(clientMobiles.map(async (mobile) => {
328
+ this.logger.logOperation(mobile, 'Disconnecting client');
329
+ await this.unregisterClient(mobile);
330
+ return mobile;
331
+ }));
332
+ results.forEach((result, index) => {
333
+ if (result.status === 'fulfilled') {
334
+ disconnectedCount++;
335
+ }
336
+ else {
337
+ this.logger.logError(clientMobiles[index], 'Failed to disconnect client', result.reason);
338
+ }
339
+ });
340
+ this.clients.clear();
341
+ const duration = Date.now() - startTime;
342
+ this.logger.logOperation('system', 'All clients disconnection completed', {
343
+ totalClients: clientMobiles.length,
344
+ successfulDisconnections: disconnectedCount,
345
+ failedDisconnections: clientMobiles.length - disconnectedCount,
346
+ duration: `${duration}ms`
347
+ });
348
+ return disconnectedCount;
349
+ }
350
+ catch (error) {
351
+ this.logger.logError('system', 'Error during disconnectAll operation', error);
352
+ throw new ConnectionManagerError('Failed to disconnect all clients', 'system', 'disconnectAll', error);
353
+ }
119
354
  }
120
355
  async registerClient(mobile, telegramManager, options = { autoDisconnect: true }) {
121
- this.clients.set(mobile, {
122
- client: telegramManager,
123
- lastUsed: Date.now(),
124
- autoDisconnect: options.autoDisconnect
125
- });
126
- this.logger.logOperation(mobile, `Client registered successfully${!options.autoDisconnect ? ' (excluded from auto-cleanup)' : ''}`);
356
+ try {
357
+ this.clients.set(mobile, {
358
+ client: telegramManager,
359
+ lastUsed: Date.now(),
360
+ autoDisconnect: options.autoDisconnect,
361
+ connectionAttempts: 0,
362
+ isConnecting: false
363
+ });
364
+ this.stats.activeConnections = this.clients.size;
365
+ this.logger.logOperation(mobile, `Client registered successfully${!options.autoDisconnect ? ' (excluded from auto-cleanup)' : ''}`, {
366
+ activeConnections: this.stats.activeConnections
367
+ });
368
+ }
369
+ catch (error) {
370
+ this.logger.logError(mobile, 'Failed to register client', error);
371
+ throw new ConnectionManagerError('Client registration failed', mobile, 'registerClient', error);
372
+ }
127
373
  }
128
374
  async unregisterClient(mobile) {
129
375
  try {
130
376
  const clientInfo = this.clients.get(mobile);
131
377
  if (clientInfo) {
132
- await clientInfo.client?.disconnect();
133
- this.logger.logOperation(mobile, 'Client unregistered successfully');
378
+ clientInfo.isConnecting = false;
379
+ if (clientInfo.client) {
380
+ await Promise.race([
381
+ clientInfo.client.disconnect(),
382
+ new Promise((resolve) => setTimeout(() => {
383
+ this.logger.logError(mobile, 'Client disconnect timeout, forcing cleanup', {});
384
+ resolve();
385
+ }, 10000))
386
+ ]);
387
+ }
388
+ this.clients.delete(mobile);
389
+ this.stats.activeConnections = this.clients.size;
390
+ this.logger.logOperation(mobile, 'Client unregistered successfully', {
391
+ activeConnections: this.stats.activeConnections
392
+ });
393
+ return true;
134
394
  }
135
395
  else {
136
- this.logger.logError(mobile, 'Client not found for unregistration', new Error('Client not found'));
396
+ this.logger.logDebug(mobile, 'Client not found for unregistration');
397
+ return false;
137
398
  }
138
399
  }
139
400
  catch (error) {
140
401
  this.logger.logError(mobile, 'Error in unregisterClient', error);
141
- }
142
- finally {
143
402
  this.clients.delete(mobile);
403
+ this.stats.activeConnections = this.clients.size;
404
+ return false;
144
405
  }
145
406
  }
146
407
  getActiveConnectionCount() {
147
408
  return this.clients.size;
148
409
  }
410
+ getConnectionStats() {
411
+ return {
412
+ ...this.stats,
413
+ activeConnections: this.clients.size
414
+ };
415
+ }
416
+ getClientInfo(mobile) {
417
+ const clientInfo = this.clients.get(mobile);
418
+ if (!clientInfo)
419
+ return null;
420
+ return {
421
+ lastUsed: clientInfo.lastUsed,
422
+ autoDisconnect: clientInfo.autoDisconnect,
423
+ connectionAttempts: clientInfo.connectionAttempts,
424
+ isConnecting: clientInfo.isConnecting
425
+ };
426
+ }
149
427
  startCleanupInterval(intervalMs = 300000) {
150
- this.cleanupInterval = setInterval(() => {
151
- this.cleanupInactiveConnections().catch(err => {
152
- this.logger.logError('system', 'Error in cleanup interval', err);
153
- });
428
+ if (this.cleanupInterval) {
429
+ this.stopCleanupInterval();
430
+ }
431
+ this.logger.logOperation('system', 'Starting cleanup interval', { intervalMs });
432
+ this.cleanupInterval = setInterval(async () => {
433
+ try {
434
+ const cleanedCount = await this.cleanupInactiveConnections();
435
+ this.logger.logDebug('system', `Cleanup interval completed: ${cleanedCount} clients cleaned`);
436
+ }
437
+ catch (error) {
438
+ this.logger.logError('system', 'Error in cleanup interval', error);
439
+ }
154
440
  }, intervalMs);
155
441
  return this.cleanupInterval;
156
442
  }
@@ -158,8 +444,50 @@ class ConnectionManager {
158
444
  if (this.cleanupInterval) {
159
445
  clearInterval(this.cleanupInterval);
160
446
  this.cleanupInterval = null;
447
+ this.logger.logOperation('system', 'Cleanup interval stopped');
448
+ }
449
+ }
450
+ async healthCheck() {
451
+ const issues = [];
452
+ let status = 'healthy';
453
+ try {
454
+ if (!this.usersService) {
455
+ issues.push('UsersService not initialized');
456
+ status = 'unhealthy';
457
+ }
458
+ const activeCount = this.getActiveConnectionCount();
459
+ if (activeCount > 100) {
460
+ issues.push(`High connection count: ${activeCount}`);
461
+ status = status === 'healthy' ? 'degraded' : status;
462
+ }
463
+ let stuckConnections = 0;
464
+ for (const [mobile, info] of this.clients.entries()) {
465
+ if (info.isConnecting && (Date.now() - info.lastUsed) > 60000) {
466
+ stuckConnections++;
467
+ }
468
+ }
469
+ if (stuckConnections > 0) {
470
+ issues.push(`${stuckConnections} stuck connections detected`);
471
+ status = status === 'healthy' ? 'degraded' : status;
472
+ }
473
+ return {
474
+ status,
475
+ activeConnections: activeCount,
476
+ stats: this.getConnectionStats(),
477
+ issues
478
+ };
479
+ }
480
+ catch (error) {
481
+ this.logger.logError('system', 'Health check failed', error);
482
+ return {
483
+ status: 'unhealthy',
484
+ activeConnections: 0,
485
+ stats: this.stats,
486
+ issues: ['Health check failed']
487
+ };
161
488
  }
162
489
  }
163
490
  }
491
+ exports.ConnectionManager = ConnectionManager;
164
492
  exports.connectionManager = ConnectionManager.getInstance();
165
493
  //# sourceMappingURL=connection-manager.js.map