@tgsnake/core
Version:
Pure Telegram MTProto library for nodejs
572 lines (571 loc) • 26.3 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Session = exports.Results = void 0;
const platform_node_js_1 = require("../platform.node.js");
const Logger_js_1 = require("../Logger.js");
const connection_js_1 = require("../connection/connection.js");
const index_js_1 = require("../raw/index.js");
const Mtproto = __importStar(require("../crypto/Mtproto.js"));
const Errors = __importStar(require("../errors/index.js"));
const MsgId_js_1 = require("./internals/MsgId.js");
const MsgFactory_js_1 = require("./internals/MsgFactory.js");
const helpers_js_1 = require("../helpers.js");
const Timeout_js_1 = require("../Timeout.js");
const Auth_js_1 = require("./Auth.js");
class Results {
value;
reject;
resolve;
constructor() {
this.value = new Promise((resolve, reject) => {
this.reject = reject;
this.resolve = resolve;
});
}
}
exports.Results = Results;
class Session {
START_TIMEOUT = 2000;
WAIT_TIMEOUT = 15000;
SLEEP_THRESHOLD = 10000;
MAX_RETRIES;
ACKS_THRESHOLD = 8;
PING_INTERVAL = 5000;
_dcId;
_authKey;
_testMode;
_proxy;
_isMedia;
_isCdn;
_authKeyId;
_connection;
_pingTask;
_client;
_sessionId = platform_node_js_1.Buffer.from(platform_node_js_1.crypto.randomBytes(8));
_msgFactory = (0, MsgFactory_js_1.MsgFactory)();
_msgId = new MsgId_js_1.MsgId();
_salt = BigInt(0);
_storedMsgId = [];
_results = new Map();
_isConnected = false;
_pendingAcks = new Set();
_task = new Timeout_js_1.Timeout();
_networkTask = true;
_mutex = new platform_node_js_1.Mutex();
constructor(client, dcId, authKey, testMode, proxy, isMedia = false, isCdn = false) {
this._client = client;
this._dcId = dcId;
this._authKey = authKey;
this._testMode = testMode;
this._proxy = proxy;
this._isMedia = isMedia;
this._isCdn = isCdn;
this._authKeyId = platform_node_js_1.crypto.createHash('sha1').update(this._authKey).digest().subarray(-8);
this.MAX_RETRIES = client._maxRetries ?? 5;
}
async _handlePacket(packet) {
Logger_js_1.Logger.debug(`[33] Unpacking ${platform_node_js_1.Buffer.byteLength(packet)} bytes packet.`);
try {
const data = await Mtproto.unpack(new index_js_1.BytesIO(packet), this._sessionId, this._authKey, this._authKeyId, this._storedMsgId);
const message = data.body instanceof index_js_1.MsgContainer ? data.body.messages : [data];
Logger_js_1.Logger.debug(`[34] Reveive ${message.length} data.`);
for (const msg of message) {
if (msg.seqNo % 2 === 0) {
Logger_js_1.Logger.debug(`[35] Setting server time: ${msg.msgId / BigInt(2 ** 32)}.`);
this._msgId.setServerTime(msg.msgId / BigInt(2 ** 32));
}
else {
if (this._pendingAcks.has(msg.msgId)) {
Logger_js_1.Logger.debug(`[36] Skiping pending acks msg id: ${msg.msgId}.`);
continue;
}
else {
Logger_js_1.Logger.debug(`[37] Add msg id ${msg.msgId} to pending acks.`);
this._pendingAcks.add(msg.msgId);
}
}
if (msg.body instanceof index_js_1.Raw.MsgDetailedInfo || msg.body instanceof index_js_1.Raw.MsgNewDetailedInfo) {
Logger_js_1.Logger.debug(`[38] Got ${msg.body.constructor.name} and adding to pending acks: ${msg.body.answerMsgId}.`);
this._pendingAcks.add(msg.body.answerMsgId);
continue;
}
if (msg.body instanceof index_js_1.Raw.NewSessionCreated) {
Logger_js_1.Logger.debug(`[39] Got ${msg.body.constructor.name} and skiping.`);
continue;
}
let msgId;
if (msg.body instanceof index_js_1.Raw.BadMsgNotification || msg.body instanceof index_js_1.Raw.BadServerSalt) {
Logger_js_1.Logger.debug(`[40] Got ${msg.body.constructor.name} and msg id is: ${msg.body.badMsgId}.`);
msgId = msg.body.badMsgId;
if (msg.body instanceof index_js_1.Raw.BadServerSalt) {
this._salt = msg.body.newServerSalt;
}
}
else if (msg.body instanceof index_js_1.Raw.FutureSalts || msg.body instanceof index_js_1.Raw.RpcResult) {
Logger_js_1.Logger.debug(`[41] Got ${msg.body.constructor.name} and msg id is: ${msg.body.reqMsgId}.`);
msgId = msg.body.reqMsgId;
if (msg.body instanceof index_js_1.Raw.RpcResult) {
msg.body;
msg.body = msg.body.result;
}
}
else if (msg.body instanceof index_js_1.Raw.Pong) {
Logger_js_1.Logger.debug(`[42] Got ${msg.body.constructor.name} and msg id is: ${msg.body.msgId}.`);
msgId = msg.body.msgId;
}
else {
Logger_js_1.Logger.debug(`[43] Handling update ${msg.body.constructor.name}.`);
this._client.handleUpdate(msg.body);
}
if (msgId !== undefined) {
const promises = this._results.get(BigInt(msgId));
if (promises !== undefined) {
Logger_js_1.Logger.debug(`[44] Setting results of msg id ${msgId} with ${msg.body.constructor.name}.`);
promises.resolve(msg.body);
}
}
}
if (this._pendingAcks.size >= this.ACKS_THRESHOLD) {
Logger_js_1.Logger.debug(`[45] Sending ${this._pendingAcks.size} pending aks.`);
try {
await this._send(new index_js_1.Raw.MsgsAck({
msgIds: Array.from(this._pendingAcks),
}), false);
Logger_js_1.Logger.debug(`[46] Clearing all pending acks`);
this._pendingAcks.clear();
}
catch (error) {
if (!(error instanceof Errors.TimeoutError)) {
Logger_js_1.Logger.debug(`[47] Clearing all pending acks`);
this._pendingAcks.clear();
}
Logger_js_1.Logger.error(`[48] Got error when sending pending acks:`, error);
}
}
}
catch (error) {
if (error instanceof Errors.SecurityCheckMismatch) {
Logger_js_1.Logger.error(`[49] Invalid to unpack ${platform_node_js_1.Buffer.byteLength(packet)} bytes packet cause: ${error.description ?? error.message}`);
return await this.stop();
}
throw error;
}
}
async _send(data, waitResponse = true, timeout = this.WAIT_TIMEOUT) {
const msg = await this._msgFactory(data, this._msgId);
const msgId = msg.msgId;
if (waitResponse) {
this._results.set(BigInt(msgId), new Results());
}
if (msgId === undefined) {
Logger_js_1.Logger.error(`[107] Can't send request ${data.className} when msgId is undefined.`);
return;
}
Logger_js_1.Logger.debug(`[50] Sending msg id ${msgId} (${data.className}), has ${platform_node_js_1.Buffer.byteLength(msg.write())} bytes message.`);
const payload = Mtproto.pack(msg, this._salt, this._sessionId, this._authKey, this._authKeyId);
try {
Logger_js_1.Logger.debug(`[51] Sending ${platform_node_js_1.Buffer.byteLength(payload)} bytes payload.`);
await this._connection.send(payload);
}
catch (error) {
Logger_js_1.Logger.error(`[52] Got error when trying to send ${platform_node_js_1.Buffer.byteLength(payload)} bytes payload:`, error);
if (error instanceof Errors.WSError.ReadClosed ||
error instanceof Errors.WSError.Disconnected ||
error instanceof Errors.ClientError.ClientDisconnected) {
Logger_js_1.Logger.debug(`[108] Restarting client due to disconnected`);
if (this._client._maxReconnectRetries) {
return this.retriesReconnect();
}
else {
this.restart();
}
return;
}
const promises = this._results.get(BigInt(msgId));
if (promises) {
promises.reject(error);
}
}
const promises = this._results.get(BigInt(msgId));
if (waitResponse && promises !== undefined) {
let response;
try {
response = await this._task.run(promises.value, timeout);
}
catch (error) {
Logger_js_1.Logger.error(`[53] Got error when waiting response:`, error);
}
if (response) {
this._results.delete(BigInt(msgId));
Logger_js_1.Logger.debug(`[54] Got response from msg id ${msgId}: ${response.constructor.name}`);
if (response instanceof index_js_1.Raw.RpcError) {
if (data instanceof index_js_1.Raw.InvokeWithoutUpdates ||
data instanceof index_js_1.Raw.InvokeWithTakeout ||
data instanceof index_js_1.Raw.InvokeWithTakeout ||
data instanceof index_js_1.Raw.InvokeWithBusinessConnection ||
data instanceof index_js_1.Raw.InvokeWithGooglePlayIntegrity ||
data instanceof index_js_1.Raw.InvokeWithApnsSecret ||
data instanceof index_js_1.Raw.InvokeWithMessagesRange) {
data = data.query;
}
await Errors.RPCError.raise(response, data);
}
else if (response instanceof index_js_1.Raw.BadMsgNotification) {
throw new Errors.BadMsgNotification(response.errorCode);
}
else if (response instanceof index_js_1.Raw.BadServerSalt) {
this._salt = response.newServerSalt;
return await this._send(data, waitResponse, timeout);
}
else {
return response;
}
}
else {
throw new Errors.TimeoutError(timeout);
}
}
}
_pingWorker() {
const ping = async () => {
try {
if (!this._isConnected)
return;
Logger_js_1.Logger.debug(`[55] Ping to telegram server.`);
await this._send(new index_js_1.Raw.PingDelayDisconnect({
pingId: BigInt(0),
disconnectDelay: this.WAIT_TIMEOUT + 10000,
}), false);
}
catch (error) {
Logger_js_1.Logger.error(`[56] Get error when trying ping to telegram :`, error);
}
return this._pingWorker();
};
this._pingTask = setTimeout(ping, this.PING_INTERVAL);
return this._pingTask;
}
async _networkWorker() {
Logger_js_1.Logger.debug(`[57] Network worker started.`);
let waiting = false;
while (true) {
if (!this._networkTask) {
Logger_js_1.Logger.debug(`[58] Network worker ended`);
return;
}
if (!waiting) {
try {
const packet = await this._connection.recv();
if (packet !== undefined && platform_node_js_1.Buffer.byteLength(packet) !== 4) {
waiting = true;
const release = await this._mutex.acquire();
try {
await this._handlePacket(packet);
}
finally {
release();
}
waiting = false;
}
else {
if (packet) {
Logger_js_1.Logger.warning(`[59] Server sent "${packet.readInt32LE(0)}"`);
}
if (this._isConnected) {
return this.restart();
}
}
}
catch (error) {
Logger_js_1.Logger.error('[139] Network worker error:', error);
if (!this._isConnected) {
break;
}
else if ((error instanceof Errors.WSError.ReadClosed ||
error instanceof Errors.WSError.Disconnected ||
error instanceof Errors.ClientError.ClientDisconnected) &&
this._client._maxReconnectRetries) {
return this.retriesReconnect();
}
else {
throw error;
}
}
}
}
}
async retriesReconnect(retries = this._client._maxReconnectRetries) {
try {
Logger_js_1.Logger.info('[136] Reconnecting to Telegram Server.');
Logger_js_1.Logger.debug('[137] Stop ping task.');
clearTimeout(this._pingTask);
this._isConnected = false;
await this._connection.close().catch(() => { });
await this._connection.connect();
this._networkWorker();
const isInited = await this.initConnection();
if (isInited) {
this._isConnected = true;
this._pingWorker();
if (!this._client._storage.isBot && this._client._takeout) {
const takeout = await this.invoke(new index_js_1.Raw.account.InitTakeoutSession({}));
this._client._takeoutId = takeout.id;
}
await this.invoke(new index_js_1.Raw.updates.GetState());
const me = await this.invoke(new index_js_1.Raw.users.GetFullUser({
id: new index_js_1.Raw.InputUserSelf(),
}));
this._client._me = me;
return me;
}
}
catch (e) {
Logger_js_1.Logger.error(`[138] Got error when trying to reconnecting to Telegram Server, retries ${retries}:`, e);
if (!retries) {
throw e;
}
}
return this.retriesReconnect(retries - 1);
}
async stop() {
const release = await this._mutex.acquire();
try {
this._networkTask = false;
this._isConnected = false;
clearTimeout(this._pingTask);
await this._connection.close().catch(() => { });
this._results.clear();
this._task.clear();
Logger_js_1.Logger.info(`[60] Session stopped.`);
}
finally {
release();
}
}
restart() {
try {
Logger_js_1.Logger.debug(`[61] Restarting client`);
this.stop();
this.start();
}
catch (_error) {
}
}
async invoke(data, retries = this.MAX_RETRIES, timeout = this.WAIT_TIMEOUT, sleepThreshold = this.SLEEP_THRESHOLD) {
Logger_js_1.Logger.debug(`[62] Invoking ${data.className} with parameters: ${retries} retries, ${timeout}ms timeout, ${sleepThreshold}ms sleep threshold.`);
if (!this._isConnected) {
Logger_js_1.Logger.error(`[63] Can't sending request when client is unconnected.`);
throw new Errors.ClientError.ClientDisconnected();
}
if (data.classType !== 'functions') {
throw new Errors.NotAFunctionClass(data.className);
}
let className = data.className;
if (data instanceof index_js_1.Raw.InvokeWithLayer ||
data instanceof index_js_1.Raw.InvokeWithoutUpdates ||
data instanceof index_js_1.Raw.InvokeWithTakeout ||
data instanceof index_js_1.Raw.InvokeWithBusinessConnection ||
data instanceof index_js_1.Raw.InvokeWithGooglePlayIntegrity ||
data instanceof index_js_1.Raw.InvokeWithApnsSecret ||
data instanceof index_js_1.Raw.InvokeWithMessagesRange) {
className = data.query.className;
}
while (true) {
if (this._isConnected) {
try {
const response = await this._send(data, true, timeout);
if (response !== undefined) {
return response;
}
await (0, helpers_js_1.sleep)(1000);
}
catch (error) {
Logger_js_1.Logger.error(`[64] Got error when trying invoking ${className}:`, error);
if (error instanceof Errors.Exceptions.Flood.FloodWait) {
error;
const amount = Number(error.value ?? 2000);
if (amount > sleepThreshold >= 0) {
throw error;
}
Logger_js_1.Logger.info(`[65] Waiting for ${amount} seconds before continuing (caused by ${className})`);
await (0, helpers_js_1.sleep)(amount);
}
else if ((error instanceof Errors.Exceptions.SeeOther.FileMigrate ||
error instanceof Errors.Exceptions.SeeOther.StatsMigrate ||
error instanceof Errors.Exceptions.SeeOther.NetworkMigrate) &&
typeof error.value !== 'undefined') {
Logger_js_1.Logger.error(`[156] Got error when trying invoking ${className}: ${error.message}. Try to reconnecting.`);
const exportedAuthKey = (await this.invoke(new index_js_1.Raw.auth.ExportAuthorization({ dcId: error.value })));
const newAuthKey = await new Auth_js_1.Auth(error.value, this._testMode, this._client._ipv6).create();
const newSession = new Session(this._client, error.value, newAuthKey, this._testMode, this._proxy, this._isMedia, this._isCdn);
Logger_js_1.Logger.debug(`[157] Reconnecting to telegram server`);
await newSession.start();
Logger_js_1.Logger.debug(`[158] Importing auth key`);
await newSession.invoke(new index_js_1.Raw.auth.ImportAuthorization({
id: exportedAuthKey.id,
bytes: exportedAuthKey.bytes,
}));
Logger_js_1.Logger.debug(`[159] Session imported, resend the query`);
const result = await newSession.invoke(data, retries, timeout, sleepThreshold);
Logger_js_1.Logger.debug(`[160] Closing session in DC${error.value}`);
await newSession.stop();
return result;
}
else {
if (!retries) {
throw error;
}
if (retries < 2) {
Logger_js_1.Logger.info(`[66] [${this.MAX_RETRIES - retries + 1}] Retrying "${className}" due to ${error.message}`);
}
else {
Logger_js_1.Logger.info(`[67] [${this.MAX_RETRIES - retries + 1}] Retrying "${className}" due to ${error.message}`, error);
}
await (0, helpers_js_1.sleep)(500);
return await this.invoke(data, retries - 1, timeout, sleepThreshold);
}
}
}
else {
throw new Errors.ClientError.ClientDisconnected();
}
}
}
async start() {
while (true) {
this._connection = new connection_js_1.Connection(this._dcId, this._testMode, this._client._ipv6, this._proxy, this._isMedia, this._client._connectionMode, this._client._local);
this._networkTask = true;
try {
Logger_js_1.Logger.debug(`[68] Connecting to telegram server`);
await this._connection.connect();
this._networkWorker();
await this.initConnection();
Logger_js_1.Logger.info(`[69] Session initialized: Layer ${index_js_1.Raw.Layer}`);
Logger_js_1.Logger.info(`[70] Device: ${this._client._deviceModel} - ${this._client._appVersion}`);
Logger_js_1.Logger.info(`[71] System: ${this._client._systemVersion} (${this._client._langCode.toUpperCase()})`);
Logger_js_1.Logger.info(`[135] Getting Update State`);
this._pingWorker();
this._isConnected = true;
Logger_js_1.Logger.info('[72] Session Started');
break;
}
catch (error) {
if (error instanceof Errors.Exceptions.NotAcceptable.AuthKeyDuplicated) {
await this.stop();
throw error;
}
else if (error instanceof Errors.TimeoutError || error instanceof Errors.RPCError) {
await (0, helpers_js_1.sleep)(1000);
await this.stop();
}
else {
break;
}
}
}
}
async initConnection() {
const ping = await this._send(new index_js_1.Raw.Ping({
pingId: BigInt(0),
}), true, this.START_TIMEOUT);
if (!this._isCdn) {
const initData = await this._send(new index_js_1.Raw.InvokeWithLayer({
layer: index_js_1.Raw.Layer,
query: new index_js_1.Raw.InitConnection({
apiId: this._client._apiId,
appVersion: this._client._appVersion,
deviceModel: this._client._deviceModel,
systemVersion: this._client._systemVersion,
systemLangCode: this._client._systemLangCode,
langCode: this._client._langCode,
langPack: '',
query: new index_js_1.Raw.help.GetConfig(),
proxy: this._proxy &&
'secret' in this._proxy &&
'port' in this._proxy &&
'server' in this._proxy
? new index_js_1.Raw.InputClientProxy({ address: this._proxy.server, port: this._proxy.port })
: undefined,
}),
}), true, this.START_TIMEOUT);
return initData;
}
return ping;
}
[Symbol.for('nodejs.util.inspect.custom')]() {
const toPrint = {
_: this.constructor.name,
};
for (const key in this) {
if (Object.prototype.hasOwnProperty.call(this, key)) {
const value = this[key];
if (!key.startsWith('_') && value !== undefined && value !== null) {
toPrint[key] = value;
}
}
}
return toPrint;
}
[Symbol.for('Deno.customInspect')]() {
return String((0, platform_node_js_1.inspect)(this[Symbol.for('nodejs.util.inspect.custom')](), { colors: true }));
}
toJSON() {
const toPrint = {
_: this.constructor.name,
};
for (const key in this) {
if (Object.prototype.hasOwnProperty.call(this, key)) {
const value = this[key];
if (!key.startsWith('_') && value !== undefined && value !== null) {
if (typeof value === 'bigint') {
toPrint[key] = String(value);
}
else if (Array.isArray(value)) {
toPrint[key] = value.map((v) => (typeof v === 'bigint' ? String(v) : v));
}
else {
toPrint[key] = value;
}
}
}
}
return toPrint;
}
toString() {
return `[constructor of ${this.constructor.name}] ${JSON.stringify(this, null, 2)}`;
}
}
exports.Session = Session;