@andrey4emk/npm-app-back-b24 3.7.0 → 3.7.1

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/README.md CHANGED
@@ -44,6 +44,12 @@ import { fetchRetry, maskUrl } from "@andrey4emk/npm-app-back-b24/fetchRetry";
44
44
 
45
45
  Корневой импорт при этом остаётся безопасным: если `APP_B24_CLIENT_ID` и `APP_B24_CLIENT_SECRET` не заданы, пакет считает, что OAuth в проекте не используется, молча выставляет `$b24 = null` и пишет об этом только в `debug`.
46
46
 
47
+ ### Строгость типов
48
+
49
+ Пакет публикуется исходниками на TypeScript и компилируется **в программе потребителя** — значит его код проверяется настройками потребителя, а не нашими. Поэтому сам пакет собирается с `strict` и `noUncheckedIndexedAccess`: на настройках Nuxt (где оба флага включены) штатный `nuxt typecheck` проходит по коду пакета без обходных скриптов и без `skipLibCheck`-заплаток.
50
+
51
+ Практическое следствие: индексация массива внутри пакета всегда сопровождается проверкой на `undefined`. Если в своём проекте эти флаги включены — ошибок из `node_modules/@andrey4emk/npm-app-back-b24` быть не должно.
52
+
47
53
  ## Использование
48
54
 
49
55
  ```js
@@ -493,6 +499,10 @@ const response = await fetchRetry("https://api.example.com/data", { method: "GET
493
499
  );
494
500
  ```
495
501
 
502
+ Конструктор сразу разрешает тип мессенджера для `whatsApp` и `telegram` — берёт `messenger[0].type` и держит в полях, вместо того чтобы индексировать массив при каждой отправке. Если конфига нет или список `messenger` пуст, тип остаётся неразрешённым, и **методы этого мессенджера возвращают** `{ error: true, message: "ChatApp: не разрешён тип мессенджера whatsApp (нет конфига или пуст messenger)", data: null }` — вместо запроса на URL с `undefined` в пути. Конструктор при этом **не бросает**: экземпляр обычно создаётся на уровне модуля, и бросок уронил бы загрузку всего модуля отправки вместе с соседними каналами.
503
+
504
+ Отказ локальный: пустой `whatsApp` не мешает `telegram`. Но конфиг всё равно нужен для **обоих** мессенджеров, даже если проект пользуется одним — иначе второй канал молча перестанет работать, и узнать об этом можно будет только по `res.error` в логе.
505
+
496
506
  **Методы:**
497
507
 
498
508
  - **`makeTokenChatApp()`** — получить токены доступа для ChatApp API.
