dograma
Version:
NodeJS/Browser MTProto API Telegram client library,
871 lines (864 loc) • 35.3 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.MTProtoSender = void 0;
/**
* MTProto Mobile Protocol sender
* (https://core.telegram.org/mtproto/description)
* This class is responsible for wrapping requests into `TLMessage`'s,
* sending them over the network and receiving them in a safe manner.
*
* Automatic reconnection due to temporary network issues is a concern
* for this class as well, including retry of messages that could not
* be sent successfully.
*
* A new authorization key will be generated on connection if no other
* key exists yet.
*/
const AuthKey_1 = require("../crypto/AuthKey");
const MTProtoState_1 = require("./MTProtoState");
const extensions_1 = require("../extensions");
const extensions_2 = require("../extensions");
const core_1 = require("../tl/core");
const tl_1 = require("../tl");
const big_integer_1 = __importDefault(require("big-integer"));
const Helpers_1 = require("../Helpers");
const RequestState_1 = require("./RequestState");
const Authenticator_1 = require("./Authenticator");
const MTProtoPlainSender_1 = require("./MTProtoPlainSender");
const errors_1 = require("../errors");
const _1 = require("./");
const Logger_1 = require("../extensions/Logger");
const async_mutex_1 = require("async-mutex");
class MTProtoSender {
/**
* @param authKey
* @param opts
*/
constructor(authKey, opts) {
const args = Object.assign(Object.assign({}, MTProtoSender.DEFAULT_OPTIONS), opts);
this._cancelSend = false;
this._connection = undefined;
this._log = args.logger;
this._dcId = args.dcId;
this._retries = args.retries;
this._delay = args.delay;
this._autoReconnect = args.autoReconnect;
this._connectTimeout = args.connectTimeout;
this._authKeyCallback = args.authKeyCallback;
this._updateCallback = args.updateCallback;
this._autoReconnectCallback = args.autoReconnectCallback;
this._isMainSender = args.isMainSender;
this._senderCallback = args.senderCallback;
this._client = args.client;
this._onConnectionBreak = args.onConnectionBreak;
this._securityChecks = args.securityChecks;
this._connectMutex = new async_mutex_1.Mutex();
this._recvCancelPromise = new Promise((resolve) => {
this._recvCancelResolve = resolve;
});
this._sendCancelPromise = new Promise((resolve) => {
this._sendCancelResolve = resolve;
});
/**
* whether we disconnected ourself or telegram did it.
*/
this.userDisconnected = false;
/**
* If a disconnection happens for any other reason and it
* was *not* user action then the pending messages won't
* be cleared but on explicit user disconnection all the
* pending futures should be cancelled.
*/
this.isConnecting = false;
this._authenticated = false;
this._userConnected = false;
this._reconnecting = false;
this._disconnected = true;
/**
* We need to join the loops upon disconnection
*/
this._sendLoopHandle = null;
this._recvLoopHandle = null;
/**
* Preserving the references of the AuthKey and state is important
*/
this.authKey = authKey || new AuthKey_1.AuthKey();
this._state = new MTProtoState_1.MTProtoState(this.authKey, this._log, this._securityChecks);
/**
* Outgoing messages are put in a queue and sent in a batch.
* Note that here we're also storing their ``_RequestState``.
*/
this._sendQueue = new extensions_2.MessagePacker(this._state, this._log);
/**
* Sent states are remembered until a response is received.
*/
this._pendingState = new Map();
/**
* Responses must be acknowledged, and we can also batch these.
*/
this._pendingAck = new Set();
/**
* Similar to pending_messages but only for the last acknowledges.
* These can't go in pending_messages because no acknowledge for them
* is received, but we may still need to resend their state on bad salts.
*/
this._lastAcks = [];
/**
* Jump table from response ID to method that handles it
*/
this._handlers = {
[core_1.RPCResult.CONSTRUCTOR_ID.toString()]: this._handleRPCResult.bind(this),
[core_1.MessageContainer.CONSTRUCTOR_ID.toString()]: this._handleContainer.bind(this),
[core_1.GZIPPacked.CONSTRUCTOR_ID.toString()]: this._handleGzipPacked.bind(this),
[tl_1.Api.Pong.CONSTRUCTOR_ID.toString()]: this._handlePong.bind(this),
[tl_1.Api.BadServerSalt.CONSTRUCTOR_ID.toString()]: this._handleBadServerSalt.bind(this),
[tl_1.Api.BadMsgNotification.CONSTRUCTOR_ID.toString()]: this._handleBadNotification.bind(this),
[tl_1.Api.MsgDetailedInfo.CONSTRUCTOR_ID.toString()]: this._handleDetailedInfo.bind(this),
[tl_1.Api.MsgNewDetailedInfo.CONSTRUCTOR_ID.toString()]: this._handleNewDetailedInfo.bind(this),
[tl_1.Api.NewSessionCreated.CONSTRUCTOR_ID.toString()]: this._handleNewSessionCreated.bind(this),
[tl_1.Api.MsgsAck.CONSTRUCTOR_ID.toString()]: this._handleAck.bind(this),
[tl_1.Api.FutureSalts.CONSTRUCTOR_ID.toString()]: this._handleFutureSalts.bind(this),
[tl_1.Api.MsgsStateReq.CONSTRUCTOR_ID.toString()]: this._handleStateForgotten.bind(this),
[tl_1.Api.MsgResendReq.CONSTRUCTOR_ID.toString()]: this._handleStateForgotten.bind(this),
[tl_1.Api.MsgsAllInfo.CONSTRUCTOR_ID.toString()]: this._handleMsgAll.bind(this),
};
}
set dcId(dcId) {
this._dcId = dcId;
}
get dcId() {
return this._dcId;
}
// Public API
/**
* Connects to the specified given connection using the given auth key.
*/
async connect(connection) {
const release = await this._connectMutex.acquire();
try {
if (this._userConnected) {
this._log.info("User is already connected!");
return false;
}
this._connection = connection;
await this._connect();
this._userConnected = true;
return true;
}
finally {
release();
}
}
isConnected() {
return this._userConnected;
}
_transportConnected() {
return (!this._reconnecting &&
this._connection &&
this._connection._connected);
}
/**
* Cleanly disconnects the instance from the network, cancels
* all pending requests, and closes the send and receive loops.
*/
async disconnect() {
const release = await this._connectMutex.acquire();
try {
await this._disconnect();
}
catch (e) {
this._log.error(e);
}
finally {
release();
}
}
/**
*
This method enqueues the given request to be sent. Its send
state will be saved until a response arrives, and a ``Future``
that will be resolved when the response arrives will be returned:
.. code-block:: javascript
async def method():
# Sending (enqueued for the send loop)
future = sender.send(request)
# Receiving (waits for the receive loop to read the result)
result = await future
Designed like this because Telegram may send the response at
any point, and it can send other items while one waits for it.
Once the response for this future arrives, it is set with the
received result, quite similar to how a ``receive()`` call
would otherwise work.
Since the receiving part is "built in" the future, it's
impossible to await receive a result that was never sent.
* @param request
* @returns {RequestState}
*/
send(request) {
if (!this._userConnected) {
throw new Error("Cannot send requests while disconnected. You need to call .connect()");
}
const state = new RequestState_1.RequestState(request);
this._sendQueue.append(state);
return state.promise;
}
/**
* Performs the actual connection, retrying, generating the
* authorization key if necessary, and starting the send and
* receive loops.
* @returns {Promise<void>}
* @private
*/
async _connect() {
this._log.info("Connecting to {0} using {1}"
.replace("{0}", this._connection.toString())
.replace("{1}", this._connection.socket.toString()));
let connected = false;
for (let attempt = 0; attempt < this._retries; attempt++) {
if (!connected) {
connected = await this._tryConnect(attempt);
if (!connected) {
continue;
}
}
if (!this.authKey.getKey()) {
try {
if (!(await this._tryGenAuthKey(attempt))) {
continue;
}
}
catch (err) {
this._log.warn(`Connection error ${attempt} during auth_key gen`);
if (this._log.canSend(Logger_1.LogLevel.ERROR)) {
console.error(err);
}
await this._connection.disconnect();
connected = false;
await (0, Helpers_1.sleep)(this._delay);
continue;
}
}
else {
this._authenticated = true;
this._log.debug("Already have an auth key ...");
}
break;
}
if (!connected) {
throw new Error(`Connection to telegram failed after ${this._retries} time(s)`);
}
if (!this.authKey.getKey()) {
const error = new Error(`auth key generation failed after ${this._retries} time(s)`);
await this._disconnect(error);
throw error;
}
this._userConnected = true;
this._log.debug("Starting receive loop");
this._recvLoopHandle = this._recvLoop();
this._log.debug("Starting send loop");
this._sendLoopHandle = this._sendLoop();
this._log.info("Connection to %s complete!".replace("%s", this._connection.toString()));
}
async _disconnect(error) {
if (!this._connection) {
this._log.info("Not disconnecting (already have no connection)");
return;
}
this._log.info("Disconnecting from %s...".replace("%s", this._connection.toString()));
this._userConnected = false;
try {
this._log.debug("Closing current connection...");
await this._connection.disconnect();
}
finally {
this._log.debug(`Cancelling ${this._pendingState.size} pending message(s)...`);
for (const state of this._pendingState.values()) {
if (error && !state.result) {
state.reject(error);
}
else {
state.reject("disconnected");
}
}
this._pendingState.clear();
this._cancelLoops();
this._log.info("Disconnecting from %s complete!".replace("%s", this._connection.toString()));
this._connection = undefined;
}
}
_cancelLoops() {
this._cancelSend = true;
this._recvCancelResolve(new extensions_1.CancelHelper());
this._sendCancelResolve(new extensions_1.CancelHelper());
this._recvCancelPromise = new Promise((resolve) => {
this._recvCancelResolve = resolve;
});
this._sendCancelPromise = new Promise((resolve) => {
this._sendCancelResolve = resolve;
});
}
/**
* This loop is responsible for popping items off the send
* queue, encrypting them, and sending them over the network.
* Besides `connect`, only this method ever sends data.
* @returns {Promise<void>}
* @private
*/
async _sendLoop() {
this._cancelSend = false;
while (this._userConnected &&
!this._reconnecting &&
!this._cancelSend) {
if (this._pendingAck.size) {
const ack = new RequestState_1.RequestState(new tl_1.Api.MsgsAck({ msgIds: Array(...this._pendingAck) }));
this._sendQueue.append(ack);
this._lastAcks.push(ack);
if (this._lastAcks.length >= 10) {
this._lastAcks.shift();
}
this._pendingAck.clear();
}
this._log.debug("Waiting for messages to send..." + this._reconnecting);
// TODO Wait for the connection send queue to be empty?
// This means that while it's not empty we can wait for
// more messages to be added to the send queue.
const res = await this._sendQueue.get();
if (!res) {
continue;
}
let { data } = res;
const { batch } = res;
this._log.debug(`Encrypting ${batch.length} message(s) in ${data.length} bytes for sending`);
data = await this._state.encryptMessageData(data);
for (const state of batch) {
if (!Array.isArray(state)) {
if (state.request.classType === "request") {
this._pendingState.set(state.msgId.toString(), state);
}
}
else {
for (const s of state) {
if (s.request.classType === "request") {
this._pendingState.set(s.msgId.toString(), s);
}
}
}
}
try {
await this._connection.send(data);
}
catch (e) {
this._log.error(e);
this._log.info("Connection closed while sending data");
this._startReconnecting(e);
return;
}
this._log.debug("Encrypted messages put in a queue to be sent");
}
}
async _recvLoop() {
let body;
let message;
while (this._userConnected && !this._reconnecting) {
this._log.debug("Receiving items from the network...");
try {
body = await Promise.race([
this._connection.recv(),
this._recvCancelPromise,
]);
if (body instanceof extensions_1.CancelHelper) {
return;
}
}
catch (e) {
this._log.error(e);
this._log.warn("Connection closed while receiving data...");
this._startReconnecting(e);
return;
}
try {
message = await this._state.decryptMessageData(body);
}
catch (e) {
if (e instanceof errors_1.TypeNotFoundError) {
// Received object which we don't know how to deserialize
this._log.info(`Type ${e.invalidConstructorId} not found, remaining data ${e.remaining}`);
continue;
}
else if (e instanceof errors_1.SecurityError) {
// A step while decoding had the incorrect data. This message
// should not be considered safe and it should be ignored.
this._log.warn(`Security error while unpacking a received message: ${e}`);
continue;
}
else if (e instanceof errors_1.InvalidBufferError) {
// 404 means that the server has "forgotten" our auth key and we need to create a new one.
if (e.code === 404) {
this._log.warn(`Broken authorization key for dc ${this._dcId}; resetting`);
if (this._updateCallback && this._isMainSender) {
this._updateCallback(this._client, new _1.UpdateConnectionState(_1.UpdateConnectionState.broken));
}
else if (this._onConnectionBreak &&
!this._isMainSender) {
// Deletes the current sender from the object
this._onConnectionBreak(this._dcId);
}
await this._disconnect(e);
}
else {
// this happens sometimes when telegram is having some internal issues.
// reconnecting should be enough usually
// since the data we sent and received is probably wrong now.
this._log.warn(`Invalid buffer ${e.code} for dc ${this._dcId}`);
this._startReconnecting(e);
}
return;
}
else {
this._log.error("Unhandled error while receiving data");
this._log.error(e);
this._startReconnecting(e);
return;
}
}
try {
await this._processMessage(message);
}
catch (e) {
this._log.error("Unhandled error while receiving data");
this._log.error(e);
}
}
}
// Response Handlers
/**
* Adds the given message to the list of messages that must be
* acknowledged and dispatches control to different ``_handle_*``
* method based on its type.
* @param message
* @returns {Promise<void>}
* @private
*/
async _processMessage(message) {
this._pendingAck.add(message.msgId);
message.obj = await message.obj;
let handler = this._handlers[message.obj.CONSTRUCTOR_ID.toString()];
if (!handler) {
handler = this._handleUpdate.bind(this);
}
await handler(message);
}
/**
* Pops the states known to match the given ID from pending messages.
* This method should be used when the response isn't specific.
* @param msgId
* @returns {*[]}
* @private
*/
_popStates(msgId) {
let state = this._pendingState.get(msgId.toString());
if (state) {
this._pendingState.delete(msgId.toString());
return [state];
}
const toPop = [];
for (const state of this._pendingState.values()) {
if (state.containerId && state.containerId.equals(msgId)) {
toPop.push(state.msgId);
}
}
if (toPop.length) {
const temp = [];
for (const x of toPop) {
temp.push(this._pendingState.get(x.toString()));
this._pendingState.delete(x.toString());
}
return temp;
}
for (const ack of this._lastAcks) {
if (ack.msgId === msgId) {
return [ack];
}
}
return [];
}
/**
* Handles the result for Remote Procedure Calls:
* rpc_result#f35c6d01 req_msg_id:long result:bytes = RpcResult;
* This is where the future results for sent requests are set.
* @param message
* @returns {Promise<void>}
* @private
*/
_handleRPCResult(message) {
const RPCResult = message.obj;
const state = this._pendingState.get(RPCResult.reqMsgId.toString());
if (state) {
this._pendingState.delete(RPCResult.reqMsgId.toString());
}
this._log.debug(`Handling RPC result for message ${RPCResult.reqMsgId}`);
if (!state) {
// TODO We should not get responses to things we never sent
// However receiving a File() with empty bytes is "common".
// See #658, #759 and #958. They seem to happen in a container
// which contain the real response right after.
try {
const reader = new extensions_1.BinaryReader(RPCResult.body);
if (!(reader.tgReadObject() instanceof tl_1.Api.upload.File)) {
throw new Error("Not an upload.File");
}
}
catch (e) {
this._log.error(e);
if (e instanceof errors_1.TypeNotFoundError) {
this._log.info(`Received response without parent request: ${RPCResult.body}`);
return;
}
else {
throw e;
}
}
return;
}
if (RPCResult.error && state.msgId) {
const error = (0, errors_1.RPCMessageToError)(RPCResult.error, state.request);
this._sendQueue.append(new RequestState_1.RequestState(new tl_1.Api.MsgsAck({ msgIds: [state.msgId] })));
state.reject(error);
}
else {
const reader = new extensions_1.BinaryReader(RPCResult.body);
const read = state.request.readResult(reader);
state.resolve(read);
}
}
/**
* Processes the inner messages of a container with many of them:
* msg_container#73f1f8dc messages:vector<%Message> = MessageContainer;
* @param message
* @returns {Promise<void>}
* @private
*/
async _handleContainer(message) {
this._log.debug("Handling container");
for (const innerMessage of message.obj.messages) {
await this._processMessage(innerMessage);
}
}
/**
* Unpacks the data from a gzipped object and processes it:
* gzip_packed#3072cfa1 packed_data:bytes = Object;
* @param message
* @returns {Promise<void>}
* @private
*/
async _handleGzipPacked(message) {
this._log.debug("Handling gzipped data");
const reader = new extensions_1.BinaryReader(message.obj.data);
message.obj = reader.tgReadObject();
await this._processMessage(message);
}
async _handleUpdate(message) {
if (message.obj.SUBCLASS_OF_ID !== 0x8af52aac) {
// crc32(b'Updates')
this._log.warn(`Note: ${message.obj.className} is not an update, not dispatching it`);
return;
}
this._log.debug("Handling update " + message.obj.className);
if (this._updateCallback) {
this._updateCallback(this._client, message.obj);
}
}
/**
* Handles pong results, which don't come inside a ``RPCResult``
* but are still sent through a request:
* pong#347773c5 msg_id:long ping_id:long = Pong;
* @param message
* @returns {Promise<void>}
* @private
*/
async _handlePong(message) {
const pong = message.obj;
this._log.debug(`Handling pong for message ${pong.msgId}`);
const state = this._pendingState.get(pong.msgId.toString());
this._pendingState.delete(pong.msgId.toString());
// Todo Check result
if (state) {
state.resolve(pong);
}
}
/**
* Corrects the currently used server salt to use the right value
* before enqueuing the rejected message to be re-sent:
* bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int
* error_code:int new_server_salt:long = BadMsgNotification;
* @param message
* @returns {Promise<void>}
* @private
*/
async _handleBadServerSalt(message) {
const badSalt = message.obj;
this._log.debug(`Handling bad salt for message ${badSalt.badMsgId}`);
this._state.salt = badSalt.newServerSalt;
const states = this._popStates(badSalt.badMsgId);
this._sendQueue.extend(states);
this._log.debug(`${states.length} message(s) will be resent`);
}
/**
* Adjusts the current state to be correct based on the
* received bad message notification whenever possible:
* bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int
* error_code:int = BadMsgNotification;
* @param message
* @returns {Promise<void>}
* @private
*/
async _handleBadNotification(message) {
const badMsg = message.obj;
const states = this._popStates(badMsg.badMsgId);
this._log.debug(`Handling bad msg ${JSON.stringify(badMsg)}`);
if ([16, 17].includes(badMsg.errorCode)) {
// Sent msg_id too low or too high (respectively).
// Use the current msg_id to determine the right time offset.
const to = this._state.updateTimeOffset((0, big_integer_1.default)(message.msgId));
this._log.info(`System clock is wrong, set time offset to ${to}s`);
}
else if (badMsg.errorCode === 32) {
// msg_seqno too low, so just pump it up by some "large" amount
// TODO A better fix would be to start with a new fresh session ID
this._state._sequence += 64;
}
else if (badMsg.errorCode === 33) {
// msg_seqno too high never seems to happen but just in case
this._state._sequence -= 16;
}
else {
for (const state of states) {
state.reject(new errors_1.BadMessageError(state.request, badMsg.errorCode));
}
return;
}
// Messages are to be re-sent once we've corrected the issue
this._sendQueue.extend(states);
this._log.debug(`${states.length} messages will be resent due to bad msg`);
}
/**
* Updates the current status with the received detailed information:
* msg_detailed_info#276d3ec6 msg_id:long answer_msg_id:long
* bytes:int status:int = MsgDetailedInfo;
* @param message
* @returns {Promise<void>}
* @private
*/
async _handleDetailedInfo(message) {
// TODO https://goo.gl/VvpCC6
const msgId = message.obj.answerMsgId;
this._log.debug(`Handling detailed info for message ${msgId}`);
this._pendingAck.add(msgId);
}
/**
* Updates the current status with the received detailed information:
* msg_new_detailed_info#809db6df answer_msg_id:long
* bytes:int status:int = MsgDetailedInfo;
* @param message
* @returns {Promise<void>}
* @private
*/
async _handleNewDetailedInfo(message) {
// TODO https://goo.gl/VvpCC6
const msgId = message.obj.answerMsgId;
this._log.debug(`Handling new detailed info for message ${msgId}`);
this._pendingAck.add(msgId);
}
/**
* Updates the current status with the received session information:
* new_session_created#9ec20908 first_msg_id:long unique_id:long
* server_salt:long = NewSession;
* @param message
* @returns {Promise<void>}
* @private
*/
async _handleNewSessionCreated(message) {
// TODO https://goo.gl/LMyN7A
this._log.debug("Handling new session created");
this._state.salt = message.obj.serverSalt;
}
/**
* Handles a server acknowledge about our messages. Normally
* these can be ignored except in the case of ``auth.logOut``:
*
* auth.logOut#5717da40 = Bool;
*
* Telegram doesn't seem to send its result so we need to confirm
* it manually. No other request is known to have this behaviour.
* Since the ID of sent messages consisting of a container is
* never returned (unless on a bad notification), this method
* also removes containers messages when any of their inner
* messages are acknowledged.
* @param message
* @returns {Promise<void>}
* @private
*/
async _handleAck(message) {
const ack = message.obj;
this._log.debug(`Handling acknowledge for ${ack.msgIds}`);
for (const msgId of ack.msgIds) {
const state = this._pendingState.get(msgId);
if (state && state.request instanceof tl_1.Api.auth.LogOut) {
this._pendingState.delete(msgId);
state.resolve(true);
}
}
}
/**
* Handles future salt results, which don't come inside a
* ``rpc_result`` but are still sent through a request:
* future_salts#ae500895 req_msg_id:long now:int
* salts:vector<future_salt> = FutureSalts;
* @param message
* @returns {Promise<void>}
* @private
*/
async _handleFutureSalts(message) {
// TODO save these salts and automatically adjust to the
// correct one whenever the salt in use expires.
this._log.debug(`Handling future salts for message ${message.msgId}`);
const state = this._pendingState.get(message.msgId.toString());
if (state) {
this._pendingState.delete(message.msgId.toString());
state.resolve(message.obj);
}
}
/**
* Handles both :tl:`MsgsStateReq` and :tl:`MsgResendReq` by
* enqueuing a :tl:`MsgsStateInfo` to be sent at a later point.
* @param message
* @returns {Promise<void>}
* @private
*/
async _handleStateForgotten(message) {
this._sendQueue.append(new RequestState_1.RequestState(new tl_1.Api.MsgsStateInfo({
reqMsgId: message.msgId,
info: String.fromCharCode(1).repeat(message.obj.msgIds),
})));
}
/**
* Handles :tl:`MsgsAllInfo` by doing nothing (yet).
* @param message
* @returns {Promise<void>}
* @private
*/
async _handleMsgAll(message) { }
async _reconnect(lastError) {
this._log.debug("Closing current connection...");
await this._connection.disconnect();
this._cancelLoops();
this._reconnecting = false;
this._state.reset();
let attempt;
let ok = true;
for (attempt = 0; attempt < this._retries; attempt++) {
try {
await this._connect();
await (0, Helpers_1.sleep)(1000);
this._sendQueue.extend([...this._pendingState.values()]);
this._pendingState.clear();
if (this._autoReconnectCallback) {
this._autoReconnectCallback();
}
break;
}
catch (err) {
if (attempt == this._retries - 1) {
ok = false;
}
if (err instanceof errors_1.InvalidBufferError) {
if (err.code === 404) {
this._log.warn(`Broken authorization key for dc ${this._dcId}; resetting`);
await this.authKey.setKey(undefined);
if (this._authKeyCallback) {
await this._authKeyCallback(undefined);
}
ok = false;
break;
}
else {
// this happens sometimes when telegram is having some internal issues.
// since the data we sent and received is probably wrong now.
this._log.warn(`Invalid buffer ${err.code} for dc ${this._dcId}`);
}
}
this._log.error(`Unexpected exception reconnecting on attempt ${attempt}`);
await (0, Helpers_1.sleep)(this._delay);
lastError = err;
}
}
if (!ok) {
this._log.error(`Automatic reconnection failed ${attempt} time(s)`);
await this._disconnect(lastError ? lastError : undefined);
}
}
async _tryConnect(attempt) {
try {
this._log.debug(`Connection attempt ${attempt}...`);
await this._connection.connect();
this._log.debug("Connection success!");
return true;
}
catch (err) {
this._log.warn(`Attempt ${attempt} at connecting failed`);
if (this._log.canSend(Logger_1.LogLevel.ERROR)) {
console.error(err);
}
await (0, Helpers_1.sleep)(this._delay);
return false;
}
}
async _tryGenAuthKey(attempt) {
const plain = new MTProtoPlainSender_1.MTProtoPlainSender(this._connection, this._log);
try {
this._log.debug(`New auth_key attempt ${attempt}...`);
this._log.debug("New auth_key attempt ...");
const res = await (0, Authenticator_1.doAuthentication)(plain, this._log);
this._log.debug("Generated new auth_key successfully");
await this.authKey.setKey(res.authKey);
this._state.timeOffset = res.timeOffset;
if (this._authKeyCallback) {
await this._authKeyCallback(this.authKey, this._dcId);
}
this._log.debug("auth_key generation success!");
return true;
}
catch (err) {
this._log.warn(`Attempt ${attempt} at generating auth key failed`);
if (this._log.canSend(Logger_1.LogLevel.ERROR)) {
console.error(err);
}
return false;
}
}
_startReconnecting(error) {
this._log.info(`Starting reconnect...`);
if (this._userConnected && !this._reconnecting) {
this._reconnecting = true;
this._reconnect(error);
}
}
}
exports.MTProtoSender = MTProtoSender;
MTProtoSender.DEFAULT_OPTIONS = {
logger: null,
retries: Infinity,
delay: 2000,
autoReconnect: true,
connectTimeout: null,
authKeyCallback: null,
updateCallback: null,
autoReconnectCallback: null,
isMainSender: null,
senderCallback: null,
onConnectionBreak: undefined,
securityChecks: true,
};