Browse Source

Rollback Safari connection

Done local forward
master
Eduard Kuzmenko 4 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 {
}); });
this.listenerSetter.add(rootScope, 'album_edit', (e) => { 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; const {peerId, groupId, deletedMids} = e;
if(peerId !== this.peerId) return; if(peerId !== this.peerId) return;
@ -319,7 +319,7 @@ export default class ChatBubbles {
const renderMaxId = getObjectKeysAndSort(this.appMessagesManager.groupedMessagesStorage[groupId], 'asc').pop(); const renderMaxId = getObjectKeysAndSort(this.appMessagesManager.groupedMessagesStorage[groupId], 'asc').pop();
this.renderMessage(this.chat.getMessage(renderMaxId), true, false, this.bubbles[renderedId], false); this.renderMessage(this.chat.getMessage(renderMaxId), true, false, this.bubbles[renderedId], false);
}); //});
}); });
this.listenerSetter.add(rootScope, 'messages_downloaded', (e) => { this.listenerSetter.add(rootScope, 'messages_downloaded', (e) => {

6
src/helpers/context.ts

@ -26,7 +26,7 @@ const notifyWorker = (...args: any[]) => {
(self as any as DedicatedWorkerGlobalScope).postMessage(...args); (self as any as DedicatedWorkerGlobalScope).postMessage(...args);
}; };
const empty = () => {}; const noop = () => {};
export const notifySomeone = isServiceWorker ? notifyServiceWorker.bind(null, false) : (isWebWorker ? notifyWorker : empty); export const notifySomeone = isServiceWorker ? notifyServiceWorker.bind(null, false) : (isWebWorker ? notifyWorker : noop);
export const notifyAll = isServiceWorker ? notifyServiceWorker.bind(null, true) : (isWebWorker ? notifyWorker : empty); 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
* *
* This should be removed once the underlying Safari issue is fixed. * 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 // https://stackoverflow.com/a/58065241
export const isAppleMobile = (/iPad|iPhone|iPod/.test(navigator.platform) || export const isAppleMobile = (/iPad|iPhone|iPod/.test(navigator.platform) ||

3
src/lib/appManagers/appDialogsManager.ts

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

131
src/lib/appManagers/appMessagesManager.ts

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

23
src/lib/mtproto/apiFileManager.ts

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

5
src/lib/mtproto/apiManager.ts

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

4
src/lib/mtproto/dcConfigurator.ts

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

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

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

24
src/lib/mtproto/mtprotoworker.ts

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

141
src/lib/mtproto/networker.ts

@ -13,6 +13,8 @@ import { longToBytes } from '../crypto/crypto_utils';
import MTTransport from './transports/transport'; import MTTransport from './transports/transport';
import { convertToUint8Array, bufferConcat, bytesCmp, bytesToHex } from '../../helpers/bytes'; import { convertToUint8Array, bufferConcat, bytesCmp, bytesToHex } from '../../helpers/bytes';
import { nextRandomInt, randomLong } from '../../helpers/random'; import { nextRandomInt, randomLong } from '../../helpers/random';
import { CancellablePromise, deferredPromise } from '../../helpers/cancellablePromise';
import { isSafari } from '../../helpers/userAgent';
/// #if MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HTTP_UPLOAD
// @ts-ignore // @ts-ignore
@ -112,6 +114,9 @@ export default class MTPNetworker {
public isOnline = false; public isOnline = false;
private lastResponseTime = 0; private lastResponseTime = 0;
private disconnectDelay: number; private disconnectDelay: number;
private pingPromise: CancellablePromise<any>;
private sentPingTimes = 0;
private tt = 0;
//public onConnectionStatusChange: (online: boolean) => void; //public onConnectionStatusChange: (online: boolean) => void;
constructor(public dcId: number, private authKey: number[], private authKeyId: Uint8Array, constructor(public dcId: number, private authKey: number[], private authKeyId: Uint8Array,
@ -161,9 +166,13 @@ export default class MTPNetworker {
// * handle outcoming dead socket, server will close the connection // * handle outcoming dead socket, server will close the connection
if((this.transport as Socket).networker) { if((this.transport as Socket).networker) {
this.disconnectDelay = (this.transport as Socket).retryTimeout / 1000 | 0; if(isSafari) {
setInterval(this.sendPingDelayDisconnect, (this.disconnectDelay - 5) * 1000); this.pingPromise = Promise.resolve();
this.sendPingDelayDisconnect(); } 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 {
} }
private sendPingDelayDisconnect = () => { private sendPingDelayDisconnect = () => {
if(!this.isOnline) return; // * already disconnected if(this.pingPromise) return;
this.wrapMtpCall('ping_delay_disconnect', {
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(), ping_id: randomLong(),
disconnect_delay: this.disconnectDelay disconnect_delay: this.disconnectDelay
}, { }, {
noResponse: true, noResponse: true,
notContentRelated: 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 {
this.log.error('timeout', message); this.log.error('timeout', message);
this.setConnectionStatus(false); this.setConnectionStatus(false);
this.getEncryptedOutput(message).then(bytes => {
this.log.error('timeout encrypted', bytes);
});
}, CONNECTION_TIMEOUT); }, CONNECTION_TIMEOUT);
promise.finally(() => { promise.finally(() => {
@ -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) { /* if(this.onConnectionStatusChange) {
this.onConnectionStatusChange(this.isOnline); this.onConnectionStatusChange(this.isOnline);
@ -845,10 +912,14 @@ export default class MTPNetworker {
}); });
} }
public sendEncryptedRequest(message: MTMessage) { public getEncryptedOutput(message: MTMessage) {
/* if(DEBUG) { /* if(DEBUG) {
this.log.debug('Send encrypted', message, this.authKeyId); 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({ const data = new TLSerialization({
startMaxLength: message.body.length + 2048 startMaxLength: message.body.length + 2048
@ -859,26 +930,48 @@ export default class MTPNetworker {
data.storeLong(message.msg_id, 'message_id'); data.storeLong(message.msg_id, 'message_id');
data.storeInt(message.seq_no, 'seq_no'); data.storeInt(message.seq_no, 'seq_no');
data.storeInt(message.body.length, 'message_data_length'); data.storeInt(message.body.length, 'message_data_length');
data.storeRawBytes(message.body, 'message_data'); 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(); 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 paddingLength = (16 - (data.offset % 16)) + 16 * (1 + nextRandomInt(5));
const padding = [...new Uint8Array(paddingLength).randomize()]; 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); const dataWithPadding = bufferConcat(dataBuffer, padding);
// this.log('Adding padding', dataBuffer, padding, dataWithPadding) // this.log('Adding padding', dataBuffer, padding, dataWithPadding)
// this.log('auth_key_id', bytesToHex(self.authKeyID)) // 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); this.log('Send encrypted: body length:', (message.body as ArrayBuffer).byteLength, paddingLength, dataWithPadding);
} */ } */
return this.getEncryptedMessage(dataWithPadding).then((encryptedResult) => { return this.getEncryptedMessage(dataWithPadding).then((encryptedResult) => {
/* if(DEBUG) { /* if(DEBUG) {
this.log.debug('Got encrypted out message', encryptedResult); this.log('Got encrypted out message', encryptedResult);
} */ } */
const request = new TLSerialization({ const request = new TLSerialization({
@ -890,10 +983,17 @@ export default class MTPNetworker {
const requestData = request.getBytes(true); const requestData = request.getBytes(true);
/* if(message.fileUpload) { //if(message.fileUpload) {
this.log('Send encrypted: requestData length:', requestData.length, requestData.length % 16, paddingLength % 16, paddingLength, data.offset); //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); const promise = this.transport.send(requestData);
/// #if !MTPROTO_HTTP && !MTPROTO_HTTP_UPLOAD /// #if !MTPROTO_HTTP && !MTPROTO_HTTP_UPLOAD
return promise; return promise;
@ -1381,17 +1481,16 @@ export default class MTPNetworker {
break; break;
} }
case 'pong': { // * https://core.telegram.org/mtproto/service_messages#ping-messages-pingpong - These messages do not require acknowledgments case 'pong': { // * https://core.telegram.org/mtproto/service_messages#ping-messages-pingpong - These messages doesn't require acknowledgments
/* if((this.transport as Socket).networker) { if((this.transport as Socket).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);
delete this.sentMessages[sentMessageId]; delete this.sentMessages[sentMessageId];
} }
}
} */
break; break;
} }

19
src/lib/mtproto/timeManager.ts

@ -1,6 +1,14 @@
import sessionStorage from '../sessionStorage'; import sessionStorage from '../sessionStorage';
import { longFromInts } from './bin_utils'; import { longFromInts } from './bin_utils';
import { nextRandomInt } from '../../helpers/random'; 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 { export class TimeManager {
private lastMessageId = [0, 0]; private lastMessageId = [0, 0];
@ -30,7 +38,12 @@ export class TimeManager {
const ret = longFromInts(messageId[0], messageId[1]); 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 return ret
} }
@ -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 {
public byteView: Uint8Array; public byteView: Uint8Array;
constructor(options: Partial<{startMaxLength: number, mtproto: true}> = {}) { 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.mtproto = options.mtproto || false;
this.createBuffer(); this.createBuffer();
} }
@ -38,11 +38,22 @@ class TLSerialization {
public getArray() { public getArray() {
const resultBuffer = new ArrayBuffer(this.offset); const resultBuffer = new ArrayBuffer(this.offset);
const resultArray = new Int32Array(resultBuffer);
//let perf = performance.now();
resultArray.set(this.intView.subarray(0, this.offset / 4)); /* 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() { public getBuffer() {

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

@ -1,8 +1,55 @@
//import aesjs from 'aes-js'; //import aesjs from 'aes-js';
import { CTR } from "@cryptography/aes"; import AES from "@cryptography/aes";
import { bytesFromWordss } from "../../../helpers/bytes"; import { bytesFromWordss } from "../../../helpers/bytes";
import { Codec } from "./codec"; 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 @cryptography/aes не работает с массивами которые не кратны 4, поэтому использую intermediate а не abridged
*/ */
@ -98,6 +145,13 @@ export default class Obfuscation {
return res; return res;
} */ } */
public encode(payload: Uint8Array) { 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 res = this.encNew.encrypt(payload);
let bytes = new Uint8Array(bytesFromWordss(res)); let bytes = new Uint8Array(bytesFromWordss(res));
@ -109,5 +163,5 @@ export default class Obfuscation {
let bytes = new Uint8Array(bytesFromWordss(res)); let bytes = new Uint8Array(bytesFromWordss(res));
return bytes; return bytes;
} } */
} }

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

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

10
webpack.common.js

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

Loading…
Cancel
Save