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.
- package/dist/components/Telegram/utils/connection-manager.d.ts +44 -5
- package/dist/components/Telegram/utils/connection-manager.js +408 -80
- package/dist/components/Telegram/utils/connection-manager.js.map +1 -1
- package/dist/components/buffer-clients/buffer-client.service.js +1 -1
- package/dist/components/buffer-clients/buffer-client.service.js.map +1 -1
- package/dist/components/transactions/schemas/transaction.schema.js +0 -4
- package/dist/components/transactions/schemas/transaction.schema.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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<
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
this.
|
|
69
|
-
await this.unregisterClient(mobile);
|
|
232
|
+
else {
|
|
233
|
+
await this.handleFinalError(mobile, error);
|
|
70
234
|
}
|
|
71
235
|
}
|
|
72
236
|
}
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
throw
|
|
262
|
+
catch (error) {
|
|
263
|
+
this.clients.delete(mobile);
|
|
264
|
+
throw error;
|
|
79
265
|
}
|
|
80
|
-
|
|
81
|
-
|
|
266
|
+
}
|
|
267
|
+
async handleFinalError(mobile, error) {
|
|
82
268
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
await
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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(
|
|
108
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
this.logger.logOperation(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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.
|
|
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
|
|
151
|
-
this.
|
|
152
|
-
|
|
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
|