@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.
- package/bitrix24/b24.ts +92 -42
- 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
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
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(
|
|
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[
|
|
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
|
-
|
|
80
|
-
*/
|
|
80
|
+
// --- Работа с токенами ---
|
|
81
|
+
|
|
82
|
+
/** Сохраняет токены в authB24.json, нормализуя домен без протокола */
|
|
81
83
|
export function saveTokens(authData: AuthData): SaveResult {
|
|
82
84
|
try {
|
|
83
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|