@bitrix24/b24jssdk
Version:
Bitrix24 REST API JavaScript SDK
211 lines (208 loc) • 5.98 kB
JavaScript
/**
* @package @bitrix24/b24jssdk
* @version 1.0.1
* @copyright (c) 2026 Bitrix24
* @license MIT
* @see https://github.com/bitrix24/b24jssdk
* @see https://bitrix24.github.io/b24jssdk/
*/
import { ErrorNotConnected, ErrorTimeout } from './errors.mjs';
import { Type } from '../tools/type.mjs';
import { Text } from '../tools/text.mjs';
import { ListRpcError } from '../types/pull.mjs';
import { LoggerFactory } from '../logger/logger-factory.mjs';
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
const JSON_RPC_VERSION = "2.0";
class JsonRpc {
static {
__name(this, "JsonRpc");
}
_logger;
_connector;
_idCounter = 0;
_handlers = {};
_rpcResponseAwaiters = /* @__PURE__ */ new Map();
constructor(options) {
this._logger = LoggerFactory.createNullLogger();
this._connector = options.connector;
if (Type.isPlainObject(options.handlers)) {
for (const method in options.handlers) {
this.handle(method, options.handlers[method]);
}
}
}
setLogger(logger) {
this._logger = logger;
}
getLogger() {
return this._logger;
}
/**
* @param {string} method
* @param {function} handler
*/
handle(method, handler) {
this._handlers[method] = handler;
}
/**
* Sends RPC command to the server.
*
* @param {string} method Method name
* @param {object} params
* @param {int} timeout
* @returns {Promise}
*/
async executeOutgoingRpcCommand(method, params, timeout = 5) {
return new Promise((resolve, reject) => {
const request = this.createRequest(method, params);
if (!this._connector.send(JSON.stringify(request))) {
reject(new ErrorNotConnected("websocket is not connected"));
}
const timeoutHandler = setTimeout(() => {
this._rpcResponseAwaiters.delete(request.id);
reject(new ErrorTimeout("no response"));
}, timeout * 1e3);
this._rpcResponseAwaiters.set(request.id, {
resolve,
reject,
timeout: timeoutHandler
});
});
}
/**
* Executes array or rpc commands.
* Returns an array of promises, each promise will be resolved individually.
*
* @param {JsonRpcRequest[]} batch
* @returns {Promise[]}
*/
// @ts-expect-error When we rewrite it to something more modern, then we'll remove this
executeOutgoingRpcBatch(batch) {
const requests = [];
const promises = [];
batch.forEach(({ method, params, id }) => {
const request = this.createRequest(method, params, id);
requests.push(request);
promises.push(
new Promise(
(resolve, reject) => this._rpcResponseAwaiters.set(request.id, {
resolve,
reject
})
)
);
});
this._connector.send(JSON.stringify(requests));
return promises;
}
processRpcResponse(response) {
if ("id" in response && this._rpcResponseAwaiters.has(Number(response.id))) {
const awaiter = this._rpcResponseAwaiters.get(Number(response.id));
if (awaiter) {
if ("result" in response) {
awaiter.resolve(response.result);
} else if ("error" in response) {
awaiter.reject(response?.error || "error");
} else {
awaiter.reject("wrong response structure");
}
clearTimeout(awaiter.timeout);
this._rpcResponseAwaiters.delete(Number(response.id));
}
return;
}
this.getLogger().error(`${Text.getDateForLog()}: Pull: Received rpc response with unknown id`, { response });
}
parseJsonRpcMessage(message) {
let decoded;
try {
decoded = JSON.parse(message);
} catch (error) {
this.getLogger().error(
`${Text.getDateForLog()}: Pull: Could not decode json rpc message`,
{ error }
);
return [];
}
if (Type.isArray(decoded)) {
return this.executeIncomingRpcBatch(decoded);
} else if (Type.isJsonRpcRequest(decoded)) {
return this.executeIncomingRpcCommand(decoded);
} else if (Type.isJsonRpcResponse(decoded)) {
this.processRpcResponse(decoded);
return [];
} else {
this.getLogger().error(
`${Text.getDateForLog()}: Pull: unknown rpc packet`,
{ decoded }
);
}
return [];
}
/**
* Executes RPC command, received from the server
*
* @param {string} method
* @param {object} params
* @returns {object} RpcCommandResult
*/
executeIncomingRpcCommand({
method,
params
}) {
if (method in this._handlers) {
return this._handlers[method].call(this, params || {});
}
return {
jsonrpc: JSON_RPC_VERSION,
error: ListRpcError.MethodNotFound
};
}
executeIncomingRpcBatch(batch) {
const result = [];
for (const command of batch) {
if ("jsonrpc" in command) {
if ("method" in command) {
const commandResult = this.executeIncomingRpcCommand(command);
if (commandResult) {
commandResult["jsonrpc"] = JSON_RPC_VERSION;
commandResult["id"] = command["id"];
result.push(commandResult);
}
} else {
this.processRpcResponse(command);
}
} else {
this.getLogger().error(
`${Text.getDateForLog()}: Pull: unknown rpc command in batch`,
{ command }
);
result.push({
jsonrpc: JSON_RPC_VERSION,
error: ListRpcError.InvalidRequest
});
}
}
return result;
}
nextId() {
return ++this._idCounter;
}
createPublishRequest(messageBatch) {
return messageBatch.map((message) => this.createRequest("publish", message));
}
createRequest(method, params, id) {
if (!id) {
id = this.nextId();
}
return {
jsonrpc: JSON_RPC_VERSION,
method,
params,
id
};
}
}
export { JsonRpc };
//# sourceMappingURL=json-rpc.mjs.map