UNPKG

telegram-mtproto

Version:
727 lines (635 loc) 24 kB
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } import Bluebird from 'bluebird'; import uuid from '../../util/uuid'; import { contains, toPairs } from 'ramda'; import { fromEvent } from 'most'; import Emitter from 'eventemitter2'; import { smartTimeout, immediate } from 'mtproto-shared'; import { tsNow } from '../time-manager'; import { NetMessage, NetContainer } from './net-message'; import { Serialization, TypeWriter } from '../../tl'; import { writeInnerMessage } from '../chain/perform-request'; import Config from '../../config-provider'; import { requestNextSeq } from '../../state/reaction'; import { queryAck, queryHomeDc } from '../../state/query'; import ApiRequest from '../main/request'; import { writeInt, writeBytes, writeLong } from '../../tl/writer'; import LongPoll from '../../plugins/long-poll'; import { NET, NETWORKER_STATE } from '../../state/action'; import '../main/index.h'; import '../../newtype.h'; import { dispatch } from '../../state'; import L1Cache from '../../l1-cache'; import Logger from 'mtproto-logger'; var log = Logger`networker`; var iii = 0; var akStopped = false; var storeIntString = writer => ([field, value]) => { switch (typeof value) { case 'string': return writeBytes(writer, value); case 'number': return writeInt(writer, value, field); default: throw new Error(`tl storeIntString field ${field} value type ${typeof value}`); } }; function addInitialMessage(serialBox, appConfig) { var mapper = storeIntString(serialBox); //$off var pairs = toPairs(appConfig); pairs.forEach(mapper); } function addAfterMessage(serialBox, id) { writeInt(serialBox, 0xcb9f372d, 'invokeAfterMsg'); writeLong(serialBox, id, 'msg_id'); } function isHomeDC(uid, dc) { return queryHomeDc(uid).map(x => x === dc).fold(() => false, x => x); } export class NetworkerThread { constructor(dc, uid) { _initialiseProps.call(this); this.uid = uid; var emitter = Config.rootEmitter(this.uid); this.emit = emitter.emit; //$off this.dcID = dc; this.iii = iii++; //$FlowIssue Object.defineProperties(this, { performSheduledRequest: { value: this.performSheduledRequest.bind(this), enumerable: false, writable: false }, wrapMtpCall: { value: this.wrapMtpCall.bind(this), enumerable: false } }); Config.fastCache.init(uid, this.dcID); Config.thread.set(uid, this.dcID, this); this.longPoll = new LongPoll(this); // // this.checkLongPollCond = this.checkLongPollCond.bind(this) // this.serverSalt = serverSalt // Bluebird.all([ // storage.set(keyNames.authKey, bytesToHex(authKey)), // storage.set(keyNames.saltKey, bytesToHex(serverSalt)) // ]).then(() => { // }) emitter.emit('new-networker', this); // this.updateSession() setInterval(() => this.checkLongPoll(), 10000); //NOTE make configurable interval // this.checkLongPoll() } get state() { return Config.fastCache.get(this.uid, this.dcID); } // updateSession() { // this.prevSessionID = this.sessionID // this.sessionID = new Array(8) // random(this.sessionID) // } updateSentMessage(sentMessageID) { if (!this.state.hasSent(sentMessageID)) { console.log(`no id`, sentMessageID); return false; } var sentMessage = this.state.getSent(sentMessageID); var newInner = []; if (sentMessage instanceof NetContainer) { for (var _iterator = sentMessage.inner, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { var _ref; if (_isArray) { if (_i >= _iterator.length) break; _ref = _iterator[_i++]; } else { _i = _iterator.next(); if (_i.done) break; _ref = _i.value; } var innerID = _ref; var innerSentMessage = this.updateSentMessage(innerID); if (innerSentMessage) newInner.push(innerSentMessage.msg_id); } } // dispatch(NETWORKER_STATE.SENT.DEL([sentMessage], this.dcID)) this.state.deleteSent(sentMessage); var seq_no = requestNextSeq(this.uid, this.dcID, sentMessage.notContentRelated || sentMessage.container); var newMessage = sentMessage.clone(seq_no, this.dcID); if (newMessage instanceof NetContainer) { newMessage.inner = newInner; } this.state.addSent(newMessage); // dispatch(NETWORKER_STATE.SENT.ADD([newMessage], this.dcID)) return newMessage; } wrapMtpCall(method, params, options) { var serializer = new Serialization({ mtproto: true }, this.uid); serializer.storeMethod(method, params); var seqNo = requestNextSeq(this.uid, this.dcID); var message = new NetMessage(this.uid, this.dcID, seqNo, serializer.getBytes(true)); this.pushMessage(message, options); return message.deferred.promise; } wrapMtpMessage(object, options = {}) { var serializer = new Serialization({ mtproto: true }, this.uid); serializer.storeObject(object, 'Object', 'wrap_message'); var seqNo = requestNextSeq(this.uid, this.dcID, options.notContentRelated); var message = new NetMessage(this.uid, this.dcID, seqNo, serializer.getBytes(true), 'ack/resend'); this.pushMessage(message, options); return message; } wrapApiCall(netReq) { var { data: { method, params }, options, requestID } = netReq; var serializer = new Serialization(options, this.uid); var serialBox = serializer.writer; if (!this.connectionInited) { addInitialMessage(serialBox, Config.apiConfig.get(this.uid)); } if (typeof options.afterMessageID === 'string') addAfterMessage(serialBox, options.afterMessageID); options.resultType = serializer.storeMethod(method, params); var seqNo = requestNextSeq(this.uid, this.dcID); var message = new NetMessage(this.uid, this.dcID, seqNo, serializer.getBytes(true), 'api'); message.isAPI = true; message.requestID = requestID; this.pushMessage(message, options); return message; } checkLongPollCond() { return this.longPoll.pendingTime > tsNow() || !!this.offline || akStopped; } checkLongPollAfterDcCond(isClean) { return isClean && !isHomeDC(this.uid, this.dcID); } checkLongPoll() { this.cleanupSent(); // if (this.checkLongPollCond()) // return false // if (this.checkLongPollAfterDcCond(isClean)) // // console.warn(dTime(), 'Send long-poll for DC is delayed', this.dcID, this.sleepAfter) // return this.pollEvents.emit('poll'); } pushMessage(message, options = {}) { message.copyOptions(options); dispatch(NETWORKER_STATE.SENT.ADD([message], this.dcID), this.uid); // dispatch(NETWORKER_STATE.PENDING.ADD([message.msg_id], this.dcID)) options.messageID = message.msg_id; //TODO remove mutable operation this.state.addSent(message); this.state.setPending(message.msg_id); if (!options.noShedule) this.sheduleRequest(); } pushResend(messageID, delay = 0) { var value = tsNow() + delay; var sentMessage = this.state.getSent(messageID); if (sentMessage instanceof NetContainer) { for (var _iterator2 = sentMessage.inner, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { var _ref2; if (_isArray2) { if (_i2 >= _iterator2.length) break; _ref2 = _iterator2[_i2++]; } else { _i2 = _iterator2.next(); if (_i2.done) break; _ref2 = _i2.value; } var msg = _ref2; this.state.setPending(msg, value); } // dispatch(NETWORKER_STATE.PENDING.ADD(sentMessage.inner, this.dcID)) } else { // dispatch(NETWORKER_STATE.PENDING.ADD([messageID], this.dcID)) this.state.setPending(messageID, value); } this.sheduleRequest(delay); } checkConnection() { return _asyncToGenerator(function* () {})(); } toggleOffline(enabled) { // console.log('toggle ', enabled, this.dcID, this.iii) if (!this.offline !== undefined && this.offline == enabled) return false; this.offline = enabled; if (this.offline) { smartTimeout.cancel(this.nextReqPromise); delete this.nextReq; if (this.checkConnectionPeriod < 1.5) this.checkConnectionPeriod = 0; this.checkConnectionPromise = smartTimeout(this.checkConnection, parseInt(this.checkConnectionPeriod * 1000)); this.checkConnectionPeriod = Math.min(30, (1 + this.checkConnectionPeriod) * 1.5); // this.onOnlineCb = this.checkConnection // this.emit('net.offline', this.onOnlineCb) } else { this.longPoll.pendingTime = Date.now(); //NOTE check long state was here this.checkLongPoll(); this.sheduleRequest(); // if (this.onOnlineCb) // this.emit('net.online', this.onOnlineCb) smartTimeout.cancel(this.checkConnectionPromise); } } performResend() { if (this.state.hasResends()) { var resendMsgIDs = [...this.state.getResends()]; // console.log('resendReq messages', resendMsgIDs) var msg = this.wrapMtpMessage({ _: 'msg_resend_req', msg_ids: resendMsgIDs }, { noShedule: true, notContentRelated: true }); this.lastResendReq = { req_msg_id: msg.msg_id, resend_msg_ids: resendMsgIDs }; } } performSheduledRequest() { //TODO extract huge method // console.log(dTime(), 'sheduled', this.dcID, this.iii) if (this.offline || akStopped) { log`Cancel sheduled`(``); return Bluebird.resolve(false); } delete this.nextReq; var ackMsgIDs = queryAck(this.uid, this.dcID); if (ackMsgIDs.length > 0) { log`acking messages`(ackMsgIDs); this.wrapMtpMessage({ _: 'msgs_ack', msg_ids: ackMsgIDs }, { notContentRelated: true, noShedule: true }); //TODO WTF Why we make wrapped message and doesnt use it? // const res = await msg.deferred.promise // log(`AWAITED`, `ack`)(res) dispatch(NET.ACK_DELETE({ dc: this.dcID, ack: ackMsgIDs }), this.uid); } this.performResend(); var messages = []; //$off var message = void 0; var messagesByteLen = 0; var lengthOverflow = false; var pendingIds = []; for (var _iterator3 = this.state.pendingIterator(), _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { var _ref3; if (_isArray3) { if (_i3 >= _iterator3.length) break; _ref3 = _iterator3[_i3++]; } else { _i3 = _iterator3.next(); if (_i3.done) break; _ref3 = _i3.value; } var [_messageID, value] = _ref3; if (value && value < tsNow()) continue; this.state.deletePending(_messageID); pendingIds.push(_messageID); if (!this.state.hasSent(_messageID)) continue; message = this.state.getSent(_messageID); var messageByteLength = message.size() + 32; var cond1 = !message.notContentRelated && lengthOverflow; var cond2 = !message.notContentRelated && messagesByteLen + messageByteLength > 655360; // 640 Kb if (cond1) continue; if (cond2) { lengthOverflow = true; continue; } messages.push(message); messagesByteLen += messageByteLength; } // dispatch(NETWORKER_STATE.PENDING.DEL(pendingIds, this.dcID)) messages.map(msg => this.emit('message-in', msg)); if (!message) return Bluebird.resolve(false); //TODO Why? if (message.isAPI && !message.longPoll) { var serializer = new Serialization({ mtproto: true }, this.uid); serializer.storeMethod('http_wait', { max_delay: 0, wait_after: 100, max_wait: 5000 }); var netMessage = new NetMessage(this.uid, this.dcID, requestNextSeq(this.uid, this.dcID), serializer.getBytesPlain(), 'polling'); this.longPoll.writePollTime(); messages.push(netMessage); } if (!messages.length) { // console.log('no sheduled messages') return Bluebird.resolve(false); } var noResponseMsgs = []; if (messages.length > 1) { var container = new Serialization({ mtproto: true, startMaxLength: messagesByteLen + 64 }, this.uid); var contBox = container.writer; writeInt(contBox, 0x73f1f8dc, 'CONTAINER[id]'); writeInt(contBox, messages.length, 'CONTAINER[count]'); var { innerMessages, noResponseMessages } = writeInnerMessage({ writer: contBox, messages }); noResponseMsgs = noResponseMessages; var innerApi = messages.reduce((acc, val) => { if (!val.isAPI) return [...acc, false]; return [...acc, val.requestID /*::|| '' */]; }, []); message = new NetContainer(this.uid, this.dcID, requestNextSeq(this.uid, this.dcID, true), container.getBytes(true), innerMessages, innerApi); } else { if (message.noResponse) noResponseMsgs.push(message.msg_id); } this.state.addSent(message); if (lengthOverflow) this.sheduleRequest(); dispatch(NET.SEND({ message, options: {}, threadID: this.threadID, thread: this, noResponseMsgs }, this.dcID), this.uid); return Bluebird.resolve(true); } /* async applyServerSalt(newServerSalt: string) { const serverSalt = longToBytes(newServerSalt) await this.storage.set(`dc${ this.dcID }_server_salt`, bytesToHex(serverSalt)) this.serverSalt = serverSalt return true } */ sheduleRequest(delay = 0) { if (this.offline) this.checkConnection(); var nextReq = tsNow() + delay; if (delay && this.nextReq && this.nextReq <= nextReq) return false; smartTimeout.cancel(this.nextReqPromise); if (delay > 0) this.nextReqPromise = smartTimeout(this.performSheduledRequest, delay);else immediate(this.performSheduledRequest); this.nextReq = nextReq; } ackMessage(msgID) { var ackMsgIDs = queryAck(this.uid, this.dcID); if (contains(msgID, ackMsgIDs)) return; dispatch(NET.ACK_ADD({ dc: this.dcID, ack: [msgID] }), this.uid); this.sheduleRequest(30000); } reqResendMessage(msgID) { log`Req resend`(msgID); this.state.addResend(msgID); this.sheduleRequest(100); } cleanupSent() { var notEmpty = false; // console.log('clean start', this.dcID/*, this.state.sent*/) var sentDel = []; for (var _iterator4 = this.state.sentIterator(), _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { var _ref4; if (_isArray4) { if (_i4 >= _iterator4.length) break; _ref4 = _iterator4[_i4++]; } else { _i4 = _iterator4.next(); if (_i4.done) break; _ref4 = _i4.value; } var [msgID, message] = _ref4; var complete = true; if (message.notContentRelated && !this.state.hasPending(msgID)) { sentDel.push(message); // console.log('clean notContentRelated', msgID) this.state.deleteSent(message); } else if (message instanceof NetContainer) { for (var _iterator5 = message.inner, _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { var _ref5; if (_isArray5) { if (_i5 >= _iterator5.length) break; _ref5 = _iterator5[_i5++]; } else { _i5 = _iterator5.next(); if (_i5.done) break; _ref5 = _i5.value; } var inner = _ref5; if (this.state.hasSent(inner)) { // console.log('clean failed, found', msgID, message.inner[i], // this.state.getSent(message.inner[i]).seq_no) notEmpty = true; complete = false; break; } } // console.log('clean container', msgID) if (complete) { sentDel.push(message); this.state.deleteSent(message); } } else notEmpty = true; } dispatch(NETWORKER_STATE.SENT.DEL(sentDel, this.dcID), this.uid); return !notEmpty; } processMessage(message, messageID, sessionID) { var _this = this; return _asyncToGenerator(function* () { if (!isFinite(messageID)) { throw new TypeError(`Message ID should be finite ${messageID} ${typeof messageID}`); } var msgidInt = parseInt(messageID, 10); if (msgidInt % 2) { console.warn('[MT] Server even message id: ', messageID, message); return; } switch (message._) { case 'msg_container': { /* for (const inner of message.messages) await this.processMessage(inner, inner.msg_id, sessionID) */ break; } case 'bad_server_salt': { // log(`Bad server salt`)(message) // const sentMessage = this.state.getSent(message.bad_msg_id) // if (!sentMessage || sentMessage.seq_no != message.bad_msg_seqno) { // log(`invalid message`)(message.bad_msg_id, message.bad_msg_seqno) // throw new Error('[MT] Bad server salt for invalid message') // } // await this.applyServerSalt(message.new_server_salt) // this.pushResend(message.bad_msg_id) // this.ackMessage(messageID) break; } case 'bad_msg_notification': { /* log(`Bad msg notification`)(message) const sentMessage = this.state.getSent(message.bad_msg_id) if (!sentMessage || sentMessage.seq_no != message.bad_msg_seqno) { log(`invalid message`)(message.bad_msg_id, message.bad_msg_seqno) throw new Error('[MT] Bad msg notification for invalid message') } if (message.error_code == 16 || message.error_code == 17) { if (applyServerTime( this.uid, rshift32(messageID) )) { log(`Update session`)() this.updateSession() } const badMessage = this.updateSentMessage(message.bad_msg_id) if (badMessage instanceof NetMessage) this.pushResend(badMessage.msg_id) this.ackMessage(messageID) } */ break; } case 'message': { /* if (this.lastServerMessages.indexOf(messageID) != -1) { // console.warn('[MT] Server same messageID: ', messageID) this.ackMessage(messageID) return } this.lastServerMessages.push(messageID) if (this.lastServerMessages.length > 100) { this.lastServerMessages.shift() } await this.processMessage(message.body, message.msg_id, sessionID) */ break; } case 'new_session_created': { // this.ackMessage(messageID) // this.processMessageAck(message.first_msg_id) // await this.applyServerSalt(message.server_salt) /* this.emit('new-session', { threadID : this.threadID, networkerDC: this.dcID, messageID, message }) */ // const baseDcID = await this.storage.get('dc') // const updateCond = // baseDcID === this.dcID && // !this.upload && // updatesProcessor // if (updateCond) // updatesProcessor(message, true) break; } case 'msgs_ack': { /* message.msg_ids.forEach(this.processMessageAck) */ break; } case 'msg_detailed_info': { /* if (!this.state.hasSent(message.msg_id)) { this.ackMessage(message.answer_msg_id) break } */ break; } case 'msg_new_detailed_info': { /* this.ackMessage(message.answer_msg_id) this.reqResendMessage(message.answer_msg_id) */ break; } case 'msgs_state_info': { /* this.ackMessage(message.answer_msg_id) const lastResendReq = this.lastResendReq if (!lastResendReq) break if (lastResendReq.req_msg_id != message.req_msg_id) break // const resendDel = [] for (const badMsgID of lastResendReq.resend_msg_ids) { // resendDel.push(badMsgID) this.state.deleteResent(badMsgID) } */ // dispatch(NETWORKER_STATE.RESEND.DEL(resendDel, this.dcID)) break; } case 'rpc_result': { _this.ackMessage(messageID); var sentMessageID = message.req_msg_id; var sentMessage = _this.state.getSent(sentMessageID); _this.processMessageAck(sentMessageID); if (!sentMessage) break; // dispatch(NETWORKER_STATE.SENT.DEL([sentMessage], this.dcID)) _this.state.deleteSent(sentMessage); if (message.result._ == 'rpc_error') { _this.emit('rpc-error', { threadID: _this.threadID, networkerDC: _this.dcID, error: message.result, sentMessage, message }); } else { _this.emit('rpc-result', { threadID: _this.threadID, networkerDC: _this.dcID, message, sentMessage, result: message.result }); if (sentMessage.isAPI) _this.connectionInited = true; } break; } default: { _this.ackMessage(messageID); /* this.emit('untyped-message', { threadID : this.threadID, networkerDC: this.dcID, message, messageID, sessionID, result : message.result }) if (updatesProcessor) updatesProcessor(message, true) */ break; } } })(); } } var _initialiseProps = function () { var _this2 = this; this.threadID = uuid(); this.upload = false; this.connectionInited = false; this.checkConnectionPeriod = 0; this.lastServerMessages = []; this.pollEvents = (() => { var emitter = new Emitter(); return emitter; })(); this.runLongPoll = _asyncToGenerator(function* () { yield _this2.longPoll.sendLongPool(); _this2.checkLongPoll(); }); this.poll = fromEvent('poll', this.pollEvents).throttle(50).observe(this.runLongPoll); this.getMsgById = ({ req_msg_id }) => this.state.getSent(req_msg_id); this.processMessageAck = messageID => { var sentMessage = this.state.getSent(messageID); if (sentMessage && !sentMessage.acked) { delete sentMessage.body; sentMessage.acked = true; return true; } return false; }; }; export function createThread(dc, uid) { return new NetworkerThread(dc, uid); } export default NetworkerThread; //# sourceMappingURL=index.js.map