bitrix24_api_client
Version:
Node.js клиент для REST API Битрикс24
410 lines (367 loc) • 16.7 kB
JavaScript
const { version } = require('./package.json');
const storeAuth = require('./utils/storeAuth');
const bitrixFetch = require('./utils/bitrixFetch');
const buildQuery = require('./utils/buildQuery');
const requestLimiter = require('./utils/requestLimiter');
const install = require('./src/install');
const { configureLogger, defaultLogger } = require('./utils/logFetch');
const { validateAuth, validateRequest, isValidOAuthEndpoint } = require('./utils/validationUtils');
const { convertToOAuthEndpoint, getDefaultHeaders, extractDomainFromEndpoint } = require('./utils/requestUtils');
const { handleError } = require('./utils/errorHandler');
/**
* Класс для работы с API Bitrix24
*
* Предоставляет функциональность для авторизации, выполнения запросов к API
* и обновления токенов доступа.
*
* @class Bitrix24API
* @since 1.0.0
*/
class Bitrix24API {
/**
* Версия клиента API
* @private
* @type {string}
*/
static #VERSION = version;
/**
* Конечная точка для OAuth-авторизации
* @private
* @type {string}
*/
static #OAUTH_TOKEN_ENDPOINT = 'https://oauth.bitrix.info/oauth/token/';
/**
* Конфигурация клиента API
* @type {Object}
* @property {string|null} client_id - Идентификатор приложения
* @property {string|null} client_secret - Секретный ключ приложения
* @property {Function} readAuth - Функция для чтения авторизации
* @property {Function} writeAuth - Функция для записи авторизации
* @property {Object} requestOptions - Настройки HTTP-запросов
* @property {number} requestOptions.tryes - Количество попыток запроса
* @property {number} requestOptions.pause - Пауза между попытками (мс)
* @property {number} requestOptions.abortTimeout - Время ожидания (мс)
* @property {string|null} proxy - Настройки прокси (null - без прокси)
* @property {Object} requestOptions.logger - Объект для логирования
* @property {Object} logger - Настройки логирования
* @property {boolean} logger.enabled - Включено ли логирование
* @property {string} logger.level - Уровень логирования
*/
static config = {
client_id: null,
client_secret: null,
readAuth: storeAuth.readAuth,
writeAuth: storeAuth.writeAuth,
requestOptions: {
tryes: 3,
pause: 1000,
abortTimeout: 15000,
},
proxy: null,
logger: defaultLogger, // Теперь здесь сам объект логгера
};
/**
* Выполняет запрос к API Bitrix24
*
* Этот метод получает данные авторизации из хранилища, выполняет запрос
* и автоматически обновляет токены доступа при необходимости.
*
* @param {string} method - Метод API Bitrix24
* @param {Object} [params={}] - Параметры запроса
* @param {Object} auth - Объект с данными авторизации или идентификатором
* @returns {Promise<Object>} Ответ от Bitrix24 API
* @example
* // Получение списка лидов
* const result = await Bitrix24API.call('crm.lead.list', { select: ['ID', 'TITLE'] }, { domain: 'example.bitrix24.ru' });
*/
static async call(method, params = {}, auth) {
validateRequest(method, auth, this.config);
const query = { method, params };
return this.#makeBitrixApiCall(query, auth);
}
/**
* Выполняет прямой запрос к API Bitrix24 с переданной авторизацией
*
* В отличие от метода call, не выполняет получение авторизации из хранилища
* и не обновляет токены при их истечении.
*
* @param {string} method - Метод API Bitrix24
* @param {Object} [params={}] - Параметры запроса
* @param {Object} directAuth - Объект с данными авторизации
* @param {string} directAuth.domain - Домен Bitrix24
* @param {string} directAuth.client_endpoint - URL REST API
* @param {string} directAuth.access_token - Токен доступа
* @returns {Promise<Object>} Ответ от Bitrix24 или объект ошибки
* @example
* // Прямой запрос с авторизацией
* const result = await Bitrix24API.callDirect('crm.lead.get',
* { id: 123 },
* { domain: 'example.bitrix24.ru', client_endpoint: 'https://example.bitrix24.ru/rest/', access_token: 'token123' }
* );
*/
static async callDirect(method, params = {}, directAuth) {
try {
validateRequest(method, directAuth, this.config);
// Формируем объект запроса в том же формате, что и для стандартного вызова
const query = { method, params };
// Используем существующий метод для подготовки запроса
const requestData = this.#prepareApiRequest(query, directAuth);
// Выполняем запрос с использованием общего метода
return await this.#executeRequest(requestData);
} catch (err) {
return handleError(err);
}
}
/**
* Устанавливает приложение для работы с API Bitrix24
*
* Выполняет процесс установки приложения и сохраняет полученные токены доступа.
*
* @param {Object} auth - Объект с данными для авторизации
* @returns {Promise<Object>} Результат установки приложения
* @example
* // Установка приложения
* const result = await Bitrix24API.installApp({
* domain: 'example.bitrix24.ru',
* code: 'authorization_code_from_bitrix'
* });
*/
static async installApp(auth) {
try {
const authSetter = async (settings) => await this.#setAuth(settings, auth);
return install(auth, authSetter, {
logger: this.config.logger
});
} catch (err) {
return handleError(err);
}
}
/**
* Настраивает логирование для API
*
* Позволяет изменить параметры логирования запросов и ответов.
*
* @param {Object} options - Настройки логирования
* @param {boolean} [options.enabled] - Включить/выключить логирование
* @param {string} [options.level] - Уровень логирования ('debug', 'info', 'warn', 'error')
* @example
* // Настройка логирования
* Bitrix24API.configureLogger({
* enabled: true,
* level: 'info'
* });
*/
static configureLogger(options) {
// Настраиваем сам объект логгера, а не отдельные настройки
const configuredLogger = configureLogger({
enabled: options.enabled,
level: options.level,
logger: this.config.logger,
});
// Заменяем объект логгера в конфигурации
this.config.logger = configuredLogger;
// Передаем логгер в лимитер запросов
requestLimiter.setLogger(configuredLogger);
return configuredLogger;
}
/**
* Получает настройки приложения из хранилища или из auth.
*
* @private
* @param {Object} auth - Данные авторизации или идентификатор
* @returns {Promise<Object|false>} Настройки приложения или false
*/
static async #getAuth(auth) {
const authData = await this.config.readAuth(auth);
if (!authData) return false;
if (validateAuth(authData)) {
return authData;
}
return false;
}
/**
* Сохраняет новые настройки приложения через кастомный обработчик или напрямую.
*
* @private
* @param {Object} auth - Настройки авторизации для сохранения
* @returns {Promise<boolean>} true если успешно, иначе false
*/
static async #setAuth(auth) {
if (validateAuth(auth)) {
return await this.config.writeAuth(auth);
}
return false;
}
/**
* Обновляет токен авторизации при его истечении.
*
* Запрашивает новый токен через refresh_token и повторяет исходный запрос
* с обновленной авторизацией. Поддерживает локальные установки Битрикс24.
*
* @private
* @param {Object} query - Исходный запрос
* @param {Object} auth - Данные авторизации
* @returns {Promise<Object|null>} Новый ответ или null
*/
static async #refreshAuth(query, auth) {
try {
// Логируем начало обновления токена
this.config.logger.debug(`Начало обновления токена для ${auth.domain}`);
// Определяем, используем ли локальные идентификаторы или глобальные
const isLocalBitrix = auth.status === 'L' &&
auth.C_REST_CLIENT_ID &&
auth.C_REST_CLIENT_SECRET;
const refreshQuery = {
this_auth: 'Y',
method: 'oauth.token',
params: {
client_id: isLocalBitrix ? auth.C_REST_CLIENT_ID : this.config.client_id,
grant_type: 'refresh_token',
client_secret: isLocalBitrix ? auth.C_REST_CLIENT_SECRET : this.config.client_secret,
refresh_token: auth.refresh_token,
},
};
// Логируем информацию о режиме работы (для отладки)
this.config.logger.debug(`Обновление токена в режиме: ${isLocalBitrix ? 'локальный Битрикс24' : 'облачный Битрикс24'}`);
// Запрашиваем новый токен
const requestData = this.#prepareOAuthRequest(refreshQuery, auth);
const updatedAuth = await this.#executeRequest(requestData);
if (updatedAuth.error) {
this.config.logger.error(`Ошибка обновления токена для ${auth.domain}: ${updatedAuth.error}`);
return null;
}
// Сохраняем локальные идентификаторы в новом объекте авторизации
const newAuth = {
...updatedAuth,
domain: extractDomainFromEndpoint(updatedAuth.client_endpoint) || auth.domain,
};
// Переносим локальные настройки, если они были в исходном объекте
if (isLocalBitrix) {
newAuth.status = 'L';
newAuth.C_REST_CLIENT_ID = auth.C_REST_CLIENT_ID;
newAuth.C_REST_CLIENT_SECRET = auth.C_REST_CLIENT_SECRET;
}
// Сохраняем новую авторизацию
const isSetAppSettings = await this.#setAuth(newAuth);
if (isSetAppSettings) {
this.config.logger.debug(`Токен успешно обновлен для ${auth.domain}`);
// Выполняем исходный запрос с обновленной авторизацией
return await this.call(query.method, query.params, newAuth);
} else {
this.config.logger.error(`Не удалось сохранить новую авторизацию для ${auth.domain}`);
return null;
}
} catch (error) {
this.config.logger.error(`Ошибка при обновлении токена для ${auth.domain}: ${error.message}`);
return null;
}
}
/**
* Основной метод для выполнения запросов к API Bitrix24
*
* Получает данные авторизации, подготавливает запрос, выполняет его
* и обрабатывает результат, включая автоматическое обновление токенов.
*
* @private
* @param {Object} query - Параметры запроса
* @param {string} query.method - Метод API
* @param {Object} query.params - Параметры метода
* @param {Object} auth - Данные авторизации или идентификатор
* @returns {Promise<Object>} Результат запроса
*/
static async #makeBitrixApiCall(query, auth) {
const appAuth = await this.#getAuth(auth);
if (!appAuth) {
return handleError({ name: 'AuthError', message: 'No valid auth found' });
}
// Подготовка параметров запроса
const requestData =
query.this_auth === 'Y' ? this.#prepareOAuthRequest(query, appAuth) : this.#prepareApiRequest(query, appAuth);
// Выполнение запроса
const result = await this.#executeRequest(requestData);
// Обработка результата и ошибок
if (result?.error === 'expired_token' && !query.this_auth) {
return await this.#refreshAuth(query, appAuth);
}
return result;
}
/**
* Выполняет HTTP-запрос к API
*
* @private
* @param {Object} requestData - Данные для запроса
* @param {string} requestData.url - URL запроса
* @param {Object} requestData.params - Параметры fetch
* @param {Object} requestData.logContext - Контекст для логирования
* @returns {Promise<Object>} Результат запроса
*/
static async #executeRequest(requestData) {
const { url, params, logContext } = requestData;
return await bitrixFetch(url, params, {
...this.config.requestOptions,
logger: this.config.logger,
proxy: this.config.proxy,
logContext,
});
}
/**
* Подготавливает запрос для OAuth-авторизации
*
* @private
* @param {Object} query - Параметры запроса
* @param {Object} auth - Данные авторизации
* @returns {Object} Подготовленные данные запроса
*/
static #prepareOAuthRequest(query, auth) {
// Определяем OAuth endpoint
let oauthEndpoint = this.#OAUTH_TOKEN_ENDPOINT;
// Если в auth есть server_endpoint, пробуем его использовать
if (auth.server_endpoint) {
// Преобразуем URL в формат для OAuth
const convertedEndpoint = convertToOAuthEndpoint(auth.server_endpoint);
// Проверяем, что URL соответствует допустимым шаблонам
if (isValidOAuthEndpoint(convertedEndpoint)) {
oauthEndpoint = convertedEndpoint;
}
}
const url = oauthEndpoint + '?' + buildQuery(query.params).toString();
return {
url,
params: {
method: 'GET',
redirect: 'manual',
headers: getDefaultHeaders(this.#VERSION),
},
logContext: {
domain: auth.domain,
apiMethod: 'oauth.token',
},
};
}
/**
* Подготавливает запрос к API Bitrix24
*
* @private
* @param {Object} query - Параметры запроса
* @param {Object} appSettings - Настройки авторизации
* @returns {Object} Подготовленные данные запроса
*/
static #prepareApiRequest(query, appSettings) {
const url = appSettings.client_endpoint + query.method + '.json';
const params = {
method: 'POST',
redirect: 'manual',
headers: getDefaultHeaders(this.#VERSION),
body: buildQuery({ ...query.params, auth: appSettings.access_token }),
};
return {
url,
params,
logContext: {
domain: appSettings.domain,
apiMethod: query.method,
},
};
}
}
module.exports = Bitrix24API;