Browse Source

HTTP transport fixes

Some fixes on auth pages
Fix messageActionInviteToGroupCall
Yesterday user's status
Fix 12-hour format time in user status
master
morethanwords 3 years ago
parent
commit
6b16337260
  1. 12
      src/components/passwordInputField.ts
  2. 19
      src/config/modes.ts
  3. 20
      src/helpers/date.ts
  4. 6
      src/lang.ts
  5. 2
      src/langSign.ts
  6. 33
      src/lib/appManagers/appMessagesManager.ts
  7. 21
      src/lib/appManagers/appUsersManager.ts
  8. 96
      src/lib/mtproto/apiManager.ts
  9. 61
      src/lib/mtproto/authorizer.ts
  10. 43
      src/lib/mtproto/dcConfigurator.ts
  11. 252
      src/lib/mtproto/networker.ts
  12. 25
      src/lib/mtproto/transports/controller.ts
  13. 72
      src/lib/mtproto/transports/http.ts
  14. 19
      src/lib/mtproto/transports/tcpObfuscated.ts
  15. 2
      src/lib/mtproto/transports/transport.ts
  16. 1
      src/pages/pageIm.ts
  17. 3
      src/pages/pagePassword.ts
  18. 3
      src/scss/partials/_input.scss
  19. 8
      src/scss/style.scss

12
src/components/passwordInputField.ts

@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
// import { IS_MOBILE_SAFARI, IS_SAFARI } from "../environment/userAgent";
import { cancelEvent } from "../helpers/dom/cancelEvent"; import { cancelEvent } from "../helpers/dom/cancelEvent";
import InputField, { InputFieldOptions } from "./inputField"; import InputField, { InputFieldOptions } from "./inputField";
@ -21,12 +22,15 @@ export default class PasswordInputField extends InputField {
const input = this.input as HTMLInputElement; const input = this.input as HTMLInputElement;
input.type = 'password'; input.type = 'password';
input.setAttribute('required', ''); input.setAttribute('required', '');
input.name = 'notsearch_password';
input.autocomplete = 'off'; input.autocomplete = 'off';
/* input.readOnly = true;
input.addEventListener('focus', () => { /* if(IS_SAFARI && !IS_MOBILE_SAFARI) {
input.removeAttribute('readonly'); input.setAttribute('readonly', '');
}, {once: true}); */ input.addEventListener('focus', () => {
input.removeAttribute('readonly');
}, {once: true});
} */
// * https://stackoverflow.com/a/35949954/6758968 // * https://stackoverflow.com/a/35949954/6758968
const stealthy = document.createElement('input'); const stealthy = document.createElement('input');

19
src/config/modes.ts

@ -9,15 +9,30 @@
* https://github.com/zhukov/webogram/blob/master/LICENSE * https://github.com/zhukov/webogram/blob/master/LICENSE
*/ */
import type { TransportType } from "../lib/mtproto/dcConfigurator";
const Modes = { const Modes = {
test: location.search.indexOf('test=1') > 0/* || true */, test: location.search.indexOf('test=1') > 0/* || true */,
debug: location.search.indexOf('debug=1') > 0, debug: location.search.indexOf('debug=1') > 0,
http: false, //location.search.indexOf('http=1') > 0, http: false,
ssl: true, // location.search.indexOf('ssl=1') > 0 || location.protocol === 'https:' && location.search.indexOf('ssl=0') === -1, ssl: true, // location.search.indexOf('ssl=1') > 0 || location.protocol === 'https:' && location.search.indexOf('ssl=0') === -1,
multipleConnections: true, multipleConnections: true,
asServiceWorker: false asServiceWorker: false,
transport: 'https' as TransportType
}; };
/// #if MTPROTO_HAS_HTTP
Modes.http = location.search.indexOf('http=1') > 0;
/// #endif
/// #if MTPROTO_HTTP || !MTPROTO_HAS_WS
Modes.http = true;
/// #endif
if(Modes.http) {
Modes.transport = 'https';
}
/// #if MTPROTO_SW /// #if MTPROTO_SW
Modes.asServiceWorker = true; Modes.asServiceWorker = true;
/// #endif /// #endif

20
src/helpers/date.ts

