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]
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
262
|
+
break;
|
|
231
263
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
const
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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,
|
|
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}`);
|