dograma
Version:
NodeJS/Browser MTProto API Telegram client library,
322 lines (321 loc) • 13.2 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelegramBaseClient = void 0;
const __1 = require("../");
const Helpers_1 = require("../Helpers");
const connection_1 = require("../network/connection");
const sessions_1 = require("../sessions");
const extensions_1 = require("../extensions");
const tl_1 = require("../tl");
const os_1 = __importDefault(require("./os"));
const entityCache_1 = require("../entityCache");
const markdown_1 = require("../extensions/markdown");
const network_1 = require("../network");
const AllTLObjects_1 = require("../tl/AllTLObjects");
const TCPMTProxy_1 = require("../network/connection/TCPMTProxy");
const async_mutex_1 = require("async-mutex");
const Logger_1 = require("../extensions/Logger");
const platform_1 = require("../platform");
const EXPORTED_SENDER_RECONNECT_TIMEOUT = 1000; // 1 sec
const EXPORTED_SENDER_RELEASE_TIMEOUT = 30000; // 30 sec
const DEFAULT_DC_ID = 4;
const DEFAULT_IPV4_IP = platform_1.isNode ? "149.154.167.91" : "vesta.web.telegram.org";
const DEFAULT_IPV6_IP = "2001:067c:04e8:f004:0000:0000:0000:000a";
const clientParamsDefault = {
connection: platform_1.isNode ? connection_1.ConnectionTCPFull : connection_1.ConnectionTCPObfuscated,
networkSocket: platform_1.isNode ? extensions_1.PromisedNetSockets : extensions_1.PromisedWebSockets,
useIPV6: false,
timeout: 10,
requestRetries: 5,
connectionRetries: Infinity,
retryDelay: 1000,
downloadRetries: 5,
autoReconnect: true,
sequentialUpdates: false,
floodSleepThreshold: 60,
deviceModel: "",
systemVersion: "",
appVersion: "",
langCode: "en",
systemLangCode: "en",
_securityChecks: true,
useWSS: platform_1.isBrowser ? window.location.protocol == "https:" : false,
testServers: false,
};
class TelegramBaseClient {
constructor(session, apiId, apiHash, clientParams) {
var _a;
/** The current gramJS version. */
this.__version__ = __1.version;
/** @hidden */
this._ALBUMS = new Map();
/** @hidden */
this._exportedSenderPromises = new Map();
/** @hidden */
this._exportedSenderReleaseTimeouts = new Map();
clientParams = Object.assign(Object.assign({}, clientParamsDefault), clientParams);
if (!apiId || !apiHash) {
throw new Error("Your API ID or Hash cannot be empty or undefined");
}
if (clientParams.baseLogger) {
this._log = clientParams.baseLogger;
}
else {
this._log = new extensions_1.Logger();
}
this._log.info("Running gramJS version " + __1.version);
if (session && typeof session == "string") {
session = new sessions_1.StoreSession(session);
}
if (!(session instanceof sessions_1.Session)) {
throw new Error("Only StringSession and StoreSessions are supported currently :( ");
}
this._floodSleepThreshold = clientParams.floodSleepThreshold;
this.session = session;
this.apiId = apiId;
this.apiHash = apiHash;
this._useIPV6 = clientParams.useIPV6;
this._requestRetries = clientParams.requestRetries;
this._downloadRetries = clientParams.downloadRetries;
this._connectionRetries = clientParams.connectionRetries;
this._retryDelay = clientParams.retryDelay || 0;
this._timeout = clientParams.timeout;
this._autoReconnect = clientParams.autoReconnect;
this._proxy = clientParams.proxy;
this._semaphore = new async_mutex_1.Semaphore(clientParams.maxConcurrentDownloads || 1);
this.testServers = clientParams.testServers || false;
this.networkSocket = clientParams.networkSocket || extensions_1.PromisedNetSockets;
if (!(clientParams.connection instanceof Function)) {
throw new Error("Connection should be a class not an instance");
}
this._connection = clientParams.connection;
let initProxy;
if ((_a = this._proxy) === null || _a === void 0 ? void 0 : _a.MTProxy) {
this._connection = TCPMTProxy_1.ConnectionTCPMTProxyAbridged;
initProxy = new tl_1.Api.InputClientProxy({
address: this._proxy.ip,
port: this._proxy.port,
});
}
this._initRequest = new tl_1.Api.InitConnection({
apiId: this.apiId,
deviceModel: clientParams.deviceModel || os_1.default.type().toString() || "Unknown",
systemVersion: clientParams.systemVersion || os_1.default.release().toString() || "1.0",
appVersion: clientParams.appVersion || "1.0",
langCode: clientParams.langCode,
langPack: "",
systemLangCode: clientParams.systemLangCode,
proxy: initProxy,
});
this._eventBuilders = [];
this._floodWaitedRequests = {};
this._borrowedSenderPromises = {};
this._bot = undefined;
this._selfInputPeer = undefined;
this.useWSS = clientParams.useWSS;
this._securityChecks = !!clientParams.securityChecks;
if (this.useWSS && this._proxy) {
throw new Error("Cannot use SSL with proxies. You need to disable the useWSS client param in TelegramClient");
}
this._entityCache = new entityCache_1.EntityCache();
// These will be set later
this._config = undefined;
this._loopStarted = false;
this._reconnecting = false;
this._destroyed = false;
// parse mode
this._parseMode = markdown_1.MarkdownParser;
}
get floodSleepThreshold() {
return this._floodSleepThreshold;
}
set floodSleepThreshold(value) {
this._floodSleepThreshold = Math.min(value || 0, 24 * 60 * 60);
}
set maxConcurrentDownloads(value) {
// @ts-ignore
this._semaphore._value = value;
}
// region connecting
async _initSession() {
await this.session.load();
if (!this.session.serverAddress) {
this.session.setDC(DEFAULT_DC_ID, this._useIPV6 ? DEFAULT_IPV6_IP : DEFAULT_IPV4_IP, this.useWSS ? 443 : 80);
}
else {
this._useIPV6 = this.session.serverAddress.includes(":");
}
}
get connected() {
return this._sender && this._sender.isConnected();
}
async disconnect() {
await this._disconnect();
await Promise.all(Object.values(this._exportedSenderPromises).map((promise) => {
return (promise &&
promise.then((sender) => {
if (sender) {
return sender.disconnect();
}
return undefined;
}));
}));
this._exportedSenderPromises = new Map();
// TODO cancel hanging promises
}
get disconnected() {
return !this._sender || this._sender._disconnected;
}
async _disconnect() {
var _a;
await ((_a = this._sender) === null || _a === void 0 ? void 0 : _a.disconnect());
}
/**
* Disconnects all senders and removes all handlers
* Disconnect is safer as it will not remove your event handlers
*/
async destroy() {
this._destroyed = true;
await Promise.all([
this.disconnect(),
...Object.values(this._borrowedSenderPromises).map((promise) => {
return promise.then((sender) => sender.disconnect());
}),
]);
this._eventBuilders = [];
}
/** @hidden */
async _authKeyCallback(authKey, dcId) {
this.session.setAuthKey(authKey, dcId);
await this.session.save();
}
/** @hidden */
async _cleanupExportedSender(dcId) {
if (this.session.dcId !== dcId) {
this.session.setAuthKey(undefined, dcId);
}
let sender = await this._exportedSenderPromises.get(dcId);
this._exportedSenderPromises.delete(dcId);
await (sender === null || sender === void 0 ? void 0 : sender.disconnect());
}
/** @hidden */
async _connectSender(sender, dcId) {
// if we don't already have an auth key we want to use normal DCs not -1
const dc = await this.getDC(dcId, !!sender.authKey.getKey());
while (true) {
try {
await sender.connect(new this._connection({
ip: dc.ipAddress,
port: dc.port,
dcId: dcId,
loggers: this._log,
proxy: this._proxy,
testServers: this.testServers,
socket: this.networkSocket,
}));
if (this.session.dcId !== dcId && !sender._authenticated) {
this._log.info(`Exporting authorization for data center ${dc.ipAddress} with layer ${AllTLObjects_1.LAYER}`);
const auth = await this.invoke(new tl_1.Api.auth.ExportAuthorization({ dcId: dcId }));
this._initRequest.query = new tl_1.Api.auth.ImportAuthorization({
id: auth.id,
bytes: auth.bytes,
});
const req = new tl_1.Api.InvokeWithLayer({
layer: AllTLObjects_1.LAYER,
query: this._initRequest,
});
await sender.send(req);
sender._authenticated = true;
}
sender.dcId = dcId;
sender.userDisconnected = false;
return sender;
}
catch (err) {
if (err.errorMessage === "DC_ID_INVALID") {
sender._authenticated = true;
sender.userDisconnected = false;
return sender;
}
if (this._log.canSend(Logger_1.LogLevel.ERROR)) {
console.error(err);
}
await (0, Helpers_1.sleep)(1000);
await sender.disconnect();
}
}
}
/** @hidden */
async _borrowExportedSender(dcId, shouldReconnect, existingSender) {
if (!this._exportedSenderPromises.get(dcId) || shouldReconnect) {
this._exportedSenderPromises.set(dcId, this._connectSender(existingSender || this._createExportedSender(dcId), dcId));
}
let sender;
try {
sender = await this._exportedSenderPromises.get(dcId);
if (!sender.isConnected()) {
if (sender.isConnecting) {
await (0, Helpers_1.sleep)(EXPORTED_SENDER_RECONNECT_TIMEOUT);
return this._borrowExportedSender(dcId, false, sender);
}
else {
return this._borrowExportedSender(dcId, true, sender);
}
}
}
catch (err) {
if (this._log.canSend(Logger_1.LogLevel.ERROR)) {
console.error(err);
}
return this._borrowExportedSender(dcId, true);
}
if (this._exportedSenderReleaseTimeouts.get(dcId)) {
clearTimeout(this._exportedSenderReleaseTimeouts.get(dcId));
this._exportedSenderReleaseTimeouts.delete(dcId);
}
this._exportedSenderReleaseTimeouts.set(dcId, setTimeout(() => {
this._exportedSenderReleaseTimeouts.delete(dcId);
sender.disconnect();
}, EXPORTED_SENDER_RELEASE_TIMEOUT));
return sender;
}
/** @hidden */
_createExportedSender(dcId) {
return new network_1.MTProtoSender(this.session.getAuthKey(dcId), {
logger: this._log,
dcId,
retries: this._connectionRetries,
delay: this._retryDelay,
autoReconnect: this._autoReconnect,
connectTimeout: this._timeout,
authKeyCallback: this._authKeyCallback.bind(this),
isMainSender: dcId === this.session.dcId,
onConnectionBreak: this._cleanupExportedSender.bind(this),
client: this,
securityChecks: this._securityChecks,
});
}
/** @hidden */
getSender(dcId) {
return dcId
? this._borrowExportedSender(dcId)
: Promise.resolve(this._sender);
}
// endregion
async getDC(dcId, download) {
throw new Error("Cannot be called from here!");
}
invoke(request) {
throw new Error("Cannot be called from here!");
}
setLogLevel(level) {
this._log.setLevel(level);
}
get logger() {
return this._log;
}
}
exports.TelegramBaseClient = TelegramBaseClient;