@bitrix24/b24jssdk
Version:
Bitrix24 REST API JavaScript SDK
172 lines (169 loc) • 5.03 kB
JavaScript
/**
* @package @bitrix24/b24jssdk
* @version 1.1.0
* @copyright (c) 2026 Bitrix24
* @license MIT
* @see https://github.com/bitrix24/b24jssdk
* @see https://bitrix24.github.io/b24jssdk/
*/
import { LoggerFactory } from '../../../logger/logger-factory.mjs';
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
class OperatingLimiter {
static {
__name(this, "OperatingLimiter");
}
#config;
#methodStats = /* @__PURE__ */ new Map();
#stats = {
/** Heavy requests */
heavyRequestCount: 0
};
_logger;
getTitle() {
return "operatingLimiter";
}
constructor(config) {
this._logger = LoggerFactory.createNullLogger();
this.#config = config;
}
// region Logger ////
setLogger(logger) {
this._logger = logger;
}
getLogger() {
return this._logger;
}
// endregion ////
get limitMs() {
return this.#config.limitMs;
}
getMethodStat(method) {
const stats = this.#methodStats.get(method);
if (!stats) {
return void 0;
}
return stats;
}
async canProceed(requestId, method, params) {
const timeToFree = await this.getTimeToFree(requestId, method, params);
return timeToFree === 0;
}
async waitIfNeeded(requestId, method, params) {
return this.getTimeToFree(requestId, method, params);
}
/**
* Returns the time until the method's operating limit is released (in ms)
* The analysis is based on the previous function call.
* It's important to understand that we're talking about locks of up to 10 minutes.
* This is a fairly strict lock based on the limit:
* - not reached - no lock
* - reached - lock until the unlock time + 1 second
*/
async getTimeToFree(requestId, method, params, _error) {
this.#cleanupOldStats();
if (method === "batch") {
return this.#getTimeToFreeBatch(requestId, params);
}
const stats = this.#methodStats.get(method);
if (!stats) {
return 0;
}
const limitWithBuffer = Math.max(1e3, this.#config.limitMs - 5e3);
if (stats.operating >= limitWithBuffer) {
const now = Date.now();
if (stats.operating_reset_at > now) {
return stats.operating_reset_at - now + 1e3;
}
return 5e3;
}
return 0;
}
/**
* For `batch` commands, returns the maximum time until the method reaches the operating limit (in ms)
*/
async #getTimeToFreeBatch(requestId, params) {
let maxWait = 0;
if (!params?.cmd || !Array.isArray(params.cmd)) {
return maxWait;
}
const batchMethods = params.cmd.map((row) => row.split("?")[0]).filter(Boolean);
for (const methodName of batchMethods) {
const waitTime = await this.getTimeToFree(requestId, `batch::${methodName}`, {});
maxWait = Math.max(maxWait, waitTime);
}
return maxWait;
}
/**
* Updates operating time statistics for the method
*/
async updateStats(requestId, method, data) {
this.#cleanupOldStats();
const { operating, operating_reset_at } = data;
if (operating === void 0 || operating === null) {
return;
}
if (!this.#methodStats.has(method)) {
this.#methodStats.set(method, {
operating: 0,
operating_reset_at: 0,
lastUpdated: Date.now()
});
}
const stats = this.#methodStats.get(method);
stats.operating = operating * 1e3;
stats.operating_reset_at = operating_reset_at * 1e3;
stats.lastUpdated = Date.now();
const usagePercent = stats.operating / this.#config.limitMs * 100;
if (usagePercent > this.#config.heavyPercent) {
this.#stats.heavyRequestCount++;
this.#logStat(requestId, method, usagePercent, stats.operating);
}
}
/**
* Clearing outdated operating limit data
*/
#cleanupOldStats() {
const now = Date.now();
const maxAge = this.#config.windowMs + 1e4;
for (const [method, stats] of this.#methodStats.entries()) {
if (now - stats.lastUpdated > maxAge) {
this.#methodStats.delete(method);
}
}
}
async reset() {
this.#methodStats.clear();
this.#stats = {
heavyRequestCount: 0
};
}
getStats() {
const operatingStats = {};
for (const [method, stats] of this.#methodStats.entries()) {
operatingStats[method] = Number.parseFloat((stats.operating / 1e3).toFixed(2));
}
return {
...this.#stats,
operatingStats
};
}
async setConfig(config) {
this.#config = config;
}
// region Log ////
#logStat(requestId, method, percent, operating) {
this.getLogger().debug(`${this.getTitle()} detected limit for method ${method}`, {
requestId,
method,
operating: {
percent: Number.parseFloat(percent.toFixed(2)),
current: Number.parseFloat((operating / 1e3).toFixed(0)),
max: Number.parseFloat((this.#config.limitMs / 1e3).toFixed(0))
}
});
}
// endregion ////
}
export { OperatingLimiter };
//# sourceMappingURL=operating-limiter.mjs.map