Browse Source

Rollback Safari connection

Done local forward
master
Eduard Kuzmenko 3 years ago
parent
commit
04442dcffa
  1. 4
      src/components/chat/bubbles.ts
  2. 6
      src/helpers/context.ts
  3. 2
      src/helpers/userAgent.ts
  4. 3
      src/lib/appManagers/appDialogsManager.ts
  5. 131
      src/lib/appManagers/appMessagesManager.ts
  6. 23
      src/lib/mtproto/apiFileManager.ts
  7. 5
      src/lib/mtproto/apiManager.ts
  8. 4
      src/lib/mtproto/dcConfigurator.ts
  9. 24
      src/lib/mtproto/mtproto.worker.ts
  10. 24
      src/lib/mtproto/mtprotoworker.ts
  11. 141
      src/lib/mtproto/networker.ts
  12. 19
      src/lib/mtproto/timeManager.ts
  13. 21
      src/lib/mtproto/tl_utils.ts
  14. 58
      src/lib/mtproto/transports/obfuscation.ts
  15. 105
      src/lib/mtproto/transports/websocket.ts
  16. 10
      webpack.common.js

4
src/components/chat/bubbles.ts

@ -308,7 +308,7 @@ export default class ChatBubbles { @@ -308,7 +308,7 @@ export default class ChatBubbles {
});
this.listenerSetter.add(rootScope, 'album_edit', (e) => {
fastRaf(() => {
//fastRaf(() => { // ! can't use delayed smth here, need original bubble to be edited
const {peerId, groupId, deletedMids} = e;
if(peerId !== this.peerId) return;
@ -319,7 +319,7 @@ export default class ChatBubbles { @@ -319,7 +319,7 @@ export default class ChatBubbles {
const renderMaxId = getObjectKeysAndSort(this.appMessagesManager.groupedMessagesStorage[groupId], 'asc').pop();
this.renderMessage(this.chat.getMessage(renderMaxId), true, false, this.bubbles[renderedId], false);
});
//});
});
this.listenerSetter.add(rootScope, 'messages_downloaded', (e) => {

6
src/helpers/context.ts

@ -26,7 +26,7 @@ const notifyWorker = (...args: any[]) => { @@ -26,7 +26,7 @@ const notifyWorker = (...args: any[]) => {
(self as any as DedicatedWorkerGlobalScope).postMessage(...args);
};
const empty = () => {};
const noop = () => {};
export const notifySomeone = isServiceWorker ? notifyServiceWorker.bind(null, false) : (isWebWorker ? notifyWorker : empty);
export const notifyAll = isServiceWorker ? notifyServiceWorker.bind(null, true) : (isWebWorker ? notifyWorker : empty);
export const notifySomeone = isServiceWorker ? notifyServiceWorker.bind(null, false) : (isWebWorker ? notifyWorker : noop);
export const notifyAll = isServiceWorker ? notifyServiceWorker.bind(null, true) : (isWebWorker ? notifyWorker : noop);

2
src/helpers/userAgent.ts

@ -11,7 +11,7 @@ export const isChromium = /Chrome/.test(navigator.userAgent) && /Google Inc/.tes @@ -11,7 +11,7 @@ export const isChromium = /Chrome/.test(navigator.userAgent) && /Google Inc/.tes
*
* This should be removed once the underlying Safari issue is fixed.
*/
const ctx = typeof(window) !== 'undefined' ? window : self;
export const ctx = typeof(window) !== 'undefined' ? window : self;
// https://stackoverflow.com/a/58065241
export const isAppleMobile = (/iPad|iPhone|iPod/.test(navigator.platform) ||

3
src/lib/appManagers/appDialogsManager.ts

@ -164,7 +164,8 @@ class ConnectionStatusComponent { @@ -164,7 +164,8 @@ class ConnectionStatusComponent {
DEBUG && this.log('setState: isShown:', this.connecting || this.updating);
};
this.setStateTimeout = window.setTimeout(cb, timeout);
//this.setStateTimeout = window.setTimeout(cb, timeout);
cb();
/* if(timeout) this.setStateTimeout = window.setTimeout(cb, timeout);
else cb(); */
});

131
src/lib/appManagers/appMessagesManager.ts

@ -176,6 +176,8 @@ export class AppMessagesManager { @@ -176,6 +176,8 @@ export class AppMessagesManager {
public dialogsStorage: DialogsStorage;
public filtersStorage: FiltersStorage;
private groupedTempId = 0;
constructor() {
this.dialogsStorage = new DialogsStorage(this, appChatsManager, appPeersManager, serverTimeManager);
this.filtersStorage = new FiltersStorage(appPeersManager, appUsersManager, /* apiManager, */ rootScope);
@ -578,6 +580,7 @@ export class AppMessagesManager { @@ -578,6 +580,7 @@ export class AppMessagesManager {
replyToMsgId: number,
threadId: number,
groupId: string,
caption: string,
entities: MessageEntity[],
width: number,
@ -591,7 +594,7 @@ export class AppMessagesManager { @@ -591,7 +594,7 @@ export class AppMessagesManager {
clearDraft: true,
scheduleDate: number,
waveform: Uint8Array
waveform: Uint8Array,
}> = {}) {
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId;
@ -771,6 +774,29 @@ export class AppMessagesManager { @@ -771,6 +774,29 @@ export class AppMessagesManager {
const sentDeferred = deferredPromise<InputMedia>();
if(preloader) {
preloader.attachPromise(sentDeferred);
sentDeferred.cancel = () => {
const error = new Error('Download canceled');
error.name = 'AbortError';
sentDeferred.reject(error);
};
sentDeferred.catch(err => {
if(err.name === 'AbortError' && !uploaded) {
this.log('cancelling upload', media);
sentDeferred.reject(err);
this.cancelPendingMessage(message.random_id);
this.setTyping(peerId, 'sendMessageCancelAction');
if(uploadPromise?.cancel) {
uploadPromise.cancel();
}
}
});
}
const media = isDocument ? undefined : {
_: photo ? 'messageMediaPhoto' : 'messageMediaDocument',
pFlags: {},
@ -821,7 +847,7 @@ export class AppMessagesManager { @@ -821,7 +847,7 @@ export class AppMessagesManager {
if(!uploaded || message.error) {
uploaded = false;
uploadPromise = appDownloadManager.upload(file);
preloader.attachPromise(uploadPromise);
sentDeferred.notifyAll({done: 0, total: file.size});
}
let thumbUploadPromise: typeof uploadPromise;
@ -886,16 +912,7 @@ export class AppMessagesManager { @@ -886,16 +912,7 @@ export class AppMessagesManager {
const percents = Math.max(1, Math.floor(100 * progress.done / progress.total));
this.setTyping(peerId, {_: actionName, progress: percents | 0});
});
uploadPromise.catch(err => {
if(err.name === 'AbortError' && !uploaded) {
this.log('cancelling upload', media);
sentDeferred.reject(err);
this.cancelPendingMessage(message.random_id);
this.setTyping(peerId, 'sendMessageCancelAction');
}
sentDeferred.notifyAll(progress);
});
return sentDeferred;
@ -995,6 +1012,8 @@ export class AppMessagesManager { @@ -995,6 +1012,8 @@ export class AppMessagesManager {
this.log('sendAlbum', files, options);
const groupId = '' + ++this.groupedTempId;
const messages = files.map((file, idx) => {
const details = options.sendFileDetails[idx];
const o: any = {
@ -1004,6 +1023,7 @@ export class AppMessagesManager { @@ -1004,6 +1023,7 @@ export class AppMessagesManager {
silent: options.silent,
replyToMsgId,
threadId: options.threadId,
groupId,
...details
};
@ -1016,23 +1036,6 @@ export class AppMessagesManager { @@ -1016,23 +1036,6 @@ export class AppMessagesManager {
return this.sendFile(peerId, file, o).message;
});
const message = messages[messages.length - 1];
const groupId = message.id;
messages.forEach(message => {
message.grouped_id = groupId;
});
const storage = options.scheduleDate ? this.getScheduledMessagesStorage(peerId) : this.getMessagesStorage(peerId);
if(options.scheduleDate) {
this.saveMessages(messages, {storage, isScheduled: true, isOutgoing: true});
rootScope.broadcast('scheduled_new', {peerId, mid: groupId});
} else {
this.saveMessages(messages, {storage, isOutgoing: true});
rootScope.broadcast('history_append', {peerId, messageId: groupId, my: true});
this.setDialogTopMessage(message);
}
if(options.clearDraft) {
appDraftsManager.syncDraft(peerId, options.threadId);
}
@ -1314,10 +1317,11 @@ export class AppMessagesManager { @@ -1314,10 +1317,11 @@ export class AppMessagesManager {
const storage = options.isScheduled ? this.getScheduledMessagesStorage(peerId) : this.getMessagesStorage(peerId);
if(options.isScheduled) {
if(!options.isGroupedItem) {
this.saveMessages([message], {storage, isScheduled: true, isOutgoing: true});
//if(!options.isGroupedItem) {
this.saveMessages([message], {storage, isScheduled: true, isOutgoing: true});
setTimeout(() => {
rootScope.broadcast('scheduled_new', {peerId, mid: messageId});
}
}, 0);
} else {
if(options.threadId && this.threadsStorage[peerId]) {
delete this.threadsStorage[peerId][options.threadId];
@ -1330,12 +1334,13 @@ export class AppMessagesManager { @@ -1330,12 +1334,13 @@ export class AppMessagesManager {
/* const historyStorage = this.getHistoryStorage(peerId);
historyStorage.history.unshift(messageId); */
if(!options.isGroupedItem) {
this.saveMessages([message], {storage, isOutgoing: true});
//if(!options.isGroupedItem) {
this.saveMessages([message], {storage, isOutgoing: true});
setTimeout(() => {
rootScope.broadcast('history_append', {peerId, messageId, my: true});
this.setDialogTopMessage(message);
}
}, 0);
}
if(!options.isGroupedItem && options.clearDraft && !options.threadId) {
@ -1344,7 +1349,7 @@ export class AppMessagesManager { @@ -1344,7 +1349,7 @@ export class AppMessagesManager {
this.pendingByRandomId[message.random_id] = {peerId, tempId: messageId, storage};
if(!options.isGroupedItem) {
if(!options.isGroupedItem && message.send) {
setTimeout(message.send, 0);
//setTimeout(message.send, 4000);
//setTimeout(message.send, 7000);
@ -1356,7 +1361,8 @@ export class AppMessagesManager { @@ -1356,7 +1361,8 @@ export class AppMessagesManager {
replyToMsgId: number,
threadId: number,
viaBotId: number,
reply_markup: any
groupId: string,
reply_markup: any,
}>) {
if(options.threadId && !options.replyToMsgId) {
options.replyToMsgId = options.threadId;
@ -1370,6 +1376,7 @@ export class AppMessagesManager { @@ -1370,6 +1376,7 @@ export class AppMessagesManager {
pFlags: this.generateFlags(peerId),
date: options.scheduleDate || (tsNow(true) + serverTimeManager.serverTimeOffset),
message: '',
grouped_id: options.groupId,
random_id: randomLong(),
reply_to: this.generateReplyHeader(options.replyToMsgId, options.threadId),
via_bot_id: options.viaBotId,
@ -1446,13 +1453,26 @@ export class AppMessagesManager { @@ -1446,13 +1453,26 @@ export class AppMessagesManager {
}
private generateForwardHeader(peerId: number, originalMessage: Message.message) {
const myId = appUsersManager.getSelf().id;
if(originalMessage.fromId === myId && !originalMessage.fwd_from) {
return;
}
const fwdHeader: MessageFwdHeader.messageFwdHeader = {
_: 'messageFwdHeader',
flags: 0,
date: originalMessage.date,
from_id: appPeersManager.getOutputPeer(originalMessage.fromId)
date: originalMessage.date
};
if(originalMessage.fwd_from) {
fwdHeader.from_id = originalMessage.fwd_from.from_id;
fwdHeader.from_name = originalMessage.fwd_from.from_name;
fwdHeader.post_author = originalMessage.fwd_from.post_author;
} else {
fwdHeader.from_id = appPeersManager.getOutputPeer(originalMessage.fromId);
fwdHeader.post_author = originalMessage.post_author;
}
if(appPeersManager.isBroadcast(originalMessage.peerId)) {
if(originalMessage.post_author) {
fwdHeader.post_author = originalMessage.post_author;
@ -1462,7 +1482,7 @@ export class AppMessagesManager { @@ -1462,7 +1482,7 @@ export class AppMessagesManager {
}
// * there is no way to detect whether user profile is hidden
if(peerId === appUsersManager.getSelf().id) {
if(peerId === myId) {
fwdHeader.saved_from_msg_id = originalMessage.id;
fwdHeader.saved_from_peer = appPeersManager.getOutputPeer(originalMessage.peerId);
}
@ -1733,6 +1753,13 @@ export class AppMessagesManager { @@ -1733,6 +1753,13 @@ export class AppMessagesManager {
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId;
mids = mids.slice().sort((a, b) => a - b);
const groups: {
[groupId: string]: {
tempId: string,
messages: any[]
}
} = {};
const newMessages = mids.map(mid => {
const originalMessage = this.getMessageByPeer(fromPeerId, mid);
const message = this.generateOutgoingMessage(peerId, options);
@ -1742,11 +1769,27 @@ export class AppMessagesManager { @@ -1742,11 +1769,27 @@ export class AppMessagesManager {
message[key] = originalMessage[key];
});
if(originalMessage.grouped_id) {
const group = groups[originalMessage.grouped_id] ?? (groups[originalMessage.grouped_id] = {tempId: '' + ++this.groupedTempId, messages: []});
group.messages.push(message);
}
return message;
});
for(const groupId in groups) {
const group = groups[groupId];
if(group.messages.length > 1) {
group.messages.forEach(message => {
message.grouped_id = group.tempId;
});
}
}
newMessages.forEach(message => {
this.beforeMessageSending(message, {
isScheduled: !!options.scheduleDate || undefined
});
return message;
});
const sentRequestOptions: InvokeApiOptions = {};
@ -1754,7 +1797,7 @@ export class AppMessagesManager { @@ -1754,7 +1797,7 @@ export class AppMessagesManager {
sentRequestOptions.afterMessageId = this.pendingAfterMsgs[peerId].messageId;
}
const promise = apiManager.invokeApiAfter('messages.forwardMessages', {
const promise = /* true ? Promise.resolve() : */apiManager.invokeApiAfter('messages.forwardMessages', {
from_peer: appPeersManager.getInputPeerById(fromPeerId),
id: mids.map(mid => this.getServerMessageId(mid)),
random_id: newMessages.map(message => message.random_id),
@ -4860,7 +4903,7 @@ export class AppMessagesManager { @@ -4860,7 +4903,7 @@ export class AppMessagesManager {
this.updateMessageRepliesIfNeeded(message);
if(!message.pFlags.out && message.pFlags.unread) {
if(!message.pFlags.out && !message.pFlags.is_outgoing && message.pFlags.unread) {
history.unread++;
}
history.count++;

23
src/lib/mtproto/apiFileManager.ts

@ -87,7 +87,7 @@ export class ApiFileManager { @@ -87,7 +87,7 @@ export class ApiFileManager {
public downloadCheck(dcId: string | number) {
const downloadPull = this.downloadPulls[dcId];
const downloadLimit = dcId === 'upload' ? 100 : 100;
const downloadLimit = dcId === 'upload' ? 24 : 48;
//const downloadLimit = Infinity;
if(this.downloadActives[dcId] >= downloadLimit || !downloadPull || !downloadPull.length) {
@ -454,6 +454,10 @@ export class ApiFileManager { @@ -454,6 +454,10 @@ export class ApiFileManager {
const id = this.tempId++;
/* setInterval(() => {
console.log(file);
}, 1e3); */
const self = this;
function* generator() {
for(let offset = 0; offset < fileSize; offset += partSize) {
@ -485,6 +489,23 @@ export class ApiFileManager { @@ -485,6 +489,23 @@ export class ApiFileManager {
}
buffer = u.buffer; */
/* setTimeout(() => {
doneParts++;
uploadResolve();
//////this.log('Progress', doneParts * partSize / fileSize);
self.log('done part', part, doneParts);
deferred.notify({done: doneParts * partSize, total: fileSize});
if(doneParts >= totalParts) {
deferred.resolve(resultInputFile);
resolved = true;
}
}, 1250);
return; */
apiManager.invokeApi(method, {
file_id: fileId,
file_part: part,

5
src/lib/mtproto/apiManager.ts

@ -13,6 +13,7 @@ import type { MethodDeclMap } from '../../layer'; @@ -13,6 +13,7 @@ import type { MethodDeclMap } from '../../layer';
import { CancellablePromise, deferredPromise } from '../../helpers/cancellablePromise';
import { bytesFromHex, bytesToHex } from '../../helpers/bytes';
//import { clamp } from '../../helpers/number';
import { isSafari } from '../../helpers/userAgent';
/// #if !MTPROTO_WORKER
import rootScope from '../rootScope';
@ -161,7 +162,7 @@ export class ApiManager { @@ -161,7 +162,7 @@ export class ApiManager {
/// #if MTPROTO_HTTP_UPLOAD
// @ts-ignore
const transportType: TransportType = connectionType === 'upload' ? 'https' : 'websocket';
const transportType: TransportType = connectionType === 'upload' && isSafari ? 'https' : 'websocket';
//const transportType: TransportType = connectionType !== 'client' ? 'https' : 'websocket';
/// #else
// @ts-ignore
@ -182,7 +183,7 @@ export class ApiManager { @@ -182,7 +183,7 @@ export class ApiManager {
}
const networkers = cache[dcId];
if(networkers.length >= /* 1 */(connectionType === 'client' ? 1 : (connectionType === 'download' ? 3 : 3))) {
if(networkers.length >= /* 1 */(connectionType === 'client' || transportType === 'https' ? 1 : (connectionType === 'download' ? 3 : 3))) {
let i = networkers.length - 1, found = false;
for(; i >= 0; --i) {
if(networkers[i].isOnline) {

4
src/lib/mtproto/dcConfigurator.ts

@ -43,6 +43,7 @@ export class DcConfigurator { @@ -43,6 +43,7 @@ export class DcConfigurator {
private chosenServers: Servers = {} as any;
/// #if !MTPROTO_HTTP
private transportSocket = (dcId: number, connectionType: ConnectionType) => {
const subdomain = this.sslSubdomains[dcId - 1];
const path = Modes.test ? 'apiws_test' : 'apiws';
@ -50,7 +51,9 @@ export class DcConfigurator { @@ -50,7 +51,9 @@ export class DcConfigurator {
const suffix = connectionType === 'upload' ? '-U' : connectionType === 'download' ? '-D' : '';
return new Socket(dcId, chosenServer, suffix, connectionType === 'client' ? 30000 : 10000);
};
/// #endif
/// #if MTPROTO_HTTP_UPLOAD || MTPROTO_HTTP
private transportHTTP = (dcId: number, connectionType: ConnectionType) => {
if(Modes.ssl || !Modes.http) {
const subdomain = this.sslSubdomains[dcId - 1] + (connectionType !== 'client' ? '-1' : '');
@ -66,6 +69,7 @@ export class DcConfigurator { @@ -66,6 +69,7 @@ export class DcConfigurator {
}
}
};
/// #endif
public chooseServer(dcId: number, connectionType: ConnectionType = 'client', transportType: TransportType = 'websocket', reuse = true) {
/* if(transportType === 'websocket' && !Modes.multipleConnections) {

24
src/lib/mtproto/mtproto.worker.ts

@ -7,11 +7,10 @@ import networkerFactory from "./networkerFactory"; @@ -7,11 +7,10 @@ import networkerFactory from "./networkerFactory";
import apiFileManager from './apiFileManager';
//import { logger, LogLevels } from '../logger';
import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from './mtproto.service';
import { ctx } from '../../helpers/userAgent';
//const log = logger('DW', LogLevels.error);
const ctx = self as any as DedicatedWorkerGlobalScope;
//console.error('INCLUDE !!!', new Error().stack);
/* function isObject(object: any) {
@ -64,7 +63,15 @@ networkerFactory.onConnectionStatusChange = (status) => { @@ -64,7 +63,15 @@ networkerFactory.onConnectionStatusChange = (status) => {
respond({type: 'connectionStatusChange', payload: status});
};
ctx.addEventListener('message', async(e) => {
/* ctx.onerror = (error) => {
console.error('error:', error);
};
ctx.onunhandledrejection = (error) => {
console.error('onunhandledrejection:', error);
}; */
const onMessage = async(e: any) => {
try {
const task = e.data;
const taskId = task.taskId;
@ -103,6 +110,10 @@ ctx.addEventListener('message', async(e) => { @@ -103,6 +110,10 @@ ctx.addEventListener('message', async(e) => {
webpSupported = task.payload;
return;
}
if(!task.task) {
return;
}
switch(task.task) {
case 'computeSRP':
@ -131,6 +142,8 @@ ctx.addEventListener('message', async(e) => { @@ -131,6 +142,8 @@ ctx.addEventListener('message', async(e) => {
} catch(error) {
respond({taskId, error});
}
break;
}
case 'getNetworker': {
@ -157,12 +170,15 @@ ctx.addEventListener('message', async(e) => { @@ -157,12 +170,15 @@ ctx.addEventListener('message', async(e) => {
}
//throw new Error('Unknown task: ' + task.task);
break;
}
}
} catch(err) {
}
});
};
ctx.addEventListener('message', onMessage);
//console.log('[WORKER] Will send ready', Date.now() / 1000);
ctx.postMessage('ready');

24
src/lib/mtproto/mtprotoworker.ts

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
import MTProtoWorker from 'worker-loader!./mtproto.worker';
//import './mtproto.worker';
import { isObject } from '../../helpers/object';
import type { MethodDeclMap } from '../../layer';
import type { InvokeApiOptions } from '../../types';
@ -9,7 +10,7 @@ import webpWorkerController from '../webp/webpWorkerController'; @@ -9,7 +10,7 @@ import webpWorkerController from '../webp/webpWorkerController';
import type { DownloadOptions } from './apiFileManager';
import { ApiError } from './apiManager';
import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from './mtproto.service';
import { MOUNT_CLASS_TO, UserAuth } from './mtproto_config';
import { DEBUG, MOUNT_CLASS_TO, UserAuth } from './mtproto_config';
import type { MTMessage } from './networker';
import referenceDatabase from './referenceDatabase';
import appDocsManager from '../appManagers/appDocsManager';
@ -32,7 +33,7 @@ type HashOptions = { @@ -32,7 +33,7 @@ type HashOptions = {
};
export class ApiManagerProxy extends CryptoWorkerMethods {
public worker: Worker;
public worker: /* Window */Worker;
public postMessage: (...args: any[]) => void;
private afterMessageIdTemp = 0;
@ -54,6 +55,8 @@ export class ApiManagerProxy extends CryptoWorkerMethods { @@ -54,6 +55,8 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
private isSWRegistered = true;
private debug = DEBUG;
constructor() {
super();
this.log('constructor');
@ -118,6 +121,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods { @@ -118,6 +121,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
//return;
const worker = new MTProtoWorker();
//const worker = window;
worker.addEventListener('message', (e) => {
if(!this.worker) {
this.worker = worker;
@ -182,23 +186,27 @@ export class ApiManagerProxy extends CryptoWorkerMethods { @@ -182,23 +186,27 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
} else {
navigator.serviceWorker.controller.postMessage(task);
}
} else {
} else if(task.hasOwnProperty('result') || task.hasOwnProperty('error')) {
this.finalizeTask(task.taskId, task.result, task.error);
}
});
worker.addEventListener('error', (err) => {
this.log.error('WORKER ERROR', err);
});
}
private finalizeTask(taskId: number, result: any, error: any) {
const deferred = this.awaiting[taskId];
if(deferred !== undefined) {
this.log.debug('done', deferred.taskName, result, error);
this.debug && this.log.debug('done', deferred.taskName, result, error);
error ? deferred.reject(error) : deferred.resolve(result);
delete this.awaiting[taskId];
}
}
public performTaskWorker<T>(task: string, ...args: any[]) {
this.log.debug('start', task, args);
this.debug && this.log.debug('start', task, args);
return new Promise<T>((resolve, reject) => {
this.awaiting[this.taskId] = {resolve, reject, taskName: task};
@ -218,12 +226,12 @@ export class ApiManagerProxy extends CryptoWorkerMethods { @@ -218,12 +226,12 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
private releasePending() {
if(this.postMessage) {
this.log.debug('releasing tasks, length:', this.pending.length);
this.debug && this.log.debug('releasing tasks, length:', this.pending.length);
this.pending.forEach(pending => {
this.postMessage(pending);
});
this.log.debug('released tasks');
this.debug && this.log.debug('released tasks');
this.pending.length = 0;
}
}
@ -262,7 +270,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods { @@ -262,7 +270,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
return this.performTaskWorker('invokeApi', method, params, options).then((result: any) => {
if(result._.includes('NotModified')) {
this.log.warn('NotModified saved!', method, queryJSON);
this.debug && this.log.warn('NotModified saved!', method, queryJSON);
return cached.result;
}

141
src/lib/mtproto/networker.ts

@ -13,6 +13,8 @@ import { longToBytes } from '../crypto/crypto_utils'; @@ -13,6 +13,8 @@ import { longToBytes } from '../crypto/crypto_utils';
import MTTransport from './transports/transport';
import { convertToUint8Array, bufferConcat, bytesCmp, bytesToHex } from '../../helpers/bytes';
import { nextRandomInt, randomLong } from '../../helpers/random';
import { CancellablePromise, deferredPromise } from '../../helpers/cancellablePromise';
import { isSafari } from '../../helpers/userAgent';
/// #if MTPROTO_HTTP_UPLOAD
// @ts-ignore
@ -112,6 +114,9 @@ export default class MTPNetworker { @@ -112,6 +114,9 @@ export default class MTPNetworker {
public isOnline = false;
private lastResponseTime = 0;
private disconnectDelay: number;
private pingPromise: CancellablePromise<any>;
private sentPingTimes = 0;
private tt = 0;
//public onConnectionStatusChange: (online: boolean) => void;
constructor(public dcId: number, private authKey: number[], private authKeyId: Uint8Array,
@ -161,9 +166,13 @@ export default class MTPNetworker { @@ -161,9 +166,13 @@ export default class MTPNetworker {
// * handle outcoming dead socket, server will close the connection
if((this.transport as Socket).networker) {
this.disconnectDelay = (this.transport as Socket).retryTimeout / 1000 | 0;
setInterval(this.sendPingDelayDisconnect, (this.disconnectDelay - 5) * 1000);
this.sendPingDelayDisconnect();
if(isSafari) {
this.pingPromise = Promise.resolve();
} else {
this.disconnectDelay = (this.transport as Socket).retryTimeout / 1000 | 0;
//setInterval(this.sendPingDelayDisconnect, (this.disconnectDelay - 5) * 1000);
this.sendPingDelayDisconnect();
}
}
}
@ -326,13 +335,63 @@ export default class MTPNetworker { @@ -326,13 +335,63 @@ export default class MTPNetworker {
}
private sendPingDelayDisconnect = () => {
if(!this.isOnline) return; // * already disconnected
this.wrapMtpCall('ping_delay_disconnect', {
if(this.pingPromise) return;
if(!this.isOnline) {
if((this.transport as Socket).connected) {
(this.transport as Socket).handleClose();
}
return;
}
this.log('sendPingDelayDisconnect', this.sentPingTimes);
/* if(this.tt) clearTimeout(this.tt);
this.tt = self.setTimeout(() => {
(this.transport as any).ws.close(1000);
this.tt = 0;
}, this.disconnectDelay * 1000); */
/* this.wrapMtpCall('ping_delay_disconnect', {
ping_id: randomLong(),
disconnect_delay: this.disconnectDelay
}, {
noResponse: true,
notContentRelated: true
}); */
const deferred = this.pingPromise = deferredPromise<void>();
const timeoutTime = this.disconnectDelay * 1000;
/* if(!this.sentPingTimes || true) {
++this.sentPingTimes; */
const startTime = Date.now();
this.wrapMtpCall('ping', {
ping_id: randomLong()
}, {}).then(pong => {
const elapsedTime = Date.now() - startTime;
this.log('sendPingDelayDisconnect: response', pong, elapsedTime > timeoutTime);
if(elapsedTime > timeoutTime) {
deferred.reject();
} else {
setTimeout(deferred.resolve, timeoutTime - elapsedTime);
}
}, deferred.reject).finally(() => {
clearTimeout(rejectTimeout);
//--this.sentPingTimes;
});
//}
const rejectTimeout = self.setTimeout(deferred.reject, timeoutTime);
deferred.catch(() => {
(this.transport as Socket).handleClose();
});
deferred.finally(() => {
this.pingPromise = null;
this.sendPingDelayDisconnect();
});
};
@ -543,6 +602,10 @@ export default class MTPNetworker { @@ -543,6 +602,10 @@ export default class MTPNetworker {
this.log.error('timeout', message);
this.setConnectionStatus(false);
this.getEncryptedOutput(message).then(bytes => {
this.log.error('timeout encrypted', bytes);
});
}, CONNECTION_TIMEOUT);
promise.finally(() => {
@ -571,7 +634,11 @@ export default class MTPNetworker { @@ -571,7 +634,11 @@ export default class MTPNetworker {
});
}
this.sendPingDelayDisconnect();
if(!this.pingPromise && (this.transport as Socket).networker) {
this.sendPingDelayDisconnect();
}
/* this.sentPingTimes = 0;
this.sendPingDelayDisconnect(); */
}
/* if(this.onConnectionStatusChange) {
this.onConnectionStatusChange(this.isOnline);
@ -845,10 +912,14 @@ export default class MTPNetworker { @@ -845,10 +912,14 @@ export default class MTPNetworker {
});
}
public sendEncryptedRequest(message: MTMessage) {
public getEncryptedOutput(message: MTMessage) {
/* if(DEBUG) {
this.log.debug('Send encrypted', message, this.authKeyId);
} */
/* if(!this.isOnline) {
this.log('trying to send message when offline:', Object.assign({}, message));
//debugger;
} */
const data = new TLSerialization({
startMaxLength: message.body.length + 2048
@ -859,26 +930,48 @@ export default class MTPNetworker { @@ -859,26 +930,48 @@ export default class MTPNetworker {
data.storeLong(message.msg_id, 'message_id');
data.storeInt(message.seq_no, 'seq_no');
data.storeInt(message.body.length, 'message_data_length');
data.storeRawBytes(message.body, 'message_data');
/* const messageDataLength = message.body.length;
let canBeLength = 0; // bytes
canBeLength += 8;
canBeLength += 8;
canBeLength += 8;
canBeLength += 4;
canBeLength += 4;
canBeLength += message.body.length; */
const dataBuffer = data.getBuffer();
/* if(dataBuffer.byteLength !== canBeLength || !bytesCmp(new Uint8Array(dataBuffer.slice(dataBuffer.byteLength - message.body.length)), new Uint8Array(message.body))) {
this.log.error('wrong length', dataBuffer, canBeLength, message.msg_id);
} */
const paddingLength = (16 - (data.offset % 16)) + 16 * (1 + nextRandomInt(5));
const padding = [...new Uint8Array(paddingLength).randomize()];
/* const padding = [167, 148, 207, 226, 86, 192, 193, 57, 124, 153, 174, 145, 159, 1, 5, 70, 127, 157,
51, 241, 46, 85, 141, 212, 139, 234, 213, 164, 197, 116, 245, 70, 184, 40, 40, 201, 233, 211, 150,
94, 57, 84, 1, 135, 108, 253, 34, 139, 222, 208, 71, 214, 90, 67, 36, 28, 167, 148, 207, 226, 86, 192, 193, 57, 124, 153, 174, 145, 159, 1, 5, 70, 127, 157,
51, 241, 46, 85, 141, 212, 139, 234, 213, 164, 197, 116, 245, 70, 184, 40, 40, 201, 233, 211, 150,
94, 57, 84, 1, 135, 108, 253, 34, 139, 222, 208, 71, 214, 90, 67, 36, 28].slice(0, paddingLength); */
const dataWithPadding = bufferConcat(dataBuffer, padding);
// this.log('Adding padding', dataBuffer, padding, dataWithPadding)
// this.log('auth_key_id', bytesToHex(self.authKeyID))
/* if(message.fileUpload) {
/* if(dataWithPadding.byteLength % 16) {
this.log.error('aaa', dataWithPadding, paddingLength);
}
if(message.fileUpload) {
this.log('Send encrypted: body length:', (message.body as ArrayBuffer).byteLength, paddingLength, dataWithPadding);
} */
return this.getEncryptedMessage(dataWithPadding).then((encryptedResult) => {
/* if(DEBUG) {
this.log.debug('Got encrypted out message', encryptedResult);
this.log('Got encrypted out message', encryptedResult);
} */
const request = new TLSerialization({
@ -890,10 +983,17 @@ export default class MTPNetworker { @@ -890,10 +983,17 @@ export default class MTPNetworker {
const requestData = request.getBytes(true);
/* if(message.fileUpload) {
this.log('Send encrypted: requestData length:', requestData.length, requestData.length % 16, paddingLength % 16, paddingLength, data.offset);
} */
//if(message.fileUpload) {
//this.log('Send encrypted: requestData length:', requestData.length, requestData.length % 16, paddingLength % 16, paddingLength, data.offset, encryptedResult.msgKey.length % 16, encryptedResult.bytes.length % 16);
//this.log('Send encrypted: messageId:', message.msg_id, requestData.length);
//}
return requestData;
});
}
public sendEncryptedRequest(message: MTMessage) {
return this.getEncryptedOutput(message).then(requestData => {
const promise = this.transport.send(requestData);
/// #if !MTPROTO_HTTP && !MTPROTO_HTTP_UPLOAD
return promise;
@ -1381,17 +1481,16 @@ export default class MTPNetworker { @@ -1381,17 +1481,16 @@ export default class MTPNetworker {
break;
}
case 'pong': { // * https://core.telegram.org/mtproto/service_messages#ping-messages-pingpong - These messages do not require acknowledgments
/* if((this.transport as Socket).networker) {
case 'pong': { // * https://core.telegram.org/mtproto/service_messages#ping-messages-pingpong - These messages doesn't require acknowledgments
if((this.transport as Socket).networker) {
const sentMessageId = message.msg_id;
const sentMessage = this.sentMessages[sentMessageId];
const sentMessage = this.sentMessages[sentMessageId];
if(sentMessage) {
sentMessage.deferred.resolve(message);
delete this.sentMessages[sentMessageId];
}
} */
}
break;
}

19
src/lib/mtproto/timeManager.ts

@ -1,6 +1,14 @@ @@ -1,6 +1,14 @@
import sessionStorage from '../sessionStorage';
import { longFromInts } from './bin_utils';
import { nextRandomInt } from '../../helpers/random';
import { MOUNT_CLASS_TO } from './mtproto_config';
/*
let lol: any = {};
for(var i = 0; i < 100; i++) {
timeManager.generateId();
}
*/
export class TimeManager {
private lastMessageId = [0, 0];
@ -30,7 +38,12 @@ export class TimeManager { @@ -30,7 +38,12 @@ export class TimeManager {
const ret = longFromInts(messageId[0], messageId[1]);
//console.log('[TimeManager]: Generated msg id', messageId, this.timeOffset, ret);
/* if(lol[ret]) {
console.error('[TimeManager]: Generated SAME msg id', messageId, this.timeOffset, ret);
}
lol[ret] = true;
console.log('[TimeManager]: Generated msg id', messageId, this.timeOffset, ret); */
return ret
}
@ -52,4 +65,6 @@ export class TimeManager { @@ -52,4 +65,6 @@ export class TimeManager {
}
}
export default new TimeManager();
const timeManager = new TimeManager();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.timeManager = timeManager);
export default timeManager;

21
src/lib/mtproto/tl_utils.ts

@ -25,7 +25,7 @@ class TLSerialization { @@ -25,7 +25,7 @@ class TLSerialization {
public byteView: Uint8Array;
constructor(options: Partial<{startMaxLength: number, mtproto: true}> = {}) {
this.maxLength = options.startMaxLength || 2048 // 2Kb
this.maxLength = options.startMaxLength || 2048; // 2Kb
this.mtproto = options.mtproto || false;
this.createBuffer();
}
@ -38,11 +38,22 @@ class TLSerialization { @@ -38,11 +38,22 @@ class TLSerialization {
public getArray() {
const resultBuffer = new ArrayBuffer(this.offset);
const resultArray = new Int32Array(resultBuffer);
resultArray.set(this.intView.subarray(0, this.offset / 4));
//let perf = performance.now();
/* const resultUint8: any = new Uint8Array(resultBuffer);
resultUint8.set(this.byteView.subarray(0, this.offset)); */
//console.log('perf uint8', performance.now() - perf);
//perf = performance.now();
const resultInt32 = new Int32Array(resultBuffer);
resultInt32.set(this.intView.subarray(0, this.offset / 4));
//console.log('perf int32', performance.now() - perf);
/* if(resultUint8.buffer.byteLength !== resultInt32.buffer.byteLength) {
console.error(resultUint8, resultInt32);
} */
return resultArray;
return resultInt32;
}
public getBuffer() {

58
src/lib/mtproto/transports/obfuscation.ts

@ -1,8 +1,55 @@ @@ -1,8 +1,55 @@
//import aesjs from 'aes-js';
import { CTR } from "@cryptography/aes";
import AES from "@cryptography/aes";
import { bytesFromWordss } from "../../../helpers/bytes";
import { Codec } from "./codec";
class Counter {
_counter: Uint8Array;
constructor(initialValue: Uint8Array) {
this._counter = initialValue;
}
increment() {
for(let i = 15; i >= 0; i--) {
if(this._counter[i] === 255) {
this._counter[i] = 0;
} else {
this._counter[i]++;
break;
}
}
}
}
class CTR {
_counter: Counter;
_remainingCounter: Uint8Array = null;
_remainingCounterIndex = 16;
_aes: AES;
constructor(key: Uint8Array, counter: Uint8Array) {
this._counter = new Counter(counter);
this._aes = new AES(key);
}
update(payload: Uint8Array) {
const encrypted = payload.slice();
for(let i = 0; i < encrypted.length; i++) {
if(this._remainingCounterIndex === 16) {
this._remainingCounter = new Uint8Array(bytesFromWordss(this._aes.encrypt(this._counter._counter)));
this._remainingCounterIndex = 0;
this._counter.increment();
}
encrypted[i] ^= this._remainingCounter[this._remainingCounterIndex++];
}
return encrypted;
}
}
/*
@cryptography/aes не работает с массивами которые не кратны 4, поэтому использую intermediate а не abridged
*/
@ -98,6 +145,13 @@ export default class Obfuscation { @@ -98,6 +145,13 @@ export default class Obfuscation {
return res;
} */
public encode(payload: Uint8Array) {
return this.encNew.update(payload);
}
public decode(payload: Uint8Array) {
return this.decNew.update(payload);
}
/* public encode(payload: Uint8Array) {
let res = this.encNew.encrypt(payload);
let bytes = new Uint8Array(bytesFromWordss(res));
@ -109,5 +163,5 @@ export default class Obfuscation { @@ -109,5 +163,5 @@ export default class Obfuscation {
let bytes = new Uint8Array(bytesFromWordss(res));
return bytes;
}
} */
}

105
src/lib/mtproto/transports/websocket.ts

@ -1,39 +1,38 @@ @@ -1,39 +1,38 @@
import MTTransport from './transport';
//import abridgetPacketCodec from './abridged';
//import abridgedPacketCodec from './abridged';
import intermediatePacketCodec from './intermediate';
import MTPNetworker from '../networker';
import { logger, LogLevels } from '../../logger';
import Obfuscation from './obfuscation';
import { DEBUG, Modes } from '../mtproto_config';
//import { debounce } from '../../../helpers/schedulers';
export default class Socket extends MTTransport {
ws: WebSocket;
public ws: WebSocket;
pending: Array<Partial<{
private pending: Array<Partial<{
resolve: any,
reject: any,
body: Uint8Array,
bodySent: boolean
}>> = [];
connected = false;
transport = 'websocket';
obfuscation = new Obfuscation();
networker: MTPNetworker;
public connected = false;
private codec = intermediatePacketCodec;
private log: ReturnType<typeof logger>;
private obfuscation = new Obfuscation();
public networker: MTPNetworker;
log: ReturnType<typeof logger>;
private lastCloseTime: number;
codec = intermediatePacketCodec;
private debug = Modes.debug && false;
//private releasePendingDebounced: () => void;
lastCloseTime: number;
debug = Modes.debug;
//releasePendingDebounced: () => void;
/* private stream: Array<any>;
private canRead: Promise<any>;
private resolveRead: () => void; */
//private lol: Uint8Array[] = [];
//private dd: () => void;
constructor(dcId: number, url: string, logSuffix: string, public retryTimeout: number) {
super(dcId, url);
@ -43,18 +42,37 @@ export default class Socket extends MTTransport { @@ -43,18 +42,37 @@ export default class Socket extends MTTransport {
this.log = logger(`WS-${dcId}` + logSuffix, logLevel);
this.log('constructor');
this.connect();
//this.releasePendingDebounced = debounce(() => this.releasePending(true), 2000, false, true);
//this.releasePendingDebounced = debounce(() => this.releasePending(true), 200, false, true);
/* this.dd = debounce(() => {
if(this.connected && this.lol.length) {
this.ws.send(this.lol.shift());
if(this.lol.length) {
this.dd();
}
}
}, 100, false, true); */
}
private removeListeners() {
this.ws.removeEventListener('open', this.handleOpen);
this.ws.removeEventListener('close', this.handleClose);
this.ws.removeEventListener('error', this.handleError);
this.ws.removeEventListener('message', this.handleMessage);
}
connect = () => {
if(this.ws) {
this.ws.removeEventListener('open', this.handleOpen);
this.ws.removeEventListener('close', this.handleClose);
this.ws.removeEventListener('error', this.handleError);
this.ws.removeEventListener('message', this.handleMessage);
this.removeListeners();
this.ws.close(1000);
}
/* this.stream = [];
this.canRead = new Promise<void>(resolve => {
this.resolveRead = resolve;
}); */
this.ws = new WebSocket(this.url, 'binary');
this.ws.binaryType = 'arraybuffer';
this.ws.addEventListener('open', this.handleOpen);
@ -89,9 +107,10 @@ export default class Socket extends MTTransport { @@ -89,9 +107,10 @@ export default class Socket extends MTTransport {
this.log.error(e);
};
handleClose = (event: CloseEvent) => {
this.log('closed', event, this.pending, this.ws.bufferedAmount);
handleClose = () => {
this.log('closed'/* , event, this.pending, this.ws.bufferedAmount */);
this.connected = false;
this.removeListeners();
const time = Date.now();
const diff = time - this.lastCloseTime;
@ -117,7 +136,7 @@ export default class Socket extends MTTransport { @@ -117,7 +136,7 @@ export default class Socket extends MTTransport {
};
handleMessage = (event: MessageEvent) => {
this.debug && this.log.debug('<-', 'handleMessage', event);
this.debug && this.log.debug('<-', 'handleMessage', /* event, */event.data.byteLength);
let data = this.obfuscation.decode(new Uint8Array(event.data));
data = this.codec.readPacket(data);
@ -125,11 +144,23 @@ export default class Socket extends MTTransport { @@ -125,11 +144,23 @@ export default class Socket extends MTTransport {
if(this.networker) { // authenticated!
//this.pending = this.pending.filter(p => p.body); // clear pending
this.debug && this.log.debug('redirecting to networker');
return this.networker.parseResponse(data).then(response => {
this.debug && this.log.debug('redirecting to networker', data.length);
this.networker.parseResponse(data).then(response => {
this.debug && this.log.debug('redirecting to networker response:', response);
this.networker.processMessage(response.response, response.messageId, response.sessionId);
try {
this.networker.processMessage(response.response, response.messageId, response.sessionId);
} catch(err) {
this.log.error('handleMessage networker processMessage error', err);
}
//this.releasePending();
}).catch(err => {
this.log.error('handleMessage networker parseResponse error', err);
});
//this.dd();
return;
}
//console.log('got hex:', data.hex);
@ -159,7 +190,7 @@ export default class Socket extends MTTransport { @@ -159,7 +190,7 @@ export default class Socket extends MTTransport {
return promise;
}
}
};
releasePending(/* tt = false */) {
if(!this.connected) {
@ -172,10 +203,14 @@ export default class Socket extends MTTransport { @@ -172,10 +203,14 @@ export default class Socket extends MTTransport {
return;
} */
//this.log.error('Pending length:', this.pending.length);
//this.log('-> messages to send:', this.pending.length);
let length = this.pending.length;
//for(let i = length - 1; i >= 0; --i) {
for(let i = 0; i < length; ++i) {
/* if(this.ws.bufferedAmount) {
break;
} */
const pending = this.pending[i];
const {body, bodySent} = pending;
if(body && !bodySent) {
@ -190,13 +225,17 @@ export default class Socket extends MTTransport { @@ -190,13 +225,17 @@ export default class Socket extends MTTransport {
this.log.error('bufferedAmount:', this.ws.bufferedAmount);
} */
if(this.ws.readyState !== this.ws.OPEN) {
/* if(this.ws.readyState !== this.ws.OPEN) {
this.log.error('ws is closed?');
this.connected = false;
break;
}
} */
this.ws.send(enc);
//this.lol.push(enc);
//setTimeout(() => {
this.ws.send(enc);
//}, 100);
//this.dd();
if(!pending.resolve) { // remove if no response needed
this.pending.splice(i--, 1);

10
webpack.common.js

@ -18,7 +18,7 @@ if(devMode) { @@ -18,7 +18,7 @@ if(devMode) {
const opts = {
MTPROTO_WORKER: true,
MTPROTO_HTTP: false,
MTPROTO_HTTP_UPLOAD: false,
MTPROTO_HTTP_UPLOAD: true,
DEBUG: devMode,
version: 3,
"ifdef-verbose": devMode, // add this for verbose output
@ -87,8 +87,8 @@ module.exports = { @@ -87,8 +87,8 @@ module.exports = {
output: {
path: path.resolve(__dirname, 'public'),
filename: "[name].bundle.js",
chunkFilename: "[name].chunk.js"
filename: "[name].[chunkhash].bundle.js",
chunkFilename: "[name].[chunkhash].chunk.js"
},
devServer: {
@ -164,8 +164,8 @@ module.exports = { @@ -164,8 +164,8 @@ module.exports = {
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: '[name].css',
chunkFilename: '[id].css',
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css',
}),
new MediaQueryPlugin({

Loading…
Cancel
Save