Browse Source

fix static stickers & added button menu & user typing action

master
Eduard Kuzmenko 5 years ago
parent
commit
2f9396be3f
  1. BIN
      .DS_Store
  2. 48
      src/components/pageIm.ts
  3. 13
      src/lib/appManagers/apiUpdatesManager.ts
  4. 22
      src/lib/appManagers/appDialogsManager.ts
  5. 35
      src/lib/appManagers/appImManager.ts
  6. 11
      src/lib/appManagers/appMessagesManager.ts
  7. 8
      src/lib/appManagers/appPhotosManager.ts
  8. 22
      src/lib/appManagers/appSidebarLeft.ts
  9. 6
      src/lib/appManagers/appSidebarRight.ts
  10. 2
      src/lib/crypto/crypto_utils.ts
  11. 12
      src/lib/mtproto/apiFileManager.ts
  12. 2
      src/lib/mtproto/networker.ts
  13. 2
      src/lib/mtproto/networkerFactory.ts
  14. 2
      src/lib/mtproto/transports/abridged.ts
  15. 2
      src/lib/mtproto/transports/http.ts
  16. 2
      src/lib/mtproto/transports/transport.ts
  17. 187
      src/lib/mtproto/transports/websocket.ts
  18. 14
      src/scss/partials/_chat.scss
  19. 4
      src/scss/partials/_sidebar.scss
  20. 81
      src/scss/style.scss

BIN
.DS_Store vendored

Binary file not shown.

48
src/components/pageIm.ts

