@andrey4emk/npm-app-back-b24 2.0.3 → 2.0.7
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 +45 -0
- package/bitrix24/errTaskB24.ts +1 -1
- package/index.ts +8 -5
- package/package.json +13 -9
- package/sendMessage/chatApp.ts +323 -0
- package/sendMessage/email.ts +97 -0
- package/sendMessage/{smsgold.js → smsgold.ts} +83 -38
- package/sendMessage/{wappi.js → wappi.ts} +151 -118
- package/utils/fetchRetry.ts +74 -0
- package/sendMessage/chatApp.js +0 -284
- package/sendMessage/email.js +0 -77
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- [Email](#email)
|
|
16
16
|
- [Wappi](#wappi)
|
|
17
17
|
- [Логирование](#логирование)
|
|
18
|
+
- [fetchRetry](#fetchretry)
|
|
18
19
|
- [Переменные окружения](#переменные-окружения)
|
|
19
20
|
- [Скрипты](#скрипты)
|
|
20
21
|
- [Лицензия](#лицензия)
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
Email,
|
|
41
42
|
Wappi,
|
|
42
43
|
logs,
|
|
44
|
+
fetchRetry,
|
|
43
45
|
} from "@andrey4emk/npm-app-back-b24";
|
|
44
46
|
|
|
45
47
|
// $b24 — готовый экземпляр B24OAuth (или null, если токены не настроены)
|
|
@@ -86,6 +88,9 @@ await wappi.sendMessageWappi("whatsApp", { phone: "+1234567890", message: "Пр
|
|
|
86
88
|
|
|
87
89
|
// Логирование
|
|
88
90
|
logs.add("Сообщение", "info");
|
|
91
|
+
|
|
92
|
+
// fetch с повторными попытками при сетевых ошибках
|
|
93
|
+
const response = await fetchRetry("https://api.example.com/data", { method: "GET" });
|
|
89
94
|
```
|
|
90
95
|
|
|
91
96
|
## API
|
|
@@ -561,6 +566,46 @@ logs.add("Сообщение", "info");
|
|
|
561
566
|
- Конфигурация хранится в файле `log.json` (через `conf`).
|
|
562
567
|
- Каждый уровень можно включить/выключить, изменить цвет.
|
|
563
568
|
|
|
569
|
+
### fetchRetry
|
|
570
|
+
|
|
571
|
+
- **`fetchRetry(url, options?, retries?, delay?)`** — обёртка над `fetch` с повторными попытками при сетевых ошибках. HTTP-ошибки (4xx, 5xx) **не** вызывают повторных попыток — повторяются только сетевые сбои (TypeError, ECONNRESET, ETIMEDOUT и т.д.).
|
|
572
|
+
|
|
573
|
+
- **Параметры:**
|
|
574
|
+
|
|
575
|
+
| Параметр | Тип | По умолчанию | Описание |
|
|
576
|
+
| --------- | ---------------------------- | ------------- | --------------------------------- |
|
|
577
|
+
| `url` | string \| URL \| Request | **обязательно** | Адрес запроса |
|
|
578
|
+
| `options` | RequestInit | `undefined` | Параметры fetch (метод, заголовки, body и т.д.) |
|
|
579
|
+
| `retries` | number | `5` | Количество попыток |
|
|
580
|
+
| `delay` | number | `500` | Задержка между попытками (мс) |
|
|
581
|
+
|
|
582
|
+
- **Возвращает:** `Promise<Response>` — ответ от fetch.
|
|
583
|
+
|
|
584
|
+
- **Обрабатываемые сетевые ошибки:**
|
|
585
|
+
`ECONNRESET`, `ECONNREFUSED`, `ETIMEDOUT`, `ENOTFOUND`, `ENETUNREACH`, `EAI_AGAIN`, `UND_ERR_CONNECT_TIMEOUT`, `UND_ERR_SOCKET`, `TypeError`, а также ошибки с сообщениями `fetch failed`, `network`, `socket`.
|
|
586
|
+
|
|
587
|
+
- **Логирование:** при каждой неудачной попытке записывает сообщение через `logs.add()` с уровнем `error`.
|
|
588
|
+
|
|
589
|
+
```js
|
|
590
|
+
import { fetchRetry } from "@andrey4emk/npm-app-back-b24";
|
|
591
|
+
|
|
592
|
+
// Простой GET-запрос с дефолтными параметрами (5 попыток, 500мс)
|
|
593
|
+
const response = await fetchRetry("https://api.example.com/data");
|
|
594
|
+
const json = await response.json();
|
|
595
|
+
|
|
596
|
+
// POST-запрос с кастомными параметрами
|
|
597
|
+
const res = await fetchRetry(
|
|
598
|
+
"https://api.example.com/webhook",
|
|
599
|
+
{
|
|
600
|
+
method: "POST",
|
|
601
|
+
headers: { "Content-Type": "application/json" },
|
|
602
|
+
body: JSON.stringify({ key: "value" }),
|
|
603
|
+
},
|
|
604
|
+
3, // 3 попытки
|
|
605
|
+
1000 // 1 секунда между попытками
|
|
606
|
+
);
|
|
607
|
+
```
|
|
608
|
+
|
|
564
609
|
## Переменные окружения
|
|
565
610
|
|
|
566
611
|
| Переменная | Назначение |
|
package/bitrix24/errTaskB24.ts
CHANGED
|
@@ -20,7 +20,7 @@ export interface ErrorTaskData {
|
|
|
20
20
|
/** Значение для UF_CRM_TASK (массив или строка). Приоритетнее entityTypeAbbr */
|
|
21
21
|
ufCrmTask?: string[] | string;
|
|
22
22
|
/** @deprecated Используйте ufCrmTask. Код CRM сущности для UF_CRM_TASK */
|
|
23
|
-
entityTypeAbbr?:
|
|
23
|
+
entityTypeAbbr?: never;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/** Результат создания задачи */
|
package/index.ts
CHANGED
|
@@ -5,11 +5,14 @@ export * from "./bitrix24/b24.ts";
|
|
|
5
5
|
export * from "./bitrix24/errTaskB24.ts";
|
|
6
6
|
export * from "./bitrix24/eventB24.ts";
|
|
7
7
|
|
|
8
|
-
// Мессенджеры и уведомления
|
|
9
|
-
export * from "./sendMessage/chatApp.
|
|
10
|
-
export * from "./sendMessage/smsgold.
|
|
11
|
-
export * from "./sendMessage/email.
|
|
12
|
-
export * from "./sendMessage/wappi.
|
|
8
|
+
// Мессенджеры и уведомления
|
|
9
|
+
export * from "./sendMessage/chatApp.ts";
|
|
10
|
+
export * from "./sendMessage/smsgold.ts";
|
|
11
|
+
export * from "./sendMessage/email.ts";
|
|
12
|
+
export * from "./sendMessage/wappi.ts";
|
|
13
13
|
|
|
14
14
|
// Логирование
|
|
15
15
|
export * from "./logs/logs.ts";
|
|
16
|
+
|
|
17
|
+
// Утилиты
|
|
18
|
+
export * from "./utils/fetchRetry.ts";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andrey4emk/npm-app-back-b24",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "Bitrix24 OAuth helpers for Node.js projects",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -27,20 +27,24 @@
|
|
|
27
27
|
"bitrix24/b24.ts",
|
|
28
28
|
"bitrix24/errTaskB24.ts",
|
|
29
29
|
"bitrix24/eventB24.ts",
|
|
30
|
-
"sendMessage/chatApp.
|
|
31
|
-
"sendMessage/smsgold.
|
|
32
|
-
"sendMessage/email.
|
|
33
|
-
"sendMessage/wappi.
|
|
34
|
-
"logs/logs.ts"
|
|
30
|
+
"sendMessage/chatApp.ts",
|
|
31
|
+
"sendMessage/smsgold.ts",
|
|
32
|
+
"sendMessage/email.ts",
|
|
33
|
+
"sendMessage/wappi.ts",
|
|
34
|
+
"logs/logs.ts",
|
|
35
|
+
"utils/fetchRetry.ts"
|
|
35
36
|
],
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"@bitrix24/b24jssdk": "^0.5.1",
|
|
38
39
|
"@types/express": "^5.0.6",
|
|
39
40
|
"@types/luxon": "^3.7.1",
|
|
40
41
|
"@types/node": "^25.0.10",
|
|
41
|
-
"
|
|
42
|
+
"conf": "^15.0.2",
|
|
42
43
|
"dotenv": "^17.2.3",
|
|
43
|
-
"
|
|
44
|
-
"
|
|
44
|
+
"luxon": "^3.4.4",
|
|
45
|
+
"nodemailer": "^7.0.6"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/nodemailer": "^7.0.9"
|
|
45
49
|
}
|
|
46
50
|
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
// ==================== Типы ====================
|
|
2
|
+
|
|
3
|
+
/** Параметры для создания токена ChatApp */
|
|
4
|
+
interface ChatAppMakeParam {
|
|
5
|
+
email: string;
|
|
6
|
+
pass: string;
|
|
7
|
+
appId: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Данные авторизации ChatApp */
|
|
11
|
+
interface ChatAppAuthParam {
|
|
12
|
+
accessToken: string;
|
|
13
|
+
accessTokenEndTime: string;
|
|
14
|
+
refreshToken: string;
|
|
15
|
+
refreshTokenEndTime: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Параметры мессенджера */
|
|
19
|
+
interface MessengerConfig {
|
|
20
|
+
licenseId: string;
|
|
21
|
+
messenger: { type: string }[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Типы мессенджеров для ChatApp */
|
|
25
|
+
interface ChatAppTypeParam {
|
|
26
|
+
whatsApp: MessengerConfig;
|
|
27
|
+
telegram: MessengerConfig;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Тип мессенджера */
|
|
31
|
+
type MessengerType = "whatsApp" | "telegram";
|
|
32
|
+
|
|
33
|
+
/** Данные для отправки текстового сообщения */
|
|
34
|
+
interface MessageData {
|
|
35
|
+
phone: string;
|
|
36
|
+
message: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Данные для отправки файла */
|
|
40
|
+
interface FileMessageData extends MessageData {
|
|
41
|
+
fileUrl: string;
|
|
42
|
+
fileName: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Стандартный результат операции */
|
|
46
|
+
interface ChatAppResult {
|
|
47
|
+
error: boolean;
|
|
48
|
+
message: string;
|
|
49
|
+
data: unknown;
|
|
50
|
+
auth?: ChatAppAuthParam | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ==================== Класс ====================
|
|
54
|
+
|
|
55
|
+
export class ChatApp {
|
|
56
|
+
private make: ChatAppMakeParam;
|
|
57
|
+
private auth: ChatAppAuthParam;
|
|
58
|
+
private type: ChatAppTypeParam;
|
|
59
|
+
private updateToken: boolean;
|
|
60
|
+
|
|
61
|
+
constructor(makeParam: ChatAppMakeParam, authParam: ChatAppAuthParam, typeParam: ChatAppTypeParam) {
|
|
62
|
+
this.make = makeParam;
|
|
63
|
+
this.auth = authParam;
|
|
64
|
+
this.type = typeParam;
|
|
65
|
+
this.updateToken = false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async sendMessageChatApp(messangerType: MessengerType, messageData: MessageData): Promise<ChatAppResult> {
|
|
69
|
+
// Проверяем что верно указан messangerType и в messageData есть phone и message
|
|
70
|
+
if (!["whatsApp", "telegram"].includes(messangerType)) {
|
|
71
|
+
return { error: true, message: "Неверный тип мессенджера", data: null };
|
|
72
|
+
}
|
|
73
|
+
if (!messageData.phone || !messageData.message) {
|
|
74
|
+
return { error: true, message: "Отсутствует phone или message", data: null };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const check = await this.checkTokenChatApp();
|
|
78
|
+
if (check.error) {
|
|
79
|
+
return { error: true, message: `Ошибка с токеном ChatApp в функции sendMessageChatApp класса ChatApp\n${check.message}`, data: null };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const phone = messageData.phone;
|
|
83
|
+
const message = messageData.message;
|
|
84
|
+
|
|
85
|
+
let url: string | undefined;
|
|
86
|
+
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`;
|
|
88
|
+
}
|
|
89
|
+
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`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const res = await fetch(url!, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: {
|
|
96
|
+
Lang: "en",
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
Accept: "application/json",
|
|
99
|
+
Authorization: this.auth.accessToken,
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify({ text: message }),
|
|
102
|
+
});
|
|
103
|
+
const data: any = await res.json();
|
|
104
|
+
|
|
105
|
+
if (!data.success) {
|
|
106
|
+
return { error: true, message: `Ошибка при отправке сообщения в ChatApp через ${messangerType}`, data };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Если в процессе запроса обновили токен, то возвращаем информацию об этом
|
|
110
|
+
let newAuth: ChatAppAuthParam | null = null;
|
|
111
|
+
if (this.updateToken) {
|
|
112
|
+
newAuth = this.auth;
|
|
113
|
+
this.updateToken = false;
|
|
114
|
+
}
|
|
115
|
+
return { error: false, message: `Сообщение успешно отправлено в ChatApp через ${messangerType}`, data, auth: newAuth };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async sendFileChatApp(messangerType: MessengerType, messageData: FileMessageData): Promise<ChatAppResult> {
|
|
119
|
+
// Проверяем что верно указан messangerType и в messageData есть phone и message
|
|
120
|
+
if (!["whatsApp", "telegram"].includes(messangerType)) {
|
|
121
|
+
return { error: true, message: "Неверный тип мессенджера", data: null };
|
|
122
|
+
}
|
|
123
|
+
if (!messageData.phone || !messageData.message) {
|
|
124
|
+
return { error: true, message: "Отсутствует phone или message", data: null };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const check = await this.checkTokenChatApp();
|
|
128
|
+
if (check.error) {
|
|
129
|
+
return { error: true, message: `Ошибка с токеном ChatApp в функции sendMessageChatApp класса ChatApp\n${check.message}`, data: null };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const phone = messageData.phone;
|
|
133
|
+
const caption = messageData.message;
|
|
134
|
+
const file = messageData.fileUrl;
|
|
135
|
+
const fileName = messageData.fileName;
|
|
136
|
+
|
|
137
|
+
let url: string | undefined;
|
|
138
|
+
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`;
|
|
140
|
+
}
|
|
141
|
+
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`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const res = await fetch(url!, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
headers: {
|
|
148
|
+
Lang: "en",
|
|
149
|
+
"Content-Type": "application/json",
|
|
150
|
+
Accept: "application/json",
|
|
151
|
+
Authorization: this.auth.accessToken,
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify({ file, fileName, caption }),
|
|
154
|
+
});
|
|
155
|
+
const data: any = await res.json();
|
|
156
|
+
|
|
157
|
+
if (!data.success) {
|
|
158
|
+
return { error: true, message: `Ошибка при отправке сообщения в ChatApp через ${messangerType}`, data };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Если в процессе запроса обновили токен, то возвращаем информацию об этом
|
|
162
|
+
let newAuth: ChatAppAuthParam | null = null;
|
|
163
|
+
if (this.updateToken) {
|
|
164
|
+
newAuth = this.auth;
|
|
165
|
+
this.updateToken = false;
|
|
166
|
+
}
|
|
167
|
+
return { error: false, message: `Сообщение успешно отправлено в ChatApp через ${messangerType}`, data, auth: newAuth };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async phoneCheckChatApp(messangerType: MessengerType, phone: string): Promise<ChatAppResult> {
|
|
171
|
+
if (!["whatsApp", "telegram"].includes(messangerType)) {
|
|
172
|
+
return { error: true, message: "Неверный тип мессенджера", data: null };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const check = await this.checkTokenChatApp();
|
|
176
|
+
if (check.error) {
|
|
177
|
+
return { error: true, message: `Ошибка с токеном ChatApp в функции phoneCheckChatApp класса ChatApp\n${check.message}`, data: check };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let url: string | undefined;
|
|
181
|
+
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`;
|
|
183
|
+
}
|
|
184
|
+
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`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const res = await fetch(url!, {
|
|
189
|
+
method: "GET",
|
|
190
|
+
headers: {
|
|
191
|
+
Lang: "en",
|
|
192
|
+
Accept: "application/json",
|
|
193
|
+
Authorization: this.auth.accessToken,
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
const data: any = await res.json();
|
|
197
|
+
|
|
198
|
+
if (!data.success) {
|
|
199
|
+
return { error: true, message: `Ошибка при проверке телефона в ChatApp через ${messangerType}`, data };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Если в процессе запроса обновили токен, то возвращаем информацию об этом
|
|
203
|
+
let newAuth: ChatAppAuthParam | null = null;
|
|
204
|
+
if (this.updateToken) {
|
|
205
|
+
newAuth = this.auth;
|
|
206
|
+
this.updateToken = false;
|
|
207
|
+
}
|
|
208
|
+
data.check = data.data.exist;
|
|
209
|
+
return { error: false, message: `Телефон успешно проверен в ChatApp через ${messangerType}`, data, auth: newAuth };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async checkTokenChatApp(): Promise<{ error: boolean; message: string; data?: null }> {
|
|
213
|
+
try {
|
|
214
|
+
const response = await fetch("https://api.chatapp.online/v1/tokens/check", {
|
|
215
|
+
method: "GET",
|
|
216
|
+
headers: {
|
|
217
|
+
Lang: "en",
|
|
218
|
+
Accept: "application/json",
|
|
219
|
+
Authorization: this.auth.accessToken,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const data: any = await response.json();
|
|
224
|
+
if (!data.success) {
|
|
225
|
+
await this.refreshTokenChatApp();
|
|
226
|
+
return { error: false, message: "Токен обновлен" };
|
|
227
|
+
}
|
|
228
|
+
return { error: false, message: "Токен действителен" };
|
|
229
|
+
} catch (error: unknown) {
|
|
230
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
231
|
+
return { error: true, message, data: null };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async makeTokenChatApp(): Promise<{ error: boolean; message?: string; data?: unknown }> {
|
|
236
|
+
console.warn("makeTokenChatApp called");
|
|
237
|
+
try {
|
|
238
|
+
const response = await fetch("https://api.chatapp.online/v1/tokens", {
|
|
239
|
+
method: "POST",
|
|
240
|
+
headers: {
|
|
241
|
+
Lang: "en",
|
|
242
|
+
"Content-Type": "application/json",
|
|
243
|
+
Accept: "application/json",
|
|
244
|
+
},
|
|
245
|
+
body: JSON.stringify({
|
|
246
|
+
email: this.make.email,
|
|
247
|
+
password: this.make.pass,
|
|
248
|
+
appId: this.make.appId,
|
|
249
|
+
}),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const data: any = await response.json();
|
|
253
|
+
if (!data.success) {
|
|
254
|
+
return { error: true, message: "Не удалось получить токен ChatApp", data };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this.auth.accessToken = data.data.accessToken;
|
|
258
|
+
this.auth.accessTokenEndTime = data.data.accessTokenEndTime;
|
|
259
|
+
this.auth.refreshToken = data.data.refreshToken;
|
|
260
|
+
this.auth.refreshTokenEndTime = data.data.refreshTokenEndTime;
|
|
261
|
+
this.updateToken = true;
|
|
262
|
+
return { error: false };
|
|
263
|
+
} catch (error: unknown) {
|
|
264
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
265
|
+
return { error: true, message, data: null };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async refreshTokenChatApp(): Promise<{ error: boolean; message?: string; data?: unknown }> {
|
|
270
|
+
try {
|
|
271
|
+
const response = await fetch("https://api.chatapp.online/v1/tokens/refresh", {
|
|
272
|
+
method: "POST",
|
|
273
|
+
headers: {
|
|
274
|
+
Lang: "en",
|
|
275
|
+
"Content-Type": "application/json",
|
|
276
|
+
Accept: "application/json",
|
|
277
|
+
},
|
|
278
|
+
body: JSON.stringify({ refreshToken: this.auth.refreshToken }),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const data: any = await response.json();
|
|
282
|
+
if (!data.success) {
|
|
283
|
+
const resMakeToken = await this.makeTokenChatApp();
|
|
284
|
+
if (resMakeToken.error) {
|
|
285
|
+
return { error: true, message: "Не удалось обновить токен ChatApp", data };
|
|
286
|
+
}
|
|
287
|
+
return { error: false };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
this.auth.accessToken = data.data.accessToken;
|
|
291
|
+
this.auth.accessTokenEndTime = data.data.accessTokenEndTime;
|
|
292
|
+
this.auth.refreshToken = data.data.refreshToken;
|
|
293
|
+
this.auth.refreshTokenEndTime = data.data.refreshTokenEndTime;
|
|
294
|
+
this.updateToken = true;
|
|
295
|
+
return { error: false };
|
|
296
|
+
} catch (error: unknown) {
|
|
297
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
298
|
+
return { error: true, message, data: null };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async getLicensesChatApp(): Promise<{ error: boolean; message: string; data: unknown }> {
|
|
303
|
+
try {
|
|
304
|
+
const response = await fetch("https://api.chatapp.online/v1/licenses", {
|
|
305
|
+
method: "GET",
|
|
306
|
+
headers: {
|
|
307
|
+
Lang: "en",
|
|
308
|
+
Accept: "application/json",
|
|
309
|
+
Authorization: this.auth.accessToken,
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
const data: any = await response.json();
|
|
313
|
+
|
|
314
|
+
if (!data.success) {
|
|
315
|
+
return { error: true, message: "Не удалось получить лицензии ChatApp", data: null };
|
|
316
|
+
}
|
|
317
|
+
return { error: false, message: "Лицензии успешно получены", data: data.licenses };
|
|
318
|
+
} catch (error: unknown) {
|
|
319
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
320
|
+
return { error: true, message, data: null };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import nodemailer from "nodemailer";
|
|
2
|
+
|
|
3
|
+
// ==================== Типы ====================
|
|
4
|
+
|
|
5
|
+
/** Параметры авторизации почты */
|
|
6
|
+
interface EmailAuthParam {
|
|
7
|
+
user: string;
|
|
8
|
+
pass: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Данные для отправки письма */
|
|
12
|
+
interface EmailData {
|
|
13
|
+
to: string;
|
|
14
|
+
subject: string;
|
|
15
|
+
text?: string;
|
|
16
|
+
html?: string;
|
|
17
|
+
fileUrl?: string;
|
|
18
|
+
fileName?: string;
|
|
19
|
+
attachments?: { filename: string; content: Buffer }[] | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Результат отправки письма */
|
|
23
|
+
interface EmailResult {
|
|
24
|
+
error: boolean;
|
|
25
|
+
message?: string;
|
|
26
|
+
info?: unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ==================== Класс ====================
|
|
30
|
+
|
|
31
|
+
export class Email {
|
|
32
|
+
private auth: EmailAuthParam;
|
|
33
|
+
private transporter: ReturnType<typeof nodemailer.createTransport>;
|
|
34
|
+
|
|
35
|
+
constructor(auth: EmailAuthParam) {
|
|
36
|
+
this.auth = auth;
|
|
37
|
+
this.transporter = nodemailer.createTransport({
|
|
38
|
+
auth: {
|
|
39
|
+
user: this.auth.user,
|
|
40
|
+
pass: this.auth.pass,
|
|
41
|
+
},
|
|
42
|
+
host: "smtp.yandex.ru",
|
|
43
|
+
port: 465, // 465 = SMTPS, 587 = STARTTLS
|
|
44
|
+
secure: true, // true для 465
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async send(dataMail: EmailData): Promise<EmailResult> {
|
|
49
|
+
try {
|
|
50
|
+
// Если прислали ссылку на файл, то скачиваем его и добавляем в attachments
|
|
51
|
+
if (dataMail.fileUrl) {
|
|
52
|
+
const resDownloadFile = await this.downloadFileToUrl(dataMail.fileUrl);
|
|
53
|
+
if (resDownloadFile.error) {
|
|
54
|
+
return { error: true, message: resDownloadFile.message };
|
|
55
|
+
}
|
|
56
|
+
dataMail.attachments = [
|
|
57
|
+
{
|
|
58
|
+
filename: dataMail.fileName!,
|
|
59
|
+
content: resDownloadFile.buffer!,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
} else {
|
|
63
|
+
dataMail.attachments = null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const info = await this.transporter.sendMail({
|
|
67
|
+
from: `Натяжные потолки Репа. <${this.auth.user}>`,
|
|
68
|
+
to: [dataMail.to, this.auth.user],
|
|
69
|
+
subject: dataMail.subject,
|
|
70
|
+
text: dataMail.text,
|
|
71
|
+
html: dataMail.html,
|
|
72
|
+
attachments: dataMail.attachments ?? undefined,
|
|
73
|
+
});
|
|
74
|
+
return { error: false, info };
|
|
75
|
+
} catch (error: unknown) {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
return { error: true, message };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private async downloadFileToUrl(fileUrl: string): Promise<{ error: boolean; message?: string; buffer?: Buffer }> {
|
|
82
|
+
try {
|
|
83
|
+
const response = await fetch(fileUrl);
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
86
|
+
}
|
|
87
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
88
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
89
|
+
|
|
90
|
+
// Возвращаем буфер
|
|
91
|
+
return { error: false, buffer };
|
|
92
|
+
} catch (error: unknown) {
|
|
93
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
+
return { error: true, message: `Ошибка при скачивании файла по fileUrl: ${message}` };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|