n8n-nodes-chat2crm 0.1.15 → 0.1.17

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.
@@ -97,13 +97,23 @@ class Chat2CrmTrigger {
97
97
  const redisConnections = new Map();
98
98
  for (const db of streamsByDb.keys()) {
99
99
  // Создаем отдельное соединение для каждой базы данных
100
+ // ВАЖНО: Каждое соединение уже настроено на свою базу данных через параметр db
100
101
  const redis = await RedisConnection_1.createRedisConnection.call(this, credentials, db);
101
102
  redisConnections.set(db, redis);
102
- // ДИАГНОСТИКА: Проверяем, что соединение использует правильную базу данных
103
+ // ДИАГНОСТИКА: Сразу после создания соединения проверяем stream'ы
103
104
  try {
104
- // Проверяем все ключи сразу после подключения
105
105
  const testKeys = await redis.keys('*');
106
- console.log(`[Chat2Crm Trigger] Test keys in DB ${db} after connection:`, testKeys);
106
+ console.log(`[Chat2Crm Trigger] Keys in DB ${db} immediately after connection:`, testKeys);
107
+ // Проверяем конкретно N8N_INCOMING_MESSAGE
108
+ if (streamsByDb.get(db)?.includes('N8N_INCOMING_MESSAGE')) {
109
+ try {
110
+ const testInfo = await redis.xinfo('STREAM', 'N8N_INCOMING_MESSAGE');
111
+ console.log(`[Chat2Crm Trigger] N8N_INCOMING_MESSAGE found immediately after connection!`, testInfo);
112
+ }
113
+ catch (e) {
114
+ console.error(`[Chat2Crm Trigger] N8N_INCOMING_MESSAGE NOT found immediately after connection:`, e.message);
115
+ }
116
+ }
107
117
  }
108
118
  catch (error) {
109
119
  console.error(`[Chat2Crm Trigger] Error checking keys in DB ${db}:`, error.message);
@@ -111,6 +121,10 @@ class Chat2CrmTrigger {
111
121
  }
112
122
  // Храним последние прочитанные ID для каждого stream
113
123
  const lastReadIds = new Map();
124
+ // ИСПРАВЛЕНИЕ: Добавляем флаг для предотвращения параллельных вызовов
125
+ let isReading = false;
126
+ // ИСПРАВЛЕНИЕ: Добавляем Set для отслеживания уже обработанных messageId
127
+ const processedMessageIds = new Set();
114
128
  // Собираем информацию о найденных streams для вывода
115
129
  const foundStreams = [];
116
130
  // Инициализируем lastReadIds для каждого stream и собираем информацию
@@ -120,6 +134,18 @@ class Chat2CrmTrigger {
120
134
  try {
121
135
  const selectResult = await redis.select(db);
122
136
  console.log(`[Chat2Crm Trigger] SELECT ${db} result:`, selectResult);
137
+ // ДИАГНОСТИКА: Проверяем текущую базу данных через CLIENT INFO или другой способ
138
+ // Попробуем напрямую проверить stream после SELECT
139
+ for (const stream of dbStreams) {
140
+ try {
141
+ console.log(`[Chat2Crm Trigger] Direct XINFO check for "${stream}" immediately after SELECT ${db}`);
142
+ const directInfo = await redis.xinfo('STREAM', stream);
143
+ console.log(`[Chat2Crm Trigger] Direct XINFO SUCCESS for "${stream}":`, directInfo);
144
+ }
145
+ catch (directError) {
146
+ console.error(`[Chat2Crm Trigger] Direct XINFO FAILED for "${stream}":`, directError.message);
147
+ }
148
+ }
123
149
  // ДИАГНОСТИКА: Проверяем все stream'ы через SCAN
124
150
  const streamKeys = [];
125
151
  let cursor = '0';
@@ -135,6 +161,24 @@ class Chat2CrmTrigger {
135
161
  } while (cursor !== '0');
136
162
  console.log(`[Chat2Crm Trigger] Found ${streamKeys.length} streams in DB ${db}:`, streamKeys);
137
163
  console.log(`[Chat2Crm Trigger] Looking for:`, dbStreams);
164
+ // ДИАГНОСТИКА: Проверяем конкретно N8N_INCOMING_MESSAGE через разные методы
165
+ if (dbStreams.includes('N8N_INCOMING_MESSAGE')) {
166
+ console.log(`[Chat2Crm Trigger] Special check for N8N_INCOMING_MESSAGE in DB ${db}`);
167
+ try {
168
+ const exists = await redis.exists('N8N_INCOMING_MESSAGE');
169
+ console.log(`[Chat2Crm Trigger] EXISTS check for N8N_INCOMING_MESSAGE:`, exists);
170
+ }
171
+ catch (e) {
172
+ console.error(`[Chat2Crm Trigger] EXISTS check failed:`, e.message);
173
+ }
174
+ try {
175
+ const type = await redis.type('N8N_INCOMING_MESSAGE');
176
+ console.log(`[Chat2Crm Trigger] TYPE check for N8N_INCOMING_MESSAGE:`, type);
177
+ }
178
+ catch (e) {
179
+ console.error(`[Chat2Crm Trigger] TYPE check failed:`, e.message);
180
+ }
181
+ }
138
182
  }
139
183
  catch (error) {
140
184
  console.error(`[Chat2Crm Trigger] Failed to select DB ${db}:`, error.message);
@@ -202,87 +246,97 @@ class Chat2CrmTrigger {
202
246
  let isClosing = false;
203
247
  // Функция для чтения сообщений
204
248
  const readMessages = async () => {
205
- // Проверяем, не закрывается ли триггер
206
- if (isClosing) {
249
+ // ИСПРАВЛЕНИЕ: Предотвращаем параллельные вызовы
250
+ if (isReading || isClosing) {
207
251
  return;
208
252
  }
209
- for (const [db, dbStreams] of streamsByDb.entries()) {
210
- // Проверяем снова перед каждой итерацией
211
- if (isClosing) {
212
- break;
213
- }
214
- const redis = redisConnections.get(db);
215
- // Читаем каждый stream отдельно через XREAD
216
- // XREAD читает напрямую из stream, независимо от consumer groups
217
- for (const stream of dbStreams) {
253
+ isReading = true;
254
+ try {
255
+ for (const [db, dbStreams] of streamsByDb.entries()) {
218
256
  if (isClosing) {
219
257
  break;
220
258
  }
221
- try {
222
- const lastId = lastReadIds.get(stream) || '$';
223
- // Используем более короткий block time для возможности быстрого закрытия
224
- const blockTime = isClosing ? 0 : Math.min(block, MAX_BLOCK_TIME_FOR_RESPONSIVENESS_MS);
225
- // Используем XREAD чтобы читать все сообщения напрямую из stream
226
- // Это позволяет читать сообщения даже если они уже обработаны другими consumer'ами
227
- const messages = await redis.xread('COUNT', count, 'BLOCK', blockTime, 'STREAMS', stream, lastId);
228
- // Проверяем снова после блокирующей операции
259
+ const redis = redisConnections.get(db);
260
+ for (const stream of dbStreams) {
229
261
  if (isClosing) {
230
- return;
262
+ break;
231
263
  }
232
- if (messages && Array.isArray(messages) && messages.length > 0) {
233
- // XREAD возвращает массив с одним элементом [streamName, messages]
234
- const streamData = messages[0];
235
- const [streamName, streamMessages] = streamData;
236
- if (Array.isArray(streamMessages) && streamMessages.length > 0) {
237
- // Обновляем lastReadId на последний прочитанный ID
238
- const lastMessageId = streamMessages[streamMessages.length - 1][0];
239
- lastReadIds.set(streamName, lastMessageId);
240
- for (const [messageId, fields] of streamMessages) {
241
- if (isClosing) {
242
- break;
243
- }
244
- const messageData = {};
245
- // Parse fields
246
- for (let j = 0; j < fields.length; j += 2) {
247
- messageData[fields[j]] = fields[j + 1];
248
- }
249
- // Parse commands if it's a string
250
- if (messageData.commands && typeof messageData.commands === 'string') {
251
- try {
252
- messageData.commands = JSON.parse(messageData.commands);
264
+ try {
265
+ const lastId = lastReadIds.get(stream) || '$';
266
+ const blockTime = isClosing ? 0 : Math.min(block, MAX_BLOCK_TIME_FOR_RESPONSIVENESS_MS);
267
+ const messages = await redis.xread('COUNT', count, 'BLOCK', blockTime, 'STREAMS', stream, lastId);
268
+ if (isClosing) {
269
+ return;
270
+ }
271
+ if (messages && Array.isArray(messages) && messages.length > 0) {
272
+ const streamData = messages[0];
273
+ const [streamName, streamMessages] = streamData;
274
+ if (Array.isArray(streamMessages) && streamMessages.length > 0) {
275
+ // ИСПРАВЛЕНИЕ: Фильтруем уже обработанные сообщения
276
+ const newMessages = streamMessages.filter(([messageId]) => {
277
+ const messageKey = `${streamName}:${messageId}`;
278
+ if (processedMessageIds.has(messageKey)) {
279
+ return false; // Пропускаем уже обработанное сообщение
280
+ }
281
+ processedMessageIds.add(messageKey);
282
+ return true;
283
+ });
284
+ // ИСПРАВЛЕНИЕ: Обновляем lastReadId сразу после чтения, ДО обработки
285
+ const lastMessageId = streamMessages[streamMessages.length - 1][0];
286
+ lastReadIds.set(streamName, lastMessageId);
287
+ // ИСПРАВЛЕНИЕ: Обрабатываем только новые сообщения
288
+ for (const [messageId, fields] of newMessages) {
289
+ if (isClosing) {
290
+ break;
253
291
  }
254
- catch (e) {
255
- // Ignore parse errors
292
+ const messageData = {};
293
+ for (let j = 0; j < fields.length; j += 2) {
294
+ messageData[fields[j]] = fields[j + 1];
256
295
  }
296
+ if (messageData.commands && typeof messageData.commands === 'string') {
297
+ try {
298
+ messageData.commands = JSON.parse(messageData.commands);
299
+ }
300
+ catch (e) {
301
+ // Ignore parse errors
302
+ }
303
+ }
304
+ const executionData = {
305
+ json: {
306
+ messageId,
307
+ stream: streamName,
308
+ data: messageData,
309
+ commandList: messageData,
310
+ },
311
+ };
312
+ this.emit([[executionData]]);
313
+ }
314
+ // ИСПРАВЛЕНИЕ: Очищаем старые записи из Set для предотвращения утечки памяти
315
+ // Оставляем только последние 1000 обработанных ID
316
+ if (processedMessageIds.size > 1000) {
317
+ const entries = Array.from(processedMessageIds);
318
+ const toRemove = entries.slice(0, entries.length - 1000);
319
+ toRemove.forEach(id => processedMessageIds.delete(id));
257
320
  }
258
- // Триггерим workflow с данными сообщения
259
- const executionData = {
260
- json: {
261
- messageId,
262
- stream: streamName,
263
- data: messageData,
264
- commandList: messageData,
265
- },
266
- };
267
- this.emit([[executionData]]);
268
321
  }
269
322
  }
270
323
  }
271
- }
272
- catch (error) {
273
- // Игнорируем ошибки при закрытии
274
- if (isClosing && (error.message?.includes('Connection is closed') || error.message?.includes('disconnect'))) {
275
- return;
276
- }
277
- // Игнорируем ошибки для несуществующих streams
278
- if (error.message?.includes('Invalid stream ID') || error.message?.includes('no such key')) {
279
- continue;
324
+ catch (error) {
325
+ if (isClosing && (error.message?.includes('Connection is closed') || error.message?.includes('disconnect'))) {
326
+ return;
327
+ }
328
+ if (error.message?.includes('Invalid stream ID') || error.message?.includes('no such key')) {
329
+ continue;
330
+ }
331
+ console.error(`[Chat2Crm Trigger] Error reading stream ${stream} from Redis DB ${db}:`, error);
280
332
  }
281
- // Логируем только критические ошибки
282
- console.error(`[Chat2Crm Trigger] Error reading stream ${stream} from Redis DB ${db}:`, error);
283
333
  }
284
334
  }
285
335
  }
336
+ finally {
337
+ // ИСПРАВЛЕНИЕ: Всегда сбрасываем флаг, даже при ошибке
338
+ isReading = false;
339
+ }
286
340
  };
287
341
  // Начинаем polling с более коротким интервалом
288
342
  let interval = null;
@@ -172,7 +172,7 @@ async function createRedisConnection(credentials, db = 0) {
172
172
  const redis = new ioredis_1.default({
173
173
  host: redisHost,
174
174
  port: redisPort,
175
- db: selectedDb, // Явно используем переданный параметр db
175
+ db: selectedDb,
176
176
  password: credentials.password,
177
177
  enableReadyCheck: true,
178
178
  maxRetriesPerRequest: 3,
@@ -182,6 +182,18 @@ async function createRedisConnection(credentials, db = 0) {
182
182
  return delay;
183
183
  },
184
184
  });
185
+ // Ждем подключения и явно выбираем базу данных
186
+ await new Promise((resolve, reject) => {
187
+ redis.once('ready', () => {
188
+ resolve();
189
+ });
190
+ redis.once('error', (err) => {
191
+ reject(err);
192
+ });
193
+ });
194
+ // Явно выбираем базу данных после подключения
195
+ await redis.select(selectedDb);
196
+ console.log(`[Redis Connection] Explicitly selected DB ${selectedDb} after connection`);
185
197
  // Добавляем обработчики событий для диагностики
186
198
  redis.on('connect', () => {
187
199
  console.log(`[Redis Connection] Connected to Redis at ${redisHost}:${redisPort}, DB ${selectedDb}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-chat2crm",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "n8n node for Chat2Crm Redis integration",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",