n8n-nodes-chat2crm 0.1.20 → 0.1.22
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.
|
@@ -170,6 +170,38 @@ class Chat2CrmTrigger {
|
|
|
170
170
|
});
|
|
171
171
|
// Флаг для отслеживания состояния закрытия
|
|
172
172
|
let isClosing = false;
|
|
173
|
+
// Функция для проверки и переподключения при необходимости
|
|
174
|
+
const ensureConnection = async (db) => {
|
|
175
|
+
const redis = redisConnections.get(db);
|
|
176
|
+
if (!redis)
|
|
177
|
+
return false;
|
|
178
|
+
// Проверяем статус соединения
|
|
179
|
+
const status = redis.status;
|
|
180
|
+
if (status === 'ready') {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
// Если соединение не готово, пытаемся переподключиться
|
|
184
|
+
if (status === 'end' || status === 'close') {
|
|
185
|
+
console.log(`[Chat2Crm Trigger] Redis connection for DB ${db} is ${status}, attempting to reconnect...`);
|
|
186
|
+
try {
|
|
187
|
+
redis.disconnect();
|
|
188
|
+
// Пересоздаем соединение
|
|
189
|
+
const newRedis = await RedisConnection_1.createRedisConnection.call(this, credentials, db);
|
|
190
|
+
redisConnections.set(db, newRedis);
|
|
191
|
+
console.log(`[Chat2Crm Trigger] Successfully reconnected to Redis DB ${db}`);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
console.error(`[Chat2Crm Trigger] Failed to reconnect to Redis DB ${db}:`, error.message);
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Если соединение в процессе подключения, ждем
|
|
200
|
+
if (status === 'connecting' || status === 'reconnecting') {
|
|
201
|
+
return false; // Пропускаем эту итерацию
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
};
|
|
173
205
|
// Функция для чтения сообщений
|
|
174
206
|
const readMessages = async () => {
|
|
175
207
|
// Проверяем, не закрывается ли триггер
|
|
@@ -181,6 +213,10 @@ class Chat2CrmTrigger {
|
|
|
181
213
|
if (isClosing) {
|
|
182
214
|
break;
|
|
183
215
|
}
|
|
216
|
+
// Проверяем и переподключаемся при необходимости
|
|
217
|
+
if (!(await ensureConnection(db))) {
|
|
218
|
+
continue; // Пропускаем эту итерацию, если соединение не готово
|
|
219
|
+
}
|
|
184
220
|
const redis = redisConnections.get(db);
|
|
185
221
|
// Читаем каждый stream отдельно через XREAD
|
|
186
222
|
// XREAD читает напрямую из stream, независимо от consumer groups
|
|
@@ -251,6 +287,26 @@ class Chat2CrmTrigger {
|
|
|
251
287
|
if (error.message?.includes('Invalid stream ID') || error.message?.includes('no such key')) {
|
|
252
288
|
continue;
|
|
253
289
|
}
|
|
290
|
+
// При ошибке соединения помечаем соединение как требующее переподключения
|
|
291
|
+
if (error.message?.includes('Connection is closed') ||
|
|
292
|
+
error.message?.includes('ECONNRESET') ||
|
|
293
|
+
error.message?.includes('ETIMEDOUT') ||
|
|
294
|
+
error.message?.includes('socket hang up') ||
|
|
295
|
+
error.message?.includes('read ECONNRESET') ||
|
|
296
|
+
error.message?.includes('write ECONNRESET')) {
|
|
297
|
+
console.error(`[Chat2Crm Trigger] Connection error for DB ${db}, will attempt reconnect:`, error.message);
|
|
298
|
+
// Сбрасываем соединение, чтобы на следующей итерации оно переподключилось
|
|
299
|
+
const redis = redisConnections.get(db);
|
|
300
|
+
if (redis) {
|
|
301
|
+
try {
|
|
302
|
+
redis.disconnect();
|
|
303
|
+
}
|
|
304
|
+
catch (e) {
|
|
305
|
+
// Игнорируем ошибки при отключении
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
254
310
|
// Логируем только критические ошибки
|
|
255
311
|
console.error(`[Chat2Crm Trigger] Error reading stream ${stream} from Redis DB ${db}:`, error);
|
|
256
312
|
}
|
|
@@ -260,10 +316,47 @@ class Chat2CrmTrigger {
|
|
|
260
316
|
// Запускаем один последовательный polling loop (без setInterval), чтобы не было наложения вызовов
|
|
261
317
|
let pollingPromise = null;
|
|
262
318
|
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
319
|
+
const KEEPALIVE_INTERVAL_MS = 30_000;
|
|
320
|
+
let lastKeepaliveAt = Date.now();
|
|
321
|
+
const keepalive = async () => {
|
|
322
|
+
const now = Date.now();
|
|
323
|
+
if (now - lastKeepaliveAt < KEEPALIVE_INTERVAL_MS)
|
|
324
|
+
return;
|
|
325
|
+
lastKeepaliveAt = now;
|
|
326
|
+
for (const [db, redis] of redisConnections.entries()) {
|
|
327
|
+
if (isClosing)
|
|
328
|
+
return;
|
|
329
|
+
// Проверяем статус перед PING
|
|
330
|
+
if (redis.status !== 'ready') {
|
|
331
|
+
if (isDebug) {
|
|
332
|
+
console.log(`[Chat2Crm Trigger] Redis DB ${db} status is ${redis.status}, skipping keepalive`);
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
await redis.ping();
|
|
338
|
+
if (isDebug) {
|
|
339
|
+
console.log(`[Chat2Crm Trigger] Keepalive PING OK (DB ${db})`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
// При ошибке PING помечаем соединение как требующее переподключения
|
|
344
|
+
console.error(`[Chat2Crm Trigger] Keepalive PING failed (DB ${db}):`, error?.message ?? error);
|
|
345
|
+
// Сбрасываем соединение, чтобы на следующей итерации оно переподключилось
|
|
346
|
+
try {
|
|
347
|
+
redis.disconnect();
|
|
348
|
+
}
|
|
349
|
+
catch (e) {
|
|
350
|
+
// Игнорируем ошибки при отключении
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
};
|
|
263
355
|
const runPollingLoop = async () => {
|
|
264
356
|
try {
|
|
265
357
|
while (!isClosing) {
|
|
266
358
|
await readMessages();
|
|
359
|
+
await keepalive();
|
|
267
360
|
// Если block=0, добавляем небольшой интервал чтобы не прожигать CPU
|
|
268
361
|
if (!isClosing) {
|
|
269
362
|
await sleep(pollInterval);
|
|
@@ -117,6 +117,13 @@ async function createRedisConnection(credentials, db = 0) {
|
|
|
117
117
|
console.log(`[Redis Connection] Creating tunnel to Redis at ${remoteRedisHost}:${remoteRedisPort} on remote server`);
|
|
118
118
|
// Создаем локальный TCP сервер для проксирования через SSH туннель
|
|
119
119
|
const localServer = net.createServer((localSocket) => {
|
|
120
|
+
// TCP keepalive помогает держать соединение живым при длительном простое (важно для SSH/прокси/фаерволов)
|
|
121
|
+
try {
|
|
122
|
+
localSocket.setKeepAlive(true, 60_000); // 60 секунд
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
// best-effort
|
|
126
|
+
}
|
|
120
127
|
// Создаем SSH туннель для каждого соединения
|
|
121
128
|
sshClient.forwardOut('127.0.0.1', 0, remoteRedisHost, remoteRedisPort, (err, stream) => {
|
|
122
129
|
if (err) {
|
|
@@ -177,6 +184,8 @@ async function createRedisConnection(credentials, db = 0) {
|
|
|
177
184
|
enableReadyCheck: true,
|
|
178
185
|
maxRetriesPerRequest: 3,
|
|
179
186
|
connectTimeout: 10000,
|
|
187
|
+
// TCP keepalive для предотвращения "тихих" разрывов соединения при простое (особенно через SSH)
|
|
188
|
+
keepAlive: 30_000,
|
|
180
189
|
retryStrategy: (times) => {
|
|
181
190
|
const delay = Math.min(times * 50, 2000);
|
|
182
191
|
return delay;
|
|
@@ -211,5 +220,8 @@ async function createRedisConnection(credentials, db = 0) {
|
|
|
211
220
|
redis.on('close', () => {
|
|
212
221
|
console.log(`[Redis Connection] Redis connection closed: ${redisHost}:${redisPort}, DB ${selectedDb}`);
|
|
213
222
|
});
|
|
223
|
+
redis.on('end', () => {
|
|
224
|
+
console.log(`[Redis Connection] Redis connection ended: ${redisHost}:${redisPort}, DB ${selectedDb}`);
|
|
225
|
+
});
|
|
214
226
|
return redis;
|
|
215
227
|
}
|