@@ -629,6 +639,8 @@ const response = await fetchRetry("https://api.example.com/data", { method: "GET
629
639
  - **Особенности:**
630
640
  - SMTP-сервер: `smtp.yandex.ru:465` (SMTPS).
631
641
  - Копия письма автоматически отправляется на адрес отправителя.
642
+ - Транспорт — `nodemailer` `^9` (диапазон, а не точная версия: иначе патч безопасности нельзя было бы применить на стороне потребителя).
643
+ - Вложения передаются готовым `Buffer` — файл по `fileUrl` пакет скачивает сам. Поля `path` и `href` намеренно не поддерживаются: nodemailer не должен ходить по URL.
632
644
 
633
645
  ### Wappi
634
646
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrey4emk/npm-app-back-b24",
3
- "version": "3.7.0",
3
+ "version": "3.7.1",
4
4
  "description": "Bitrix24 OAuth helpers for Node.js projects",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -39,12 +39,12 @@
39
39
  "dependencies": {
40
40
  "@bitrix24/b24jssdk": "2.0.0",
41
41
  "@types/express": "5.0.6",
42
- "@types/luxon": "3.7.1",
43
- "@types/node": "25.0.10",
44
- "@types/nodemailer": "7.0.9",
45
- "conf": "15.0.2",
46
- "dotenv": "17.2.3",
47
- "luxon": "3.4.4",
48
- "nodemailer": "7.0.6"
42
+ "@types/luxon": "3.7.4",
43
+ "@types/node": "25.9.5",
44
+ "@types/nodemailer": "8.0.1",
45
+ "conf": "15.1.0",
46
+ "dotenv": "17.4.2",
47
+ "luxon": "3.7.2",
48
+ "nodemailer": "^9.0.5"
49
49
  }
50
50
  }
@@ -57,12 +57,32 @@ export class ChatApp {
57
57
  private auth: ChatAppAuthParam;
58
58
  private type: ChatAppTypeParam;
59
59
  private updateToken: boolean;
60
+ private readonly whatsAppType: string | null;
61
+ private readonly telegramType: string | null;
62
+
63
+ /**
64
+ * Тип мессенджера из конфига. Массив приходит из внешнего источника
65
+ * (config/chatApp.json обновляется данными API ChatApp), поэтому проверяем:
66
+ * пустой messenger дал бы URL с undefined и «отправку в никуда».
67
+ * Не бросаем: экземпляр создаётся на уровне модуля, и бросок уронил бы
68
+ * импорт всего модуля отправки вместе с остальными каналами. Отказ локальный —
69
+ * его возвращают методы того мессенджера, который не разрешился.
70
+ */
71
+ private static resolveMessengerType(config: MessengerConfig | undefined): string | null {
72
+ const first = config?.messenger?.[0];
73
+ if (typeof first?.type !== "string" || !first.type.trim()) {
74
+ return null;
75
+ }
76
+ return first.type;
77
+ }
60
78
 
61
79
  constructor(makeParam: ChatAppMakeParam, authParam: ChatAppAuthParam, typeParam: ChatAppTypeParam) {
62
80
  this.make = makeParam;
63
81
  this.auth = authParam;
64
82
  this.type = typeParam;
65
83
  this.updateToken = false;
84
+ this.whatsAppType = ChatApp.resolveMessengerType(typeParam?.whatsApp);
85
+ this.telegramType = ChatApp.resolveMessengerType(typeParam?.telegram);
66
86
  }
67
87
 
68
88
  async sendMessageChatApp(messangerType: MessengerType, messageData: MessageData): Promise<ChatAppResult> {
@@ -73,6 +93,11 @@ export class ChatApp {
73
93
  if (!messageData.phone || !messageData.message) {
74
94
  return { error: true, message: "Отсутствует phone или message", data: null };
75
95
  }
96
+ // Тип мессенджера не разрешился из конфига — отправлять некуда
97
+ const resolvedType = messangerType === "whatsApp" ? this.whatsAppType : this.telegramType;
98
+ if (!resolvedType) {
99
+ return { error: true, message: `ChatApp: не разрешён тип мессенджера ${messangerType} (нет конфига или пуст messenger)`, data: null };
100
+ }
76
101
 
77
102
  const check = await this.checkTokenChatApp();
78
103
  if (check.error) {
@@ -84,10 +109,10 @@ export class ChatApp {
84
109
 
85
110
  let url: string | undefined;
86
111
  if (messangerType === "whatsApp") {
87
- url = `https://api.chatapp.online/v1/licenses/${this.type.whatsApp.licenseId}/messengers/${this.type.whatsApp.messenger[0].type}/chats/${phone}/messages/text`;
112
+ url = `https://api.chatapp.online/v1/licenses/${this.type.whatsApp.licenseId}/messengers/${resolvedType}/chats/${phone}/messages/text`;
88
113
  }
89
114
  if (messangerType === "telegram") {
90
- url = `https://api.chatapp.online/v1/licenses/${this.type.telegram.licenseId}/messengers/${this.type.telegram.messenger[0].type}/chats/${phone}/messages/text`;
115
+ url = `https://api.chatapp.online/v1/licenses/${this.type.telegram.licenseId}/messengers/${resolvedType}/chats/${phone}/messages/text`;
91
116
  }
92
117
 
93
118
  const res = await fetch(url!, {
@@ -123,6 +148,11 @@ export class ChatApp {
123
148
  if (!messageData.phone || !messageData.message) {
124
149
  return { error: true, message: "Отсутствует phone или message", data: null };
125
150
  }
151
+ // Тип мессенджера не разрешился из конфига — отправлять некуда
152
+ const resolvedType = messangerType === "whatsApp" ? this.whatsAppType : this.telegramType;
153
+ if (!resolvedType) {
154
+ return { error: true, message: `ChatApp: не разрешён тип мессенджера ${messangerType} (нет конфига или пуст messenger)`, data: null };
155
+ }
126
156
 
127
157
  const check = await this.checkTokenChatApp();
128
158
  if (check.error) {
@@ -136,10 +166,10 @@ export class ChatApp {
136
166
 
137
167
  let url: string | undefined;
138
168
  if (messangerType === "whatsApp") {
139
- url = `https://api.chatapp.online/v1/licenses/${this.type.whatsApp.licenseId}/messengers/${this.type.whatsApp.messenger[0].type}/chats/${phone}/messages/file`;
169
+ url = `https://api.chatapp.online/v1/licenses/${this.type.whatsApp.licenseId}/messengers/${resolvedType}/chats/${phone}/messages/file`;
140
170
  }
141
171
  if (messangerType === "telegram") {
142
- url = `https://api.chatapp.online/v1/licenses/${this.type.telegram.licenseId}/messengers/${this.type.telegram.messenger[0].type}/chats/${phone}/messages/file`;
172
+ url = `https://api.chatapp.online/v1/licenses/${this.type.telegram.licenseId}/messengers/${resolvedType}/chats/${phone}/messages/file`;
143
173
  }
144
174
 
145
175
  const res = await fetch(url!, {
@@ -171,6 +201,11 @@ export class ChatApp {
171
201
  if (!["whatsApp", "telegram"].includes(messangerType)) {
172
202
  return { error: true, message: "Неверный тип мессенджера", data: null };
173
203
  }
204
+ // Тип мессенджера не разрешился из конфига — проверять телефон негде
205
+ const resolvedType = messangerType === "whatsApp" ? this.whatsAppType : this.telegramType;
206
+ if (!resolvedType) {
207
+ return { error: true, message: `ChatApp: не разрешён тип мессенджера ${messangerType} (нет конфига или пуст messenger)`, data: null };
208
+ }
174
209
 
175
210
  const check = await this.checkTokenChatApp();
176
211
  if (check.error) {
@@ -179,10 +214,10 @@ export class ChatApp {
179
214
 
180
215
  let url: string | undefined;
181
216
  if (messangerType === "whatsApp") {
182
- url = `https://api.chatapp.online/v1/licenses/${this.type.whatsApp.licenseId}/messengers/${this.type.whatsApp.messenger[0].type}/phones/${phone}/check`;
217
+ url = `https://api.chatapp.online/v1/licenses/${this.type.whatsApp.licenseId}/messengers/${resolvedType}/phones/${phone}/check`;
183
218
  }
184
219
  if (messangerType === "telegram") {
185
- url = `https://api.chatapp.online/v1/licenses/${this.type.telegram.licenseId}/messengers/${this.type.telegram.messenger[0].type}/phones/${phone}/check`;
220
+ url = `https://api.chatapp.online/v1/licenses/${this.type.telegram.licenseId}/messengers/${resolvedType}/phones/${phone}/check`;
186
221
  }
187
222
 
188
223
  const res = await fetch(url!, {