n8n-nodes-chat2crm 0.1.19 → 0.1.21
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.
|
@@ -260,25 +260,60 @@ class Chat2CrmTrigger {
|
|
|
260
260
|
// Запускаем один последовательный polling loop (без setInterval), чтобы не было наложения вызовов
|
|
261
261
|
let pollingPromise = null;
|
|
262
262
|
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
263
|
+
const KEEPALIVE_INTERVAL_MS = 30_000;
|
|
264
|
+
let lastKeepaliveAt = Date.now();
|
|
265
|
+
const keepalive = async () => {
|
|
266
|
+
const now = Date.now();
|
|
267
|
+
if (now - lastKeepaliveAt < KEEPALIVE_INTERVAL_MS)
|
|
268
|
+
return;
|
|
269
|
+
lastKeepaliveAt = now;
|
|
270
|
+
for (const [db, redis] of redisConnections.entries()) {
|
|
271
|
+
if (isClosing)
|
|
272
|
+
return;
|
|
273
|
+
try {
|
|
274
|
+
await redis.ping();
|
|
275
|
+
if (isDebug) {
|
|
276
|
+
console.log(`[Chat2Crm Trigger] Keepalive PING OK (DB ${db})`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
// Не роняем триггер из-за keepalive — это best-effort. Ошибка даст сигнал, что SSH/Redis начинает умирать.
|
|
281
|
+
console.error(`[Chat2Crm Trigger] Keepalive PING failed (DB ${db}):`, error?.message ?? error);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
263
285
|
const runPollingLoop = async () => {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
286
|
+
try {
|
|
287
|
+
while (!isClosing) {
|
|
288
|
+
await readMessages();
|
|
289
|
+
await keepalive();
|
|
290
|
+
// Если block=0, добавляем небольшой интервал чтобы не прожигать CPU
|
|
291
|
+
if (!isClosing) {
|
|
292
|
+
await sleep(pollInterval);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
// Важно: не даём polling loop'у "повесить" деактивацию воркфлоу
|
|
267
298
|
if (!isClosing) {
|
|
268
|
-
|
|
299
|
+
console.error('[Chat2Crm Trigger] Polling loop crashed:', error);
|
|
269
300
|
}
|
|
270
301
|
}
|
|
271
302
|
};
|
|
272
303
|
pollingPromise = runPollingLoop();
|
|
304
|
+
const withTimeout = async (promise, timeoutMs) => {
|
|
305
|
+
return await Promise.race([
|
|
306
|
+
promise,
|
|
307
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('close timeout')), timeoutMs)),
|
|
308
|
+
]);
|
|
309
|
+
};
|
|
273
310
|
// Возвращаем объект с функцией closeFunction для cleanup
|
|
274
311
|
return {
|
|
275
312
|
closeFunction: async () => {
|
|
276
313
|
console.log(`[Chat2Crm Trigger] Closing trigger, cleaning up resources...`);
|
|
277
314
|
isClosing = true; // Устанавливаем флаг закрытия
|
|
278
315
|
try {
|
|
279
|
-
//
|
|
280
|
-
await pollingPromise?.catch(() => undefined);
|
|
281
|
-
// Используем disconnect() вместо quit() для более быстрого закрытия
|
|
316
|
+
// ВАЖНО: СНАЧАЛА рвём соединения, чтобы прервать возможный зависший XREAD (особенно через SSH)
|
|
282
317
|
for (const [db, redis] of redisConnections.entries()) {
|
|
283
318
|
try {
|
|
284
319
|
redis.disconnect();
|
|
@@ -288,6 +323,15 @@ class Chat2CrmTrigger {
|
|
|
288
323
|
}
|
|
289
324
|
}
|
|
290
325
|
redisConnections.clear();
|
|
326
|
+
// Не даём unpublish/deactivate висеть бесконечно: ждём loop ограниченное время
|
|
327
|
+
if (pollingPromise) {
|
|
328
|
+
try {
|
|
329
|
+
await withTimeout(pollingPromise.catch(() => undefined), 1000);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// Игнорируем таймаут, главное — быстро закрыть триггер
|
|
333
|
+
}
|
|
334
|
+
}
|
|
291
335
|
console.log(`[Chat2Crm Trigger] Cleanup completed`);
|
|
292
336
|
}
|
|
293
337
|
catch (error) {
|
|
@@ -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;
|