@andrey4emk/npm-app-back-b24 4.0.0 → 4.0.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
@@ -442,7 +442,7 @@ const sent = await fetchWithTimeout("https://api.example.com/send", { method: "P
442
442
  | `createdBy` | number | `138` | ID создателя |
443
443
  | `responsibleId` | number | `1` | ID ответственного |
444
444
  | `deadline` | ISO string | `DateTime.now() + 1 день` | Дедлайн в ISO формате |
445
- | `groupId` | number\|null | `null` | ID группы (опционально) |
445
+ | `groupId` | number\|null | `B24_ERROR_TASK_GROUP_ID` или `null` | ID группы; не передан — берётся переменная окружения |
446
446
  | `accomplices` | number[] | `[]` | Массив ID соисполнителей |
447
447
  | `maxTasks` | number | `100` | Максимум существующих задач с таким названием |
448
448
  | `ufCrmTask` | string\|string[] | `""` | Значение для `UF_CRM_TASK` (массив или строка) |
@@ -459,6 +459,10 @@ const sent = await fetchWithTimeout("https://api.example.com/send", { method: "P
459
459
  });
460
460
  ```
461
461
 
462
+ **Отказ портала виден в логе.** Если портал отказал в создании задачи (нет прав, указанной группы не существует), пишется одна строка уровня `error` с заголовком задачи, номером группы и текстом ошибки. Сетевые сбои этой строки не дают: их уже записал слой повторов, и вторая ушла бы дублем в чат B24.
463
+
464
+ **Группа задачи по умолчанию.** Если `groupId` не передан, группа берётся из переменной окружения `B24_ERROR_TASK_GROUP_ID`. Явно переданное значение всегда важнее переменной: `null` и `0` означают осознанное «без группы» и дефолт не подхватывают. Переменная читается один раз при загрузке модуля — правка `process.env` в рантайме дефолт не двигает, нужен перезапуск процесса. Если переменная не задана или непригодна (не целое больше нуля), поведение прежнее — задача создаётся без группы, плюс одна строка уровня `warn` за процесс. Строка пишется при первом вызове, которому дефолт реально понадобился: у потребителя, который всегда передаёт группу сам, в логе не появится ничего.
465
+
462
466
  - **`checkB24Scope()`** — проверяет и логирует права (скоупы) приложения Bitrix24. Вызывается автоматически при старте, если `$b24` инициализирован.
463
467
 
464
468
  ### События
@@ -989,6 +993,7 @@ const sent = await fetchWithTimeout("https://api.example.com/send", { method: "P
989
993
  | `APP_B24_CLIENT_SECRET` | Client Secret приложения Bitrix24 |
990
994
  | `APP_ENV` | Определяет окружение, используется как ключ секции авторизации |
991
995
  | `APP_NAME` | Название приложения (используется в описании задач) |
996
+ | `B24_ERROR_TASK_GROUP_ID` | ID группы по умолчанию для задач `errorB24()` (не задана — задача без группы) |
992
997
  | `CONFIG_DIR` | Путь к директории с конфигами (по умолчанию `../config`) |
993
998
  | `FETCH_TIMEOUT_MS` | Бюджет одной попытки `fetchRetry`/`fetchWithTimeout`, мс (по умолчанию `60000`; `0` — без таймаута) |
994
999
  | `FETCH_TIMEOUT_QUICK_MS` | Бюджет `FETCH_TIMEOUTS.quick` — токены, мс (по умолчанию `10000`) |
@@ -1010,6 +1015,10 @@ APP_NAME=MyApp
1010
1015
  CONFIG_DIR=../config
1011
1016
  FETCH_TIMEOUT_MS=60000
1012
1017
 
1018
+ # Группа по умолчанию для задач об ошибках. ID свой у каждого портала —
1019
+ # посмотреть в адресе группы на портале. Не задана — задачи создаются без группы
1020
+ B24_ERROR_TASK_GROUP_ID=196
1021
+
1013
1022
  # Именованные бюджеты пакета — задавать только при необходимости, иначе действуют дефолты
1014
1023
  # FETCH_TIMEOUT_QUICK_MS=10000
1015
1024
  # FETCH_TIMEOUT_API_MS=20000
@@ -402,8 +402,9 @@ export async function runWithRetry<T>(fn: () => Promise<T>, label: string, block
402
402
  const proven = isPreConnectionError(error);
403
403
 
404
404
  // Окончательные отказы логируем на error: собственные модули пакета
405
- // (errorB24, Event, Smsgold) ошибку только возвращают вызывающему коду,
406
- // но не пишут в лог — без этих строк сбой $b24 не виден нигде
405
+ // (Event, Smsgold) ошибку только возвращают вызывающему коду,
406
+ // но не пишут в лог — без этих строк сбой $b24 не виден нигде.
407
+ // errorB24 пишет сама только несетевой отказ, сетевой ждёт от нас
407
408
 
408
409
  // Ветка обмена токена стоит до проверки исчерпания попыток: иначе она
409
410
  // сработала бы только на пятой попытке, ради чего всё и затевалось
@@ -1,5 +1,6 @@
1
1
  import { DateTime } from "luxon";
2
2
  import { $b24, getResultData } from "./b24.ts";
3
+ import { isB24NetworkError } from "./b24/retry.ts";
3
4
  import { logs } from "../logs/logs.ts";
4
5
  import dotEnv from "dotenv";
5
6
 
@@ -14,6 +15,10 @@ export interface ErrorTaskData {
14
15
  createdBy?: number;
15
16
  responsibleId?: number;
16
17
  deadline?: string;
18
+ /**
19
+ * ID группы задачи. Поле не передано — берётся `B24_ERROR_TASK_GROUP_ID`;
20
+ * явные `null` и `0` означают «без группы» и дефолт не подхватывают
21
+ */
17
22
  groupId?: number | null;
18
23
  accomplices?: number[];
19
24
  maxTasks?: number;
@@ -41,6 +46,76 @@ interface TaskItem {
41
46
 
42
47
  const appName: string = process.env.APP_NAME || "Задай название приложения в .env";
43
48
 
49
+ /** Имя переменной окружения с группой по умолчанию для задач об ошибках */
50
+ const ERROR_TASK_GROUP_ENV = "B24_ERROR_TASK_GROUP_ID";
51
+
52
+ /** Разобранная группа по умолчанию плюс текст претензии к значению, если она есть */
53
+ interface DefaultGroup {
54
+ /** ID группы либо `null` — «без группы» */
55
+ id: number | null;
56
+ /** Текст строки `warn`; `null` — предупреждать не о чем */
57
+ warning: string | null;
58
+ }
59
+
60
+ /**
61
+ * Разбирает значение `B24_ERROR_TASK_GROUP_ID`.
62
+ *
63
+ * `Number`, а не `parseInt`: последний молча съел бы «196abc» и вернул 196.
64
+ * Непригодное значение группой не становится — задача создаётся без группы,
65
+ * как до 4.0.1.
66
+ */
67
+ function parseDefaultGroupId(raw: string | undefined): DefaultGroup {
68
+ const value = (raw ?? "").trim();
69
+
70
+ if (value === "") {
71
+ return {
72
+ id: null,
73
+ warning: `${ERROR_TASK_GROUP_ENV} не задана — задачи об ошибках создаются без группы`,
74
+ };
75
+ }
76
+
77
+ const parsed = Number(value);
78
+
79
+ if (!Number.isInteger(parsed) || parsed <= 0) {
80
+ return {
81
+ id: null,
82
+ warning: `${ERROR_TASK_GROUP_ENV}: значение '${value}' непригодно (нужно целое больше нуля) — задачи об ошибках создаются без группы`,
83
+ };
84
+ }
85
+
86
+ return { id: parsed, warning: null };
87
+ }
88
+
89
+ /**
90
+ * Значение читается один раз при загрузке модуля — тем же правилом, что и бюджеты
91
+ * таймаутов в `utils/fetchRetry.ts`. Правка `process.env` в рантайме дефолт не двигает.
92
+ */
93
+ const defaultGroup: DefaultGroup = parseDefaultGroupId(process.env[ERROR_TASK_GROUP_ENV]);
94
+
95
+ /** Строка `warn` о непригодном дефолте пишется один раз за процесс, а не на каждый вызов */
96
+ let defaultGroupWarned = false;
97
+
98
+ /**
99
+ * Выбирает группу задачи.
100
+ *
101
+ * Явно переданное значение приоритетнее дефолта: `null` и `0` означают осознанное
102
+ * «без группы» (так же вёл себя прежний `dataTask.groupId || null`), а отсутствие поля
103
+ * и `undefined` — «решай сам», то есть дефолт из окружения.
104
+ *
105
+ * Предупреждение пишется только когда дефолт реально понадобился: у потребителя,
106
+ * который всегда передаёт группу сам, в логе не появится ни строки.
107
+ */
108
+ function resolveGroupId(explicit: number | null | undefined): number | null {
109
+ if (explicit !== undefined) return explicit || null;
110
+
111
+ if (defaultGroup.warning !== null && !defaultGroupWarned) {
112
+ defaultGroupWarned = true;
113
+ logs.add(defaultGroup.warning, "warn");
114
+ }
115
+
116
+ return defaultGroup.id;
117
+ }
118
+
44
119
  // ==================== Функция ====================
45
120
 
46
121
  /**
@@ -85,13 +160,16 @@ export async function errorB24(dataTask: ErrorTaskData): Promise<ErrorB24Result>
85
160
  };
86
161
  }
87
162
 
163
+ // Резолв группы стоит до `try`: сама функция не бросает, а номер группы нужен
164
+ // в `catch` — без него по строке отказа не понять, куда задача не попала
165
+ const groupId = resolveGroupId(dataTask.groupId);
166
+
88
167
  try {
89
168
  const title = dataTask.title;
90
169
  const description = dataTask.description || "";
91
170
  const createdBy = dataTask.createdBy || 138;
92
171
  const responsibleId = dataTask.responsibleId || 1;
93
172
  const deadline = dataTask.deadline || DateTime.now().plus({ days: 1 }).toISO();
94
- const groupId = dataTask.groupId || null;
95
173
  const accomplices = dataTask.accomplices || [];
96
174
  const maxTasks = dataTask.maxTasks || 100;
97
175
  const ufCrmTask = dataTask.ufCrmTask ?? dataTask.entityTypeAbbr ?? "";
@@ -137,6 +215,19 @@ export async function errorB24(dataTask: ErrorTaskData): Promise<ErrorB24Result>
137
215
  };
138
216
  } catch (error: unknown) {
139
217
  const message = error instanceof Error ? error.message : String(error);
218
+
219
+ // Отказ портала иначе не виден нигде: несетевую ошибку retry-слой отдаёт
220
+ // наружу как есть, без единой строки в логе, а `errorB24()` зовут последним
221
+ // средством и её результат обычно не читают — отказ прав или неверный номер
222
+ // группы уходил бы в тишину.
223
+ //
224
+ // Условие обязательно: сетевой отказ retry-слой уже записал сам — строкой
225
+ // про исчерпание попыток либо про отменённый повтор. Без проверки вторая
226
+ // строка ушла бы дублем в чат B24, куда уходит уровень `error`
227
+ if (!isB24NetworkError(error)) {
228
+ logs.add(`errorB24: задача «${dataTask.title}» не создана (группа ${groupId ?? "не задана"}) — ${message}`, "error");
229
+ }
230
+
140
231
  return {
141
232
  error: true,
142
233
  message: `Не удалось создать задачу в Битрикс24: ${message}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrey4emk/npm-app-back-b24",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "Bitrix24 OAuth helpers for Node.js projects",
5
5
  "main": "index.ts",
6
6
  "type": "module",