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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-chat2crm",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "n8n node for Chat2Crm Redis integration",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",