UNPKG

telegram-mtproto

Version:
711 lines (661 loc) 18.1 kB
import { append, reject, isEmpty, chain, filter, pipe, lensPath, over, defaultTo } from 'ramda'; import { cache } from 'fluture'; // import { Just } from 'folktale/maybe' import { Maybe } from 'apropos'; var { Just, Nothing } = Maybe; import './index.h'; import { dispatch } from '../state'; import describeProtocolError from './describe-protocol-error'; import { MAIN, NETWORKER_STATE } from '../state/action'; import { longToBytes, rshift32 } from '../bin'; import guard from '../util/match-spec'; import warning from '../util/warning'; import random from '../service/secure-random'; import { toDCNumber } from '../newtype.h'; import '../mtp.h.js'; import { queryRequest, queryAck } from '../state/query'; import Logger from 'mtproto-logger'; import { applyServerTime } from '../service/time-manager'; import invoke from '../service/invoke'; import { NetMessage } from '../service/networker/net-message'; import Config from '../config-provider'; var log = Logger`single-handler`; //eslint-disable-next-line var appendRO = (() => { /*:: declare function appendReadOnly<T>( value: T, list: $ReadOnlyArray<T> ): $ReadOnlyArray<T> */ return append /*:: , appendReadOnly */; })(); /*:: interface Writer<+T> { set next(x: T): void, read(): $ReadOnlyArray<T>, } */ class WriterFacade { constructor() { this.state = []; } set next(x) { this.state = appendRO(x, this.state); } read() { return this.state; } } var getFlags = e => e.flags; var selector = select => pipe(filter(pipe(getFlags, select, e => !!e)), chain(select)); var noEmpty = reject(isEmpty); export default function singleHandler(ctx, message) { var { flags } = message; /*:: const inners = handleInner(ctx, message) const unrels = handleUnrelated(ctx, message) */ var patches = new WriterFacade(); var result = message; if (flags.inner) { patches.next = handleInner(ctx, message); } if (isUnrelatedBody(flags)) { var unrel = handleUnrelated(ctx, message); if (unrel !== void 0) patches.next = unrel; } if (flags.error) { var { info, patch } = handleError(ctx, message); patches.next = patch; result = info; } var collected = patches.read(); // .map(({ flags, ...e }) => ({ // flags: { // ...emptyPatch().flags, // ...flags, // }, // ...e // })) // collected.forEach(e => log`patches`(e)) var summary = makeSummary(collected); //$off log`summary`(noEmpty(summary)); return { message: result, summary }; } var isUnrelatedBody = guard({ api: false, container: false, body: true }); //$off var processAckChain = selector(e => e.processAck); //$off var ackChain = selector(e => e.ack); //$off // const homeChain = selector(e => e.home) // //$off // const authChain = selector(e => e.authKey) //$off var reqResendChain = selector(e => e.reqResend); // //$off // const resendChain = selector(e => e.resend) // //$off // const lastMessagesChain = selector(e => e.lastServerMessages) // //$off // const saltChain = selector(e => e.salt) // //$off // const sessionChain = selector(e => e.session) function makeSummary(collected) { var processAck = processAckChain(collected); var ack = ackChain(collected); // const home: ᐸPatchᐳHome[] = homeChain(collected) // const auth: ᐸPatchᐳAuthKey[] = authChain(collected) var reqResend = reqResendChain(collected); // const resend: ᐸPatchᐳResend[] = resendChain(collected) // const lastMessages: ᐸPatchᐳLastMesages[] = lastMessagesChain(collected) // const salt: ᐸPatchᐳSalt[] = saltChain(collected) // const session: ᐸPatchᐳSession[] = sessionChain(collected) return { processAck, ack, // home, // auth, reqResend // resend, // lastMessages, // salt, // session, }; } var patchState = (() => { var defArray = defaultTo([]); // const lensProcessAck = lensPath(['processAck']) // const lensAck = lensPath(['ack']) // const lensReqResend = lensPath(['reqResend']) // const lensFlags = lensPath(['flags']) class PatchState { constructor(value) { this.value = value; } ack(data) { return new PatchState(Object.assign({}, this.value, { ack: [...defArray(this.value.ack), ...data] })); } processAck(data) { return new PatchState(Object.assign({}, this.value, { processAck: [...defArray(this.value.processAck), ...data] })); } reqResend(data) { return new PatchState(Object.assign({}, this.value, { reqResend: [...defArray(this.value.reqResend), ...data] })); } } return () => new PatchState(emptyPatch()); })(); function handleUnrelated(ctx, message) { var { thread, uid, dc } = ctx; //$off var cast = message; var { body } = cast; var { id } = cast; switch (body._) { case 'msgs_ack': { // body.msg_ids.forEach(thread.processMessageAck) var msg_ids = body.msg_ids; return patchState().processAck(msg_ids.map(msg => ({ dc, id: msg }))).value; } case 'msg_detailed_info': { if (!Config.fastCache.get(uid, dc).hasSent(body.msg_id)) { var _id = body.answer_msg_id; thread.ackMessage(_id); return patchState().ack([{ dc, id: _id }]).value; } return emptyPatch(); } case 'msg_new_detailed_info': { var { answer_msg_id: _id2 } = body; var state = patchState(); if (queryAck(uid, dc).indexOf(_id2) === -1) state = state.reqResend([{ dc, id: _id2 }]); return state // .ack([{ dc, id }]) .value; } case 'msgs_state_info': { var { answer_msg_id } = body; // thread.ackMessage(answer_msg_id) var lastResendReq = thread.lastResendReq; if (!lastResendReq) break; if (lastResendReq.req_msg_id != body.req_msg_id) break; // const resendDel = [] for (var _iterator = lastResendReq.resend_msg_ids, _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 badMsgID = _ref; // resendDel.push(badMsgID) Config.fastCache.get(uid, dc).deleteResent(badMsgID); } return patchState().ack([{ dc, id: answer_msg_id }]).reqResend([{ dc, id }]).value; // dispatch(NETWORKER_STATE.RESEND.DEL(resendDel, this.dcID)) } case 'rpc_result': { return handleRpcResult(ctx, message); } case 'new_session_created': { // thread.emit('new-session', { // threadID : thread.threadID, // networkerDC: message.dc, // messageID : message.id, // message : body // }) return handleNewSession(ctx, message); } case 'bad_server_salt': { return handleBadSalt(ctx, message); } case 'bad_msg_notification': { return handleBadNotify(ctx, message); } default: { var { id: _id3 } = message; thread.ackMessage(message.id); thread.emit('untyped-message', { threadID: thread.threadID, networkerDC: message.dc, message: body, messageID: message.id, sessionID: Config.session.get(ctx.thread.uid, message.dc), result: message }); return patchState().ack([{ dc, id: _id3 }]).value; } } } function handleInner(ctx, message) { var { thread } = ctx; var { id, dc } = message; if (thread.lastServerMessages.indexOf(id) != -1) { // console.warn('[MT] Server same messageID: ', messageID) // thread.ackMessage(id) return patchState().ack([{ dc, id }]).value; } else { thread.lastServerMessages.push(id); if (thread.lastServerMessages.length > 100) { thread.lastServerMessages.shift(); } return { flags: { net: true, lastServerMessages: true }, net: [{ dc, lastServerMessages: [id] }], lastServerMessages: [{ dc, id }] }; } } var migrateRegexp = /^(PHONE_MIGRATE_|NETWORK_MIGRATE_|USER_MIGRATE_)(\d+)/; var fileMigrateRegexp = /^(FILE_MIGRATE_)(\d+)/; var floodWaitRegexp = /^(FLOOD_WAIT_)(\d+)/; function handleError(ctx, data) { var err = data.error; var { code, message } = err; if (floodWaitRegexp.test(message)) { return handleFloodWait(message, data, code, ctx); } else if (fileMigrateRegexp.test(message)) { return handleFileMigrate(message, data, code, ctx); } else if (migrateRegexp.test(message)) { return handleMigrateError(message, data, code, ctx); } else { switch (message) { case 'AUTH_KEY_UNREGISTERED': return handleAuthUnreg(ctx, message, data, code); case 'AUTH_RESTART': return handleAuthRestart(message, data, code); } } return { info: data, patch: emptyPatch() }; } function numberFromError(message, regexp) { var matched = message.match(regexp); if (!matched || matched.length < 2) return Nothing(); var [,, numStr] = matched; if (!isFinite(numStr)) return Nothing(); var num = parseInt(numStr, 10); return Just(num); } var patchNothing = data => () => ({ info: data, patch: emptyPatch() }); var floodWarning = warning({ isIssue: false, message: ['Flood wait! Too many requests, you should wait', 'seconds before new requests'] }); function handleFloodWait(message, data, code, ctx) { return numberFromError(message, floodWaitRegexp).fold(patchNothing(data), waitTime => { floodWarning(waitTime); var info = Object.assign({}, data, { error: { code, message, handled: true } }); return { info, patch: emptyPatch() }; }); } function handleFileMigrate(message, data, code, ctx) { var { uid, dc } = ctx; return numberFromError(message, fileMigrateRegexp) /*:: .map(toDCNumber) */ .pred(dc => data.flags.methodResult).chain(newDc => queryRequest(uid, dc, data.methodResult.outID).map(req => ({ req, newDc }))).fold(patchNothing(data), ({ req, newDc }) => { req.dc = Just(newDc); var futureAuth = Config.authRequest.get(uid, newDc); if (!futureAuth) { var authReq = cache(invoke(uid, 'auth.exportAuthorization', { dc_id: newDc }).map(resp => (console.log(resp), resp)).map(resp => { if (typeof resp === 'object' && resp != null) { if (typeof resp.id === 'number') { var { id } = resp; if (resp.bytes != null) { var { bytes } = resp; return { id, bytes: [...bytes] }; } } } console.error('incorrect', resp); return resp; }).chain(resp => invoke(uid, 'auth.importAuthorization', resp, { dcID: newDc }))); Config.authRequest.set(uid, newDc, authReq); authReq.promise(); } var info = Object.assign({}, data, { error: { code, message, handled: true } }); return { info, patch: emptyPatch() }; }); } function handleMigrateError(message, data, code, ctx) { var { uid, dc } = ctx; return numberFromError(message, migrateRegexp) /*:: .map(toDCNumber) */ .fold(patchNothing(data), newDc => { dispatch(MAIN.RECOVERY_MODE({ halt: dc, recovery: newDc, uid }), uid); Config.fastCache.init(uid, dc); Config.seq.set(uid, dc, 0); Config.halt.set(uid, dc, true); Config.halt.set(uid, newDc, false); //$off Config.session.set(uid, ctx.dc, null); Promise.all([Config.storageAdapter.set.dc(uid, newDc), Config.storageAdapter.set.nearestDC(uid, newDc)]).then(() => { dispatch(MAIN.DC_DETECTED({ dc: newDc, uid }), uid); }); var patch = { flags: { net: true, home: true }, net: [{ dc: data.dc, home: false }, { dc: newDc, home: true }], home: [newDc] }; var info = Object.assign({}, data, { error: { code, message, handled: true } }); return { info, patch }; }); } function handleAuthRestart(message, data, code) { var { dc } = data; // dispatch(MAIN.AUTH_UNREG(dc)) var info = Object.assign({}, data, { error: { code, message, handled: true } }); return { info, patch: { flags: { net: true, authKey: true }, net: [{ dc, authKey: [] }], authKey: [{ dc, authKey: false }] } }; } function handleAuthUnreg(ctx, message, data, code) { var { dc, uid } = ctx; dispatch(MAIN.AUTH_UNREG(dc), uid); var info = Object.assign({}, data, { error: { code, message, handled: true } }); return { info, patch: { flags: { net: true, authKey: true }, net: [{ dc, authKey: [] }], authKey: [{ dc, authKey: false }] } }; } //$off var emptyPatch = () => ({ flags: { /*:: net: true, */ } }); function handleNewSession(ctx, message) { var body = message.body; var { first_msg_id, server_salt } = body; var salt = longToBytes(server_salt); var { dc, id } = message; // const session = new Array(8) // random(session) // Config.seq.set(ctx.thread.uid, dc, 0) return { flags: { net: true, // session : true, salt: true, ack: true, processAck: true }, net: [{ dc, salt, // session, seq: 0, first: first_msg_id // Refers to outcoming api message }], // session: [{ // dc, // session, // seq : 0, // first: first_msg_id, // }], salt: [{ dc, salt }], ack: [{ dc, id }], processAck: [{ dc, id: first_msg_id }] }; } function handleBadNotify(ctx, message) { var body = message.body; var { dc, uid } = ctx; log`Bad msg notification`(message); var { bad_msg_id: badMsg, bad_msg_seqno: seq, error_code: code } = body; var sentMessage = Config.fastCache.get(uid, dc).getSent(badMsg); var error = describeProtocolError(code || 0); errorPrint: { log`protocol error, code`(error.code); log`protocol error, message`(error.message); log`protocol error, description`(error.description); } if (!sentMessage || sentMessage.seq_no != seq) { log`Bad msg notification, seq`(badMsg, seq); // throw error } var { id } = message; var flags = {/*:: ack: true */}; var data = {}; if (code === 16 || code === 17) { if (applyServerTime(ctx.thread.uid, rshift32(id))) { var _session = new Array(8); random(_session); flags = Object.assign({}, flags, { session: true }); data = Object.assign({}, data, { session: [{ dc, session: _session, seq: 0, first: badMsg }] }); var badMessage = ctx.thread.updateSentMessage(badMsg); if (badMessage instanceof NetMessage) { flags = Object.assign({}, flags, { resend: true }); data = Object.assign({}, data, { resend: [{ dc, id: badMsg }] }); } flags = Object.assign({}, flags, { ack: true }); data = Object.assign({}, data, { ack: [{ dc, id }] }); } } return Object.assign({}, data, { flags }); } function handleBadSalt(ctx, message) { var body = message.body; log`Bad server salt`(message); var { bad_msg_id: badMsg, bad_msg_seqno: seq, error_code: code, new_server_salt: newSalt } = body; var { dc, uid } = ctx; var sentMessage = Config.fastCache.get(uid, dc).getSent(badMsg); var error = describeProtocolError(code || 0); errorPrint: { log`protocol error, code`(error.code); log`protocol error, message`(error.message); log`protocol error, description`(error.description); } if (!sentMessage || sentMessage.seq_no != seq) { log`invalid message, seq`(badMsg, seq); // throw error } var salt = longToBytes(newSalt); var { id } = message; var session = new Array(8); random(session); ctx.thread.pushResend(badMsg); return { flags: { net: true, session: true, salt: true, ack: true, resend: true }, net: [{ dc, salt, session, seq: 0, first: badMsg }], session: [{ dc, session, seq: 0, first: badMsg }], salt: [{ dc, salt }], ack: [{ dc, id }], resend: [{ dc, id: badMsg }] }; } function handleRpcResult(ctx, message) { var { thread, dc, uid } = ctx; var { id } = message; var body = message.body; thread.ackMessage(id); var sentMessageID = body.req_msg_id; var sentMessage = Config.fastCache.get(uid, dc).getSent(sentMessageID); // thread.processMessageAck(sentMessageID) if (!sentMessage) { console.warn('No sent message!', sentMessageID, message); return emptyPatch(); } dispatch(NETWORKER_STATE.SENT.DEL([sentMessage], dc), uid); Config.fastCache.get(uid, dc).deleteSent(sentMessage); if (body.result) { if (body.result._ == 'rpc_error') { thread.emit('rpc-error', { threadID: thread.threadID, networkerDC: dc, error: body.result, sentMessage, message }); } else { thread.emit('rpc-result', { threadID: thread.threadID, networkerDC: dc, message, sentMessage, result: body.result }); } } else { console.warn('No result!', sentMessageID, message); } if (sentMessage.isAPI) thread.connectionInited = true; return emptyPatch(); } //# sourceMappingURL=single-handler.js.map