@ihazz/bitrix24 0.2.4 → 1.0.0

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.
package/src/types.ts CHANGED
@@ -1,187 +1,215 @@
1
- // ─── Bitrix24 Inbound Event Types ────────────────────────────────────────────
1
+ // ─── Shared Logger interface ─────────────────────────────────────────────────
2
2
 
3
- /** Auth block present in every B24 webhook event */
4
- export interface B24Auth {
5
- access_token: string;
6
- expires: number;
7
- scope: string;
8
- domain: string;
9
- client_endpoint: string;
10
- member_id: string;
11
- user_id: number;
12
- application_token: string;
3
+ /** Logger interface used across all plugin modules. */
4
+ export interface Logger {
5
+ info: (...args: unknown[]) => void;
6
+ warn: (...args: unknown[]) => void;
7
+ error: (...args: unknown[]) => void;
8
+ debug: (...args: unknown[]) => void;
13
9
  }
14
10
 
15
- /** Bot entry in data.BOT map keyed by BOT_ID */
16
- export interface B24BotEntry {
17
- access_token: string;
18
- expires: number;
19
- scope: string;
20
- domain: string;
21
- client_endpoint: string;
22
- member_id: string;
23
- user_id: number;
24
- client_id: string;
25
- application_token: string;
26
- AUTH?: B24Auth;
27
- BOT_ID: number;
28
- BOT_CODE: string;
29
- }
30
-
31
- /** User info from data.USER */
32
- export interface B24User {
33
- ID: number;
34
- NAME: string;
35
- FIRST_NAME: string;
36
- LAST_NAME: string;
37
- WORK_POSITION?: string;
38
- GENDER?: string;
39
- IS_BOT?: string;
40
- IS_CONNECTOR?: string;
41
- IS_NETWORK?: string;
42
- IS_EXTRANET?: string;
43
- }
44
-
45
- /** File attached to a message */
46
- export interface B24File {
11
+ // ─── Bitrix24 V2 Event Entity Types (camelCase) ─────────────────────────────
12
+
13
+ /** Bot entity from V2 events (FETCH mode — full format) */
14
+ export interface B24V2Bot {
15
+ id: number;
16
+ code: string;
17
+ type: string;
18
+ isHidden: boolean;
19
+ isSupportOpenline: boolean;
20
+ isReactionsEnabled: boolean;
21
+ backgroundId: string | null;
22
+ language: string;
23
+ moduleId?: string;
24
+ appId?: string;
25
+ eventMode?: string;
26
+ countMessage?: number;
27
+ countCommand?: number;
28
+ countChat?: number;
29
+ countUser?: number;
30
+ }
31
+
32
+ /** Bot entity from V2 webhook events (simplified format with auth) */
33
+ export interface B24V2WebhookBot {
34
+ id: number;
35
+ code: string;
36
+ auth: {
37
+ access_token: string;
38
+ refresh_token: string;
39
+ [key: string]: unknown;
40
+ };
41
+ }
42
+
43
+ /** User entity from V2 events */
44
+ export interface B24V2User {
45
+ id: number;
46
+ active: boolean;
47
+ name: string;
48
+ firstName: string;
49
+ lastName: string;
50
+ workPosition: string;
51
+ color: string;
52
+ avatar: string;
53
+ gender: string;
54
+ birthday: string;
55
+ extranet: boolean;
56
+ bot: boolean;
57
+ connector: boolean;
58
+ externalAuthId: string;
59
+ status: string;
60
+ idle: string | false;
61
+ lastActivityDate: string | false;
62
+ absent: string | false;
63
+ departments: number[];
64
+ phones: Record<string, string> | false;
65
+ type: string;
66
+ }
67
+
68
+ /** Message entity from V2 events */
69
+ export interface B24V2Message {
47
70
  id: number;
48
71
  chatId: number;
49
- date: string;
72
+ authorId: number;
73
+ date: string | null;
74
+ text: string;
75
+ isSystem: boolean;
76
+ uuid: string;
77
+ forward: { id: number; userId: number; chatId: number; date: string } | null;
78
+ params: Record<string, unknown>;
79
+ viewedByOthers: boolean;
80
+ }
81
+
82
+ /** Chat entity from V2 events */
83
+ export interface B24V2Chat {
84
+ id: number;
85
+ dialogId: string;
86
+ type: string;
87
+ name: string;
88
+ messageType?: string;
89
+ entityType: string;
90
+ owner: number;
91
+ avatar: string;
92
+ color: string;
93
+ description?: string;
94
+ extranet?: boolean;
95
+ diskFolderId?: number | null;
96
+ }
97
+
98
+ /** Command entity from V2 ONIMBOTV2COMMANDADD events */
99
+ export interface B24V2Command {
100
+ id: number;
101
+ command: string;
102
+ params: string;
103
+ context: 'textarea' | 'keyboard' | 'menu';
104
+ }
105
+
106
+ /** File entity from V2 responses */
107
+ export interface B24V2File {
108
+ id: number;
109
+ chatId: number;
110
+ date: string | null;
50
111
  type: string;
51
112
  name: string;
52
113
  extension: string;
53
114
  size: number;
54
- image: number;
55
- status: string;
56
- progress: number;
115
+ image: { height: number; width: number } | false;
57
116
  authorId: number;
58
117
  authorName: string;
59
- urlPreview: string;
60
- urlShow: string;
61
- urlDownload: string;
62
- viewerAttrs?: {
63
- viewer: string;
64
- viewerType: string;
65
- src: string;
66
- objectId: number;
67
- title: string;
68
- };
118
+ isTranscribable?: boolean;
119
+ isVideoNote?: boolean;
120
+ isVoiceNote?: boolean;
69
121
  }
70
122
 
71
- /** PARAMS block in ONIMBOTMESSAGEADD */
72
- export interface B24MessageParams {
73
- MESSAGE: string;
74
- TEMPLATE_ID?: string;
75
- MESSAGE_TYPE: string;
76
- FROM_USER_ID: number;
77
- DIALOG_ID: string;
78
- TO_CHAT_ID: number;
79
- AUTHOR_ID?: number;
80
- TO_USER_ID: number;
81
- MESSAGE_ID: number;
82
- CHAT_TYPE: string;
83
- LANGUAGE?: string;
84
- PLATFORM_CONTEXT?: string;
85
- CHAT_USER_COUNT?: number;
86
- PARAMS?: {
87
- FILE_ID?: number[];
88
- [key: string]: unknown;
89
- };
90
- FILES?: Record<string, B24File>;
91
- [key: string]: unknown;
92
- }
93
-
94
- /** Full ONIMBOTMESSAGEADD event payload */
95
- export interface B24MessageEvent {
96
- event: 'ONIMBOTMESSAGEADD';
97
- event_handler_id?: number;
98
- data: {
99
- BOT: Record<string, B24BotEntry>;
100
- PARAMS: B24MessageParams;
101
- USER: B24User;
102
- };
103
- ts: number;
104
- auth: B24Auth;
105
- }
106
-
107
- /** ONIMBOTJOINCHAT event payload */
108
- export interface B24JoinChatEvent {
109
- event: 'ONIMBOTJOINCHAT';
110
- event_handler_id?: number;
111
- data: {
112
- BOT: Record<string, B24BotEntry>;
113
- PARAMS: {
114
- CHAT_TYPE: string;
115
- MESSAGE_TYPE: string;
116
- BOT_ID: number;
117
- USER_ID: number;
118
- DIALOG_ID: string;
119
- LANGUAGE?: string;
120
- [key: string]: unknown;
121
- };
122
- USER: B24User;
123
- };
124
- ts: number;
125
- auth: B24Auth;
126
- }
127
-
128
- /** Command entry in data.COMMAND map — keyed by COMMAND_ID */
129
- export interface B24CommandEntry {
130
- access_token: string;
131
- BOT_ID: number;
132
- BOT_CODE: string;
133
- COMMAND: string;
134
- COMMAND_ID: number;
135
- COMMAND_PARAMS: string;
136
- COMMAND_CONTEXT: 'TEXTAREA' | 'KEYBOARD';
137
- MESSAGE_ID: number;
138
- [key: string]: unknown;
139
- }
140
-
141
- /** ONIMCOMMANDADD event payload */
142
- export interface B24CommandEvent {
143
- event: 'ONIMCOMMANDADD';
144
- event_handler_id?: number;
145
- data: {
146
- COMMAND: Record<string, B24CommandEntry>;
147
- PARAMS: {
148
- MESSAGE: string;
149
- FROM_USER_ID: number;
150
- DIALOG_ID: string;
151
- TO_CHAT_ID: number;
152
- MESSAGE_ID: number;
153
- CHAT_TYPE: string;
154
- [key: string]: unknown;
155
- };
156
- USER: B24User;
157
- };
158
- ts: number;
159
- auth: B24Auth;
123
+ // ─── V2 Event Data Structures ───────────────────────────────────────────────
124
+
125
+ /** ONIMBOTV2MESSAGEADD / ONIMBOTV2MESSAGEUPDATE data */
126
+ export interface B24V2MessageEventData {
127
+ bot: B24V2Bot | B24V2WebhookBot;
128
+ message: B24V2Message;
129
+ chat: B24V2Chat;
130
+ user: B24V2User;
131
+ language: string;
160
132
  }
161
133
 
162
- /** ONAPPINSTALL event payload */
163
- export interface B24AppInstallEvent {
164
- event: 'ONAPPINSTALL';
165
- data: Record<string, unknown>;
166
- ts: number;
167
- auth: B24Auth;
134
+ /** ONIMBOTV2MESSAGEDELETE data */
135
+ export interface B24V2MessageDeleteEventData {
136
+ bot: B24V2Bot | B24V2WebhookBot;
137
+ messageId: number;
138
+ chat: B24V2Chat;
139
+ user: B24V2User;
140
+ language: string;
168
141
  }
169
142
 
170
- /** ONIMBOTDELETE event payload */
171
- export interface B24BotDeleteEvent {
172
- event: 'ONIMBOTDELETE';
173
- data: Record<string, unknown>;
174
- ts: number;
175
- auth: B24Auth;
143
+ /** ONIMBOTV2JOINCHAT data */
144
+ export interface B24V2JoinChatEventData {
145
+ bot: B24V2Bot | B24V2WebhookBot;
146
+ dialogId: string;
147
+ chat: B24V2Chat;
148
+ user: B24V2User;
149
+ language: string;
176
150
  }
177
151
 
178
- /** Union of all event types */
179
- export type B24Event =
180
- | B24MessageEvent
181
- | B24JoinChatEvent
182
- | B24CommandEvent
183
- | B24AppInstallEvent
184
- | B24BotDeleteEvent;
152
+ /** ONIMBOTV2COMMANDADD data */
153
+ export interface B24V2CommandEventData {
154
+ bot: B24V2Bot | B24V2WebhookBot;
155
+ command: B24V2Command;
156
+ message: B24V2Message;
157
+ chat: B24V2Chat;
158
+ user: B24V2User;
159
+ language: string;
160
+ }
161
+
162
+ /** ONIMBOTV2DELETE data */
163
+ export interface B24V2DeleteEventData {
164
+ bot: B24V2Bot | B24V2WebhookBot;
165
+ }
166
+
167
+ /** ONIMBOTV2REACTIONCHANGE data */
168
+ export interface B24V2ReactionEventData {
169
+ bot: B24V2Bot | B24V2WebhookBot;
170
+ reaction: string;
171
+ action: 'set' | 'delete';
172
+ message: B24V2Message;
173
+ chat: B24V2Chat;
174
+ user: B24V2User;
175
+ language: string;
176
+ }
177
+
178
+ /** Union of V2 event data */
179
+ export type B24V2EventData =
180
+ | B24V2MessageEventData
181
+ | B24V2MessageDeleteEventData
182
+ | B24V2JoinChatEventData
183
+ | B24V2CommandEventData
184
+ | B24V2DeleteEventData
185
+ | B24V2ReactionEventData;
186
+
187
+ // ─── V2 FETCH Event Types ───────────────────────────────────────────────────
188
+
189
+ /** Single event item from imbot.v2.Event.get */
190
+ export interface B24V2FetchEventItem {
191
+ eventId: number;
192
+ type: string;
193
+ date: string;
194
+ data: B24V2EventData;
195
+ }
196
+
197
+ /** Response from imbot.v2.Event.get */
198
+ export interface B24V2EventGetResult {
199
+ events: B24V2FetchEventItem[];
200
+ lastEventId: number;
201
+ hasMore: boolean;
202
+ }
203
+
204
+ // ─── V2 Webhook Event (incoming POST) ──────────────────────────────────────
205
+
206
+ /** V2 webhook event payload (POST to webhookUrl) */
207
+ export interface B24V2WebhookEvent {
208
+ event: string;
209
+ data: B24V2EventData;
210
+ ts?: number;
211
+ auth?: Record<string, unknown>;
212
+ }
185
213
 
186
214
  // ─── Bitrix24 API Response Types ─────────────────────────────────────────────
187
215
 
@@ -191,7 +219,7 @@ export interface B24ApiResult<T = unknown> {
191
219
  start: number;
192
220
  finish: number;
193
221
  duration: number;
194
- processing: number;
222
+ processing?: number;
195
223
  date_start: string;
196
224
  date_finish: string;
197
225
  };
@@ -199,9 +227,72 @@ export interface B24ApiResult<T = unknown> {
199
227
  error_description?: string;
200
228
  }
201
229
 
202
- export interface B24ApiError {
203
- error: string;
204
- error_description: string;
230
+ // ─── V2 API Response Types ──────────────────────────────────────────────────
231
+
232
+ /** Response from imbot.v2.Bot.register / Bot.update */
233
+ export interface B24V2BotResult {
234
+ bot: B24V2Bot;
235
+ users: B24V2User[];
236
+ }
237
+
238
+ /** Response from imbot.v2.Bot.list */
239
+ export interface B24V2BotListResult {
240
+ bots: B24V2Bot[];
241
+ users: B24V2User[];
242
+ hasNextPage: boolean;
243
+ }
244
+
245
+ /** Response from imbot.v2.Chat.Message.send */
246
+ export interface B24V2SendMessageResult {
247
+ id: number;
248
+ uuidMap: Record<string, number>;
249
+ }
250
+
251
+ /** Available status codes for imbot.v2.Chat.InputAction.notify */
252
+ export type B24InputActionStatusCode =
253
+ | 'IMBOT_AGENT_ACTION_THINKING'
254
+ | 'IMBOT_AGENT_ACTION_SEARCHING'
255
+ | 'IMBOT_AGENT_ACTION_GENERATING'
256
+ | 'IMBOT_AGENT_ACTION_ANALYZING'
257
+ | 'IMBOT_AGENT_ACTION_PROCESSING'
258
+ | 'IMBOT_AGENT_ACTION_TRANSLATING'
259
+ | 'IMBOT_AGENT_ACTION_CONNECTING'
260
+ | 'IMBOT_AGENT_ACTION_CHECKING'
261
+ | 'IMBOT_AGENT_ACTION_CALCULATING'
262
+ | 'IMBOT_AGENT_ACTION_READING_DOCS'
263
+ | 'IMBOT_AGENT_ACTION_COMPOSING';
264
+
265
+ /** Response from imbot.v2.Chat.Message.read */
266
+ export interface B24V2ReadMessageResult {
267
+ chatId: number;
268
+ lastId: number;
269
+ counter: number;
270
+ viewedMessages: number[];
271
+ }
272
+
273
+ /** Response from imbot.v2.Command.register */
274
+ export interface B24V2CommandResult {
275
+ command: {
276
+ id: number;
277
+ botId: number;
278
+ command: string;
279
+ common: boolean;
280
+ hidden: boolean;
281
+ extranetSupport: boolean;
282
+ };
283
+ }
284
+
285
+ /** Response from imbot.v2.File.upload */
286
+ export interface B24V2FileUploadResult {
287
+ file: B24V2File;
288
+ messageId: number;
289
+ chatId: number;
290
+ dialogId: string;
291
+ }
292
+
293
+ /** Response from imbot.v2.File.download */
294
+ export interface B24V2FileDownloadResult {
295
+ downloadUrl: string;
205
296
  }
206
297
 
207
298
  // ─── Outbound Message Types ──────────────────────────────────────────────────
@@ -229,31 +320,37 @@ export interface KeyboardNewline {
229
320
  /** B24 keyboard: flat array with NEWLINE separators between rows */
230
321
  export type B24Keyboard = (KeyboardButton | KeyboardNewline)[];
231
322
 
232
- export interface SendMessageOptions {
233
- ATTACH?: unknown;
234
- KEYBOARD?: B24Keyboard;
235
- MENU?: unknown;
236
- SYSTEM?: 'Y' | 'N';
237
- URL_PREVIEW?: 'Y' | 'N';
238
- }
239
-
240
323
  export interface SendMessageResult {
241
324
  ok: boolean;
242
325
  messageId?: number;
243
326
  error?: string;
244
327
  }
245
328
 
329
+ // ─── FETCH Mode Context ─────────────────────────────────────────────────────
330
+
331
+ /** Context passed from PollingService to InboundHandler for FETCH events */
332
+ export interface FetchContext {
333
+ webhookUrl: string;
334
+ botId: number;
335
+ botToken: string;
336
+ }
337
+
246
338
  // ─── Plugin Config Types ─────────────────────────────────────────────────────
247
339
 
248
340
  export interface Bitrix24AccountConfig {
249
341
  enabled?: boolean;
250
342
  webhookUrl?: string;
343
+ botToken?: string;
251
344
  botName?: string;
252
345
  botCode?: string;
253
346
  botAvatar?: string;
254
- callbackUrl?: string;
255
- dmPolicy?: 'open' | 'allowlist' | 'pairing';
256
347
  allowFrom?: string[];
348
+ callbackUrl?: string;
349
+ eventMode?: 'fetch' | 'webhook';
350
+ agentMode?: boolean;
351
+ pollingIntervalMs?: number;
352
+ pollingFastIntervalMs?: number;
353
+ dmPolicy?: 'pairing' | 'webhookUser';
257
354
  showTyping?: boolean;
258
355
  streamUpdates?: boolean;
259
356
  updateIntervalMs?: number;
@@ -273,16 +370,15 @@ export interface B24MsgContext {
273
370
  chatId: string;
274
371
  chatInternalId: string;
275
372
  messageId: string;
373
+ replyToMessageId?: string;
374
+ isForwarded?: boolean;
276
375
  text: string;
277
376
  isDm: boolean;
278
377
  isGroup: boolean;
279
378
  media: B24MediaItem[];
280
379
  platform?: string;
281
380
  language?: string;
282
- raw: B24Event;
283
- botToken: string;
284
- userToken: string;
285
- clientEndpoint: string;
381
+ raw: B24V2FetchEventItem | B24V2WebhookEvent;
286
382
  botId: number;
287
383
  memberId: string;
288
384
  }
@@ -302,6 +398,4 @@ export interface PortalState {
302
398
  botId: number;
303
399
  memberId: string;
304
400
  domain: string;
305
- clientEndpoint: string;
306
- applicationToken: string;
307
401
  }
package/src/utils.ts CHANGED
@@ -6,6 +6,46 @@ export function maskToken(token: string): string {
6
6
  return `${token.slice(0, 4)}...${token.slice(-4)}`;
7
7
  }
8
8
 
9
+ /**
10
+ * Mask Bitrix24 webhook credentials in a REST URL before logging it.
11
+ */
12
+ export function maskWebhookUrl(webhookUrl: string): string {
13
+ try {
14
+ const url = new URL(webhookUrl);
15
+ const pathParts = url.pathname.split('/').filter(Boolean);
16
+
17
+ if (pathParts[0] === 'rest' && pathParts.length >= 3) {
18
+ pathParts[1] = '***';
19
+ pathParts[2] = '***';
20
+ url.pathname = `/${pathParts.join('/')}`;
21
+ }
22
+
23
+ url.username = '';
24
+ url.password = '';
25
+ url.search = '';
26
+ return url.toString();
27
+ } catch {
28
+ return '[masked webhook url]';
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Convert an unknown error into a log-friendly plain object.
34
+ */
35
+ export function serializeError(error: unknown): Record<string, unknown> {
36
+ if (error instanceof Error) {
37
+ return {
38
+ name: error.name,
39
+ message: error.message,
40
+ stack: error.stack,
41
+ };
42
+ }
43
+
44
+ return {
45
+ message: String(error),
46
+ };
47
+ }
48
+
9
49
  /**
10
50
  * Retry a function with exponential backoff.
11
51
  */
@@ -23,12 +63,10 @@ export async function withRetry<T>(
23
63
  const maxDelayMs = opts.maxDelayMs ?? 10000;
24
64
  const shouldRetry = opts.shouldRetry ?? (() => true);
25
65
 
26
- let lastError: unknown;
27
66
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
28
67
  try {
29
68
  return await fn();
30
69
  } catch (error) {
31
- lastError = error;
32
70
  if (attempt >= maxRetries || !shouldRetry(error)) {
33
71
  throw error;
34
72
  }
@@ -37,7 +75,8 @@ export async function withRetry<T>(
37
75
  await new Promise((r) => setTimeout(r, jitter));
38
76
  }
39
77
  }
40
- throw lastError;
78
+ // Unreachable: loop always returns or throws
79
+ throw new Error('withRetry: unexpected end of retry loop');
41
80
  }
42
81
 
43
82
  /**
@@ -63,6 +102,18 @@ export class Bitrix24ApiError extends Error {
63
102
  }
64
103
  }
65
104
 
105
+ /**
106
+ * Regex matching the channel prefix variants: "bitrix24:", "b24:", "bx24:".
107
+ */
108
+ export const CHANNEL_PREFIX_RE = /^(bitrix24|b24|bx24):/i;
109
+
110
+ /**
111
+ * Strip channel prefix from an identifier (e.g. "bitrix24:42" → "42").
112
+ */
113
+ export function stripChannelPrefix(id: string): string {
114
+ return id.replace(CHANNEL_PREFIX_RE, '');
115
+ }
116
+
66
117
  /**
67
118
  * Simple console logger fallback.
68
119
  */