@andrey4emk/npm-app-back-b24 3.0.2 → 3.1.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/logs/logs.ts CHANGED
@@ -62,8 +62,20 @@ class LogsAPI {
62
62
 
63
63
  private readonly resetColor = "\x1b[0m";
64
64
 
65
+ /** Токен Telegram-бота для отправки error-логов */
66
+ private readonly tgBotToken: string | undefined;
67
+
68
+ /** ID чата Telegram для отправки error-логов */
69
+ private readonly tgChatId: string | undefined;
70
+
71
+ /** Имя приложения для заголовка сообщения */
72
+ private readonly appName: string;
73
+
65
74
  constructor() {
66
75
  this.initConfig();
76
+ this.tgBotToken = process.env.LOG_TG_BOT_TOKEN;
77
+ this.tgChatId = process.env.LOG_TG_CHAT_ID;
78
+ this.appName = process.env.APP_NAME || "Unknown App";
67
79
  }
68
80
 
69
81
  /** Проверяем и заполняем конфиг дефолтными значениями если данных нет */
@@ -83,6 +95,12 @@ class LogsAPI {
83
95
  */
84
96
  add(message: string | object, level?: LogLevel, jsonData?: unknown): void {
85
97
  const levelStr: LogLevel = level || "info";
98
+ const messageText = typeof message === "string" ? message : JSON.stringify(message);
99
+
100
+ // Отправляем error-логи в Telegram независимо от настроек консольного вывода
101
+ if (levelStr === "error") {
102
+ this.sendToTelegram(messageText, jsonData);
103
+ }
86
104
 
87
105
  // Проверяем конфиг, и если логирование отключено, то выходим
88
106
  const isLoggingEnabled = confLog.get(`${levelStr}.enabled`) as boolean;
@@ -90,8 +108,6 @@ class LogsAPI {
90
108
  return;
91
109
  }
92
110
 
93
- const messageText = typeof message === "string" ? message : JSON.stringify(message);
94
-
95
111
  // Получаем цвет для уровня
96
112
  const color = (confLog.get(`${levelStr}.color`) as string) || "\x1b[37m";
97
113
 
@@ -106,6 +122,86 @@ class LogsAPI {
106
122
  console.dir(jsonData, { depth: null, colors: true });
107
123
  }
108
124
  }
125
+
126
+ /** Лимит длины текста для Telegram (с запасом на HTML-обёртку) */
127
+ private readonly TG_TEXT_LIMIT = 4000;
128
+
129
+ /**
130
+ * Отправляет сообщение в Telegram-чат (fire-and-forget)
131
+ * Использует нативный fetch, а не fetchRetry — во избежание циклической зависимости
132
+ * Ошибки не пробрасываются, только console.error
133
+ */
134
+ private sendToTelegram(message: string, jsonData?: unknown): void {
135
+ if (!this.tgBotToken || !this.tgChatId) return;
136
+
137
+ const url = `https://api.telegram.org/bot${this.tgBotToken}/sendMessage`;
138
+
139
+ // Экранируем HTML-спецсимволы в тексте сообщения
140
+ const escapedMessage = this.escapeHtml(message);
141
+ let text = `<b>ERROR</b> | ${this.escapeHtml(this.appName)}\n${escapedMessage}`;
142
+
143
+ // Добавляем jsonData если есть
144
+ if (jsonData !== undefined) {
145
+ // Безопасная сериализация — логгер не должен бросать исключения
146
+ let jsonStr: string;
147
+ try {
148
+ jsonStr = JSON.stringify(jsonData, null, 2);
149
+ } catch {
150
+ jsonStr = "[Не удалось сериализовать jsonData]";
151
+ }
152
+ text += `\n<pre>${this.escapeHtml(jsonStr)}</pre>`;
153
+ }
154
+
155
+ // Обрезаем до лимита Telegram
156
+ let isTruncated = false;
157
+ if (text.length > this.TG_TEXT_LIMIT) {
158
+ const truncatedSuffix = "\n...(обрезано)";
159
+ text = text.slice(0, this.TG_TEXT_LIMIT - truncatedSuffix.length) + truncatedSuffix;
160
+ isTruncated = true;
161
+ }
162
+
163
+ // Если текст обрезан — отправляем как plain text, чтобы не разорвать HTML-теги
164
+ if (isTruncated) {
165
+ text = this.stripHtml(text);
166
+ }
167
+
168
+ fetch(url, {
169
+ method: "POST",
170
+ headers: { "Content-Type": "application/json" },
171
+ body: JSON.stringify({
172
+ chat_id: this.tgChatId,
173
+ text,
174
+ ...(isTruncated ? {} : { parse_mode: "HTML" }),
175
+ }),
176
+ })
177
+ .then((response) => {
178
+ if (!response.ok) {
179
+ console.error(`Logs Telegram: HTTP ${response.status}`);
180
+ }
181
+ })
182
+ .catch((error: unknown) => {
183
+ const errMsg = error instanceof Error ? error.message : String(error);
184
+ console.error(`Logs Telegram: ошибка отправки — ${errMsg}`);
185
+ });
186
+ }
187
+
188
+ /** Экранирует HTML-спецсимволы для Telegram parse_mode HTML */
189
+ private escapeHtml(text: string): string {
190
+ return text
191
+ .replace(/&/g, "&amp;")
192
+ .replace(/</g, "&lt;")
193
+ .replace(/>/g, "&gt;");
194
+ }
195
+
196
+ /** Убирает HTML-теги и декодирует HTML-entities для plain text отправки */
197
+ private stripHtml(text: string): string {
198
+ return text
199
+ .replace(/<\/?b>/g, "")
200
+ .replace(/<\/?pre>/g, "")
201
+ .replace(/&lt;/g, "<")
202
+ .replace(/&gt;/g, ">")
203
+ .replace(/&amp;/g, "&");
204
+ }
109
205
  }
110
206
 
111
207
  export const logs = new LogsAPI();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrey4emk/npm-app-back-b24",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "Bitrix24 OAuth helpers for Node.js projects",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -64,7 +64,7 @@ export async function fetchRetry(
64
64
 
65
65
  logs.add(
66
66
  `fetchRetry: попытка ${attempt}/${retries} не удалась (${lastError.message}), повтор через ${delay}мс — ${String(url)}`,
67
- "error"
67
+ "warn"
68
68
  );
69
69
 
70
70
  await new Promise((resolve) => setTimeout(resolve, delay));