@ -47,7 +47,9 @@ export function formatDateAccordingToTodayNew(time: Date) {
}).element; }).element;
} }
export function formatFullSentTime(timestamp: number) { export function formatFullSentTimeRaw(timestamp: number, options: {
capitalize?: boolean
} = {}) {
const date = new Date(); const date = new Date();
const time = new Date(timestamp * 1000); const time = new Date(timestamp * 1000);
const now = date.getTime() / 1000; const now = date.getTime() / 1000;
@ -56,9 +58,13 @@ export function formatFullSentTime(timestamp: number) {
let dateEl: Node | string; let dateEl: Node | string;
if((now - timestamp) < ONE_DAY && date.getDate() === time.getDate()) { // if the same day if((now - timestamp) < ONE_DAY && date.getDate() === time.getDate()) { // if the same day
dateEl = i18n('Date.Today'); dateEl = i18n(options.capitalize ? 'Date.Today' : 'Peer.Status.Today');
} else if((now - timestamp) < (ONE_DAY * 2) && (date.getDate() - 1) === time.getDate()) { // yesterday } else if((now - timestamp) < (ONE_DAY * 2) && (date.getDate() - 1) === time.getDate()) { // yesterday
dateEl = capitalizeFirstLetter(I18n.format('Yesterday', true)); dateEl = i18n(options.capitalize ? 'Yesterday' : 'Peer.Status.Yesterday');
if(options.capitalize) {
(dateEl as HTMLElement).style.textTransform = 'capitalize';
}
} else if(date.getFullYear() !== time.getFullYear()) { // different year } else if(date.getFullYear() !== time.getFullYear()) { // different year
dateEl = new I18n.IntlDateElement({ dateEl = new I18n.IntlDateElement({
date: time, date: time,
@ -80,6 +86,14 @@ export function formatFullSentTime(timestamp: number) {
// dateStr = months[time.getMonth()].slice(0, 3) + ' ' + time.getDate(); // dateStr = months[time.getMonth()].slice(0, 3) + ' ' + time.getDate();
} }
return {dateEl, timeEl};
}
export function formatFullSentTime(timestamp: number) {
const {dateEl, timeEl} = formatFullSentTimeRaw(timestamp, {
capitalize: true
});
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
fragment.append(dateEl, ' ', i18n('ScheduleController.at'), ' ', timeEl); fragment.append(dateEl, ' ', i18n('ScheduleController.at'), ' ', timeEl);
return fragment; return fragment;

6
src/lang.ts

@ -480,9 +480,6 @@ const lang = {
"Emoji": "Emoji", "Emoji": "Emoji",
"AddContactTitle": "Add Contact", "AddContactTitle": "Add Contact",
"HiddenName": "Deleted Account", "HiddenName": "Deleted Account",
"ActionGroupCallInvited": "un1 invited un2 to the voice chat",
"ActionGroupCallYouInvited": "You invited un2 to the voice chat",
"ActionGroupCallInvitedYou": "un1 invited you to the voice chat",
"Seconds": { "Seconds": {
"one_value": "%1$d second", "one_value": "%1$d second",
"other_value": "%1$d seconds" "other_value": "%1$d seconds"
@ -655,6 +652,9 @@ const lang = {
"Chat.Service.VoiceChatFinished": "%1$@ ended the video chat (%2$@)", "Chat.Service.VoiceChatFinished": "%1$@ ended the video chat (%2$@)",
"Chat.Service.VoiceChatFinishedYou": "You ended the video chat (%@)", "Chat.Service.VoiceChatFinishedYou": "You ended the video chat (%@)",
"Chat.Service.VoiceChatFinished.Channel": "Live Stream ended (%1$@)", "Chat.Service.VoiceChatFinished.Channel": "Live Stream ended (%1$@)",
"Chat.Service.VoiceChatInvitation": "%1$@ invited %2$@ to the [video chat](open)",
"Chat.Service.VoiceChatInvitationByYou": "You invited %1$@ to the [video chat](open)",
"Chat.Service.VoiceChatInvitationForYou": "%1$@ invited you to the [video chat](open)",
"ChatList.Service.VoiceChatScheduled": "%1$@ scheduled a video chat for %2$@", "ChatList.Service.VoiceChatScheduled": "%1$@ scheduled a video chat for %2$@",
"ChatList.Service.VoiceChatScheduledYou": "You scheduled a video chat for %2$@", "ChatList.Service.VoiceChatScheduledYou": "You scheduled a video chat for %2$@",
"Chat.Poll.Unvote": "Retract Vote", "Chat.Poll.Unvote": "Retract Vote",

2
src/langSign.ts

@ -28,7 +28,7 @@ const lang = {
"Login.ContinueOnLanguage": "Continue in English", "Login.ContinueOnLanguage": "Continue in English",
"Login.QR.Title": "Log in to Telegram by QR Code", "Login.QR.Title": "Log in to Telegram by QR Code",
"Login.QR.Help1": "Open Telegram on your phone", "Login.QR.Help1": "Open Telegram on your phone",
"Login.QR.Help2": "Go to **Settings** > **Devices** > **Scan QR**", "Login.QR.Help2": "Go to **Settings** > **Devices** > **Link Desktop Device**",
"Login.QR.Help3": "Point your phone at this screen to confirm login", "Login.QR.Help3": "Point your phone at this screen to confirm login",
"Login.QR.Cancel": "Log in by phone Number", "Login.QR.Cancel": "Log in by phone Number",
"Login.QR.Login": "Log in by QR Code", "Login.QR.Login": "Log in by QR Code",

33
src/lib/appManagers/appMessagesManager.ts

@ -2933,6 +2933,20 @@ export class AppMessagesManager {
return el; return el;
} }
private wrapJoinVoiceChatAnchor(message: Message.messageService) {
const action = message.action as MessageAction.messageActionInviteToGroupCall;
const {onclick, url} = RichTextProcessor.wrapUrl(`tg://voicechat?chat_id=${message.peerId.toChatId()}&id=${action.call.id}&access_hash=${action.call.access_hash}`);
if(!onclick) {
return document.createElement('span');
}
const a = document.createElement('a');
a.href = url;
a.setAttribute('onclick', onclick + '(this)');
return a;
}
public wrapMessageActionTextNew(message: MyMessage, plain: true): string; public wrapMessageActionTextNew(message: MyMessage, plain: true): string;
public wrapMessageActionTextNew(message: MyMessage, plain?: false): HTMLElement; public wrapMessageActionTextNew(message: MyMessage, plain?: false): HTMLElement;
public wrapMessageActionTextNew(message: MyMessage, plain: boolean): HTMLElement | string; public wrapMessageActionTextNew(message: MyMessage, plain: boolean): HTMLElement | string;
@ -2979,16 +2993,7 @@ export class AppMessagesManager {
if(action.duration !== undefined) { if(action.duration !== undefined) {
args.push(formatCallDuration(action.duration, plain)); args.push(formatCallDuration(action.duration, plain));
} else { } else {
const {onclick, url} = RichTextProcessor.wrapUrl(`tg://voicechat?chat_id=${message.peerId.toChatId()}&id=${action.call.id}&access_hash=${action.call.access_hash}`); args.push(this.wrapJoinVoiceChatAnchor(message as any));
if(!onclick) {
args.push(document.createElement('span'));
break;
}
const a = document.createElement('a');
a.href = url;
a.setAttribute('onclick', onclick + '(this)');
args.push(a);
} }
break; break;
@ -2996,15 +3001,15 @@ export class AppMessagesManager {
case 'messageActionInviteToGroupCall': { case 'messageActionInviteToGroupCall': {
const peerIds = [message.fromId, action.users[0].toPeerId()]; const peerIds = [message.fromId, action.users[0].toPeerId()];
let a = 'ActionGroupCall'; let a = 'Chat.Service.VoiceChatInvitation';
const myId = appUsersManager.getSelf().id; const myId = appUsersManager.getSelf().id;
if(peerIds[0] === myId) a += 'You'; if(peerIds[0] === myId) a += 'ByYou';
a += 'Invited'; else if(peerIds[1] === myId) a += 'ForYou';
if(peerIds[1] === myId) a += 'You';
indexOfAndSplice(peerIds, myId); indexOfAndSplice(peerIds, myId);
langPackKey = a as LangPackKey; langPackKey = a as LangPackKey;
args = peerIds.map(peerId => getNameDivHTML(peerId, plain)); args = peerIds.map(peerId => getNameDivHTML(peerId, plain));
args.push(this.wrapJoinVoiceChatAnchor(message as any));
break; break;
} }

21
src/lib/appManagers/appUsersManager.ts

@ -14,7 +14,7 @@ import { filterUnique, indexOfAndSplice } from "../../helpers/array";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
import cleanSearchText from "../../helpers/cleanSearchText"; import cleanSearchText from "../../helpers/cleanSearchText";
import cleanUsername from "../../helpers/cleanUsername"; import cleanUsername from "../../helpers/cleanUsername";
import { tsNow } from "../../helpers/date"; import { formatFullSentTimeRaw, tsNow } from "../../helpers/date";
import { formatPhoneNumber } from "../../helpers/formatPhoneNumber"; import { formatPhoneNumber } from "../../helpers/formatPhoneNumber";
import { safeReplaceObject, isObject } from "../../helpers/object"; import { safeReplaceObject, isObject } from "../../helpers/object";
import { Chat, InputContact, InputMedia, InputPeer, InputUser, User as MTUser, UserProfilePhoto, UserStatus } from "../../layer"; import { Chat, InputContact, InputMedia, InputPeer, InputUser, User as MTUser, UserProfilePhoto, UserStatus } from "../../layer";
@ -581,23 +581,24 @@ export class AppUsersManager {
case 'userStatusOffline': { case 'userStatusOffline': {
const date = user.status.was_online; const date = user.status.was_online;
const now = Date.now() / 1000; const today = new Date();
const now = today.getTime() / 1000 | 0;
if((now - date) < 60) { const diff = now - date;
if(diff < 60) {
key = 'Peer.Status.justNow'; key = 'Peer.Status.justNow';
} else if((now - date) < 3600) { } else if(diff < 3600) {
key = 'Peer.Status.minAgo'; key = 'Peer.Status.minAgo';
const c = (now - date) / 60 | 0; const c = diff / 60 | 0;
args = [c]; args = [c];
} else if(now - date < 86400) { } else if(diff < 86400 && today.getDate() === new Date(date * 1000).getDate()) {
key = 'LastSeen.HoursAgo'; key = 'LastSeen.HoursAgo';
const c = (now - date) / 3600 | 0; const c = diff / 3600 | 0;
args = [c]; args = [c];
} else { } else {
key = 'Peer.Status.LastSeenAt'; key = 'Peer.Status.LastSeenAt';
const d = new Date(date * 1000); const {dateEl, timeEl} = formatFullSentTimeRaw(date);
args = [('0' + d.getDate()).slice(-2) + '.' + ('0' + (d.getMonth() + 1)).slice(-2), args = [dateEl, timeEl];
('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2)];
} }
break; break;

96
src/lib/mtproto/apiManager.ts

@ -30,6 +30,7 @@ import IDBStorage from '../idb';
import CryptoWorker from "../crypto/cryptoworker"; import CryptoWorker from "../crypto/cryptoworker";
import ctx from '../../environment/ctx'; import ctx from '../../environment/ctx';
import noop from '../../helpers/noop'; import noop from '../../helpers/noop';
import Modes from '../../config/modes';
/// #if !MTPROTO_WORKER /// #if !MTPROTO_WORKER
import rootScope from '../rootScope'; import rootScope from '../rootScope';
@ -73,25 +74,39 @@ export class ApiManager {
private cachedNetworkers: { private cachedNetworkers: {
[transportType in TransportType]: { [transportType in TransportType]: {
[connectionType in ConnectionType]: { [connectionType in ConnectionType]: {
[dcId: number]: MTPNetworker[] [dcId: DcId]: MTPNetworker[]
} }
} }
} = {} as any; };
private cachedExportPromise: {[x: number]: Promise<unknown>} = {}; private cachedExportPromise: {[x: number]: Promise<unknown>};
private gettingNetworkers: {[dcIdAndType: string]: Promise<MTPNetworker>} = {}; private gettingNetworkers: {[dcIdAndType: string]: Promise<MTPNetworker>};
private baseDcId: DcId = 0 as DcId; private baseDcId: DcId;
//public telegramMeNotified = false; //public telegramMeNotified = false;
private log: ReturnType<typeof logger> = logger('API'); private log: ReturnType<typeof logger>;
private afterMessageTempIds: { private afterMessageTempIds: {
[tempId: string]: { [tempId: string]: {
messageId: string, messageId: string,
promise: Promise<any> promise: Promise<any>
} }
} = {}; };
private transportType: TransportType;
constructor() {
this.log = logger('API');
this.cachedNetworkers = {} as any;
this.cachedExportPromise = {};
this.gettingNetworkers = {};
this.baseDcId = 0;
this.afterMessageTempIds = {};
this.transportType = Modes.transport;
}
//private lol = false; //private lol = false;
@ -112,6 +127,61 @@ export class ApiManager {
} }
} */ } */
private getTransportType(connectionType: ConnectionType) {
/// #if MTPROTO_HTTP_UPLOAD
// @ts-ignore
const transportType: TransportType = connectionType === 'upload' && IS_SAFARI ? 'https' : 'websocket';
//const transportType: TransportType = connectionType !== 'client' ? 'https' : 'websocket';
/// #else
// @ts-ignore
const transportType: TransportType = this.transportType;
/// #endif
return transportType;
}
private iterateNetworkers(callback: (o: {networker: MTPNetworker, dcId: DcId, connectionType: ConnectionType, transportType: TransportType, index: number, array: MTPNetworker[]}) => void) {
for(const transportType in this.cachedNetworkers) {
const connections = this.cachedNetworkers[transportType as TransportType];
for(const connectionType in connections) {
const dcs = connections[connectionType as ConnectionType];
for(const dcId in dcs) {
const networkers = dcs[dcId as any as DcId];
networkers.forEach((networker, idx, arr) => {
callback({
networker,
dcId: +dcId as DcId,
connectionType: connectionType as ConnectionType,
transportType: transportType as TransportType,
index: idx,
array: arr
});
});
}
}
}
}
private chooseServer(dcId: DcId, connectionType: ConnectionType, transportType: TransportType) {
return dcConfigurator.chooseServer(dcId, connectionType, transportType, connectionType === 'client');
}
public changeTransportType(transportType: TransportType) {
const oldTransportType = this.transportType;
const oldObject = this.cachedNetworkers[oldTransportType];
const newObject = this.cachedNetworkers[transportType];
this.cachedNetworkers[transportType] = oldObject;
this.cachedNetworkers[oldTransportType] = newObject;
this.transportType = transportType;
this.iterateNetworkers((info) => {
const transportType = this.getTransportType(info.connectionType);
const transport = this.chooseServer(info.dcId, info.connectionType, transportType);
info.networker.changeTransport(transport);
});
}
public async getBaseDcId() { public async getBaseDcId() {
if(this.baseDcId) { if(this.baseDcId) {
return this.baseDcId; return this.baseDcId;
@ -203,15 +273,7 @@ export class ApiManager {
const connectionType: ConnectionType = options.fileDownload ? 'download' : (options.fileUpload ? 'upload' : 'client'); const connectionType: ConnectionType = options.fileDownload ? 'download' : (options.fileUpload ? 'upload' : 'client');
//const connectionType: ConnectionType = 'client'; //const connectionType: ConnectionType = 'client';
/// #if MTPROTO_HTTP_UPLOAD const transportType = this.getTransportType(connectionType);
// @ts-ignore
const transportType: TransportType = connectionType === 'upload' && IS_SAFARI ? 'https' : 'websocket';
//const transportType: TransportType = connectionType !== 'client' ? 'https' : 'websocket';
/// #else
// @ts-ignore
const transportType = 'websocket';
/// #endif
if(!this.cachedNetworkers.hasOwnProperty(transportType)) { if(!this.cachedNetworkers.hasOwnProperty(transportType)) {
this.cachedNetworkers[transportType] = { this.cachedNetworkers[transportType] = {
client: {}, client: {},
@ -250,9 +312,9 @@ export class ApiManager {
const ak: DcAuthKey = `dc${dcId}_auth_key` as any; const ak: DcAuthKey = `dc${dcId}_auth_key` as any;
const ss: DcServerSalt = `dc${dcId}_server_salt` as any; const ss: DcServerSalt = `dc${dcId}_server_salt` as any;
const transport = this.chooseServer(dcId, connectionType, transportType);
return this.gettingNetworkers[getKey] = Promise.all([ak, ss].map(key => sessionStorage.get(key))) return this.gettingNetworkers[getKey] = Promise.all([ak, ss].map(key => sessionStorage.get(key)))
.then(async([authKeyHex, serverSaltHex]) => { .then(async([authKeyHex, serverSaltHex]) => {
const transport = dcConfigurator.chooseServer(dcId, connectionType, transportType, connectionType === 'client');
let networker: MTPNetworker; let networker: MTPNetworker;
if(authKeyHex && authKeyHex.length === 512) { if(authKeyHex && authKeyHex.length === 512) {
if(!serverSaltHex || serverSaltHex.length !== 16) { if(!serverSaltHex || serverSaltHex.length !== 16) {

61
src/lib/mtproto/authorizer.ts

@ -21,7 +21,8 @@ import { bytesCmp, bytesToHex, bytesFromHex, bytesXor } from "../../helpers/byte
import DEBUG from "../../config/debug"; import DEBUG from "../../config/debug";
import { cmp, int2bigInt, one, pow, str2bigInt, sub } from "../../vendor/leemon"; import { cmp, int2bigInt, one, pow, str2bigInt, sub } from "../../vendor/leemon";
import { addPadding } from "./bin_utils"; import { addPadding } from "./bin_utils";
import { Awaited } from "../../types"; import { Awaited, DcId } from "../../types";
import { ApiError } from "./apiManager";
/* let fNewNonce: any = bytesFromHex('8761970c24cb2329b5b2459752c502f3057cb7e8dbab200e526e8767fdc73b3c').reverse(); /* let fNewNonce: any = bytesFromHex('8761970c24cb2329b5b2459752c502f3057cb7e8dbab200e526e8767fdc73b3c').reverse();
let fNonce: any = bytesFromHex('b597720d11faa5914ef485c529cde414').reverse(); let fNonce: any = bytesFromHex('b597720d11faa5914ef485c529cde414').reverse();
@ -66,8 +67,6 @@ type AuthOptions = {
localTime?: number, localTime?: number,
serverTime?: any, serverTime?: any,
localTry?: number
}; };
type ResPQ = { type ResPQ = {
@ -100,16 +99,17 @@ type req_DH_params = {
export class Authorizer { export class Authorizer {
private cached: { private cached: {
[dcId: number]: Promise<AuthOptions> [dcId: DcId]: Promise<AuthOptions>
} = {}; };
private log: ReturnType<typeof logger>; private log: ReturnType<typeof logger>;
constructor() { constructor() {
this.cached = {};
this.log = logger(`AUTHORIZER`, LogTypes.Error | LogTypes.Log); this.log = logger(`AUTHORIZER`, LogTypes.Error | LogTypes.Log);
} }
private sendPlainRequest(dcId: number, requestArray: Uint8Array) { private sendPlainRequest(dcId: DcId, requestArray: Uint8Array) {
const requestLength = requestArray.byteLength; const requestLength = requestArray.byteLength;
const header = new TLSerialization(); const header = new TLSerialization();
@ -571,39 +571,38 @@ export class Authorizer {
} }
} }
public async auth(dcId: number): Promise<AuthOptions> { public auth(dcId: DcId) {
if(dcId in this.cached) { let promise = this.cached[dcId];
return this.cached[dcId]; if(promise) {
return promise;
} }
const nonce = /* fNonce ? fNonce : */new Uint8Array(16).randomize();
/* const nonce = new Array(16);
MTProto.secureRandom.nextBytes(nonce); */
if(!dcConfigurator.chooseServer(dcId)) { if(!dcConfigurator.chooseServer(dcId)) {
throw new Error('[MT] No server found for dc ' + dcId); throw new Error('[MT] No server found for dc ' + dcId);
} }
// await new Promise((resolve) => setTimeout(resolve, 2e3)); promise = new Promise(async(resolve, reject) => {
let error: ApiError;
const auth: AuthOptions = {dcId, nonce, localTry: 1}; let _try = 1;
while(_try++ <= 3) {
try { try {
const promise = this.sendReqPQ(auth); const auth: AuthOptions = {
this.cached[dcId] = promise; dcId,
return await promise; nonce: new Uint8Array(16).randomize()
} catch(err) { };
if(/* err.originalError === -404 && */auth.localTry <= 3) {
return this.sendReqPQ({ const promise = this.sendReqPQ(auth);
dcId: auth.dcId, resolve(await promise);
nonce: new Uint8Array(16).randomize(), return;
localTry: auth.localTry + 1 } catch(err) {
}); error = err;
}
} }
delete this.cached[dcId]; reject(error);
throw err; });
}
return this.cached[dcId] = promise;
} }
} }

43
src/lib/mtproto/dcConfigurator.ts

@ -11,18 +11,20 @@
import MTTransport, { MTConnectionConstructable } from './transports/transport'; import MTTransport, { MTConnectionConstructable } from './transports/transport';
import Modes from '../../config/modes'; import Modes from '../../config/modes';
import { indexOfAndSplice } from '../../helpers/array';
import App from '../../config/app';
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HAS_HTTP
import HTTP from './transports/http'; import HTTP from './transports/http';
/// #endif /// #endif
/// #if !MTPROTO_HTTP /// #if MTPROTO_HAS_WS
import Socket from './transports/websocket'; import Socket from './transports/websocket';
import TcpObfuscated from './transports/tcpObfuscated'; import TcpObfuscated from './transports/tcpObfuscated';
import { IS_SAFARI } from '../../environment/userAgent'; import { IS_SAFARI } from '../../environment/userAgent';
import { IS_WEB_WORKER } from '../../helpers/context'; import { IS_WEB_WORKER } from '../../helpers/context';
import SocketProxied from './transports/socketProxied'; import SocketProxied from './transports/socketProxied';
import App from '../../config/app'; import { DcId } from '../../types';
/// #endif /// #endif
export type TransportType = 'websocket' | 'https' | 'http'; export type TransportType = 'websocket' | 'https' | 'http';
@ -30,7 +32,7 @@ export type ConnectionType = 'client' | 'download' | 'upload';
type Servers = { type Servers = {
[transportType in TransportType]: { [transportType in TransportType]: {
[connectionType in ConnectionType]: { [connectionType in ConnectionType]: {
[dcId: number]: MTTransport[] [dcId: DcId]: MTTransport[]
} }
} }
}; };
@ -56,8 +58,8 @@ export class DcConfigurator {
public chosenServers: Servers = {} as any; public chosenServers: Servers = {} as any;
/// #if !MTPROTO_HTTP /// #if MTPROTO_HAS_WS
private transportSocket = (dcId: number, connectionType: ConnectionType, suffix: string) => { private transportSocket = (dcId: DcId, connectionType: ConnectionType, suffix: string) => {
const path = 'apiws' + TEST_SUFFIX; const path = 'apiws' + TEST_SUFFIX;
const chosenServer = `wss://${App.suffix.toLowerCase()}ws${dcId}${suffix}.web.telegram.org/${path}`; const chosenServer = `wss://${App.suffix.toLowerCase()}ws${dcId}${suffix}.web.telegram.org/${path}`;
const logSuffix = connectionType === 'upload' ? '-U' : connectionType === 'download' ? '-D' : ''; const logSuffix = connectionType === 'upload' ? '-U' : connectionType === 'download' ? '-D' : '';
@ -70,25 +72,33 @@ export class DcConfigurator {
}; };
/// #endif /// #endif
/// #if MTPROTO_HTTP_UPLOAD || MTPROTO_HTTP /// #if MTPROTO_HAS_HTTP
private transportHTTP = (dcId: number, connectionType: ConnectionType, suffix: string) => { private transportHTTP = (dcId: DcId, connectionType: ConnectionType, suffix: string) => {
let chosenServer: string;
if(Modes.ssl || !Modes.http) { if(Modes.ssl || !Modes.http) {
const subdomain = this.sslSubdomains[dcId - 1] + (connectionType !== 'client' ? '-1' : ''); const subdomain = this.sslSubdomains[dcId - 1] + (connectionType !== 'client' ? '-1' : '');
const path = Modes.test ? 'apiw_test1' : 'apiw1'; const path = Modes.test ? 'apiw_test1' : 'apiw1';
const chosenServer = 'https://' + subdomain + '.web.telegram.org/' + path; chosenServer = 'https://' + subdomain + '.web.telegram.org/' + path;
return new HTTP(dcId, chosenServer);
} else { } else {
for(let dcOption of this.dcOptions) { for(let dcOption of this.dcOptions) {
if(dcOption.id === dcId) { if(dcOption.id === dcId) {
const chosenServer = 'http://' + dcOption.host + (dcOption.port !== 80 ? ':' + dcOption.port : '') + '/apiw1'; chosenServer = 'http://' + dcOption.host + (dcOption.port !== 80 ? ':' + dcOption.port : '') + '/apiw1';
return new HTTP(dcId, chosenServer); break;
} }
} }
} }
const logSuffix = connectionType === 'upload' ? '-U' : connectionType === 'download' ? '-D' : '';
return new HTTP(dcId, chosenServer, logSuffix);
}; };
/// #endif /// #endif
public chooseServer(dcId: number, connectionType: ConnectionType = 'client', transportType: TransportType = 'websocket', reuse = true) { public chooseServer(
dcId: DcId,
connectionType: ConnectionType = 'client',
transportType: TransportType = Modes.transport,
reuse = true
) {
/* if(transportType === 'websocket' && !Modes.multipleConnections) { /* if(transportType === 'websocket' && !Modes.multipleConnections) {
connectionType = 'client'; connectionType = 'client';
} */ } */
@ -114,7 +124,7 @@ export class DcConfigurator {
const suffix = connectionType === 'client' ? '' : '-1'; const suffix = connectionType === 'client' ? '' : '-1';
/// #if MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HAS_WS && MTPROTO_HAS_HTTP
transport = (transportType === 'websocket' ? this.transportSocket : this.transportHTTP)(dcId, connectionType, suffix); transport = (transportType === 'websocket' ? this.transportSocket : this.transportHTTP)(dcId, connectionType, suffix);
/// #elif !MTPROTO_HTTP /// #elif !MTPROTO_HTTP
transport = this.transportSocket(dcId, connectionType, suffix); transport = this.transportSocket(dcId, connectionType, suffix);
@ -145,10 +155,7 @@ export class DcConfigurator {
for(const dcId in obj[transportType][connectionType]) { for(const dcId in obj[transportType][connectionType]) {
// @ts-ignore // @ts-ignore
const transports: T[] = obj[transportType][connectionType][dcId]; const transports: T[] = obj[transportType][connectionType][dcId];
const idx = transports.indexOf(transport); indexOfAndSplice(transports, transport);
if(idx !== -1) {
transports.splice(idx, 1);
}
} }
} }
} }

252
src/lib/mtproto/networker.ts

@ -27,7 +27,7 @@ import DEBUG from '../../config/debug';
import Modes from '../../config/modes'; import Modes from '../../config/modes';
import noop from '../../helpers/noop'; import noop from '../../helpers/noop';
/// #if MTPROTO_HTTP_UPLOAD || MTPROTO_HTTP /// #if MTPROTO_HAS_HTTP
import HTTP from './transports/http'; import HTTP from './transports/http';
/// #endif /// #endif
@ -35,6 +35,7 @@ import type TcpObfuscated from './transports/tcpObfuscated';
import { bigInt2str, rightShift_, str2bigInt } from '../../vendor/leemon'; import { bigInt2str, rightShift_, str2bigInt } from '../../vendor/leemon';
import { forEachReverse } from '../../helpers/array'; import { forEachReverse } from '../../helpers/array';
import { ConnectionStatus } from './connectionStatus'; import { ConnectionStatus } from './connectionStatus';
import ctx from '../../environment/ctx';
//console.error('networker included!', new Error().stack); //console.error('networker included!', new Error().stack);
@ -95,21 +96,22 @@ export default class MTPNetworker {
private pendingMessages: {[msgId: MTLong]: number} = {}; private pendingMessages: {[msgId: MTLong]: number} = {};
private pendingAcks: Array<MTLong> = []; private pendingAcks: Array<MTLong> = [];
private pendingResends: Array<MTLong> = []; private pendingResends: Array<MTLong> = [];
public connectionInited = false; public connectionInited: boolean;
private nextReqTimeout: number; private nextReqTimeout: number;
private nextReq: number = 0; private nextReq: number = 0;
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HAS_HTTP
//private longPollInt: number; private longPollInterval: number;
private longPollPending = 0; private longPollPending: number;
private checkConnectionTimeout: number; private checkConnectionTimeout: number;
private checkConnectionPeriod = 0; private checkConnectionPeriod = 0;
private sleepAfter = 0; private sleepAfter: number;
private offline = false; private offline = false;
private sendingLongPoll: boolean;
/// #endif /// #endif
private seqNo: number = 0; private seqNo: number;
private prevSessionId: Uint8Array; private prevSessionId: Uint8Array;
private sessionId: Uint8Array; private sessionId: Uint8Array;
private serverSalt: Uint8Array; private serverSalt: Uint8Array;
@ -133,14 +135,22 @@ export default class MTPNetworker {
public onDrain: () => void; public onDrain: () => void;
private onDrainTimeout: number; private onDrainTimeout: number;
public transport: MTTransport;
//private disconnectDelay: number; //private disconnectDelay: number;
//private pingPromise: CancellablePromise<any>; //private pingPromise: CancellablePromise<any>;
//public onConnectionStatusChange: (online: boolean) => void; //public onConnectionStatusChange: (online: boolean) => void;
//private debugRequests: Array<{before: Uint8Array, after: Uint8Array}> = []; //private debugRequests: Array<{before: Uint8Array, after: Uint8Array}> = [];
constructor(public dcId: number, private authKey: Uint8Array, private authKeyId: Uint8Array, constructor(
serverSalt: Uint8Array, public transport: MTTransport, options: InvokeApiOptions = {}) { public dcId: number,
private authKey: Uint8Array,
private authKeyId: Uint8Array,
serverSalt: Uint8Array,
transport: MTTransport,
options: InvokeApiOptions = {}
) {
this.authKeyUint8 = convertToUint8Array(this.authKey); this.authKeyUint8 = convertToUint8Array(this.authKey);
this.serverSalt = convertToUint8Array(serverSalt); this.serverSalt = convertToUint8Array(serverSalt);
@ -151,7 +161,7 @@ export default class MTPNetworker {
const suffix = this.isFileUpload ? '-U' : this.isFileDownload ? '-D' : ''; const suffix = this.isFileUpload ? '-U' : this.isFileDownload ? '-D' : '';
this.name = 'NET-' + dcId + suffix; this.name = 'NET-' + dcId + suffix;
//this.log = logger(this.name, this.upload && this.dcId === 2 ? LogLevels.debug | LogLevels.warn | LogLevels.log | LogLevels.error : LogLevels.error); //this.log = logger(this.name, this.upload && this.dcId === 2 ? LogLevels.debug | LogLevels.warn | LogLevels.log | LogLevels.error : LogLevels.error);
this.log = logger(this.name, LogTypes.Log | /* LogTypes.Debug | */LogTypes.Error | LogTypes.Warn, undefined); this.log = logger(this.name, LogTypes.Log | LogTypes.Debug | LogTypes.Error | LogTypes.Warn, undefined);
this.log('constructor'/* , this.authKey, this.authKeyID, this.serverSalt */); this.log('constructor'/* , this.authKey, this.authKeyID, this.serverSalt */);
// Test resend after bad_server_salt // Test resend after bad_server_salt
@ -168,22 +178,7 @@ export default class MTPNetworker {
// rootScope.offlineConnecting = true */ // rootScope.offlineConnecting = true */
// } // }
/// #if MTPROTO_HTTP_UPLOAD this.changeTransport(transport);
if(this.transport instanceof HTTP) {
/* this.longPollInt = */setInterval(this.checkLongPoll, 10000);
this.checkLongPoll();
} else {
(this.transport as TcpObfuscated).networker = this;
}
/// #elif MTPROTO_HTTP
//if(this.transport instanceof HTTP) {
/* this.longPollInt = */setInterval(this.checkLongPoll, 10000);
this.checkLongPoll();
/// #else
//} else {
(this.transport as TcpObfuscated).networker = this;
//}
/// #endif
// * handle outcoming dead socket, server will close the connection // * handle outcoming dead socket, server will close the connection
// if((this.transport as TcpObfuscated).networker) { // if((this.transport as TcpObfuscated).networker) {
@ -191,10 +186,6 @@ export default class MTPNetworker {
// //setInterval(this.sendPingDelayDisconnect, (this.disconnectDelay - 5) * 1000); // //setInterval(this.sendPingDelayDisconnect, (this.disconnectDelay - 5) * 1000);
// this.sendPingDelayDisconnect(); // this.sendPingDelayDisconnect();
// } // }
if((this.transport as TcpObfuscated).connected) {
this.setConnectionStatus(ConnectionStatus.Connected);
}
} }
private updateSession() { private updateSession() {
@ -232,9 +223,9 @@ export default class MTPNetworker {
sentMessage.msg_id = timeManager.generateId(); sentMessage.msg_id = timeManager.generateId();
sentMessage.seq_no = this.generateSeqNo(sentMessage.notContentRelated || sentMessage.container); sentMessage.seq_no = this.generateSeqNo(sentMessage.notContentRelated || sentMessage.container);
/* if(DEBUG) { if(this.debug) {
this.log('updateSentMessage', sentMessage.msg_id, sentMessageId); this.log(`updateSentMessage, old=${sentMessageId}, new=${sentMessage.msg_id}`);
} */ }
this.sentMessages[sentMessage.msg_id] = sentMessage; this.sentMessages[sentMessage.msg_id] = sentMessage;
delete this.sentMessages[sentMessageId]; delete this.sentMessages[sentMessageId];
@ -372,9 +363,57 @@ export default class MTPNetworker {
return this.pushMessage(message, options); return this.pushMessage(message, options);
} }
public changeTransport(transport?: MTTransport) {
const oldTransport = this.transport;
if(oldTransport) {
if((oldTransport as TcpObfuscated).destroy) {
(oldTransport as TcpObfuscated).destroy();
}
if(this.nextReqTimeout) {
clearTimeout(this.nextReqTimeout);
this.nextReqTimeout = 0;
this.nextReq = 0;
}
/// #if MTPROTO_HAS_HTTP
if(this.longPollInterval !== undefined) {
clearInterval(this.longPollInterval);
this.longPollInterval = undefined;
}
if(this.checkConnectionTimeout !== undefined) {
clearTimeout(this.checkConnectionTimeout);
this.checkConnectionTimeout = undefined;
}
/// #endif
}
this.transport = transport;
if(!transport) {
return;
}
/// #if MTPROTO_HAS_HTTP
/// #if MTPROTO_HAS_WS
if(transport instanceof HTTP) {
/// #endif
this.longPollInterval = ctx.setInterval(this.checkLongPoll, 10000);
this.checkLongPoll();
/// #if MTPROTO_HAS_WS
}
/// #endif
/// #endif
transport.networker = this;
if((transport as TcpObfuscated).connected) {
this.setConnectionStatus(ConnectionStatus.Connected);
}
}
public destroy() { public destroy() {
//assumeType<TcpObfuscated>(this.transport); this.changeTransport();
(this.transport as TcpObfuscated).destroy();
} }
public forceReconnectTimeout() { public forceReconnectTimeout() {
@ -496,13 +535,14 @@ export default class MTPNetworker {
// }); // });
// }; // };
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HAS_HTTP
private checkLongPoll = () => { private checkLongPoll = () => {
const isClean = this.cleanupSent(); const isClean = this.cleanupSent();
//this.log.error('Check lp', this.longPollPending, this.dcId, isClean, this); //this.log.error('Check lp', this.longPollPending, this.dcId, isClean, this);
if((this.longPollPending && Date.now() < this.longPollPending) || if((this.longPollPending && Date.now() < this.longPollPending) ||
this.offline || this.offline ||
this.isStopped()) { this.isStopped() ||
this.isFileNetworker) {
//this.log('No lp this time'); //this.log('No lp this time');
return false; return false;
} }
@ -510,7 +550,6 @@ export default class MTPNetworker {
sessionStorage.get('dc').then((baseDcId) => { sessionStorage.get('dc').then((baseDcId) => {
if(isClean && ( if(isClean && (
baseDcId !== this.dcId || baseDcId !== this.dcId ||
this.isFileNetworker ||
(this.sleepAfter && Date.now() > this.sleepAfter) (this.sleepAfter && Date.now() > this.sleepAfter)
)) { )) {
//console.warn(dT(), 'Send long-poll for DC is delayed', this.dcId, this.sleepAfter); //console.warn(dT(), 'Send long-poll for DC is delayed', this.dcId, this.sleepAfter);
@ -522,10 +561,12 @@ export default class MTPNetworker {
}; };
private sendLongPoll() { private sendLongPoll() {
if(this.sendingLongPoll) return;
this.sendingLongPoll = true;
const maxWait = 25000; const maxWait = 25000;
this.longPollPending = Date.now() + maxWait; this.longPollPending = Date.now() + maxWait;
//this.log('Set lp', this.longPollPending, tsNow()) this.debug && this.log.debug('sendLongPoll', this.longPollPending);
this.wrapMtpCall('http_wait', { this.wrapMtpCall('http_wait', {
max_delay: 500, max_delay: 500,
@ -535,19 +576,19 @@ export default class MTPNetworker {
noResponse: true, noResponse: true,
longPoll: true longPoll: true
}).then(() => { }).then(() => {
this.longPollPending = 0; this.longPollPending = undefined;
setTimeout(this.checkLongPoll, 0); setTimeout(this.checkLongPoll, 0);
}, (error: ErrorEvent) => { }, (error: ErrorEvent) => {
this.log('Long-poll failed', error); this.log('Long-poll failed', error);
}).finally(() => {
this.sendingLongPoll = undefined;
}); });
} }
private checkConnection = (event: Event | string) => { private checkConnection = (event: Event | string) => {
/* rootScope.offlineConnecting = true */ this.debug && this.log('Check connection', event);
this.log('Check connection', event);
clearTimeout(this.checkConnectionTimeout); clearTimeout(this.checkConnectionTimeout);
this.checkConnectionTimeout = 0; this.checkConnectionTimeout = undefined;
const serializer = new TLSerialization({mtproto: true}); const serializer = new TLSerialization({mtproto: true});
const pingId = randomLong(); const pingId = randomLong();
@ -562,16 +603,12 @@ export default class MTPNetworker {
body: serializer.getBytes(true) body: serializer.getBytes(true)
}; };
this.sendEncryptedRequest(pingMessage).then((result) => { this.sendEncryptedRequest(pingMessage).then(() => {
/* delete rootScope.offlineConnecting */
this.toggleOffline(false); this.toggleOffline(false);
}, () => { }, () => {
this.log('Delay ', this.checkConnectionPeriod * 1000); this.debug && this.log('Delay ', this.checkConnectionPeriod * 1000);
this.checkConnectionTimeout = setTimeout(this.checkConnection, this.checkConnectionPeriod * 1000 | 0); this.checkConnectionTimeout = ctx.setTimeout(this.checkConnection, this.checkConnectionPeriod * 1000 | 0);
this.checkConnectionPeriod = Math.min(60, this.checkConnectionPeriod * 1.5); this.checkConnectionPeriod = Math.min(60, this.checkConnectionPeriod * 1.5);
/* setTimeout(function() {
delete rootScope.offlineConnecting
}, 1000); */
}); });
}; };
@ -582,8 +619,6 @@ export default class MTPNetworker {
} }
this.offline = enabled; this.offline = enabled;
/* rootScope.offline = enabled;
rootScope.offlineConnecting = false; */
if(this.offline) { if(this.offline) {
clearTimeout(this.nextReqTimeout); clearTimeout(this.nextReqTimeout);
@ -594,7 +629,7 @@ export default class MTPNetworker {
this.checkConnectionPeriod = 0; this.checkConnectionPeriod = 0;
} }
this.checkConnectionTimeout = setTimeout(this.checkConnection, this.checkConnectionPeriod * 1000 | 0); this.checkConnectionTimeout = ctx.setTimeout(this.checkConnection, this.checkConnectionPeriod * 1000 | 0);
this.checkConnectionPeriod = Math.min(30, (1 + this.checkConnectionPeriod) * 1.5); this.checkConnectionPeriod = Math.min(30, (1 + this.checkConnectionPeriod) * 1.5);
/// #if !MTPROTO_WORKER /// #if !MTPROTO_WORKER
@ -612,16 +647,14 @@ export default class MTPNetworker {
/// #endif /// #endif
clearTimeout(this.checkConnectionTimeout); clearTimeout(this.checkConnectionTimeout);
this.checkConnectionTimeout = 0; this.checkConnectionTimeout = undefined;
} }
} }
private handleSentEncryptedRequestHTTP(promise: ReturnType<MTPNetworker['sendEncryptedRequest']>, message: MTMessage, noResponseMsgs: string[]) { private handleSentEncryptedRequestHTTP(promise: ReturnType<MTPNetworker['sendEncryptedRequest']>, message: MTMessage, noResponseMsgs: string[]) {
promise promise.then((result) => {
.then((result) => {
this.toggleOffline(false); this.toggleOffline(false);
// this.log('parse for', message) // this.log('parse for', message);
this.parseResponse(result).then((response) => { this.parseResponse(result).then((response) => {
if(Modes.debug) { if(Modes.debug) {
this.log.debug('Server response', response); this.log.debug('Server response', response);
@ -645,7 +678,7 @@ export default class MTPNetworker {
this.log.error('Encrypted request failed', error, message); this.log.error('Encrypted request failed', error, message);
if(message.container) { if(message.container) {
message.inner.forEach((msgId: string) => { message.inner.forEach((msgId) => {
this.pendingMessages[msgId] = 0; this.pendingMessages[msgId] = 0;
}); });
@ -661,7 +694,7 @@ export default class MTPNetworker {
delete this.pendingMessages[msgId]; delete this.pendingMessages[msgId];
deferred.reject(); deferred.reject();
} }
}) });
this.toggleOffline(true); this.toggleOffline(true);
}); });
@ -732,7 +765,7 @@ export default class MTPNetworker {
public setDrainTimeout() { public setDrainTimeout() {
if(!this.activeRequests && this.onDrain && this.onDrainTimeout === undefined) { if(!this.activeRequests && this.onDrain && this.onDrainTimeout === undefined) {
this.onDrainTimeout = self.setTimeout(() => { this.onDrainTimeout = ctx.setTimeout(() => {
this.onDrainTimeout = undefined; this.onDrainTimeout = undefined;
this.log('drain'); this.log('drain');
this.onDrain(); this.onDrain();
@ -890,7 +923,7 @@ export default class MTPNetworker {
//const currentTime = Date.now(); //const currentTime = Date.now();
let messagesByteLen = 0; let messagesByteLen = 0;
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HAS_HTTP
let hasApiCall = false; let hasApiCall = false;
let hasHttpWait = false; let hasHttpWait = false;
/// #endif /// #endif
@ -923,7 +956,7 @@ export default class MTPNetworker {
messages.push(message); messages.push(message);
messagesByteLen += messageByteLength; messagesByteLen += messageByteLength;
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HAS_HTTP
if(message.isAPI) { if(message.isAPI) {
hasApiCall = true; hasApiCall = true;
} else if(message.longPoll) { } else if(message.longPoll) {
@ -940,10 +973,10 @@ export default class MTPNetworker {
//} //}
} }
/// #if MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HAS_HTTP
/// #if MTPROTO_HAS_WS
if(this.transport instanceof HTTP) if(this.transport instanceof HTTP)
/// #endif /// #endif
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD
if(hasApiCall && !hasHttpWait) { if(hasApiCall && !hasHttpWait) {
const serializer = new TLSerialization({mtproto: true}); const serializer = new TLSerialization({mtproto: true});
serializer.storeMethod('http_wait', { serializer.storeMethod('http_wait', {
@ -965,7 +998,7 @@ export default class MTPNetworker {
return; return;
} }
/// #if MTPROTO_HTTP_UPLOAD || MTPROTO_HTTP /// #if MTPROTO_HAS_HTTP
const noResponseMsgs: Array<string> = messages.filter(message => message.noResponse).map(message => message.msg_id); const noResponseMsgs: Array<string> = messages.filter(message => message.noResponse).map(message => message.msg_id);
/// #endif /// #endif
@ -982,18 +1015,18 @@ export default class MTPNetworker {
const promise = this.sendEncryptedRequest(outMessage); const promise = this.sendEncryptedRequest(outMessage);
/// #if MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HAS_HTTP
if(!(this.transport instanceof HTTP)) { /// #if MTPROTO_HAS_WS
//if(noResponseMsgs.length) this.log.error('noResponseMsgs length!', noResponseMsgs); if(this.transport instanceof HTTP)
this.cleanupSent(); // ! WARNING /// #endif
} else {
this.handleSentEncryptedRequestHTTP(promise, outMessage, noResponseMsgs);
}
/// #elif !MTPROTO_HTTP
this.cleanupSent(); // ! WARNING
/// #else
this.handleSentEncryptedRequestHTTP(promise, outMessage, noResponseMsgs); this.handleSentEncryptedRequestHTTP(promise, outMessage, noResponseMsgs);
//} /// #endif
/// #if MTPROTO_HAS_WS
/// #if MTPROTO_HAS_HTTP
if(!(this.transport instanceof HTTP))
/// #endif
this.cleanupSent(); // ! WARNING
/// #endif /// #endif
if(lengthOverflow) { if(lengthOverflow) {
@ -1157,10 +1190,14 @@ export default class MTPNetworker {
this.debug && this.log.debug('sendEncryptedRequest: launching message into space:', message, [message.msg_id].concat(message.inner || [])); this.debug && this.log.debug('sendEncryptedRequest: launching message into space:', message, [message.msg_id].concat(message.inner || []));
const promise: Promise<Uint8Array> = this.transport.send(requestData) as any; const promise: Promise<Uint8Array> = this.transport.send(requestData) as any;
/// #if !MTPROTO_HTTP && !MTPROTO_HTTP_UPLOAD
/// #if !MTPROTO_HAS_HTTP
return promise; return promise;
/// #else /// #else
/// #if MTPROTO_HAS_WS
if(!(this.transport instanceof HTTP)) return promise; if(!(this.transport instanceof HTTP)) return promise;
/// #endif
const baseError = { const baseError = {
code: 406, code: 406,
@ -1322,14 +1359,19 @@ export default class MTPNetworker {
return; return;
} */ } */
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HAS_HTTP
if(!(this.transport instanceof HTTP)) { /// #if MTPROTO_HAS_WS
this.performScheduledRequest(); if(this.transport instanceof HTTP) {
return; /// #endif
} else if(this.offline) { if(this.offline) {
this.checkConnection('forced schedule'); this.checkConnection('forced schedule');
}
delay ||= 0; // set zero timeout to pack other messages too
/// #if MTPROTO_HAS_WS
} }
/// #endif /// #endif
/// #endif
const nextReq = Date.now() + (delay || 0); const nextReq = Date.now() + (delay || 0);
if(this.nextReq && (delay === undefined || this.nextReq <= nextReq)) { if(this.nextReq && (delay === undefined || this.nextReq <= nextReq)) {
@ -1354,23 +1396,22 @@ export default class MTPNetworker {
this.nextReqTimeout = 0; this.nextReqTimeout = 0;
this.nextReq = 0; this.nextReq = 0;
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HAS_HTTP
/// #if MTPROTO_HAS_WS
if(this.transport instanceof HTTP)
/// #endif
if(this.offline) { if(this.offline) {
//this.log('Cancel scheduled'); //this.log('Cancel scheduled');
return; return;
} }
/// #else /// #endif
/* if(!this.isOnline) {
return;
} */
this.performScheduledRequest(); this.performScheduledRequest();
/// #endif
}; };
this.nextReq = nextReq; this.nextReq = nextReq;
if(delay) { if(delay !== undefined) {
this.nextReqTimeout = self.setTimeout(cb, delay); this.nextReqTimeout = self.setTimeout(cb, delay);
} else { } else {
cb(); cb();
@ -1381,11 +1422,16 @@ export default class MTPNetworker {
// this.log('ack message', msgID) // this.log('ack message', msgID)
this.pendingAcks.push(msgId); this.pendingAcks.push(msgId);
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD let delay: number;
this.scheduleRequest(30000);
/// #else /// #if MTPROTO_HAS_HTTP
this.scheduleRequest(); /// #if MTPROTO_HAS_WS
if(this.transport instanceof HTTP)
/// #endif
delay = 30000;
/// #endif /// #endif
this.scheduleRequest(delay);
} }
private reqResendMessage(msgId: MTLong) { private reqResendMessage(msgId: MTLong) {
@ -1680,15 +1726,13 @@ export default class MTPNetworker {
break; break;
} }
case 'pong': { // * https://core.telegram.org/mtproto/service_messages#ping-messages-pingpong - These messages doesn't require acknowledgments case 'pong': { // * https://core.telegram.org/mtproto/service_messages#ping-messages-pingpong - These messages don't require acknowledgments
if((this.transport as TcpObfuscated).networker) { const sentMessageId = message.msg_id;
const sentMessageId = message.msg_id; const sentMessage = this.sentMessages[sentMessageId];
const sentMessage = this.sentMessages[sentMessageId];
if(sentMessage) { if(sentMessage) {
sentMessage.deferred.resolve(message); sentMessage.deferred.resolve(message);
delete this.sentMessages[sentMessageId]; delete this.sentMessages[sentMessageId];
}
} }
break; break;

25
src/lib/mtproto/transports/controller.ts

@ -0,0 +1,25 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { TransportType } from "../dcConfigurator";
export class MTTransportController {
private opened: Map<TransportType, number>;
constructor() {
this.opened = new Map();
}
public setTransportOpened(type: TransportType, value: boolean) {
let length = this.opened.get(type) || 0;
length += value ? 1 : -1;
this.opened.set(type, length);
}
}
export default new MTTransportController();

72
src/lib/mtproto/transports/http.ts

@ -4,28 +4,84 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import { pause } from '../../../helpers/schedulers/pause';
import { DcId } from '../../../types';
import { logger } from '../../logger';
import type MTPNetworker from '../networker';
import MTTransport from './transport'; import MTTransport from './transport';
export default class HTTP implements MTTransport { export default class HTTP implements MTTransport {
constructor(protected dcId: number, protected url: string) { public networker: MTPNetworker;
} private log: ReturnType<typeof logger>;
private pending: Array<{
resolve: (body: Uint8Array) => void,
reject: any,
body: Uint8Array
}> = [];
private releasing: boolean;
public send(data: Uint8Array) { constructor(protected dcId: DcId, protected url: string, logSuffix: string) {
return fetch(this.url, {method: 'POST', body: data}).then(response => { this.log = logger(`HTTP-${dcId}` + logSuffix);
//console.log('http response', response/* , response.arrayBuffer() */); }
private _send(body: Uint8Array) {
return fetch(this.url, {method: 'POST', body}).then(response => {
if(response.status !== 200) { if(response.status !== 200) {
response.arrayBuffer().then(buffer => { response.arrayBuffer().then(buffer => {
console.log('not 200', this.log.error('not 200',
new TextDecoder("utf-8").decode(new Uint8Array(buffer))); new TextDecoder("utf-8").decode(new Uint8Array(buffer)));
}) });
throw response; throw response;
} }
// * test resending by dropping random request
// if(Math.random() > .5) {
// throw 'asd';
// }
return response.arrayBuffer().then(buffer => { return response.arrayBuffer().then(buffer => {
return new Uint8Array(buffer); return new Uint8Array(buffer);
}); });
}); });
} }
public send(body: Uint8Array) {
if(this.networker) {
return this._send(body);
} else {
const promise = new Promise<typeof body>((resolve, reject) => {
this.pending.push({resolve, reject, body});
});
this.releasePending();
return promise;
}
}
private async releasePending() {
if(this.releasing) return;
this.releasing = true;
// this.log('-> messages to send:', this.pending.length);
for(let i = 0; i < this.pending.length; ++i) {
const pending = this.pending[i];
const {body, resolve} = pending;
try {
const result = await this._send(body);
resolve(result);
this.pending.splice(i, 1);
} catch(err) {
this.log.error('Send plain request error:', err);
await pause(5000);
}
--i;
}
this.releasing = false;
}
} }

19
src/lib/mtproto/transports/tcpObfuscated.ts

@ -274,10 +274,6 @@ export default class TcpObfuscated implements MTTransport {
let length = this.pending.length; let length = this.pending.length;
//for(let i = length - 1; i >= 0; --i) { //for(let i = length - 1; i >= 0; --i) {
for(let i = 0; i < length; ++i) { for(let i = 0; i < length; ++i) {
/* if(this.ws.bufferedAmount) {
break;
} */
const pending = this.pending[i]; const pending = this.pending[i];
const {body, bodySent} = pending; const {body, bodySent} = pending;
let encoded = pending.encoded; let encoded = pending.encoded;
@ -286,25 +282,12 @@ export default class TcpObfuscated implements MTTransport {
//this.debugPayloads.push({before: body.slice(), after: enc}); //this.debugPayloads.push({before: body.slice(), after: enc});
this.debug && this.log.debug('-> body length to send:', body.length); this.debug && this.log.debug('-> body length to send:', body.length);
/* if(this.ws.bufferedAmount) {
this.log.error('bufferedAmount:', this.ws.bufferedAmount);
} */
/* if(this.ws.readyState !== this.ws.OPEN) {
this.log.error('ws is closed?');
this.connected = false;
break;
} */
if(!encoded) { if(!encoded) {
encoded = pending.encoded = this.encodeBody(body); encoded = pending.encoded = this.encodeBody(body);
} }
//this.lol.push(body); this.connection.send(encoded);
//setTimeout(() => {
this.connection.send(encoded);
//}, 100);
//this.dd();
if(!pending.resolve) { // remove if no response needed if(!pending.resolve) { // remove if no response needed
this.pending.splice(i--, 1); this.pending.splice(i--, 1);

2
src/lib/mtproto/transports/transport.ts

@ -5,8 +5,10 @@
*/ */
import type EventListenerBase from "../../../helpers/eventListenerBase"; import type EventListenerBase from "../../../helpers/eventListenerBase";
import type MTPNetworker from "../networker";
export default interface MTTransport { export default interface MTTransport {
networker: MTPNetworker;
send: (data: Uint8Array) => void; send: (data: Uint8Array) => void;
} }

1
src/pages/pageIm.ts

@ -35,6 +35,7 @@ let onFirstMount = () => {
// setTimeout(() => { // setTimeout(() => {
const promise = import('../lib/appManagers/appDialogsManager'); const promise = import('../lib/appManagers/appDialogsManager');
promise.finally(async() => { promise.finally(async() => {
document.getElementById('auth-pages').remove();
//alert('pageIm!'); //alert('pageIm!');
resolve(); resolve();

3
src/pages/pagePassword.ts

@ -84,6 +84,9 @@ let onFirstMount = (): Promise<any> => {
btnNextI18n.update({key: 'PleaseWait'}); btnNextI18n.update({key: 'PleaseWait'});
const preloader = putPreloader(btnNext); const preloader = putPreloader(btnNext);
passwordInputField.setValueSilently('' + Math.random()); // prevent saving suggestion
passwordInputField.setValueSilently(value); // prevent saving suggestion
passwordManager.check(value, state).then((response) => { passwordManager.check(value, state).then((response) => {
//console.log('passwordManager response:', response); //console.log('passwordManager response:', response);

3
src/scss/partials/_input.scss

@ -393,6 +393,9 @@ input:focus, button:focus {
&[type="password"] { &[type="password"] {
font-size: 2.25rem; font-size: 2.25rem;
padding-left: calc(.875rem - var(--border-width)); padding-left: calc(.875rem - var(--border-width));
line-height: 1;
padding-top: 0;
padding-bottom: 0;
@media (-webkit-min-device-pixel-ratio: 2) { @media (-webkit-min-device-pixel-ratio: 2) {
font-size: 1.75rem; font-size: 1.75rem;

8
src/scss/style.scss

@ -555,6 +555,14 @@ input, textarea {
-webkit-appearance: none; -webkit-appearance: none;
} }
// Possible fix Safari's password autocomplete
input::-webkit-contacts-auto-fill-button,
input::-webkit-credentials-auto-fill-button {
visibility: hidden;
position: absolute;
right: 0;
}
/* input:-webkit-autofill, /* input:-webkit-autofill,
input:-webkit-autofill:hover, input:-webkit-autofill:hover,
input:-webkit-autofill:focus, input:-webkit-autofill:focus,

Loading…
Cancel
Save