@andrey4emk/npm-app-back-b24 2.0.3 → 2.0.6

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
@@ -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/index.ts CHANGED
@@ -13,3 +13,6 @@ export * from "./sendMessage/wappi.js";
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",
3
+ "version": "2.0.6",
4
4
  "description": "Bitrix24 OAuth helpers for Node.js projects",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -31,7 +31,8 @@
31
31
  "sendMessage/smsgold.js",
32
32
  "sendMessage/email.js",
33
33
  "sendMessage/wappi.js",
34
- "logs/logs.ts"
34
+ "logs/logs.ts",
35
+ "utils/fetchRetry.ts"
35
36
  ],
36
37
  "dependencies": {
37
38
  "@bitrix24/b24jssdk": "^0.5.1",
@@ -0,0 +1,74 @@
1
+ import { logs } from "../logs/logs.ts";
2
+
3
+ /** Ошибки сети, при которых нужно повторять запрос */
4
+ const NETWORK_ERROR_CODES = [
5
+ "ECONNRESET",
6
+ "ECONNREFUSED",
7
+ "ETIMEDOUT",
8
+ "ENOTFOUND",
9
+ "ENETUNREACH",
10
+ "EAI_AGAIN",
11
+ "UND_ERR_CONNECT_TIMEOUT",
12
+ "UND_ERR_SOCKET",
13
+ ];
14
+
15
+ /**
16
+ * Проверяет, является ли ошибка сетевой (стоит повторить запрос)
17
+ */
18
+ function isNetworkError(error: unknown): boolean {
19
+ if (error instanceof TypeError) return true;
20
+
21
+ if (error instanceof Error) {
22
+ const code = (error as NodeJS.ErrnoException).code;
23
+ if (code && NETWORK_ERROR_CODES.includes(code)) return true;
24
+
25
+ const msg = error.message.toLowerCase();
26
+ if (msg.includes("fetch failed") || msg.includes("network") || msg.includes("socket")) {
27
+ return true;
28
+ }
29
+ }
30
+
31
+ return false;
32
+ }
33
+
34
+ /**
35
+ * Обёртка над fetch с повторными попытками при сетевых ошибках.
36
+ * Повторяет запрос только при проблемах с сетью (TypeError, ECONNRESET и т.д.),
37
+ * HTTP-ошибки (4xx, 5xx) НЕ вызывают повторных попыток.
38
+ *
39
+ * @param url — адрес запроса
40
+ * @param options — параметры fetch (метод, заголовки, body и т.д.)
41
+ * @param retries — количество попыток (по умолчанию 5)
42
+ * @param delay — задержка между попытками в мс (по умолчанию 500)
43
+ * @returns Response от fetch
44
+ */
45
+ export async function fetchRetry(
46
+ url: string | URL | Request,
47
+ options?: RequestInit,
48
+ retries: number = 5,
49
+ delay: number = 500
50
+ ): Promise<Response> {
51
+ let lastError: Error | null = null;
52
+
53
+ for (let attempt = 1; attempt <= retries; attempt++) {
54
+ try {
55
+ const response = await fetch(url, options);
56
+ return response;
57
+ } catch (error: unknown) {
58
+ lastError = error instanceof Error ? error : new Error(String(error));
59
+
60
+ if (!isNetworkError(error) || attempt === retries) {
61
+ throw lastError;
62
+ }
63
+
64
+ logs.add(
65
+ `fetchRetry: попытка ${attempt}/${retries} не удалась (${lastError.message}), повтор через ${delay}мс — ${String(url)}`,
66
+ "error"
67
+ );
68
+
69
+ await new Promise((resolve) => setTimeout(resolve, delay));
70
+ }
71
+ }
72
+
73
+ throw lastError;
74
+ }