n8n-nodes-chat2crm 0.1.7 → 0.1.9

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.
@@ -3,6 +3,5 @@ export declare class Chat2CrmSend implements INodeType {
3
3
  description: INodeTypeDescription;
4
4
  getStreams(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
5
5
  private static createRedisConnectionForLoadOptions;
6
- private static getStreamDb;
7
6
  execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
8
7
  }
@@ -7,6 +7,7 @@ exports.Chat2CrmSend = void 0;
7
7
  const n8n_workflow_1 = require("n8n-workflow");
8
8
  const ioredis_1 = __importDefault(require("ioredis"));
9
9
  const RedisConnection_1 = require("../Chat2CrmTrigger/Infra/RedisConnection");
10
+ const StreamConfig_1 = require("../Chat2CrmTrigger/Infra/StreamConfig");
10
11
  class Chat2CrmSend {
11
12
  constructor() {
12
13
  this.description = {
@@ -35,72 +36,15 @@ class Chat2CrmSend {
35
36
  type: 'multiOptions',
36
37
  required: true,
37
38
  description: 'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
38
- options: [
39
- {
40
- name: 'Chat Incoming Message',
41
- value: 'chat_incoming_message',
42
- description: 'Messages to chat (DB 0)',
43
- },
44
- {
45
- name: 'Chat Outgoing Message',
46
- value: 'chat_outgoing_message',
47
- description: 'Messages from chat to CRM (DB 0)',
48
- },
49
- {
50
- name: 'Chat Outgoing Status',
51
- value: 'chat_outgoing_status',
52
- description: 'Status messages from chat (DB 0)',
53
- },
54
- {
55
- name: 'CRM Contacts',
56
- value: 'crm_contacts',
57
- description: 'CRM contacts stream (DB 1)',
58
- },
59
- {
60
- name: 'CRM Incoming Message',
61
- value: 'crm_incoming_message',
62
- description: 'Messages to CRM (DB 1)',
63
- },
64
- {
65
- name: 'CRM Incoming Status',
66
- value: 'crm_incoming_status',
67
- description: 'Status messages to CRM (DB 0)',
68
- },
69
- {
70
- name: 'CRM Lazy Incoming Message',
71
- value: 'crm_lazy_incoming_message',
72
- description: 'Lazy incoming messages to CRM (DB 1)',
73
- },
74
- {
75
- name: 'CRM Outgoing Message',
76
- value: 'crm_outgoing_message',
77
- description: 'Messages from CRM to chat (DB 1)',
78
- },
79
- {
80
- name: 'CRM Outgoing Status',
81
- value: 'crm_outgoing_status',
82
- description: 'Status messages from CRM (DB 0)',
83
- },
84
- {
85
- name: 'N8N Incoming Message',
86
- value: 'n8n_incoming_message',
87
- description: 'Incoming messages to N8N (DB 0)',
88
- },
89
- {
90
- name: 'N8N Outgoing Message',
91
- value: 'n8n_outgoing_message',
92
- description: 'Outgoing messages from N8N (DB 0)',
93
- },
94
- ],
39
+ options: (0, StreamConfig_1.getStreamOptions)(),
95
40
  default: [],
96
41
  },
97
42
  {
98
43
  displayName: 'Message Data',
99
44
  name: 'messageData',
100
45
  type: 'json',
101
- required: true,
102
46
  default: '{}',
103
- description: 'Message data to send to Redis stream (JSON object)',
47
+ description: 'Message data to send to Redis stream (JSON object). If not provided, will use data from input item (from trigger).',
104
48
  },
105
49
  {
106
50
  displayName: 'Message ID',
@@ -292,37 +236,21 @@ class Chat2CrmSend {
292
236
  });
293
237
  return redis;
294
238
  }
295
- // Определяем базу данных для стрима
296
- static getStreamDb(stream) {
297
- // Streams в DB 1 (CrmWorker)
298
- if (stream === 'crm_outgoing_message' ||
299
- stream === 'crm_incoming_message' ||
300
- stream === 'crm_lazy_incoming_message' ||
301
- stream === 'crm_contacts') {
302
- return 1;
303
- }
304
- // Все остальные streams находятся в DB 0
305
- return 0;
306
- }
307
239
  async execute() {
308
240
  const items = this.getInputData();
309
241
  const returnData = [];
310
242
  const credentials = await this.getCredentials('chat2CrmRedisApi');
311
243
  const selectedStreams = this.getNodeParameter('streams', 0);
312
244
  const messageId = this.getNodeParameter('messageId', 0);
313
- // Проверяем, что выбраны streams
245
+ // Валидация входных данных
314
246
  if (!selectedStreams || selectedStreams.length === 0) {
315
247
  throw new n8n_workflow_1.ApplicationError('Please select at least one stream to send message to', { level: 'warning' });
316
248
  }
317
- // Группируем выбранные streams по базам данных
318
- const streamsByDb = new Map();
319
- selectedStreams.forEach(stream => {
320
- const db = Chat2CrmSend.getStreamDb(stream);
321
- if (!streamsByDb.has(db)) {
322
- streamsByDb.set(db, []);
323
- }
324
- streamsByDb.get(db).push(stream);
325
- });
249
+ if (messageId && typeof messageId !== 'string') {
250
+ throw new n8n_workflow_1.ApplicationError('Message ID must be a string', { level: 'error' });
251
+ }
252
+ // Группируем выбранные streams по базам данных используя общую функцию
253
+ const streamsByDb = (0, StreamConfig_1.groupStreamsByDb)(selectedStreams);
326
254
  // Создаем подключения к Redis только для нужных баз данных
327
255
  const redisConnections = new Map();
328
256
  try {
@@ -332,34 +260,46 @@ class Chat2CrmSend {
332
260
  }
333
261
  // Обрабатываем каждый элемент входных данных
334
262
  for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
335
- const messageDataParam = this.getNodeParameter('messageData', itemIndex);
336
- // Парсим JSON данные сообщения
263
+ const item = items[itemIndex];
264
+ const inputJson = item.json;
265
+ // Определяем данные для отправки: приоритет - data из входного элемента, затем commandList, затем параметр messageData
337
266
  let messageData = {};
338
- try {
339
- if (typeof messageDataParam === 'string') {
340
- // Если это строка, пытаемся распарсить JSON
341
- if (messageDataParam.trim() === '') {
342
- messageData = {};
267
+ if (inputJson.data && typeof inputJson.data === 'object') {
268
+ // Используем data из входного элемента (из триггера)
269
+ messageData = inputJson.data;
270
+ }
271
+ else if (inputJson.commandList && typeof inputJson.commandList === 'object') {
272
+ // Используем commandList из входного элемента (из триггера)
273
+ messageData = inputJson.commandList;
274
+ }
275
+ else {
276
+ // Используем параметр messageData из настроек ноды
277
+ const messageDataParam = this.getNodeParameter('messageData', itemIndex);
278
+ try {
279
+ if (typeof messageDataParam === 'string') {
280
+ // Если это строка, пытаемся распарсить JSON
281
+ if (messageDataParam.trim() === '') {
282
+ messageData = {};
283
+ }
284
+ else {
285
+ messageData = JSON.parse(messageDataParam);
286
+ }
343
287
  }
344
- else {
345
- messageData = JSON.parse(messageDataParam);
288
+ else if (typeof messageDataParam === 'object' && messageDataParam !== null) {
289
+ // Если это уже объект, используем его напрямую
290
+ messageData = messageDataParam;
346
291
  }
347
292
  }
348
- else if (typeof messageDataParam === 'object' && messageDataParam !== null) {
349
- // Если это уже объект, используем его напрямую
350
- messageData = messageDataParam;
293
+ catch (error) {
294
+ throw new n8n_workflow_1.ApplicationError(`Invalid JSON in messageData: ${error.message}`, { level: 'error' });
351
295
  }
352
- else {
353
- throw new n8n_workflow_1.ApplicationError('messageData must be a JSON object or string', { level: 'error' });
354
- }
355
- }
356
- catch (error) {
357
- throw new n8n_workflow_1.ApplicationError(`Invalid JSON in messageData: ${error.message}`, { level: 'error' });
358
296
  }
359
297
  // Проверяем, что messageData не пустой
360
298
  if (Object.keys(messageData).length === 0) {
361
- throw new n8n_workflow_1.ApplicationError('messageData cannot be empty', { level: 'error' });
299
+ throw new n8n_workflow_1.ApplicationError('messageData cannot be empty. Provide data in input item or in messageData parameter', { level: 'error' });
362
300
  }
301
+ // Определяем messageId: приоритет - из входного элемента, затем из параметра
302
+ const finalMessageId = inputJson.messageId || messageId || '*';
363
303
  // Отправляем сообщение в каждый выбранный stream
364
304
  const sent = [];
365
305
  const errors = [];
@@ -383,7 +323,7 @@ class Chat2CrmSend {
383
323
  }
384
324
  }
385
325
  // Отправляем сообщение в stream
386
- const id = await redis.xadd(stream, messageId || '*', ...fields);
326
+ const id = await redis.xadd(stream, finalMessageId, ...fields);
387
327
  if (!id) {
388
328
  throw new n8n_workflow_1.ApplicationError('Failed to add message to stream: XADD returned null', { level: 'error' });
389
329
  }
@@ -3,6 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Chat2CrmTrigger = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
5
  const RedisConnection_1 = require("./Infra/RedisConnection");
6
+ const StreamConfig_1 = require("./Infra/StreamConfig");
7
+ // Константы для валидации
8
+ const MIN_POLL_INTERVAL_SECONDS = 0.01;
9
+ const MAX_POLL_INTERVAL_SECONDS = 60;
10
+ const MIN_BLOCK_TIME_MS = 0;
11
+ const MAX_BLOCK_TIME_MS = 60000; // 60 секунд
12
+ const MIN_COUNT = 1;
13
+ const MAX_COUNT = 1000;
14
+ const MAX_BLOCK_TIME_FOR_RESPONSIVENESS_MS = 500;
6
15
  class Chat2CrmTrigger {
7
16
  constructor() {
8
17
  this.description = {
@@ -29,70 +38,9 @@ class Chat2CrmTrigger {
29
38
  displayName: 'Streams',
30
39
  name: 'streams',
31
40
  type: 'multiOptions',
32
- required: true, // Вернули required: true
41
+ required: true,
33
42
  description: 'Select Redis streams to listen to',
34
- options: [
35
- {
36
- name: 'Chat Incoming Message',
37
- value: 'chat_incoming_message',
38
- description: 'Messages to chat (DB 0)',
39
- },
40
- {
41
- name: 'Chat Outgoing Message',
42
- value: 'chat_outgoing_message',
43
- description: 'Messages from chat to CRM (DB 0)',
44
- },
45
- {
46
- name: 'Chat Outgoing Status',
47
- value: 'chat_outgoing_status',
48
- description: 'Status messages from chat (DB 0)',
49
- },
50
- {
51
- name: 'Chat Outgoing Status (CRM)',
52
- value: 'chat_outgoing_status',
53
- description: 'Chat outgoing status in CRM DB (DB 1)',
54
- },
55
- {
56
- name: 'CRM Contacts',
57
- value: 'crm_contacts',
58
- description: 'CRM contacts stream (DB 1)',
59
- },
60
- {
61
- name: 'CRM Incoming Message',
62
- value: 'crm_incoming_message',
63
- description: 'Messages to CRM (DB 1)',
64
- },
65
- {
66
- name: 'CRM Incoming Status',
67
- value: 'crm_incoming_status',
68
- description: 'Status messages to CRM (DB 0)',
69
- },
70
- {
71
- name: 'CRM Lazy Incoming Message',
72
- value: 'crm_lazy_incoming_message',
73
- description: 'Lazy incoming messages to CRM (DB 1)',
74
- },
75
- {
76
- name: 'CRM Outgoing Message',
77
- value: 'crm_outgoing_message',
78
- description: 'Messages from CRM to chat (DB 1)',
79
- },
80
- {
81
- name: 'CRM Outgoing Status',
82
- value: 'crm_outgoing_status',
83
- description: 'Status messages from CRM (DB 1)',
84
- },
85
- {
86
- name: 'N8N Incoming Message',
87
- value: 'n8n_incoming_message',
88
- description: 'Incoming messages to N8N (DB 0)',
89
- },
90
- {
91
- name: 'N8N Outgoing Message',
92
- value: 'n8n_outgoing_message',
93
- description: 'Outgoing messages from N8N (DB 0)',
94
- },
95
- ],
43
+ options: (0, StreamConfig_1.getStreamOptions)(),
96
44
  default: ['chat_outgoing_message'],
97
45
  },
98
46
  {
@@ -125,29 +73,22 @@ class Chat2CrmTrigger {
125
73
  const block = this.getNodeParameter('block', 1000);
126
74
  const count = this.getNodeParameter('count', 10);
127
75
  const pollIntervalSeconds = this.getNodeParameter('pollInterval', 0.5);
128
- const pollInterval = Math.min(pollIntervalSeconds * 1000, 500); // Максимум 500ms
129
- // Проверяем, что выбраны streams
76
+ // Валидация входных данных
130
77
  if (!selectedStreams || selectedStreams.length === 0) {
131
78
  throw new n8n_workflow_1.ApplicationError('Please select at least one stream to monitor', { level: 'warning' });
132
79
  }
133
- // Группируем выбранные streams по базам данных
134
- const streamsByDb = new Map();
135
- selectedStreams.forEach(stream => {
136
- let db = 0; // По умолчанию DB 0
137
- // Streams в DB 1 (CrmWorker)
138
- if (stream === 'crm_outgoing_message' ||
139
- stream === 'crm_incoming_message' ||
140
- stream === 'crm_lazy_incoming_message' ||
141
- stream === 'crm_contacts') {
142
- db = 1;
143
- }
144
- // Все остальные streams (chat_incoming_message, chat_outgoing_message,
145
- // crm_incoming_status, crm_outgoing_status) находятся в DB 0
146
- if (!streamsByDb.has(db)) {
147
- streamsByDb.set(db, []);
148
- }
149
- streamsByDb.get(db).push(stream);
150
- });
80
+ if (pollIntervalSeconds < MIN_POLL_INTERVAL_SECONDS || pollIntervalSeconds > MAX_POLL_INTERVAL_SECONDS) {
81
+ throw new n8n_workflow_1.ApplicationError(`Poll interval must be between ${MIN_POLL_INTERVAL_SECONDS} and ${MAX_POLL_INTERVAL_SECONDS} seconds`, { level: 'error' });
82
+ }
83
+ if (block < MIN_BLOCK_TIME_MS || block > MAX_BLOCK_TIME_MS) {
84
+ throw new n8n_workflow_1.ApplicationError(`Block time must be between ${MIN_BLOCK_TIME_MS} and ${MAX_BLOCK_TIME_MS} milliseconds`, { level: 'error' });
85
+ }
86
+ if (count < MIN_COUNT || count > MAX_COUNT) {
87
+ throw new n8n_workflow_1.ApplicationError(`Count must be between ${MIN_COUNT} and ${MAX_COUNT}`, { level: 'error' });
88
+ }
89
+ const pollInterval = Math.min(pollIntervalSeconds * 1000, MAX_BLOCK_TIME_FOR_RESPONSIVENESS_MS);
90
+ // Группируем выбранные streams по базам данных используя общую функцию
91
+ const streamsByDb = (0, StreamConfig_1.groupStreamsByDb)(selectedStreams);
151
92
  // Создаем подключения к Redis только для нужных баз данных
152
93
  const redisConnections = new Map();
153
94
  for (const db of streamsByDb.keys()) {
@@ -241,7 +182,7 @@ class Chat2CrmTrigger {
241
182
  try {
242
183
  const lastId = lastReadIds.get(stream) || '$';
243
184
  // Используем более короткий block time для возможности быстрого закрытия
244
- const blockTime = isClosing ? 0 : Math.min(block, 500); // Максимум 500ms для более быстрого отклика
185
+ const blockTime = isClosing ? 0 : Math.min(block, MAX_BLOCK_TIME_FOR_RESPONSIVENESS_MS);
245
186
  // Используем XREAD чтобы читать все сообщения напрямую из stream
246
187
  // Это позволяет читать сообщения даже если они уже обработаны другими consumer'ами
247
188
  const messages = await redis.xread('COUNT', count, 'BLOCK', blockTime, 'STREAMS', stream, lastId);
@@ -0,0 +1,35 @@
1
+ import { INodePropertyOptions } from 'n8n-workflow';
2
+ /**
3
+ * Интерфейс для определения stream
4
+ */
5
+ export interface StreamDefinition {
6
+ name: string;
7
+ value: string;
8
+ description: string;
9
+ db: number;
10
+ }
11
+ /**
12
+ * Полный список всех доступных streams с их конфигурацией
13
+ */
14
+ export declare const STREAM_DEFINITIONS: StreamDefinition[];
15
+ /**
16
+ * Маппинг stream names к номерам баз данных
17
+ */
18
+ export declare const STREAM_DB_MAP: Record<string, number>;
19
+ /**
20
+ * Определяет номер базы данных для указанного stream
21
+ * @param stream - Имя stream
22
+ * @returns Номер базы данных (0 или 1)
23
+ */
24
+ export declare function getStreamDb(stream: string): number;
25
+ /**
26
+ * Группирует streams по базам данных
27
+ * @param streams - Массив имен streams
28
+ * @returns Map с ключом - номер DB, значением - массив streams
29
+ */
30
+ export declare function groupStreamsByDb(streams: string[]): Map<number, string[]>;
31
+ /**
32
+ * Возвращает опции streams для n8n properties
33
+ * @returns Массив опций для multiOptions
34
+ */
35
+ export declare function getStreamOptions(): INodePropertyOptions[];
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STREAM_DB_MAP = exports.STREAM_DEFINITIONS = void 0;
4
+ exports.getStreamDb = getStreamDb;
5
+ exports.groupStreamsByDb = groupStreamsByDb;
6
+ exports.getStreamOptions = getStreamOptions;
7
+ /**
8
+ * Полный список всех доступных streams с их конфигурацией
9
+ */
10
+ exports.STREAM_DEFINITIONS = [
11
+ {
12
+ name: 'Chat Incoming Message',
13
+ value: 'chat_incoming_message',
14
+ description: 'Messages to chat (DB 0)',
15
+ db: 0,
16
+ },
17
+ {
18
+ name: 'Chat Outgoing Message',
19
+ value: 'chat_outgoing_message',
20
+ description: 'Messages from chat to CRM (DB 0)',
21
+ db: 0,
22
+ },
23
+ {
24
+ name: 'Chat Outgoing Status',
25
+ value: 'chat_outgoing_status',
26
+ description: 'Status messages from chat (DB 0)',
27
+ db: 0,
28
+ },
29
+ {
30
+ name: 'Chat Outgoing Status (CRM)',
31
+ value: 'chat_outgoing_status',
32
+ description: 'Chat outgoing status in CRM DB (DB 1)',
33
+ db: 1,
34
+ },
35
+ {
36
+ name: 'CRM Contacts',
37
+ value: 'crm_contacts',
38
+ description: 'CRM contacts stream (DB 1)',
39
+ db: 1,
40
+ },
41
+ {
42
+ name: 'CRM Incoming Message',
43
+ value: 'crm_incoming_message',
44
+ description: 'Messages to CRM (DB 1)',
45
+ db: 1,
46
+ },
47
+ {
48
+ name: 'CRM Incoming Status',
49
+ value: 'crm_incoming_status',
50
+ description: 'Status messages to CRM (DB 0)',
51
+ db: 0,
52
+ },
53
+ {
54
+ name: 'CRM Lazy Incoming Message',
55
+ value: 'crm_lazy_incoming_message',
56
+ description: 'Lazy incoming messages to CRM (DB 1)',
57
+ db: 1,
58
+ },
59
+ {
60
+ name: 'CRM Outgoing Message',
61
+ value: 'crm_outgoing_message',
62
+ description: 'Messages from CRM to chat (DB 1)',
63
+ db: 1,
64
+ },
65
+ {
66
+ name: 'CRM Outgoing Status',
67
+ value: 'crm_outgoing_status',
68
+ description: 'Status messages from CRM (DB 1)',
69
+ db: 1,
70
+ },
71
+ {
72
+ name: 'N8N Incoming Message',
73
+ value: 'n8n_incoming_message',
74
+ description: 'Incoming messages to N8N (DB 0)',
75
+ db: 0,
76
+ },
77
+ {
78
+ name: 'N8N Outgoing Message',
79
+ value: 'n8n_outgoing_message',
80
+ description: 'Outgoing messages from N8N (DB 0)',
81
+ db: 0,
82
+ },
83
+ ];
84
+ /**
85
+ * Маппинг stream names к номерам баз данных
86
+ */
87
+ exports.STREAM_DB_MAP = {
88
+ crm_outgoing_message: 1,
89
+ crm_incoming_message: 1,
90
+ crm_lazy_incoming_message: 1,
91
+ crm_contacts: 1,
92
+ crm_outgoing_status: 1,
93
+ };
94
+ /**
95
+ * Определяет номер базы данных для указанного stream
96
+ * @param stream - Имя stream
97
+ * @returns Номер базы данных (0 или 1)
98
+ */
99
+ function getStreamDb(stream) {
100
+ return exports.STREAM_DB_MAP[stream] ?? 0;
101
+ }
102
+ /**
103
+ * Группирует streams по базам данных
104
+ * @param streams - Массив имен streams
105
+ * @returns Map с ключом - номер DB, значением - массив streams
106
+ */
107
+ function groupStreamsByDb(streams) {
108
+ const streamsByDb = new Map();
109
+ streams.forEach(stream => {
110
+ const db = getStreamDb(stream);
111
+ if (!streamsByDb.has(db)) {
112
+ streamsByDb.set(db, []);
113
+ }
114
+ streamsByDb.get(db).push(stream);
115
+ });
116
+ return streamsByDb;
117
+ }
118
+ /**
119
+ * Возвращает опции streams для n8n properties
120
+ * @returns Массив опций для multiOptions
121
+ */
122
+ function getStreamOptions() {
123
+ return exports.STREAM_DEFINITIONS.map(def => ({
124
+ name: def.name,
125
+ value: def.value,
126
+ description: def.description,
127
+ }));
128
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-chat2crm",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "n8n node for Chat2Crm Redis integration",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",