UNPKG

tdlib-native

Version:

🚀 Telegram TDLib native nodejs wrapper

302 lines (301 loc) • 7.44 kB
import { $AsyncApi, $SyncApi, typename } from "./generated/types.mjs"; import { deserialize, serialize } from "./json.mjs"; import { promiseWithResolvers } from "./shared/async.mjs"; import { EventBus } from "./event-bus.mjs"; import debug from "debug"; import { TDLibOptions } from "./options.mjs"; import { assert } from "./assert.mjs"; const debugJson = debug("tdlib-native:json"); const tag = "@extra"; class TDError extends Error { [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 [typename]: this[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(); _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 $AsyncApi(this); syncApi = new $SyncApi(this); _tdlibOptions = new 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 = promiseWithResolvers(); assignTemporary(parameters, { [typename]: method, [tag]: extra }, (merged) => { const value = 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 && 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, { [typename]: method, [tag]: extra }, (merged) => { const value2 = serialize(merged); debugJson("Sent sync %s", value2); return adapter.execute(client ?? null, value2); } ); debugJson("Received sync %s", value); assert(value, new TDError("Method returned null", { method, parameters })); let data; try { data = deserialize(value); } catch { throw new TDError("Method returned invalid json", { method, parameters }); } assert( typeof data === "object" && data && typename in data, new TDError("Returned not an object", { method, parameters }) ); if (data[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 = deserialize(value); } catch { continue; } if (typeof data !== "object" || !data) { continue; } const extra = data == null ? void 0 : data[tag]; if (extra) { const async = this._requests.get(extra); delete data[tag]; if (data[typename] === "error") { async == null ? void 0 : async.reject(data); } else { async == null ? void 0 : async.resolve(data); } this._requests.delete(extra); continue; } if (data[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( 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( 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 async of this._requests.values()) { async.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; } export { Client, TDError };