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

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.
Files changed (2) hide show
  1. package/bitrix24/b24.ts +92 -42
  2. package/package.json +1 -1
package/bitrix24/b24.ts CHANGED
@@ -8,7 +8,7 @@ import type { Request, Response } from "express";
8
8
  import dotEnv from "dotenv";
9
9
  dotEnv.config();
10
10
 
11
- // ==================== Интерфейсы ====================
11
+ // --- Интерфейсы ---
12
12
 
13
13
  /** Результат операций с токенами */
14
14
  interface SaveResult {
@@ -16,43 +16,45 @@ interface SaveResult {
16
16
  message: string;
17
17
  }
18
18
 
19
- // ==================== Настройки ====================
19
+ // --- Настройки ---
20
20
 
21
- const configDir: string = process.env.CONFIG_DIR || "../config";
22
- const appEnv: string = process.env.APP_ENV || "PROD";
23
- const clientId: string | undefined = appEnv === "DEV" ? process.env.APP_B24_CLIENT_ID_DEV : process.env.APP_B24_CLIENT_ID;
24
- const clientSecret: string | undefined = appEnv === "DEV" ? process.env.APP_B24_CLIENT_SECRET_DEV : process.env.APP_B24_CLIENT_SECRET;
21
+ const CONFIG_DIR: string = process.env.CONFIG_DIR || "../config";
22
+ const APP_ENV: string = process.env.APP_ENV || "PROD";
23
+ const CLIENT_ID: string | undefined = APP_ENV === "DEV" ? process.env.APP_B24_CLIENT_ID_DEV : process.env.APP_B24_CLIENT_ID;
24
+ const CLIENT_SECRET: string | undefined = APP_ENV === "DEV" ? process.env.APP_B24_CLIENT_SECRET_DEV : process.env.APP_B24_CLIENT_SECRET;
25
+
26
+ /** Интервал проактивного обновления токена (мс). 25 минут при TTL токена 30 минут */
27
+ const PROACTIVE_REFRESH_INTERVAL_MS = 25 * 60 * 1000;
25
28
 
26
- // Инициализация Conf для работы с authB24.json
27
29
  const confAuthB24 = new Conf({
28
- cwd: path.resolve(configDir),
30
+ cwd: path.resolve(CONFIG_DIR),
29
31
  configName: "authB24",
30
32
  });
31
33
 
32
- // ==================== Функции ====================
34
+ // --- Утилиты ---
35
+
36
+ /** Убирает протокол из домена (https://example.bitrix24.ru → example.bitrix24.ru) */
37
+ function cleanDomain(domain: string): string {
38
+ return domain.replace(/^https?:\/\//, "");
39
+ }
40
+
41
+ // --- Создание экземпляра B24OAuth ---
33
42
 
34
- /**
35
- * Создаёт экземпляр B24OAuth или возвращает null при ошибке
36
- */
37
43
  function createB24Instance(): B24OAuth | null {
38
- // Получаем данные авторизации из конфига
39
44
  const store = confAuthB24.store as Record<string, AuthData>;
40
- const authConfig = store[appEnv];
45
+ const authConfig = store[APP_ENV];
41
46
 
42
- // Проверяем данные авторизации
43
47
  if (!authConfig?.domain || !authConfig?.access_token || !authConfig?.refresh_token) {
44
48
  logs.add("В конфиге authB24 не хватает данных для авторизации. Сохрани токены через клиент и перезагрузи докер", "error");
45
49
  return null;
46
50
  }
47
51
 
48
- // Проверяем секреты приложения
49
- if (!clientId || !clientSecret) {
52
+ if (!CLIENT_ID || !CLIENT_SECRET) {
50
53
  logs.add("Не заданы APP_B24_CLIENT_ID или APP_B24_CLIENT_SECRET в .env", "error");
51
54
  return null;
52
55
  }
53
56
 
54
- // Формируем параметры и создаём экземпляр
55
- const domain = authConfig.domain.replace(/^https?:\/\//, "");
57
+ const domain = cleanDomain(authConfig.domain);
56
58
 
57
59
  const authParams: B24OAuthParams = {
58
60
  applicationToken: "",
@@ -70,22 +72,17 @@ function createB24Instance(): B24OAuth | null {
70
72
  issuer: "store",
71
73
  };
72
74
 
73
- const secret: B24OAuthSecret = { clientId, clientSecret };
75
+ const secret: B24OAuthSecret = { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET };
74
76
 
75
77
  return new B24OAuth(authParams, secret);
76
78
  }
77
79
 
78
- /**
79
- * Сохраняет токены в authB24.json
80
- */
80
+ // --- Работа с токенами ---
81
+
82
+ /** Сохраняет токены в authB24.json, нормализуя домен без протокола */
81
83
  export function saveTokens(authData: AuthData): SaveResult {
82
84
  try {
83
- const cleanData: AuthData = {
84
- ...authData,
85
- domain: authData.domain.replace(/^https?:\/\//, ""),
86
- };
87
-
88
- confAuthB24.set(appEnv, cleanData);
85
+ confAuthB24.set(APP_ENV, { ...authData, domain: cleanDomain(authData.domain) });
89
86
  logs.add("Токены Bitrix24 сохранены", "debug");
90
87
  return { error: false, message: "Токены сохранены." };
91
88
  } catch (error: any) {
@@ -94,9 +91,7 @@ export function saveTokens(authData: AuthData): SaveResult {
94
91
  }
95
92
  }
96
93
 
97
- /**
98
- * HTTP-обработчик для сохранения токенов с фронта
99
- */
94
+ /** HTTP-обработчик для сохранения токенов с фронта */
100
95
  export function saveAuthB24Handler(req: Request, res: Response): void {
101
96
  const { access_token, refresh_token, domain, expires_in, member_id } = req.body;
102
97
 
@@ -121,8 +116,36 @@ export function saveAuthB24Handler(req: Request, res: Response): void {
121
116
  }
122
117
  }
123
118
 
119
+ // --- Мьютекс для refresh токена ---
120
+
124
121
  /**
125
- * Обновляет токены из текущего экземпляра $b24 и сохраняет в файл
122
+ * Дедупликация refresh-запросов.
123
+ * Пока промис не завершён, все новые вызовы ждут его результат
124
+ * вместо параллельных запросов к oauth.bitrix.info.
125
+ */
126
+ let refreshInProgress: Promise<AuthData> | null = null;
127
+
128
+ async function refreshAuthWithMutex(): Promise<AuthData> {
129
+ if (refreshInProgress) {
130
+ logs.add("refreshAuth: ожидаем завершения текущего refresh", "debug");
131
+ return refreshInProgress;
132
+ }
133
+
134
+ refreshInProgress = (async () => {
135
+ try {
136
+ if (!$b24) throw new Error("$b24 не инициализирован");
137
+ return await $b24.auth.refreshAuth();
138
+ } finally {
139
+ refreshInProgress = null;
140
+ }
141
+ })();
142
+
143
+ return refreshInProgress;
144
+ }
145
+
146
+ /**
147
+ * Обновляет токены через мьютекс и сохраняет в файл.
148
+ * Используется проактивным таймером и может вызываться вручную.
126
149
  */
127
150
  export async function refreshAndSaveTokens(): Promise<SaveResult> {
128
151
  if (!$b24) {
@@ -130,10 +153,7 @@ export async function refreshAndSaveTokens(): Promise<SaveResult> {
130
153
  }
131
154
 
132
155
  try {
133
- let authData = $b24.auth.getAuthData();
134
- if (!authData) {
135
- authData = await $b24.auth.refreshAuth();
136
- }
156
+ const authData = await refreshAuthWithMutex();
137
157
  return saveTokens(authData);
138
158
  } catch (error: any) {
139
159
  logs.add(`Ошибка обновления токенов: ${error.message}`, "error");
@@ -141,15 +161,40 @@ export async function refreshAndSaveTokens(): Promise<SaveResult> {
141
161
  }
142
162
  }
143
163
 
144
- // ==================== Инициализация ====================
164
+ // --- Проактивное обновление токена ---
165
+
166
+ let proactiveRefreshTimer: ReturnType<typeof setInterval> | null = null;
167
+
168
+ /** Запускает проактивное обновление токена с заданным интервалом */
169
+ function startProactiveRefresh(): void {
170
+ if (proactiveRefreshTimer) clearInterval(proactiveRefreshTimer);
171
+
172
+ proactiveRefreshTimer = setInterval(async () => {
173
+ logs.add("Проактивное обновление токена Bitrix24", "debug");
174
+ const result = await refreshAndSaveTokens();
175
+ if (result.error) {
176
+ logs.add(`Ошибка проактивного обновления токена: ${result.message}`, "error");
177
+ }
178
+ }, PROACTIVE_REFRESH_INTERVAL_MS);
179
+
180
+ logs.add(`Проактивное обновление токена запущено (каждые ${PROACTIVE_REFRESH_INTERVAL_MS / 60000} мин)`, "debug");
181
+ }
182
+
183
+ /** Останавливает проактивное обновление токена */
184
+ export function stopProactiveRefresh(): void {
185
+ if (!proactiveRefreshTimer) return;
186
+ clearInterval(proactiveRefreshTimer);
187
+ proactiveRefreshTimer = null;
188
+ logs.add("Проактивное обновление токена остановлено", "debug");
189
+ }
190
+
191
+ // --- Инициализация ---
145
192
 
146
- // Создаем экземпляр Битрикс24
147
193
  export const $b24 = createB24Instance();
148
194
 
149
- // ==================== Подписываемся на событие обновления токенов ====================
150
195
  if ($b24) {
151
- $b24.setCallbackRefreshAuth(async ({ authData, b24OAuthParams }) => {
152
- // Сохраняем новые токены в файл
196
+ // Сохраняем токены в файл при каждом refresh (реактивном или проактивном)
197
+ $b24.setCallbackRefreshAuth(async ({ authData }) => {
153
198
  const result = saveTokens(authData);
154
199
  if (result.error) {
155
200
  logs.add(`Ошибка при автосохранении токенов: ${result.message}`, "error");
@@ -157,4 +202,9 @@ if ($b24) {
157
202
  logs.add("Токены автоматически обновлены и сохранены в authB24.json", "debug");
158
203
  }
159
204
  });
205
+
206
+ // Обновляем токен при старте, затем запускаем проактивный таймер
207
+ refreshAndSaveTokens().then(() => {
208
+ startProactiveRefresh();
209
+ });
160
210
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrey4emk/npm-app-back-b24",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Bitrix24 OAuth helpers for Node.js projects",
5
5
  "main": "index.ts",
6
6
  "type": "module",