@ -533,15 +533,24 @@ export default () => import('../lib/services').then(services => {
} }
}); });
let lastTimeType = 0;
messageInput.addEventListener('input', function(this: typeof messageInput, e) { messageInput.addEventListener('input', function(this: typeof messageInput, e) {
//console.log('messageInput input', this.innerText, serializeNodes(Array.from(messageInput.childNodes))); //console.log('messageInput input', this.innerText, serializeNodes(Array.from(messageInput.childNodes)));
if(!this.innerText.trim() && !serializeNodes(Array.from(messageInput.childNodes)).trim()) { if(!this.innerText.trim() && !serializeNodes(Array.from(messageInput.childNodes)).trim()) {
this.innerHTML = ''; this.innerHTML = '';
btnSend.classList.remove('tgico-send'); btnSend.classList.remove('tgico-send');
btnSend.classList.add('tgico-microphone2'); btnSend.classList.add('tgico-microphone2');
appImManager.setTyping('sendMessageCancelAction');
} else if(!btnSend.classList.contains('tgico-send')) { } else if(!btnSend.classList.contains('tgico-send')) {
btnSend.classList.add('tgico-send'); btnSend.classList.add('tgico-send');
btnSend.classList.remove('tgico-microphone2'); btnSend.classList.remove('tgico-microphone2');
let time = Date.now();
if(time - lastTimeType >= 6000) {
lastTimeType = time;
appImManager.setTyping('sendMessageTypingAction');
}
} }
}); });
@ -749,6 +758,45 @@ export default () => import('../lib/services').then(services => {
toggleEmoticons.classList.toggle('active'); toggleEmoticons.classList.toggle('active');
}; */ }; */
let openedMenu: HTMLDivElement = null;
let onMouseMove = (e: MouseEvent) => {
let rect = openedMenu.getBoundingClientRect();
let {clientX, clientY} = e;
let diffX = clientX >= rect.right ? clientX - rect.right : rect.left - clientX;
let diffY = clientY >= rect.bottom ? clientY - rect.bottom : rect.top - clientY;
if(diffX >= 100 || diffY >= 100) {
openedMenu.parentElement.click();
}
//console.log('mousemove', diffX, diffY);
};
Array.from(document.getElementsByClassName('btn-menu-toggle')).forEach((el) => {
el.addEventListener('click', (e) => {
window.removeEventListener('mousemove', onMouseMove);
openedMenu = el.querySelector('.btn-menu');
e.cancelBubble = true;
if(el.classList.contains('menu-open')) {
el.classList.remove('menu-open');
openedMenu.classList.remove('active');
} else {
el.classList.add('menu-open');
openedMenu.classList.add('active');
window.addEventListener('click', () => {
//(el as HTMLDivElement).click();
el.classList.remove('menu-open');
openedMenu.classList.remove('active');
window.removeEventListener('mousemove', onMouseMove);
}, {once: true});
window.addEventListener('mousemove', onMouseMove);
}
});
});
loadDialogs().then(result => { loadDialogs().then(result => {
onScroll(); onScroll();

13
src/lib/appManagers/apiUpdatesManager.ts

@ -1,4 +1,5 @@
import { MTProto } from "../mtproto/mtproto"; import apiManager from '../mtproto/apiManager';
import networkerFactory from '../mtproto/networkerFactory';
import { dT, $rootScope, tsNow } from "../utils"; import { dT, $rootScope, tsNow } from "../utils";
import appPeersManager from "./appPeersManager"; import appPeersManager from "./appPeersManager";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
@ -26,7 +27,7 @@ export class ApiUpdatesManager {
public myID = 0; public myID = 0;
constructor() { constructor() {
MTProto.apiManager.getUserID().then((id) => { apiManager.getUserID().then((id) => {
this.myID = id; this.myID = id;
}); });
} }
@ -193,7 +194,7 @@ export class ApiUpdatesManager {
updatesState.syncPending = false; updatesState.syncPending = false;
} }
MTProto.apiManager.invokeApi('updates.getDifference', { apiManager.invokeApi('updates.getDifference', {
pts: updatesState.pts, pts: updatesState.pts,
date: updatesState.date, date: updatesState.date,
qts: -1 qts: -1
@ -267,7 +268,7 @@ export class ApiUpdatesManager {
channelState.syncPending = false; channelState.syncPending = false;
} }
// console.log(dT(), 'Get channel diff', appChatsManager.getChat(channelID), channelState.pts) // console.log(dT(), 'Get channel diff', appChatsManager.getChat(channelID), channelState.pts)
MTProto.apiManager.invokeApi('updates.getChannelDifference', { apiManager.invokeApi('updates.getChannelDifference', {
channel: appChatsManager.getChannelInput(channelID), channel: appChatsManager.getChannelInput(channelID),
filter: {_: 'channelMessagesFilterEmpty'}, filter: {_: 'channelMessagesFilterEmpty'},
pts: channelState.pts, pts: channelState.pts,
@ -499,8 +500,8 @@ export class ApiUpdatesManager {
} }
public attach() { public attach() {
MTProto.networkerFactory.setUpdatesProcessor(this.processUpdateMessage.bind(this)); networkerFactory.setUpdatesProcessor(this.processUpdateMessage.bind(this));
MTProto.apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult: any) => { apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult: any) => {
this.updatesState.seq = stateResult.seq; this.updatesState.seq = stateResult.seq;
this.updatesState.pts = stateResult.pts; this.updatesState.pts = stateResult.pts;
this.updatesState.date = stateResult.date; this.updatesState.date = stateResult.date;

22
src/lib/appManagers/appDialogsManager.ts

@ -1,10 +1,10 @@
import { MTProto } from "../mtproto/mtproto"; import apiManager from "../mtproto/apiManager";
import apiFileManager from '../mtproto/apiFileManager';
import { $rootScope, findUpTag } from "../utils"; import { $rootScope, findUpTag } from "../utils";
import appImManager from "./appImManager"; import appImManager from "./appImManager";
import appPeersManager from './appPeersManager'; import appPeersManager from './appPeersManager';
import appMessagesManager from "./appMessagesManager"; import appMessagesManager from "./appMessagesManager";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
import appSidebarRight from "./appSidebarRight";
import { RichTextProcessor } from "../richtextprocessor"; import { RichTextProcessor } from "../richtextprocessor";
import { ripple } from "../../components/misc"; import { ripple } from "../../components/misc";
@ -23,14 +23,13 @@ export class AppDialogsManager {
public pinnedChatList = document.getElementById('dialogs-pinned') as HTMLUListElement; public pinnedChatList = document.getElementById('dialogs-pinned') as HTMLUListElement;
public chatList = document.getElementById('dialogs') as HTMLUListElement; public chatList = document.getElementById('dialogs') as HTMLUListElement;
private topbar: HTMLDivElement = null;
private chatInput: HTMLDivElement = null;
public myID = 0; public myID = 0;
public doms: {[x: number]: any} = {}; public doms: {[x: number]: any} = {};
constructor() { constructor() {
MTProto.apiManager.getUserID().then((id) => { apiManager.getUserID().then((id) => {
this.myID = id; this.myID = id;
}); });
@ -40,8 +39,6 @@ export class AppDialogsManager {
}); });
//let chatClosedDiv = document.getElementById('chat-closed'); //let chatClosedDiv = document.getElementById('chat-closed');
this.topbar = document.getElementById('topbar') as HTMLDivElement;
this.chatInput = document.getElementById('chat-input') as HTMLDivElement;
this.setListClickListener(this.pinnedChatList); this.setListClickListener(this.pinnedChatList);
this.setListClickListener(this.chatList); this.setListClickListener(this.chatList);
@ -52,21 +49,22 @@ export class AppDialogsManager {
let target = e.target as HTMLElement; let target = e.target as HTMLElement;
let elem = target.tagName != 'LI' ? findUpTag(target, 'LI') : target; let elem = target.tagName != 'LI' ? findUpTag(target, 'LI') : target;
if(!elem) {
return;
}
if(elem) { if(elem) {
/* if(chatClosedDiv) { /* if(chatClosedDiv) {
chatClosedDiv.style.display = 'none'; chatClosedDiv.style.display = 'none';
} */ } */
this.topbar.style.display = this.chatInput.style.display = '';
if(onFound) onFound(); if(onFound) onFound();
let peerID = +elem.getAttribute('data-peerID'); let peerID = +elem.getAttribute('data-peerID');
let lastMsgID = +elem.getAttribute('data-mid'); let lastMsgID = +elem.getAttribute('data-mid');
appImManager.setPeer(peerID, lastMsgID); appImManager.setPeer(peerID, lastMsgID);
} else /* if(chatClosedDiv) */ { } else /* if(chatClosedDiv) */ {
appSidebarRight.toggleSidebar(false); appImManager.setPeer(0);
this.topbar.style.display = this.chatInput.style.display = 'none';
//chatClosedDiv.style.display = ''; //chatClosedDiv.style.display = '';
} }
}); });
@ -114,7 +112,7 @@ export class AppDialogsManager {
return true; return true;
} }
let res = await MTProto.apiFileManager.downloadSmallFile({ let res = await apiFileManager.downloadSmallFile({
_: 'inputPeerPhotoFileLocation', _: 'inputPeerPhotoFileLocation',
dc_id: location.dc_id, dc_id: location.dc_id,
flags: 0, flags: 0,

35
src/lib/appManagers/appImManager.ts

@ -1,4 +1,4 @@
import { MTProto } from "../mtproto/mtproto"; import apiManager from '../mtproto/apiManager';
import { $rootScope, isElementInViewport, numberWithCommas } from "../utils"; import { $rootScope, isElementInViewport, numberWithCommas } from "../utils";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
import appMessagesManager from "./appMessagesManager"; import appMessagesManager from "./appMessagesManager";
@ -88,6 +88,7 @@ class ScrollPosition {
export class AppImManager { export class AppImManager {
public pageEl = document.querySelector('.page-chats') as HTMLDivElement; public pageEl = document.querySelector('.page-chats') as HTMLDivElement;
public btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement; public btnMute = this.pageEl.querySelector('.tool-mute') as HTMLButtonElement;
public btnMenuMute = this.pageEl.querySelector('.menu-mute') as HTMLButtonElement;
public avatarEl = document.getElementById('im-avatar') as HTMLDivElement; public avatarEl = document.getElementById('im-avatar') as HTMLDivElement;
public titleEl = document.getElementById('im-title') as HTMLDivElement; public titleEl = document.getElementById('im-title') as HTMLDivElement;
public subtitleEl = document.getElementById('im-subtitle') as HTMLDivElement; public subtitleEl = document.getElementById('im-subtitle') as HTMLDivElement;
@ -129,15 +130,21 @@ export class AppImManager {
private typingTimeouts: {[peerID: number]: number} = {}; private typingTimeouts: {[peerID: number]: number} = {};
private typingUsers: {[userID: number]: number} = {} // to peerID private typingUsers: {[userID: number]: number} = {} // to peerID
private topbar: HTMLDivElement = null;
private chatInput: HTMLDivElement = null;
constructor() { constructor() {
this.log = logger('IM'); this.log = logger('IM');
this.preloader = new ProgressivePreloader(null, false); this.preloader = new ProgressivePreloader(null, false);
MTProto.apiManager.getUserID().then((id) => { apiManager.getUserID().then((id) => {
this.myID = id; this.myID = id;
}); });
this.topbar = document.getElementById('topbar') as HTMLDivElement;
this.chatInput = document.getElementById('chat-input') as HTMLDivElement;
$rootScope.$on('user_auth', (e: CustomEvent) => { $rootScope.$on('user_auth', (e: CustomEvent) => {
let userAuth = e.detail; let userAuth = e.detail;
this.myID = userAuth ? userAuth.id : 0; this.myID = userAuth ? userAuth.id : 0;
@ -328,7 +335,7 @@ export class AppImManager {
if(!this.myID) return Promise.resolve(); if(!this.myID) return Promise.resolve();
appUsersManager.setUserStatus(this.myID, this.offline); appUsersManager.setUserStatus(this.myID, this.offline);
return MTProto.apiManager.invokeApi('account.updateStatus', { return apiManager.invokeApi('account.updateStatus', {
offline: this.offline offline: this.offline
}, {noErrorBox: true}); }, {noErrorBox: true});
} }
@ -542,6 +549,13 @@ export class AppImManager {
} }
public setPeer(peerID: number, lastMsgID = 0) { public setPeer(peerID: number, lastMsgID = 0) {
if(peerID == 0) {
appSidebarRight.toggleSidebar(false);
this.topbar.style.display = this.chatInput.style.display = 'none';
this.cleanup();
return Promise.resolve(false);
}
let samePeer = this.peerID == peerID; let samePeer = this.peerID == peerID;
if(samePeer && !testScroll && !lastMsgID) { if(samePeer && !testScroll && !lastMsgID) {
@ -596,6 +610,7 @@ export class AppImManager {
this.titleEl.innerText = appSidebarRight.profileElements.name.innerText = dom.titleSpan.innerText; this.titleEl.innerText = appSidebarRight.profileElements.name.innerText = dom.titleSpan.innerText;
this.topbar.style.display = this.chatInput.style.display = '';
appSidebarRight.toggleSidebar(true); appSidebarRight.toggleSidebar(true);
return Promise.all([ return Promise.all([
@ -634,6 +649,20 @@ export class AppImManager {
}); });
} }
public setTyping(action: any): Promise<boolean> {
if(!this.peerID) return Promise.resolve(false);
if(typeof(action) == 'string') {
action = {_: action};
}
let input = appPeersManager.getInputPeerByID(this.peerID);
return apiManager.invokeApi('messages.setTyping', {
peer: input,
action: action
}) as Promise<boolean>;
}
public updateUnreadByDialog(dialog: any) { public updateUnreadByDialog(dialog: any) {
let maxID = dialog.read_outbox_max_id; let maxID = dialog.read_outbox_max_id;

11
src/lib/appManagers/appMessagesManager.ts

@ -15,6 +15,7 @@ import ServerTimeManager from "../mtproto/serverTimeManager";
import apiFileManager, { CancellablePromise } from "../mtproto/apiFileManager"; import apiFileManager, { CancellablePromise } from "../mtproto/apiFileManager";
import { MTDocument, ProgressivePreloader } from "../../components/misc"; import { MTDocument, ProgressivePreloader } from "../../components/misc";
import appDocsManager from "./appDocsManager"; import appDocsManager from "./appDocsManager";
import appImManager from "./appImManager";
type HistoryStorage = { type HistoryStorage = {
count: number | null, count: number | null,
@ -390,24 +391,30 @@ export class AppMessagesManager {
caption = RichTextProcessor.parseMarkdown(caption, entities); caption = RichTextProcessor.parseMarkdown(caption, entities);
} }
let actionName = '';
if(!options.isMedia) { if(!options.isMedia) {
attachType = 'document'; attachType = 'document';
apiFileName = 'document.' + fileType.split('/')[1]; apiFileName = 'document.' + fileType.split('/')[1];
actionName = 'sendMessageUploadDocumentAction';
} else if(isDocument) { // maybe it's a sticker } else if(isDocument) { // maybe it's a sticker
attachType = 'document'; attachType = 'document';
apiFileName = ''; apiFileName = '';
} else if(['image/jpeg', 'image/png', 'image/bmp'].indexOf(fileType) >= 0) { } else if(['image/jpeg', 'image/png', 'image/bmp'].indexOf(fileType) >= 0) {
attachType = 'photo'; attachType = 'photo';
apiFileName = 'photo.' + fileType.split('/')[1]; apiFileName = 'photo.' + fileType.split('/')[1];
actionName = 'sendMessageUploadPhotoAction';
} else if(fileType.substr(0, 6) == 'audio/' || ['video/ogg'].indexOf(fileType) >= 0) { } else if(fileType.substr(0, 6) == 'audio/' || ['video/ogg'].indexOf(fileType) >= 0) {
attachType = 'audio'; attachType = 'audio';
apiFileName = 'audio.' + (fileType.split('/')[1] == 'ogg' ? 'ogg' : 'mp3'); apiFileName = 'audio.' + (fileType.split('/')[1] == 'ogg' ? 'ogg' : 'mp3');
actionName = 'sendMessageUploadAudioAction';
} else if(fileType.substr(0, 6) == 'video/') { } else if(fileType.substr(0, 6) == 'video/') {
attachType = 'video'; attachType = 'video';
apiFileName = 'video.mp4'; apiFileName = 'video.mp4';
actionName = 'sendMessageUploadVideoAction';
} else { } else {
attachType = 'document'; attachType = 'document';
apiFileName = 'document.' + fileType.split('/')[1]; apiFileName = 'document.' + fileType.split('/')[1];
actionName = 'sendMessageUploadDocumentAction';
} }
// console.log(attachType, apiFileName, file.type) // console.log(attachType, apiFileName, file.type)
@ -457,6 +464,7 @@ export class AppMessagesManager {
preloader.preloader.onclick = () => { preloader.preloader.onclick = () => {
console.log('cancelling upload', media); console.log('cancelling upload', media);
appImManager.setTyping('sendMessageCancelAction');
media.progress.cancel(); media.progress.cancel();
}; };
@ -504,6 +512,8 @@ export class AppMessagesManager {
uploadPromise: ReturnType<typeof apiFileManager.uploadFile> = null; uploadPromise: ReturnType<typeof apiFileManager.uploadFile> = null;
let invoke = (flags: number, inputMedia: any) => { let invoke = (flags: number, inputMedia: any) => {
appImManager.setTyping('sendMessageCancelAction');
return MTProto.apiManager.invokeApi('messages.sendMedia', { return MTProto.apiManager.invokeApi('messages.sendMedia', {
flags: flags, flags: flags,
peer: AppPeersManager.getInputPeerByID(peerID), peer: AppPeersManager.getInputPeerByID(peerID),
@ -604,6 +614,7 @@ export class AppMessagesManager {
console.log('upload progress', progress); console.log('upload progress', progress);
media.progress.done = progress.done; media.progress.done = progress.done;
media.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); media.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
appImManager.setTyping({_: actionName, progress: media.progress.percent | 0});
preloader.setProgress(media.progress.percent); // lol, nice preloader.setProgress(media.progress.percent); // lol, nice
$rootScope.$broadcast('history_update', {peerID: peerID}); $rootScope.$broadcast('history_update', {peerID: peerID});
}; };

8
src/lib/appManagers/appPhotosManager.ts

@ -88,6 +88,8 @@ export class AppPhotosManager {
//console.log('choosePhotoSize', photo); //console.log('choosePhotoSize', photo);
let sizes = photo.sizes || photo.thumbs; let sizes = photo.sizes || photo.thumbs;
if(!sizes) return bestPhotoSize;
sizes.forEach((photoSize: typeof bestPhotoSize) => { sizes.forEach((photoSize: typeof bestPhotoSize) => {
if(!photoSize.w || !photoSize.h) return; if(!photoSize.w || !photoSize.h) return;
@ -190,7 +192,7 @@ export class AppPhotosManager {
return photoSize; return photoSize;
} }
public async preloadPhoto(photoID: any, photoSize?: any): Promise<Blob> { public async preloadPhoto(photoID: any, photoSize?: MTPhotoSize): Promise<Blob> {
let photo: any = null; let photo: any = null;
if(typeof(photoID) === 'string') { if(typeof(photoID) === 'string') {
@ -207,7 +209,7 @@ export class AppPhotosManager {
photoSize = this.choosePhotoSize(photo, fullWidth, fullHeight); photoSize = this.choosePhotoSize(photo, fullWidth, fullHeight);
} }
if(photoSize) { if(photoSize && photoSize._ != 'photoSizeEmpty') {
photoSize.preloaded = true; photoSize.preloaded = true;
// maybe it's a thumb // maybe it's a thumb
@ -242,7 +244,7 @@ export class AppPhotosManager {
console.log('Photos downloadSmallFile exec', photo, location); console.log('Photos downloadSmallFile exec', photo, location);
return MTProto.apiFileManager.downloadSmallFile(location); return MTProto.apiFileManager.downloadSmallFile(location);
} }
} else return Promise.reject('no fullPhotoSize'); } else return Promise.reject('no photoSize');
} }
public getPhoto(photoID: string) { public getPhoto(photoID: string) {

22
src/lib/appManagers/appSidebarLeft.ts

@ -2,8 +2,10 @@ import { logger } from "../polyfill";
import { scrollable } from "../../components/misc"; import { scrollable } from "../../components/misc";
import appMessagesManager from "./appMessagesManager"; import appMessagesManager from "./appMessagesManager";
import appDialogsManager from "./appDialogsManager"; import appDialogsManager from "./appDialogsManager";
import { isElementInViewport } from "../utils"; import { isElementInViewport, $rootScope } from "../utils";
import appMessagesIDsManager from "./appMessagesIDsManager"; import appMessagesIDsManager from "./appMessagesIDsManager";
import apiManager from '../mtproto/apiManager';
import appImManager from "./appImManager";
class AppSidebarLeft { class AppSidebarLeft {
private sidebarEl = document.querySelector('.page-chats .chats-container') as HTMLDivElement; private sidebarEl = document.querySelector('.page-chats .chats-container') as HTMLDivElement;
@ -11,6 +13,9 @@ class AppSidebarLeft {
private toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement; private toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement;
private searchContainer = this.sidebarEl.querySelector('#search-container') as HTMLDivElement; private searchContainer = this.sidebarEl.querySelector('#search-container') as HTMLDivElement;
private menuEl = this.toolsBtn.querySelector('.btn-menu');
private savedBtn = this.menuEl.querySelector('.menu-saved');
private listsContainer: HTMLDivElement = null; private listsContainer: HTMLDivElement = null;
private searchMessagesList: HTMLUListElement = null; private searchMessagesList: HTMLUListElement = null;
@ -27,10 +32,25 @@ class AppSidebarLeft {
private query = ''; private query = '';
public myID = 0;
constructor() { constructor() {
this.listsContainer = scrollable(this.searchContainer); this.listsContainer = scrollable(this.searchContainer);
this.searchMessagesList = document.createElement('ul'); this.searchMessagesList = document.createElement('ul');
apiManager.getUserID().then((id) => {
this.myID = id;
});
$rootScope.$on('user_auth', (e: CustomEvent) => {
let userAuth = e.detail;
this.myID = userAuth ? userAuth.id : 0;
});
this.savedBtn.addEventListener('click', () => {
appImManager.setPeer(this.myID);
});
this.listsContainer.addEventListener('scroll', this.onSidebarScroll.bind(this)); this.listsContainer.addEventListener('scroll', this.onSidebarScroll.bind(this));
this.searchContainer.append(this.listsContainer); this.searchContainer.append(this.listsContainer);

6
src/lib/appManagers/appSidebarRight.ts

@ -380,6 +380,12 @@ class AppSidebarRight {
} else { } else {
appImManager.btnMute.style.display = 'none'; appImManager.btnMute.style.display = 'none';
} }
appImManager.btnMenuMute.classList.remove('tgico-mute', 'tgico-unmute');
appImManager.btnMenuMute.classList.add(muted ? 'tgico-unmute' : 'tgico-mute');
let rp = appImManager.btnMenuMute.firstElementChild;
appImManager.btnMenuMute.innerText = muted ? 'Unmute' : 'Mute';
appImManager.btnMenuMute.appendChild(rp);
} }
//this.loadSidebarMedia(); //this.loadSidebarMedia();

2
src/lib/crypto/crypto_utils.ts

@ -6,7 +6,7 @@ import {str2bigInt, bpe, equalsInt, greater,
divide_, one, bigInt2str, powMod} from 'leemon'; divide_, one, bigInt2str, powMod} from 'leemon';
// @ts-ignore // @ts-ignore
import {BigInteger, SecureRandom} from 'jsbn'; import {BigInteger} from 'jsbn';
import CryptoJS from './crypto.js'; import CryptoJS from './crypto.js';
import { addPadding, bytesToHex, bytesFromHex, nextRandomInt, bytesFromBigInt, dT, bytesFromWords } from '../bin_utils'; import { addPadding, bytesToHex, bytesFromHex, nextRandomInt, bytesFromBigInt, dT, bytesFromWords } from '../bin_utils';

12
src/lib/mtproto/apiFileManager.ts

@ -179,6 +179,8 @@ export class ApiFileManager {
return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'}); return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'});
} }
this.log('downloadSmallFile', location, options);
let dcID = options.dcID || location.dc_id; let dcID = options.dcID || location.dc_id;
let mimeType = options.mimeType || 'image/jpeg'; let mimeType = options.mimeType || 'image/jpeg';
var fileName = this.getFileName(location); var fileName = this.getFileName(location);
@ -270,7 +272,7 @@ export class ApiFileManager {
var cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName]; var cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName];
var fileStorage = this.getFileStorage(); var fileStorage = this.getFileStorage();
this.log('downloadFile', fileStorage.name, fileName, fileName.length, location, arguments); //this.log('downloadFile', fileStorage.name, fileName, fileName.length, location, arguments);
if(cachedPromise) { if(cachedPromise) {
if(toFileEntry) { if(toFileEntry) {
@ -281,7 +283,7 @@ export class ApiFileManager {
}); });
} }
this.log('downloadFile cachedPromise'); //this.log('downloadFile cachedPromise');
if(size) { if(size) {
let blob = await cachedPromise; let blob = await cachedPromise;
@ -296,7 +298,7 @@ export class ApiFileManager {
} }
} }
this.log('arriba'); //this.log('arriba');
//var deferred = $q.defer() //var deferred = $q.defer()
let deferredHelper: any = {notify: () => {}}; let deferredHelper: any = {notify: () => {}};
@ -321,7 +323,7 @@ export class ApiFileManager {
}; };
fileStorage.getFile(fileName, size).then(async(blob: Blob) => { fileStorage.getFile(fileName, size).then(async(blob: Blob) => {
this.log('is that i wanted'); //this.log('is that i wanted');
if(blob.size < size) { if(blob.size < size) {
this.log('downloadFile need to deleteFile 2, wrong size:', blob.size, size); this.log('downloadFile need to deleteFile 2, wrong size:', blob.size, size);
@ -338,7 +340,7 @@ export class ApiFileManager {
} }
//}, () => { //}, () => {
}).catch(() => { }).catch(() => {
this.log('not i wanted'); //this.log('not i wanted');
var fileWriterPromise = toFileEntry ? FileManager.getFileWriter(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType); var fileWriterPromise = toFileEntry ? FileManager.getFileWriter(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType);
var processDownloaded = (bytes: any) => { var processDownloaded = (bytes: any) => {

2
src/lib/mtproto/networker.ts

@ -100,7 +100,7 @@ class MTPNetworker {
this.log = logger('NET-' + dcID + (this.upload ? '-U' : '')); this.log = logger('NET-' + dcID + (this.upload ? '-U' : ''));
this.log('constructor', this.authKey, this.authKeyID, this.serverSalt); this.log('constructor'/* , this.authKey, this.authKeyID, this.serverSalt */);
this.updateSession(); this.updateSession();

2
src/lib/mtproto/networkerFactory.ts

@ -34,7 +34,7 @@ export class NetworkerFactory {
} }
public getNetworker(dcID: number, authKey: number[], authKeyID: Uint8Array, serverSalt: number[], options: any) { public getNetworker(dcID: number, authKey: number[], authKeyID: Uint8Array, serverSalt: number[], options: any) {
console.log(dT(), 'NetworkerFactory: creating new instance of MTPNetworker:', dcID, options); //console.log(dT(), 'NetworkerFactory: creating new instance of MTPNetworker:', dcID, options);
return new MTPNetworker(dcID, authKey, authKeyID, serverSalt, options); return new MTPNetworker(dcID, authKey, authKeyID, serverSalt, options);
} }
} }

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

@ -11,7 +11,7 @@ class AbridgedPacketCodec {
header = new Uint8Array([len]); header = new Uint8Array([len]);
} else { } else {
header = new Uint8Array([0x7f, ...addPadding(bytesFromHex(len.toString(16)).reverse(), 3, true)/* .reverse() */]); header = new Uint8Array([0x7f, ...addPadding(bytesFromHex(len.toString(16)).reverse(), 3, true)/* .reverse() */]);
console.log('got nobody cause im braindead', header, len); //console.log('got nobody cause im braindead', header, len);
} }
return new Uint8Array([...header, ...data]); return new Uint8Array([...header, ...data]);

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

@ -8,7 +8,7 @@ export default class HTTP extends MTTransport {
send = (data: Uint8Array) => { send = (data: Uint8Array) => {
return fetch(this.url, {method: 'POST', body: data}).then(response => { return fetch(this.url, {method: 'POST', body: data}).then(response => {
console.log('http response', response/* , response.arrayBuffer() */); //console.log('http response', response/* , response.arrayBuffer() */);
if(response.status != 200) { if(response.status != 200) {
response.arrayBuffer().then(buffer => { response.arrayBuffer().then(buffer => {

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

@ -3,5 +3,5 @@ export default abstract class MTTransport {
} }
abstract send: (data: Uint8Array/* , msgKey?: Uint8Array*/) => Promise<Uint8Array>; abstract send: (data: Uint8Array) => Promise<Uint8Array>;
} }

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

@ -4,26 +4,14 @@ import aesjs from 'aes-js';
import abridgetPacketCodec from './abridged'; import abridgetPacketCodec from './abridged';
import {MTPNetworker} from '../networker'; import {MTPNetworker} from '../networker';
import { logger } from '../../polyfill'; import { logger } from '../../polyfill';
//import '../../types.d.ts';
export class Obfuscation { export class Obfuscation {
/** Encription Cipher */
public enc: aesjs.ModeOfOperation.ModeOfOperationCTR; public enc: aesjs.ModeOfOperation.ModeOfOperationCTR;
/** Decription Cipher */
public dec: aesjs.ModeOfOperation.ModeOfOperationCTR; public dec: aesjs.ModeOfOperation.ModeOfOperationCTR;
//public initPayload: Uint8Array;
/**
* Creates initialization payload for establishing web-socket connection
*/
public init() { public init() {
//if(this.initPayload) return this.initPayload;
const initPayload = new Uint8Array(64); const initPayload = new Uint8Array(64);
initPayload.randomize(); initPayload.randomize();
//initPayload.set(new Uint8Array(bytesFromHex('8546029e63835e4138142813963d2987482dd6126089a1852ffadec149b4375c568dd0591d6b66cc95cec4b280b16f82fb6461ee1842b26fafc9ea76991ea4b1')));
while(true) { while(true) {
let val = (initPayload[3] << 24) | (initPayload[2] << 16) | (initPayload[1] << 8) | (initPayload[0]); let val = (initPayload[3] << 24) | (initPayload[2] << 16) | (initPayload[1] << 8) | (initPayload[0]);
@ -41,15 +29,9 @@ export class Obfuscation {
initPayload.randomize(); initPayload.randomize();
} }
//initPayload[0] = 0xFF;
//let h = initPayload.hex;
//console.log('initPayload.hex', initPayload.hex);
////////////////////////initPayload.subarray(60, 62).hex = dcID; ////////////////////////initPayload.subarray(60, 62).hex = dcID;
//console.log(initPayload.hex, h == initPayload.hex);
//console.log('initPayload', initPayload.hex);
const reversedPayload = initPayload.slice().reverse(); const reversedPayload = initPayload.slice().reverse();
//console.log('initPayload', initPayload.hex);
let encKey = initPayload.slice(8, 40); let encKey = initPayload.slice(8, 40);
let encIv = initPayload.slice(40, 56); let encIv = initPayload.slice(40, 56);
@ -61,43 +43,28 @@ export class Obfuscation {
initPayload.set(abridgetPacketCodec.obfuscateTag, 56); initPayload.set(abridgetPacketCodec.obfuscateTag, 56);
const encrypted = this.encode(initPayload); const encrypted = this.encode(initPayload);
//console.log('encrypted', encrypted.hex);
initPayload.set(encrypted.slice(56, 64), 56); initPayload.set(encrypted.slice(56, 64), 56);
//console.log('initPayload.hex', initPayload.hex);
return /* this.initPayload = */ initPayload; return initPayload;
} }
/**
* Obfuscates data
*/
public encode(payload: Uint8Array) { public encode(payload: Uint8Array) {
return this.enc.encrypt(payload); return this.enc.encrypt(payload);
} }
/**
* Decodes obfuscated data
*/
public decode(data: Uint8Array) { public decode(data: Uint8Array) {
return this.dec.encrypt(data); return this.dec.encrypt(data);
} }
} }
//let obfuscation = new Obfuscation();
export default class Socket extends MTTransport { export default class Socket extends MTTransport {
/** Connection handler */
ws: WebSocket | undefined; ws: WebSocket | undefined;
/** Pending requests */ pending: Array<{resolve?: any, reject?: any, body?: Uint8Array}> = [];
pending: Array<{resolve?: any, reject?: any, body?: Uint8Array/* , msgKey?: Uint8Array */}> = [];
/** WebSocket connecting flag */
connected = false; connected = false;
/** Instance transport */
transport = 'websocket'; transport = 'websocket';
obfuscation = new Obfuscation(); obfuscation = new Obfuscation();
@ -108,9 +75,6 @@ export default class Socket extends MTTransport {
debug = false; debug = false;
/**
* Creates new web socket handler
*/
constructor(dcID: number, url: string) { constructor(dcID: number, url: string) {
super(dcID, url); super(dcID, url);
@ -129,65 +93,24 @@ export default class Socket extends MTTransport {
this.ws.close(1000); this.ws.close(1000);
} }
this.ws = new WebSocket(/* dcConfigurator.chooseServer(this.dcID, false, 'websocket') */this.url, 'binary'); this.ws = new WebSocket(this.url, 'binary');
this.ws.binaryType = 'arraybuffer'; this.ws.binaryType = 'arraybuffer';
this.ws.onopen = this.handleOpen; this.ws.onopen = this.handleOpen;
this.ws.onclose = this.handleClose; this.ws.onclose = this.handleClose;
this.ws.onmessage = this.handleMessage; this.ws.onmessage = this.handleMessage;
}; };
/**
* Handles onopen event at websocket object
*/
handleOpen = () => { handleOpen = () => {
this.log('opened'); this.log('opened');
//obfuscation.init();
//this.ws.send(new Uint8Array(bytesFromHex('ffc8b30b09c27d42791242f256b5186d3716f6f4f28121cf10cadc2196e496f092f97d13ed2c5a8b7181ca08ebe18e714ccac1cd60e88c4989bb4255682331c0')));
this.ws.send(this.obfuscation.init()); this.ws.send(this.obfuscation.init());
this.connected = true; this.connected = true;
this.releasePending(); this.releasePending();
/* let request = new TLSerialization({mtproto: true});
//let nonce = new Uint8Array(bytesFromHex('a370ec66e6e03c7b83843f3dfda22fd4').reverse());
let nonce = new Uint8Array(16).randomize();
request.storeMethod('req_pq_multi', {nonce: nonce});
let body = request.getBytes(true) as Uint8Array;
this.send(body); */
//this.ws.send(new Uint8Array(bytesFromHex('0e3cae6322eb93d4fc09d924fb17eb7887f1002679dedec754a6130f31e66c19016e6f3693a803b0a44d0567fcd01fe6a38b70fd328d3ebe9302f73454edd93a')).buffer);
//initPayload.slice(56).raw = encrypted.slice(56).raw;
/* const { dc, thread, protocol } = this.cfg;
const payload = {
dc, thread, protocol, transport: this.transport,
};
async('transport_init', payload, (initPayload: Bytes) => {
if (!this.ws) return;
this.ws.send(initPayload.buffer.buffer);
this.isConnecting = false;
log(this.cfg.dc, 'ready');
this.releasePending();
}); */
}; };
/**
* Handles onclose event at websocket object
*/
handleClose = (event: CloseEvent) => { handleClose = (event: CloseEvent) => {
this.log('closed', event); this.log('closed', event);
//this.emit('disconnected');
//this.pending = [];
this.connected = false; this.connected = false;
this.pending.length = 0; this.pending.length = 0;
@ -197,16 +120,8 @@ export default class Socket extends MTTransport {
this.log('trying to reconnect...'); this.log('trying to reconnect...');
this.connect(); this.connect();
//this.cfg.resolveError(this.cfg.dc, this.cfg.thread, this.transport, this.lastNonce || '', event.code, event.reason);
}; };
/* set networker(networker: MTPNetworker) {
this.networker = networker;
} */
/**
* Handles onmessage event at websocket object
*/
handleMessage = (event: MessageEvent) => { handleMessage = (event: MessageEvent) => {
this.debug && this.log('<-', 'handleMessage', event); this.debug && this.log('<-', 'handleMessage', event);
@ -230,51 +145,9 @@ export default class Socket extends MTTransport {
} }
pending.resolve(data); pending.resolve(data);
/* try {
let deserializer = new TLDeserialization(data.buffer, {mtproto: true});
let auth_key_id = deserializer.fetchLong('auth_key_id');
if(auth_key_id != 0) console.error('auth_key_id != 0', auth_key_id);
let msg_id = deserializer.fetchLong('msg_id');
if(msg_id == 0) console.error('msg_id == 0', msg_id);
let msg_len = deserializer.fetchInt('msg_len');
if(!msg_len) console.error('no msg_len', msg_len);
let response = deserializer.fetchObject('ResPQ');
console.log(response);
} catch(e) {
console.error('mtpSendPlainRequest: deserialization went bad', e);
throw e;
} */
/* const authKey = this.svc.getAuthKey(this.cfg.dc);
const { dc, thread } = this.cfg;
const payload = {
dc, thread, transport: this.transport, authKey: authKey ? authKey.key : '', msg: new Bytes(event.data),
};
if (!event.data) return;
async('transport_decrypt', payload, (msg: Message | PlainMessage | Bytes) => {
if (msg instanceof PlainMessage) this.lastNonce = msg.nonce;
if (msg instanceof Message || msg instanceof PlainMessage) {
this.cfg.resolve(msg, {
dc: this.cfg.dc,
thread: this.cfg.thread,
transport: this.transport,
msgID: msg.id,
});
} else {
throw new Error(`Unexpected answer: ${msg.hex}`);
}
}); */
}; };
/** send = (body: Uint8Array) => {
* Method sends bytes to server via web socket.
*/
send = (body: Uint8Array/* , msgKey?: Uint8Array */) => {
this.debug && this.log('-> body length to pending:', body.length); this.debug && this.log('-> body length to pending:', body.length);
if(this.networker) { if(this.networker) {
@ -289,58 +162,6 @@ export default class Socket extends MTTransport {
return promise; return promise;
} }
/* let promise = new Promise<Uint8Array>((resolve, reject) => {
this.pending.push({resolve, reject, body});
this.releasePending();
});
return promise; */
/* // let msg_id = '6784284127384679768';//timeManager.generateID();
let msg_id = timeManager.generateID();
console.log('generated msg_id:', msg_id);
let packed = new TLSerialization({
mtproto: true
});
//packed.storeRawBytes([0x0a]);
packed.storeLong(0);
packed.storeLong(msg_id);
packed.storeInt(body.byteLength);
packed.storeRawBytes(body);
console.log('packed', (packed.getBytes(true) as Uint8Array).hex,
bytesToHex([...new Uint8Array(bigStringInt(msg_id).toByteArray())]));
let toEncode = abridgetPacketCodec.encodePacket(packed.getBytes(true) as Uint8Array);
console.log('send req_pq:', toEncode.hex);
//let enc = obfuscation.encode(request.getBytes(true) as Uint8Array);
let enc = obfuscation.encode(toEncode);
//let enc = new Uint8Array(bytesFromHex('b6f899247854750f879db416e95fd41145e8f7f910741b50c02a20025d3f9cbd09b09f3306be378c43'));
//let enc = obfuscation.encode(new Uint8Array(bytesFromHex('00000000000000000424ec94a191265e14000000f18e7ebef8c6203ebc2ae31b44a3aafd8afdf367')));
console.log('send req_pq:', enc.hex);
this.ws.send(enc); */
//if (msg instanceof PlainMessage) this.lastNonce = msg.nonce;
/* if(this.ws && this.ws.readyState === 1) {
const authKey = this.svc.getAuthKey(this.cfg.dc);
const { dc, thread } = this.cfg;
const payload = {
msg, dc, thread, transport: this.transport, authKey: authKey ? authKey.key : '',
};
async('transport_encrypt', payload, (data: Bytes) => {
if (this.ws) this.ws.send(data.buffer.buffer);
});
this.releasePending();
return;
} */
//this.pending.push(msg);
} }
releasePending() { releasePending() {

14
src/scss/partials/_chat.scss

@ -227,11 +227,6 @@
} }
} }
img, video {
/* object-fit: contain; */
object-fit: cover;
}
.emoji { .emoji {
height: 18px; height: 18px;
width: 18px; width: 18px;
@ -296,6 +291,10 @@
max-width: 300px; max-width: 300px;
max-height: 300px; max-height: 300px;
img {
object-fit: contain;
}
.message.message-empty { .message.message-empty {
display: none; display: none;
} }
@ -336,6 +335,11 @@
max-width: 380px; max-width: 380px;
max-height: 380px; max-height: 380px;
} }
img, video {
/* object-fit: contain; */
object-fit: cover;
}
} }
&.video { &.video {

4
src/scss/partials/_sidebar.scss

@ -30,10 +30,6 @@
} }
} }
.sidebar-menu-button > div {
display: none;
}
.profile-content { .profile-content {
.profile-name { .profile-name {
text-align: center; text-align: center;

81
src/scss/style.scss

@ -127,6 +127,86 @@ input {
} }
} }
.danger {
color: $color-error!important;
}
.btn-menu-toggle {
position: relative;
overflow: visible;
&.menu-open {
background-color: rgba(112, 117, 121, 0.08);
}
.btn-menu {
visibility: hidden;
position: absolute;
background: #fff;
box-shadow: 0 5px 8px 1px rgba(0,0,0,.24);
z-index: 1;
top: 100%;
margin-top: 8px;
padding: 9px 0;
border-radius: $border-radius;
opacity: 0;
transform: scale(.8);
transition-property: opacity,transform,visibility;
transition-duration: .2s;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
font-size: 16px;
&.active {
visibility: visible;
opacity: 1;
transform: scale(1);
}
&.bottom-left {
right: 0;
top: 100%;
transform-origin: top right;
}
&.bottom-right {
left: 0;
top: 100%;
transform-origin: top left;
}
> div {
display: flex;
position: relative;
padding: 0 40px 0 20px;
height: 56px;
cursor: pointer;
background-position: 16px center;
background-size: 24px 24px;
background-repeat: no-repeat;
color: #000;
text-transform: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
align-items: center;
&:hover {
background-color: rgba(112, 117, 121, 0.06);
}
&:before {
color: $color-gray;
font-size: 1.5rem;
margin-right: 35px;
}
&.danger:before {
color: $color-error;
}
}
}
}
.user-avatar { .user-avatar {
color: #fff; color: #fff;
width: 52px; width: 52px;
@ -200,6 +280,7 @@ input {
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
background: transparent; background: transparent;
border-radius: inherit;
} }
.c-ripple__circle { .c-ripple__circle {

Loading…
Cancel
Save