tdlib-native
Version:
🚀 Telegram TDLib native nodejs wrapper
237 lines (201 loc) • 5.29 kB
text/typescript
/* eslint-disable @typescript-eslint/class-literal-property-style */
import path from "path";
import type { TDLib, TDLibClient } from "../shared/client";
import { getAddonFolderPath } from "./path";
import { createRequire } from "module";
import { promiseWithResolvers } from "../shared/async";
import { Addon } from "./native-exports";
const builtinAddonPath = path.resolve(
getAddonFolderPath(),
"..",
"..",
"build",
"Release",
"td.node"
);
/**
*
*
* @param {string} [addonPath]
* @returns {Addon} {Addon}
*/
async function loadAddon(addonPath: string = builtinAddonPath): Promise<Addon> {
const baseDirectory = getAddonFolderPath();
const load = createRequire(baseDirectory);
const addon: Addon = load(addonPath);
return addon;
}
/**
*
*
* @returns {Promise<string>} {Promise<string>}
*/
async function getTDLibPath(): Promise<string> {
const { tdlibPath } = await import("@tdlib-native/tdjson");
return tdlibPath;
}
class ReceiveQueueEntry {
constructor(
public readonly client: TDLibClient,
public readonly promise: PromiseWithResolvers<string | null>
) {
Object.freeze(this);
}
}
class ClientMeta {
destroyed = false;
constructor(public readonly timeout: number) {}
}
/**
*
*
* @export
* @class TDLibAddon
* @implements {TDLib}
*/
export class TDLibAddon implements TDLib {
/**
*
*
* @static
* @param {string} [tdlibPath] Resolves to prebuild TDLib for your platform
* @param {string} [addonPath]
* @returns {Promise<TDLib>}
* @memberof TDLibAddon
*/
static async create(tdlibPath?: string, addonPath?: string): Promise<TDLibAddon> {
tdlibPath ??= await getTDLibPath();
const addon = await loadAddon(addonPath);
addon.load_tdjson(tdlibPath);
return new TDLibAddon(addon);
}
/**
* Creates an instance of TDLibAddon.
* @param {Addon} _addon
* @memberof TDLibAddon
*/
private constructor(private readonly _addon: Addon) {}
readonly _isTDLib = true;
private readonly _clients = new WeakMap<TDLibClient, ClientMeta>();
/**
*
*
* @readonly
* @memberof TDLibAddon
*/
get name() {
return "addon";
}
/**
*
*
* @returns {TDLibClient} {TDLibClient}
* @memberof TDLibAddon
*/
create(timeout: number): TDLibClient {
const client = this._addon.td_json_client_create(timeout);
this._clients.set(client, new ClientMeta(timeout));
return client;
}
private _getMeta(client: TDLibClient): ClientMeta {
const meta = this._clients.get(client);
if (!meta) {
throw new Error("Unknown client");
}
return meta;
}
/**
*
*
* @param {TDLibClient} client
* @memberof TDLibAddon
* @returns {Promise<void>}
*/
async destroy(client: TDLibClient): Promise<void> {
if (this._getMeta(client).destroyed) {
throw new Error("Client already destroyed");
}
for (const entry of this._queue) {
if (entry.client === client) {
entry.promise.reject(new Error("Client is destroyed"));
}
}
await this._thread;
this._addon.td_json_client_destroy(client);
}
/**
*
*
* @param {(TDLibClient | null)} client
* @param {string} json
* @returns {(string | null)} {(string | null)}
* @memberof TDLibAddon
*/
execute(client: TDLibClient | null, json: string): string | null {
return this._addon.td_json_client_execute(client, json);
}
private readonly _queue: ReceiveQueueEntry[] = [];
private _thread: Promise<void> | undefined;
private async _receive() {
while (this._queue.length > 0) {
const task = this._queue.shift();
if (!task) break;
await this._addon
.td_json_client_receive(task.client)
.then(task.promise.resolve, task.promise.reject);
}
}
/**
*
*
* @param {TDLibClient} client
* @returns {Promise<string|null>} {(Promise<string | null>)}
* @memberof TDLibAddon
*/
receive(client: TDLibClient): Promise<string | null> {
const meta = this._getMeta(client);
if (meta.destroyed) {
return Promise.reject(new Error("Client is destroyed"));
}
const entryWithSameClient = this._queue.find((entry) => entry.client === client);
if (entryWithSameClient) {
return Promise.reject(new Error("This client is already receiving updates"));
}
const promise = promiseWithResolvers<string | null>();
const entry = new ReceiveQueueEntry(client, promise);
this._queue.push(entry);
if (!this._thread) {
this._thread = this._receive().finally(() => {
this._thread = undefined;
});
}
return promise.promise;
}
/**
*
*
* @param {TDLibClient} client
* @param {string} json
* @memberof TDLibAddon
* @returns {void}
*/
send(client: TDLibClient, json: string): void {
if (this._getMeta(client).destroyed) {
throw new Error("Client is destroyed");
}
this._addon.td_json_client_send(client, json);
}
/**
*
*
* @param {function(errorMessage: string): void=} callback
* @memberof TDLibAddon
* @returns {void}
*/
setLogMessageCallback(
level: number,
callback: ((errorMessage: string) => void) | null
): void {
this._addon.td_set_log_message_callback(level, callback);
}
}