UNPKG

tdlib-native

Version:

🚀 Telegram TDLib native nodejs wrapper

302 lines (301 loc) • 7.64 kB
"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;