|
@@ -1,35 +1,3 @@
|
|
|
-const MtProtoPlainSender = require('./MTProtoPlainSender')
|
|
|
-const MTProtoState = require('./MTProtoState')
|
|
|
-const Helpers = require('../Helpers')
|
|
|
-const AuthKey = require('../crypto/AuthKey')
|
|
|
-const doAuthentication = require('./Authenticator')
|
|
|
-const RPCResult = require('../tl/core/RPCResult')
|
|
|
-const MessageContainer = require('../tl/core/MessageContainer')
|
|
|
-const GZIPPacked = require('../tl/core/GZIPPacked')
|
|
|
-const RequestState = require('./RequestState')
|
|
|
-const { MsgsAck, upload, MsgsStateInfo, Pong } = require('../tl').constructors
|
|
|
-const MessagePacker = require('../extensions/MessagePacker')
|
|
|
-const BinaryReader = require('../extensions/BinaryReader')
|
|
|
-const { UpdateConnectionState } = require('./index')
|
|
|
-const { BadMessageError } = require('../errors/Common')
|
|
|
-const {
|
|
|
- BadServerSalt,
|
|
|
- BadMsgNotification,
|
|
|
- MsgDetailedInfo,
|
|
|
- MsgNewDetailedInfo,
|
|
|
- NewSessionCreated,
|
|
|
- FutureSalts,
|
|
|
- MsgsStateReq,
|
|
|
- MsgResendReq,
|
|
|
- MsgsAllInfo,
|
|
|
-} = require('../tl').constructors
|
|
|
-const { SecurityError } = require('../errors/Common')
|
|
|
-const { InvalidBufferError } = require('../errors/Common')
|
|
|
-const { LogOut } = require('../tl').requests.auth
|
|
|
-const { RPCMessageToError } = require('../errors')
|
|
|
-const { TypeNotFoundError } = require('../errors/Common')
|
|
|
-
|
|
|
-
|
|
|
/**
|
|
|
* MTProto Mobile Protocol sender
|
|
|
* (https://core.telegram.org/mtproto/description)
|
|
@@ -43,7 +11,38 @@ const { TypeNotFoundError } = require('../errors/Common')
|
|
|
* A new authorization key will be generated on connection if no other
|
|
|
* key exists yet.
|
|
|
*/
|
|
|
-class MTProtoSender {
|
|
|
+import {AuthKey} from "../crypto/AuthKey";
|
|
|
+import {MTProtoState} from "./MTProtoState";
|
|
|
+import {BinaryReader, MessagePacker} from "../extensions";
|
|
|
+import {GZIPPacked, MessageContainer, RPCResult, TLMessage} from "../tl/core";
|
|
|
+import {Api} from "../tl";
|
|
|
+import bigInt from 'big-integer'
|
|
|
+import {sleep} from "../Helpers";
|
|
|
+import {RequestState} from "./RequestState";
|
|
|
+import {doAuthentication} from "./Authenticator";
|
|
|
+import {MTProtoPlainSender} from "./MTProtoPlainSender";
|
|
|
+import {BadMessageError, InvalidBufferError, RPCMessageToError, SecurityError, TypeNotFoundError} from "../errors";
|
|
|
+import {UpdateConnectionState} from "./index";
|
|
|
+
|
|
|
+interface DEFAULT_OPTIONS {
|
|
|
+ logger: any,
|
|
|
+ retries: number,
|
|
|
+ delay: number,
|
|
|
+ autoReconnect: boolean,
|
|
|
+ connectTimeout: any,
|
|
|
+ authKeyCallback: any,
|
|
|
+ updateCallback?: any,
|
|
|
+ autoReconnectCallback?: any,
|
|
|
+ isMainSender: boolean,
|
|
|
+ dcId: number,
|
|
|
+ senderCallback?: any,
|
|
|
+}
|
|
|
+
|
|
|
+{
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+export class MTProtoSender {
|
|
|
static DEFAULT_OPTIONS = {
|
|
|
logger: null,
|
|
|
retries: Infinity,
|
|
@@ -55,26 +54,50 @@ class MTProtoSender {
|
|
|
autoReconnectCallback: null,
|
|
|
isMainSender: null,
|
|
|
senderCallback: null,
|
|
|
- }
|
|
|
+ };
|
|
|
+ private _connection: any;
|
|
|
+ private _log: any;
|
|
|
+ private _dcId: number;
|
|
|
+ private _retries: number;
|
|
|
+ private _delay: number;
|
|
|
+ private _connectTimeout: null;
|
|
|
+ private _autoReconnect: boolean;
|
|
|
+ private _authKeyCallback: any;
|
|
|
+ private _updateCallback: any;
|
|
|
+ private _autoReconnectCallback?: any;
|
|
|
+ private _senderCallback: any;
|
|
|
+ private _isMainSender: boolean;
|
|
|
+ private _userConnected: boolean;
|
|
|
+ private _reconnecting: boolean;
|
|
|
+ private _disconnected: boolean;
|
|
|
+ private _sendLoopHandle: any;
|
|
|
+ private _recvLoopHandle: any;
|
|
|
+ private authKey: AuthKey;
|
|
|
+ private _state: MTProtoState;
|
|
|
+ private _sendQueue: MessagePacker;
|
|
|
+ private _pendingState: Map<string, RequestState>;
|
|
|
+ private _pendingAck: Set<any>;
|
|
|
+ private _lastAcks: any[];
|
|
|
+ private _handlers: any;
|
|
|
|
|
|
/**
|
|
|
* @param authKey
|
|
|
* @param opts
|
|
|
*/
|
|
|
- constructor(authKey, opts) {
|
|
|
- const args = { ...MTProtoSender.DEFAULT_OPTIONS, ...opts }
|
|
|
- this._connection = null
|
|
|
- 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
|
|
|
+ constructor(authKey: undefined | AuthKey, opts: DEFAULT_OPTIONS) {
|
|
|
+ const args = {...MTProtoSender.DEFAULT_OPTIONS, ...opts};
|
|
|
+ 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;
|
|
|
|
|
|
/**
|
|
|
* Whether the user has explicitly connected or disconnected.
|
|
@@ -84,67 +107,75 @@ class MTProtoSender {
|
|
|
* be cleared but on explicit user disconnection all the
|
|
|
* pending futures should be cancelled.
|
|
|
*/
|
|
|
- this._user_connected = false
|
|
|
- this._reconnecting = false
|
|
|
- this._disconnected = true
|
|
|
+ this._userConnected = false;
|
|
|
+ this._reconnecting = false;
|
|
|
+ this._disconnected = true;
|
|
|
|
|
|
/**
|
|
|
* We need to join the loops upon disconnection
|
|
|
*/
|
|
|
- this._send_loop_handle = null
|
|
|
- this._recv_loop_handle = null
|
|
|
+ this._sendLoopHandle = null;
|
|
|
+ this._recvLoopHandle = null;
|
|
|
|
|
|
/**
|
|
|
* Preserving the references of the AuthKey and state is important
|
|
|
*/
|
|
|
- this.authKey = authKey || new AuthKey()
|
|
|
- this._state = new MTProtoState(this.authKey, this._log)
|
|
|
+ this.authKey = authKey || new AuthKey();
|
|
|
+ this._state = new MTProtoState(this.authKey, this._log);
|
|
|
|
|
|
/**
|
|
|
* Outgoing messages are put in a queue and sent in a batch.
|
|
|
* Note that here we're also storing their ``_RequestState``.
|
|
|
*/
|
|
|
- this._send_queue = new MessagePacker(this._state, this._log)
|
|
|
+ this._sendQueue = new MessagePacker(this._state, this._log);
|
|
|
|
|
|
/**
|
|
|
* Sent states are remembered until a response is received.
|
|
|
*/
|
|
|
- this._pending_state = {}
|
|
|
+ this._pendingState = new Map<string, any>();
|
|
|
|
|
|
/**
|
|
|
* Responses must be acknowledged, and we can also batch these.
|
|
|
*/
|
|
|
- this._pending_ack = new Set()
|
|
|
+ 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._last_acks = []
|
|
|
+ this._lastAcks = [];
|
|
|
|
|
|
/**
|
|
|
* Jump table from response ID to method that handles it
|
|
|
*/
|
|
|
|
|
|
this._handlers = {
|
|
|
- [RPCResult.CONSTRUCTOR_ID]: this._handleRPCResult.bind(this),
|
|
|
- [MessageContainer.CONSTRUCTOR_ID]: this._handleContainer.bind(this),
|
|
|
- [GZIPPacked.CONSTRUCTOR_ID]: this._handleGzipPacked.bind(this),
|
|
|
- [Pong.CONSTRUCTOR_ID]: this._handlePong.bind(this),
|
|
|
- [BadServerSalt.CONSTRUCTOR_ID]: this._handleBadServerSalt.bind(this),
|
|
|
- [BadMsgNotification.CONSTRUCTOR_ID]: this._handleBadNotification.bind(this),
|
|
|
- [MsgDetailedInfo.CONSTRUCTOR_ID]: this._handleDetailedInfo.bind(this),
|
|
|
- [MsgNewDetailedInfo.CONSTRUCTOR_ID]: this._handleNewDetailedInfo.bind(this),
|
|
|
- [NewSessionCreated.CONSTRUCTOR_ID]: this._handleNewSessionCreated.bind(this),
|
|
|
- [MsgsAck.CONSTRUCTOR_ID]: this._handleAck.bind(this),
|
|
|
- [FutureSalts.CONSTRUCTOR_ID]: this._handleFutureSalts.bind(this),
|
|
|
- [MsgsStateReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this),
|
|
|
- [MsgResendReq.CONSTRUCTOR_ID]: this._handleStateForgotten.bind(this),
|
|
|
- [MsgsAllInfo.CONSTRUCTOR_ID]: this._handleMsgAll.bind(this),
|
|
|
+ [RPCResult.CONSTRUCTOR_ID.toString()]: this._handleRPCResult.bind(this),
|
|
|
+ [MessageContainer.CONSTRUCTOR_ID.toString()]: this._handleContainer.bind(this),
|
|
|
+ [GZIPPacked.CONSTRUCTOR_ID.toString()]: this._handleGzipPacked.bind(this),
|
|
|
+ [Api.Pong.CONSTRUCTOR_ID.toString()]: this._handlePong.bind(this),
|
|
|
+ [Api.BadServerSalt.CONSTRUCTOR_ID.toString()]: this._handleBadServerSalt.bind(this),
|
|
|
+ [Api.BadMsgNotification.CONSTRUCTOR_ID.toString()]: this._handleBadNotification.bind(this),
|
|
|
+ [Api.MsgDetailedInfo.CONSTRUCTOR_ID.toString()]: this._handleDetailedInfo.bind(this),
|
|
|
+ [Api.MsgNewDetailedInfo.CONSTRUCTOR_ID.toString()]: this._handleNewDetailedInfo.bind(this),
|
|
|
+ [Api.NewSessionCreated.CONSTRUCTOR_ID.toString()]: this._handleNewSessionCreated.bind(this),
|
|
|
+ [Api.MsgsAck.CONSTRUCTOR_ID.toString()]: this._handleAck.bind(this),
|
|
|
+ [Api.FutureSalts.CONSTRUCTOR_ID.toString()]: this._handleFutureSalts.bind(this),
|
|
|
+ [Api.MsgsStateReq.CONSTRUCTOR_ID.toString()]: this._handleStateForgotten.bind(this),
|
|
|
+ [Api.MsgResendReq.CONSTRUCTOR_ID.toString()]: this._handleStateForgotten.bind(this),
|
|
|
+ [Api.MsgsAllInfo.CONSTRUCTOR_ID.toString()]: this._handleMsgAll.bind(this),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ set dcId(dcId: number) {
|
|
|
+ this._dcId = dcId;
|
|
|
+ }
|
|
|
+
|
|
|
+ get dcId() {
|
|
|
+ return this._dcId;
|
|
|
+ }
|
|
|
+
|
|
|
// Public API
|
|
|
|
|
|
/**
|
|
@@ -153,34 +184,34 @@ class MTProtoSender {
|
|
|
* @param eventDispatch {function}
|
|
|
* @returns {Promise<boolean>}
|
|
|
*/
|
|
|
- async connect(connection, eventDispatch=null) {
|
|
|
- if (this._user_connected) {
|
|
|
- this._log.info('User is already connected!')
|
|
|
+ async connect(connection: any, eventDispatch?: any) {
|
|
|
+ if (this._userConnected) {
|
|
|
+ this._log.info('User is already connected!');
|
|
|
return false
|
|
|
}
|
|
|
- this._connection = connection
|
|
|
+ this._connection = connection;
|
|
|
|
|
|
- const retries = this._retries
|
|
|
+ const retries = this._retries;
|
|
|
|
|
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
|
try {
|
|
|
- await this._connect()
|
|
|
+ await this._connect();
|
|
|
break
|
|
|
} catch (e) {
|
|
|
- if (attempt===0 && eventDispatch!==null){
|
|
|
- eventDispatch({ update: new UpdateConnectionState(-1) })
|
|
|
+ if (attempt === 0 && eventDispatch) {
|
|
|
+ eventDispatch({update: new UpdateConnectionState(-1)})
|
|
|
}
|
|
|
- console.dir(e)
|
|
|
+ console.dir(e);
|
|
|
|
|
|
- this._log.error('WebSocket connection failed attempt : '+(attempt+1))
|
|
|
- await Helpers.sleep(this._delay)
|
|
|
+ this._log.error('WebSocket connection failed attempt : ' + (attempt + 1));
|
|
|
+ await sleep(this._delay)
|
|
|
}
|
|
|
}
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
isConnected() {
|
|
|
- return this._user_connected
|
|
|
+ return this._userConnected
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -217,14 +248,14 @@ class MTProtoSender {
|
|
|
* @param request
|
|
|
* @returns {RequestState}
|
|
|
*/
|
|
|
- send(request) {
|
|
|
- if (!this._user_connected) {
|
|
|
+ send(request: Api.AnyRequest) {
|
|
|
+ if (!this._userConnected) {
|
|
|
throw new Error('Cannot send requests while disconnected')
|
|
|
}
|
|
|
//CONTEST
|
|
|
- const state = new RequestState(request)
|
|
|
- this._send_queue.append(state)
|
|
|
- return state.promise
|
|
|
+ const state = new RequestState(request);
|
|
|
+ this._sendQueue.append(state);
|
|
|
+ return state.promise;
|
|
|
/*
|
|
|
if (!Helpers.isArrayLike(request)) {
|
|
|
const state = new RequestState(request)
|
|
@@ -243,18 +274,19 @@ class MTProtoSender {
|
|
|
* @private
|
|
|
*/
|
|
|
async _connect() {
|
|
|
- this._log.info('Connecting to {0}...'.replace('{0}', this._connection))
|
|
|
- await this._connection.connect()
|
|
|
- this._log.debug('Connection success!')
|
|
|
+
|
|
|
+ this._log.info('Connecting to {0}...'.replace('{0}', this._connection.toString()));
|
|
|
+ await this._connection.connect();
|
|
|
+ this._log.debug('Connection success!');
|
|
|
//process.exit(0)
|
|
|
if (!this.authKey.getKey()) {
|
|
|
- const plain = new MtProtoPlainSender(this._connection, this._log)
|
|
|
- this._log.debug('New auth_key attempt ...')
|
|
|
- const res = await doAuthentication(plain, this._log)
|
|
|
- this._log.debug('Generated new auth_key successfully')
|
|
|
- await this.authKey.setKey(res.authKey)
|
|
|
+ const plain = new MTProtoPlainSender(this._connection, this._log);
|
|
|
+ this._log.debug('New auth_key attempt ...');
|
|
|
+ const res = await doAuthentication(plain, this._log);
|
|
|
+ this._log.debug('Generated new auth_key successfully');
|
|
|
+ await this.authKey.setKey(res.authKey);
|
|
|
|
|
|
- this._state.time_offset = res.timeOffset
|
|
|
+ this._state.timeOffset = res.timeOffset;
|
|
|
|
|
|
/**
|
|
|
* This is *EXTREMELY* important since we don't control
|
|
@@ -268,14 +300,14 @@ class MTProtoSender {
|
|
|
} else {
|
|
|
this._log.debug('Already have an auth key ...')
|
|
|
}
|
|
|
- this._user_connected = true
|
|
|
- this._reconnecting = false
|
|
|
+ this._userConnected = true;
|
|
|
+ this._reconnecting = false;
|
|
|
|
|
|
- this._log.debug('Starting send loop')
|
|
|
- this._send_loop_handle = this._sendLoop()
|
|
|
+ this._log.debug('Starting send loop');
|
|
|
+ this._sendLoopHandle = this._sendLoop();
|
|
|
|
|
|
- this._log.debug('Starting receive loop')
|
|
|
- this._recv_loop_handle = this._recvLoop()
|
|
|
+ this._log.debug('Starting receive loop');
|
|
|
+ this._recvLoopHandle = this._recvLoop();
|
|
|
|
|
|
// _disconnected only completes after manual disconnection
|
|
|
// or errors after which the sender cannot continue such
|
|
@@ -286,15 +318,15 @@ class MTProtoSender {
|
|
|
|
|
|
async _disconnect(error = null) {
|
|
|
if (this._connection === null) {
|
|
|
- this._log.info('Not disconnecting (already have no connection)')
|
|
|
+ this._log.info('Not disconnecting (already have no connection)');
|
|
|
return
|
|
|
}
|
|
|
- if (this._updateCallback){
|
|
|
+ if (this._updateCallback) {
|
|
|
this._updateCallback(-1)
|
|
|
}
|
|
|
- this._log.info('Disconnecting from %s...'.replace('%s', this._connection.toString()))
|
|
|
- this._user_connected = false
|
|
|
- this._log.debug('Closing current connection...')
|
|
|
+ this._log.info('Disconnecting from %s...'.replace('%s', this._connection.toString()));
|
|
|
+ this._userConnected = false;
|
|
|
+ this._log.debug('Closing current connection...');
|
|
|
await this._connection.disconnect()
|
|
|
}
|
|
|
|
|
@@ -306,20 +338,20 @@ class MTProtoSender {
|
|
|
* @private
|
|
|
*/
|
|
|
async _sendLoop() {
|
|
|
- this._send_queue = new MessagePacker(this._state, this._log)
|
|
|
-
|
|
|
- while (this._user_connected && !this._reconnecting) {
|
|
|
- if (this._pending_ack.size) {
|
|
|
- const ack = new RequestState(new MsgsAck({ msgIds: Array(...this._pending_ack) }))
|
|
|
- this._send_queue.append(ack)
|
|
|
- this._last_acks.push(ack)
|
|
|
- this._pending_ack.clear()
|
|
|
+ this._sendQueue = new MessagePacker(this._state, this._log);
|
|
|
+
|
|
|
+ while (this._userConnected && !this._reconnecting) {
|
|
|
+ if (this._pendingAck.size) {
|
|
|
+ const ack = new RequestState(new Api.MsgsAck({msgIds: Array(...this._pendingAck)}));
|
|
|
+ this._sendQueue.append(ack);
|
|
|
+ this._lastAcks.push(ack);
|
|
|
+ this._pendingAck.clear()
|
|
|
}
|
|
|
- this._log.debug('Waiting for messages to send...'+this._reconnecting)
|
|
|
+ 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._send_queue.get()
|
|
|
+ const res = await this._sendQueue.get();
|
|
|
|
|
|
if (this._reconnecting) {
|
|
|
return
|
|
@@ -328,28 +360,28 @@ class MTProtoSender {
|
|
|
if (!res) {
|
|
|
continue
|
|
|
}
|
|
|
- let data = res.data
|
|
|
- const batch = res.batch
|
|
|
- this._log.debug(`Encrypting ${batch.length} message(s) in ${data.length} bytes for sending`)
|
|
|
+ let data = res.data;
|
|
|
+ const batch = res.batch;
|
|
|
+ this._log.debug(`Encrypting ${batch.length} message(s) in ${data.length} bytes for sending`);
|
|
|
|
|
|
- data = await this._state.encryptMessageData(data)
|
|
|
+ data = await this._state.encryptMessageData(data);
|
|
|
|
|
|
try {
|
|
|
await this._connection.send(data)
|
|
|
} catch (e) {
|
|
|
- this._log.error(e)
|
|
|
- this._log.info('Connection closed while sending data')
|
|
|
+ this._log.error(e);
|
|
|
+ this._log.info('Connection closed while sending data');
|
|
|
return
|
|
|
}
|
|
|
for (const state of batch) {
|
|
|
if (!Array.isArray(state)) {
|
|
|
if (state.request.classType === 'request') {
|
|
|
- this._pending_state[state.msgId] = state
|
|
|
+ this._pendingState.set(state.msgId.toString(), state)
|
|
|
}
|
|
|
} else {
|
|
|
for (const s of state) {
|
|
|
if (s.request.classType === 'request') {
|
|
|
- this._pending_state[s.msgId] = s
|
|
|
+ this._pendingState.set(s.msgId.toString(), s)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -359,18 +391,18 @@ class MTProtoSender {
|
|
|
}
|
|
|
|
|
|
async _recvLoop() {
|
|
|
- let body
|
|
|
- let message
|
|
|
+ let body;
|
|
|
+ let message;
|
|
|
|
|
|
- while (this._user_connected && !this._reconnecting) {
|
|
|
+ while (this._userConnected && !this._reconnecting) {
|
|
|
// this._log.debug('Receiving items from the network...');
|
|
|
- this._log.debug('Receiving items from the network...')
|
|
|
+ this._log.debug('Receiving items from the network...');
|
|
|
try {
|
|
|
body = await this._connection.recv()
|
|
|
} catch (e) {
|
|
|
// this._log.info('Connection closed while receiving data');
|
|
|
- this._log.warn('Connection closed while receiving data')
|
|
|
- this._startReconnect()
|
|
|
+ this._log.warn('Connection closed while receiving data');
|
|
|
+ this._startReconnect();
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
@@ -378,19 +410,19 @@ class MTProtoSender {
|
|
|
} catch (e) {
|
|
|
if (e instanceof TypeNotFoundError) {
|
|
|
// Received object which we don't know how to deserialize
|
|
|
- this._log.info(`Type ${e.invalidConstructorId} not found, remaining data ${e.remaining}`)
|
|
|
+ this._log.info(`Type ${e.invalidConstructorId} not found, remaining data ${e.remaining}`);
|
|
|
continue
|
|
|
} else if (e instanceof 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}`)
|
|
|
+ this._log.warn(`Security error while unpacking a received message: ${e}`);
|
|
|
continue
|
|
|
} else if (e instanceof InvalidBufferError) {
|
|
|
- this._log.info('Broken authorization key; resetting')
|
|
|
- if (this._updateCallback && this._isMainSender){
|
|
|
+ this._log.info('Broken authorization key; resetting');
|
|
|
+ if (this._updateCallback && this._isMainSender) {
|
|
|
// 0 == broken
|
|
|
this._updateCallback(0)
|
|
|
- } else if (this._senderCallback && !this._isMainSender){
|
|
|
+ } else if (this._senderCallback && !this._isMainSender) {
|
|
|
// Deletes the current sender from the object
|
|
|
this._senderCallback(this._dcId)
|
|
|
}
|
|
@@ -406,18 +438,18 @@ class MTProtoSender {
|
|
|
*/
|
|
|
return
|
|
|
} else {
|
|
|
- this._log.error('Unhandled error while receiving data')
|
|
|
- this._log.error(e)
|
|
|
- console.log(e)
|
|
|
- this._startReconnect()
|
|
|
+ this._log.error('Unhandled error while receiving data');
|
|
|
+ this._log.error(e);
|
|
|
+ console.log(e);
|
|
|
+ this._startReconnect();
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
try {
|
|
|
await this._processMessage(message)
|
|
|
} catch (e) {
|
|
|
- this._log.error('Unhandled error while receiving data')
|
|
|
- console.log(e)
|
|
|
+ this._log.error('Unhandled error while receiving data');
|
|
|
+ console.log(e);
|
|
|
this._log.error(e)
|
|
|
}
|
|
|
}
|
|
@@ -433,11 +465,11 @@ class MTProtoSender {
|
|
|
* @returns {Promise<void>}
|
|
|
* @private
|
|
|
*/
|
|
|
- async _processMessage(message) {
|
|
|
- this._pending_ack.add(message.msgId)
|
|
|
+ async _processMessage(message: TLMessage) {
|
|
|
+ this._pendingAck.add(message.msgId);
|
|
|
// eslint-disable-next-line require-atomic-updates
|
|
|
- message.obj = await message.obj
|
|
|
- let handler = this._handlers[message.obj.CONSTRUCTOR_ID]
|
|
|
+ message.obj = await message.obj;
|
|
|
+ let handler = this._handlers[message.obj.CONSTRUCTOR_ID.toString()];
|
|
|
if (!handler) {
|
|
|
handler = this._handleUpdate.bind(this)
|
|
|
}
|
|
@@ -452,31 +484,31 @@ class MTProtoSender {
|
|
|
* @returns {*[]}
|
|
|
* @private
|
|
|
*/
|
|
|
- _popStates(msgId) {
|
|
|
- let state = this._pending_state[msgId]
|
|
|
+ _popStates(msgId: bigInt.BigInteger) {
|
|
|
+ let state = this._pendingState.get(msgId.toString());
|
|
|
if (state) {
|
|
|
- delete this._pending_state[msgId]
|
|
|
+ this._pendingState.delete(msgId.toString());
|
|
|
return [state]
|
|
|
}
|
|
|
|
|
|
- const toPop = []
|
|
|
+ const toPop = [];
|
|
|
|
|
|
- for (state of Object.values(this._pending_state)) {
|
|
|
+ for (const state of Object.values(this._pendingState)) {
|
|
|
if (state.containerId && state.containerId.equals(msgId)) {
|
|
|
toPop.push(state.msgId)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (toPop.length) {
|
|
|
- const temp = []
|
|
|
+ const temp = [];
|
|
|
for (const x of toPop) {
|
|
|
- temp.push(this._pending_state[x])
|
|
|
- delete this._pending_state[x]
|
|
|
+ temp.push(this._pendingState.get(x));
|
|
|
+ this._pendingState.delete(x);
|
|
|
}
|
|
|
return temp
|
|
|
}
|
|
|
|
|
|
- for (const ack of this._last_acks) {
|
|
|
+ for (const ack of this._lastAcks) {
|
|
|
if (ack.msgId === msgId) {
|
|
|
return [ack]
|
|
|
}
|
|
@@ -493,13 +525,13 @@ class MTProtoSender {
|
|
|
* @returns {Promise<void>}
|
|
|
* @private
|
|
|
*/
|
|
|
- _handleRPCResult(message) {
|
|
|
- const RPCResult = message.obj
|
|
|
- const state = this._pending_state[RPCResult.reqMsgId]
|
|
|
+ _handleRPCResult(message: TLMessage) {
|
|
|
+ const RPCResult = message.obj;
|
|
|
+ const state = this._pendingState.get(RPCResult.reqMsgId.toString());
|
|
|
if (state) {
|
|
|
- delete this._pending_state[RPCResult.reqMsgId]
|
|
|
+ this._pendingState.delete(RPCResult.reqMsgId.toString())
|
|
|
}
|
|
|
- this._log.debug(`Handling RPC result for message ${RPCResult.reqMsgId}`)
|
|
|
+ this._log.debug(`Handling RPC result for message ${RPCResult.reqMsgId}`);
|
|
|
|
|
|
if (!state) {
|
|
|
// TODO We should not get responses to things we never sent
|
|
@@ -507,28 +539,30 @@ class MTProtoSender {
|
|
|
// See #658, #759 and #958. They seem to happen in a container
|
|
|
// which contain the real response right after.
|
|
|
try {
|
|
|
- const reader = new BinaryReader(RPCResult.body)
|
|
|
- if (!(reader.tgReadObject() instanceof upload.File)) {
|
|
|
- throw new TypeNotFoundError('Not an upload.File')
|
|
|
+ const reader = new BinaryReader(RPCResult.body);
|
|
|
+ if (!(reader.tgReadObject() instanceof Api.upload.File)) {
|
|
|
+ throw new Error('Not an upload.File')
|
|
|
}
|
|
|
} catch (e) {
|
|
|
- this._log.error(e)
|
|
|
+ this._log.error(e);
|
|
|
if (e instanceof TypeNotFoundError) {
|
|
|
- this._log.info(`Received response without parent request: ${RPCResult.body}`)
|
|
|
+ this._log.info(`Received response without parent request: ${RPCResult.body}`);
|
|
|
return
|
|
|
} else {
|
|
|
throw e
|
|
|
}
|
|
|
}
|
|
|
+ return;
|
|
|
}
|
|
|
- if (RPCResult.error) {
|
|
|
+ if (RPCResult.error && state.msgId) {
|
|
|
// eslint-disable-next-line new-cap
|
|
|
- const error = RPCMessageToError(RPCResult.error, state.request)
|
|
|
- this._send_queue.append(new RequestState(new MsgsAck({ msgIds: [state.msgId] })))
|
|
|
+ const error = RPCMessageToError(RPCResult.error, state.request);
|
|
|
+ this._sendQueue.append(new RequestState(new Api.MsgsAck({msgIds: [state.msgId]})));
|
|
|
state.reject(error)
|
|
|
} else {
|
|
|
- const reader = new BinaryReader(RPCResult.body)
|
|
|
- const read = state.request.readResult(reader)
|
|
|
+ const reader = new BinaryReader(RPCResult.body);
|
|
|
+ const read = state.request.readResult(reader);
|
|
|
+ //console.log("patcfh goes here ?", read);
|
|
|
state.resolve(read)
|
|
|
}
|
|
|
}
|
|
@@ -540,8 +574,8 @@ class MTProtoSender {
|
|
|
* @returns {Promise<void>}
|
|
|
* @private
|
|
|
*/
|
|
|
- async _handleContainer(message) {
|
|
|
- this._log.debug('Handling container')
|
|
|
+ async _handleContainer(message: TLMessage) {
|
|
|
+ this._log.debug('Handling container');
|
|
|
for (const innerMessage of message.obj.messages) {
|
|
|
await this._processMessage(innerMessage)
|
|
|
}
|
|
@@ -554,20 +588,20 @@ class MTProtoSender {
|
|
|
* @returns {Promise<void>}
|
|
|
* @private
|
|
|
*/
|
|
|
- async _handleGzipPacked(message) {
|
|
|
- this._log.debug('Handling gzipped data')
|
|
|
- const reader = new BinaryReader(message.obj.data)
|
|
|
- message.obj = reader.tgReadObject()
|
|
|
+ async _handleGzipPacked(message: TLMessage) {
|
|
|
+ this._log.debug('Handling gzipped data');
|
|
|
+ const reader = new BinaryReader(message.obj.data);
|
|
|
+ message.obj = reader.tgReadObject();
|
|
|
await this._processMessage(message)
|
|
|
}
|
|
|
|
|
|
- async _handleUpdate(message) {
|
|
|
+ async _handleUpdate(message: TLMessage) {
|
|
|
if (message.obj.SUBCLASS_OF_ID !== 0x8af52aac) {
|
|
|
// crc32(b'Updates')
|
|
|
- this._log.warn(`Note: ${message.obj.className} is not an update, not dispatching it`)
|
|
|
+ this._log.warn(`Note: ${message.obj.className} is not an update, not dispatching it`);
|
|
|
return
|
|
|
}
|
|
|
- this._log.debug('Handling update ' + message.obj.className)
|
|
|
+ this._log.debug('Handling update ' + message.obj.className);
|
|
|
if (this._updateCallback) {
|
|
|
this._updateCallback(message.obj)
|
|
|
}
|
|
@@ -581,11 +615,11 @@ class MTProtoSender {
|
|
|
* @returns {Promise<void>}
|
|
|
* @private
|
|
|
*/
|
|
|
- async _handlePong(message) {
|
|
|
- const pong = message.obj
|
|
|
- this._log.debug(`Handling pong for message ${pong.msgId}`)
|
|
|
- const state = this._pending_state[pong.msgId]
|
|
|
- delete this._pending_state[pong.msgId]
|
|
|
+ async _handlePong(message: TLMessage) {
|
|
|
+ const pong = message.obj;
|
|
|
+ this._log.debug(`Handling pong for message ${pong.msgId}`);
|
|
|
+ const state = this._pendingState.get(pong.msgId);
|
|
|
+ this._pendingState.delete(pong.msgId);
|
|
|
|
|
|
// Todo Check result
|
|
|
if (state) {
|
|
@@ -602,12 +636,12 @@ class MTProtoSender {
|
|
|
* @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._send_queue.extend(states)
|
|
|
+ async _handleBadServerSalt(message: TLMessage) {
|
|
|
+ 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`)
|
|
|
}
|
|
|
|
|
@@ -620,14 +654,14 @@ class MTProtoSender {
|
|
|
* @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)}`)
|
|
|
+ async _handleBadNotification(message: TLMessage) {
|
|
|
+ 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(message.msgId)
|
|
|
+ const to = this._state.updateTimeOffset(bigInt(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
|
|
@@ -645,7 +679,7 @@ class MTProtoSender {
|
|
|
return
|
|
|
}
|
|
|
// Messages are to be re-sent once we've corrected the issue
|
|
|
- this._send_queue.extend(states)
|
|
|
+ this._sendQueue.extend(states);
|
|
|
this._log.debug(`${states.length} messages will be resent due to bad msg`)
|
|
|
}
|
|
|
|
|
@@ -657,11 +691,11 @@ class MTProtoSender {
|
|
|
* @returns {Promise<void>}
|
|
|
* @private
|
|
|
*/
|
|
|
- async _handleDetailedInfo(message) {
|
|
|
+ async _handleDetailedInfo(message: TLMessage) {
|
|
|
// TODO https://goo.gl/VvpCC6
|
|
|
- const msgId = message.obj.answerMsgId
|
|
|
- this._log.debug(`Handling detailed info for message ${msgId}`)
|
|
|
- this._pending_ack.add(msgId)
|
|
|
+ const msgId = message.obj.answerMsgId;
|
|
|
+ this._log.debug(`Handling detailed info for message ${msgId}`);
|
|
|
+ this._pendingAck.add(msgId)
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -672,11 +706,11 @@ class MTProtoSender {
|
|
|
* @returns {Promise<void>}
|
|
|
* @private
|
|
|
*/
|
|
|
- async _handleNewDetailedInfo(message) {
|
|
|
+ async _handleNewDetailedInfo(message: TLMessage) {
|
|
|
// TODO https://goo.gl/VvpCC6
|
|
|
- const msgId = message.obj.answerMsgId
|
|
|
- this._log.debug(`Handling new detailed info for message ${msgId}`)
|
|
|
- this._pending_ack.add(msgId)
|
|
|
+ const msgId = message.obj.answerMsgId;
|
|
|
+ this._log.debug(`Handling new detailed info for message ${msgId}`);
|
|
|
+ this._pendingAck.add(msgId)
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -687,9 +721,9 @@ class MTProtoSender {
|
|
|
* @returns {Promise<void>}
|
|
|
* @private
|
|
|
*/
|
|
|
- async _handleNewSessionCreated(message) {
|
|
|
+ async _handleNewSessionCreated(message: TLMessage) {
|
|
|
// TODO https://goo.gl/LMyN7A
|
|
|
- this._log.debug('Handling new session created')
|
|
|
+ this._log.debug('Handling new session created');
|
|
|
this._state.salt = message.obj.serverSalt
|
|
|
}
|
|
|
|
|
@@ -711,13 +745,13 @@ class MTProtoSender {
|
|
|
* @returns {Promise<void>}
|
|
|
* @private
|
|
|
*/
|
|
|
- async _handleAck(message) {
|
|
|
- const ack = message.obj
|
|
|
- this._log.debug(`Handling acknowledge for ${ack.msgIds}`)
|
|
|
+ async _handleAck(message: TLMessage) {
|
|
|
+ const ack = message.obj;
|
|
|
+ this._log.debug(`Handling acknowledge for ${ack.msgIds}`);
|
|
|
for (const msgId of ack.msgIds) {
|
|
|
- const state = this._pending_state[msgId]
|
|
|
- if (state && state.request instanceof LogOut) {
|
|
|
- delete this._pending_state[msgId]
|
|
|
+ const state = this._pendingState.get(msgId);
|
|
|
+ if (state && state.request instanceof Api.auth.LogOut) {
|
|
|
+ this._pendingState.delete(msgId);
|
|
|
state.resolve(true)
|
|
|
}
|
|
|
}
|
|
@@ -732,14 +766,14 @@ class MTProtoSender {
|
|
|
* @returns {Promise<void>}
|
|
|
* @private
|
|
|
*/
|
|
|
- async _handleFutureSalts(message) {
|
|
|
+ async _handleFutureSalts(message: TLMessage) {
|
|
|
// 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._pending_state[message.msgId]
|
|
|
+ this._log.debug(`Handling future salts for message ${message.msgId}`);
|
|
|
+ const state = this._pendingState.get(message.msgId.toString());
|
|
|
|
|
|
if (state) {
|
|
|
- delete this._pending_state[message]
|
|
|
+ this._pendingState.delete(message.msgId.toString());
|
|
|
state.resolve(message.obj)
|
|
|
}
|
|
|
}
|
|
@@ -751,9 +785,12 @@ class MTProtoSender {
|
|
|
* @returns {Promise<void>}
|
|
|
* @private
|
|
|
*/
|
|
|
- async _handleStateForgotten(message) {
|
|
|
- this._send_queue.append(
|
|
|
- new RequestState(new MsgsStateInfo(message.msgId, String.fromCharCode(1).repeat(message.obj.msgIds))),
|
|
|
+ async _handleStateForgotten(message: TLMessage) {
|
|
|
+ this._sendQueue.append(
|
|
|
+ new RequestState(new Api.MsgsStateInfo({
|
|
|
+ reqMsgId: message.msgId,
|
|
|
+ info: String.fromCharCode(1).repeat(message.obj.msgIds)
|
|
|
+ }))
|
|
|
)
|
|
|
}
|
|
|
|
|
@@ -763,53 +800,53 @@ class MTProtoSender {
|
|
|
* @returns {Promise<void>}
|
|
|
* @private
|
|
|
*/
|
|
|
- async _handleMsgAll(message) {
|
|
|
+ async _handleMsgAll(message: TLMessage) {
|
|
|
}
|
|
|
|
|
|
async _startReconnect() {
|
|
|
- if (this._user_connected && !this._reconnecting) {
|
|
|
- this._reconnecting = true
|
|
|
+ if (this._userConnected && !this._reconnecting) {
|
|
|
+ this._reconnecting = true;
|
|
|
// TODO Should we set this?
|
|
|
// this._user_connected = false
|
|
|
- this._log.info('Started reconnecting')
|
|
|
+ this._log.info('Started reconnecting');
|
|
|
this._reconnect()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async _reconnect() {
|
|
|
- this._log.debug('Closing current connection...')
|
|
|
+ this._log.debug('Closing current connection...');
|
|
|
try {
|
|
|
await this.disconnect()
|
|
|
} catch (err) {
|
|
|
this._log.warn(err)
|
|
|
}
|
|
|
- this._send_queue.append(null)
|
|
|
+ // @ts-ignore
|
|
|
+ this._sendQueue.append(null);
|
|
|
|
|
|
- this._state.reset()
|
|
|
- const retries = this._retries
|
|
|
+ this._state.reset();
|
|
|
+ const retries = this._retries;
|
|
|
|
|
|
|
|
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
|
try {
|
|
|
- await this._connect()
|
|
|
+ await this._connect();
|
|
|
// uncomment this if you want to resend
|
|
|
//this._send_queue.extend(Object.values(this._pending_state))
|
|
|
- this._pending_state = {}
|
|
|
+ this._pendingState = new Map<string, RequestState>();
|
|
|
if (this._autoReconnectCallback) {
|
|
|
await this._autoReconnectCallback()
|
|
|
}
|
|
|
- if (this._updateCallback){
|
|
|
+ if (this._updateCallback) {
|
|
|
this._updateCallback(1)
|
|
|
}
|
|
|
|
|
|
break
|
|
|
} catch (e) {
|
|
|
- this._log.error('WebSocket connection failed attempt : '+(attempt+1))
|
|
|
- console.log(e)
|
|
|
- await Helpers.sleep(this._delay)
|
|
|
+ this._log.error('WebSocket connection failed attempt : ' + (attempt + 1));
|
|
|
+ console.log(e);
|
|
|
+ await sleep(this._delay)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-module.exports = MTProtoSender
|