tdlib-native
Version:
🚀 Telegram TDLib native nodejs wrapper
302 lines (301 loc) • 7.64 kB
JavaScript
"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const types = require("./generated/types.js");
const json = require("./json.js");
const async = require("./shared/async.js");
const eventBus = require("./event-bus.js");
const debug = require("debug");
const options = require("./options.js");
const assert = require("./assert.js");
const debugJson = debug("tdlib-native:json");
const tag = "@extra";
class TDError extends Error {
[types.typename] = "error";
code;
method;
parameters;
name = "TDError";
/**
* Creates an instance of TDError.
* @param {string} message
* @param {*} [options={}]
* @memberof TDError
*/
constructor(message, { code = Number.NaN, method = "unknown method", parameters = {} } = {}) {
super(message);
this.code = code;
this.method = method;
this.parameters = parameters;
}
/**
*
*
* @returns {object}
* @memberof TDError
*/
toJSON() {
return {
// eslint-disable-next-line security/detect-object-injection
[types.typename]: this[types.typename],
name: this.name,
message: this.message,
code: this.code,
method: this.method,
parameters: this.parameters
};
}
}
class Client {
_client;
_requests = /* @__PURE__ */ new Map();
_updates = new eventBus.EventBus();
_adapter;
_state = "PAUSED";
/**
* Creates an instance of Client.
* @param {TDLib} adapter
* @memberof Client
*/
constructor(adapter) {
this._adapter = adapter;
this._client = adapter.create(300);
Object.seal(this);
}
api = new types.$AsyncApi(this);
syncApi = new types.$SyncApi(this);
_tdlibOptions = new options.TDLibOptions(this.api);
/**
*
*
* @template {keyof $AsyncApi} T
* @param {T} method
* @param {object} parameters
* @returns {object} {Promise<ReturnType<$AsyncApi[T]>>}
* @throws {TDError} - {@link TDError}
* @memberof Client
*/
async invoke(method, parameters) {
const extra = Math.random();
const data = async.promiseWithResolvers();
assignTemporary(parameters, { [types.typename]: method, [tag]: extra }, (merged) => {
const value = json.serialize(merged);
debugJson("Sent %s", value);
this._adapter.send(this._client, value);
});
this._requests.set(extra, data);
try {
return await data.promise;
} catch (error2) {
if (typeof error2 === "object" && error2 && types.typename in error2) {
const value = error2;
throw new TDError(value.message, { code: value.code, method, parameters });
}
throw error2;
}
}
/**
* Disables logging of TDLib instance
*
* @static
* @param {TDLib} lib
* @memberof Client
* @returns {void}
*/
static disableLogs(lib) {
Client.execute(lib, "setLogVerbosityLevel", { new_verbosity_level: 0 });
}
/**
*
*
* @template {keyof $SyncApi} T
* @param {TDLib|Client} executor
* @param {T} method
* @param {object} parameters
* @returns {object} {Promise<ReturnType<$SyncMethodsDict[T]>>}
* @throws {TDError} - {@link TDError}
* @memberof Client
*/
static execute(executor, method, parameters) {
let adapter;
let client;
if (executor instanceof Client) {
adapter = executor._adapter;
client = executor._client;
} else if (executor._isTDLib) {
adapter = executor;
} else {
throw new TDError("Invalid executor passed", {
code: -100,
method,
parameters
});
}
const extra = Math.random();
const value = assignTemporary(
parameters,
{ [types.typename]: method, [tag]: extra },
(merged) => {
const value2 = json.serialize(merged);
debugJson("Sent sync %s", value2);
return adapter.execute(client ?? null, value2);
}
);
debugJson("Received sync %s", value);
assert.assert(value, new TDError("Method returned null", { method, parameters }));
let data;
try {
data = json.deserialize(value);
} catch {
throw new TDError("Method returned invalid json", { method, parameters });
}
assert.assert(
typeof data === "object" && data && types.typename in data,
new TDError("Returned not an object", { method, parameters })
);
if (data[types.typename] === "error") {
const error2 = data;
throw new TDError(error2.message, { code: error2.code, method, parameters });
}
if (tag in data && data[tag]) {
delete data[tag];
}
return data;
}
/**
*
*
* @template {keyof $SyncApi} T
* @param {T} method
* @param {object} parameters
* @returns {object} {Promise<ReturnType<$SyncApi[T]>>}
* @throws {TDError} - {@link TDError}
* @memberof Client
*/
execute(method, parameters) {
return Client.execute(this, method, parameters);
}
_running;
/**
*
*
* @private
* @returns {Promise<void>}
* @memberof Client
*/
async _thread() {
while (this._state === "RUNNING") {
let value;
try {
value = await this._adapter.receive(this._client);
} catch (error2) {
if (error2 instanceof Error && error2.message.includes("destroyed")) {
break;
}
throw error2;
}
debugJson("Received %s", value);
if (!value) {
continue;
}
let data;
try {
data = json.deserialize(value);
} catch {
continue;
}
if (typeof data !== "object" || !data) {
continue;
}
const extra = data == null ? void 0 : data[tag];
if (extra) {
const async2 = this._requests.get(extra);
delete data[tag];
if (data[types.typename] === "error") {
async2 == null ? void 0 : async2.reject(data);
} else {
async2 == null ? void 0 : async2.resolve(data);
}
this._requests.delete(extra);
continue;
}
if (data[types.typename].startsWith("update")) {
this._updates.emit(data);
}
}
}
/**
*
* @see https://core.telegram.org/tdlib/options
* @readonly
* @memberof Client
*/
get tdlibOptions() {
return this._tdlibOptions;
}
/**
*
*
* @readonly
* @type {Observable<Update>}
* @memberof Client
*/
get updates() {
return this._updates;
}
/**
*
*
* @returns {Promise<void>}
* @memberof Client
*/
async start() {
assert.assert(
this._state === "PAUSED" && !this._running,
"Cannot start: This client is running or destroyed"
);
this._state = "RUNNING";
this._running = this._thread().finally(() => {
this._running = void 0;
});
}
/**
*
*
* @returns {this}
* @memberof Client
*/
async pause() {
assert.assert(
this._state === "RUNNING",
"Cannot pause: This client is paused or destroyed"
);
this._state = "PAUSED";
await this._running;
}
/**
*
*
* @returns {void}
* @memberof Client
*/
async destroy() {
if (this._state === "STOPPED") return;
this._state = "STOPPED";
this._updates.complete();
for (const async2 of this._requests.values()) {
async2.reject(new TDError("Client is destroyed", { code: -1 }));
}
this._requests.clear();
await this._adapter.destroy(this._client);
}
}
function assignTemporary(object, data, callback) {
const merged = Object.assign(object, data);
const result = callback(merged);
for (const key of Object.keys(data)) {
delete object[key];
}
return result;
}
exports.Client = Client;
exports.TDError = TDError;