Browse Source

Fix search saved messages in forward

Connect to different DC's on sign in
Media viewer fixes
Auto focus field on sign pages
Reference expired fix for stream
master
morethanwords 4 years ago
parent
commit
2be9a62a37
  1. 2
      src/components/appSearch.ts
  2. 21
      src/components/appSelectPeers.ts
  3. 2
      src/components/chat/contextMenu.ts
  4. 9
      src/components/chat/input.ts
  5. 243
      src/components/dialogsContextMenu.ts
  6. 4
      src/components/emoticonsDropdown/index.ts
  7. 2
      src/components/emoticonsDropdown/tabs/emoji.ts
  8. 2
      src/components/emoticonsDropdown/tabs/gifs.ts
  9. 2
      src/components/emoticonsDropdown/tabs/stickers.ts
  10. 2
      src/components/gifsMasonry.ts
  11. 9
      src/components/horizontalMenu.ts
  12. 2
      src/components/poll.ts
  13. 2
      src/components/popupCreatePoll.ts
  14. 2
      src/components/popupStickers.ts
  15. 6
      src/components/preloader.ts
  16. 0
      src/components/scrollable.ts
  17. 2
      src/components/searchInput.ts
  18. 257
      src/components/sidebarLeft/index.ts
  19. 10
      src/components/sidebarLeft/tabs/addMembers.ts
  20. 52
      src/components/sidebarLeft/tabs/archivedTab.ts
  21. 24
      src/components/sidebarLeft/tabs/chatFolders.ts
  22. 30
      src/components/sidebarLeft/tabs/contacts.ts
  23. 18
      src/components/sidebarLeft/tabs/editFolder.ts
  24. 18
      src/components/sidebarLeft/tabs/editProfile.ts
  25. 18
      src/components/sidebarLeft/tabs/includedChats.ts
  26. 8
      src/components/sidebarLeft/tabs/newChannel.ts
  27. 16
      src/components/sidebarLeft/tabs/newGroup.ts
  28. 12
      src/components/sidebarLeft/tabs/settings.ts
  29. 18
      src/components/sidebarRight/index.ts
  30. 10
      src/components/sidebarRight/tabs/forward.ts
  31. 28
      src/components/sidebarRight/tabs/gifs.ts
  32. 16
      src/components/sidebarRight/tabs/pollResults.ts
  33. 8
      src/components/sidebarRight/tabs/search.ts
  34. 36
      src/components/sidebarRight/tabs/sharedMedia.ts
  35. 26
      src/components/sidebarRight/tabs/stickers.ts
  36. 2
      src/components/wrappers.ts
  37. 303
      src/lib/appManagers/appDialogsManager.ts
  38. 12
      src/lib/appManagers/appDownloadManager.ts
  39. 6
      src/lib/appManagers/appImManager.ts
  40. 20
      src/lib/appManagers/appMediaViewer.ts
  41. 41
      src/lib/bin_utils.ts
  42. 11
      src/lib/crypto/crypto_methods.ts
  43. 20
      src/lib/logger.ts
  44. 115
      src/lib/mtproto/apiFileManager.ts
  45. 261
      src/lib/mtproto/apiManager.ts
  46. 2
      src/lib/mtproto/authorizer.ts
  47. 46
      src/lib/mtproto/dcConfigurator.ts
  48. 8
      src/lib/mtproto/mtproto.worker.ts
  49. 7
      src/lib/mtproto/mtprotoworker.ts
  50. 313
      src/lib/mtproto/networker.ts
  51. 42
      src/lib/mtproto/referenceDatabase.ts
  52. 31
      src/lib/mtproto/tl_utils.ts
  53. 2
      src/lib/mtproto/transports/abridged.ts
  54. 41
      src/lib/mtproto/transports/intermediate.ts
  55. 106
      src/lib/mtproto/transports/obfuscation.ts
  56. 29
      src/lib/mtproto/transports/padded.ts
  57. 135
      src/lib/mtproto/transports/websocket.ts
  58. 4
      src/lib/services.ts
  59. 2
      src/pages/page.ts
  60. 7
      src/pages/pageAuthCode.ts
  61. 11
      src/pages/pageIm.ts
  62. 8
      src/pages/pagePassword.ts
  63. 19
      src/pages/pageSignIn.ts
  64. 9
      src/pages/pagesManager.ts
  65. 16
      src/types.d.ts
  66. 7
      webpack.common.js

2
src/components/appSearch.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import appDialogsManager from "../lib/appManagers/appDialogsManager";
import Scrollable from "./scrollable_new";
import Scrollable from "./scrollable";
import appMessagesIDsManager from "../lib/appManagers/appMessagesIDsManager";
import appUsersManager from "../lib/appManagers/appUsersManager";
import appPeersManager from '../lib/appManagers/appPeersManager';

21
src/components/appSelectPeers.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import Scrollable from "./scrollable_new";
import Scrollable from "./scrollable";
import appMessagesManager, { Dialog } from "../lib/appManagers/appMessagesManager";
import { cancelEvent, findUpClassName, findUpAttribute } from "../lib/utils";
import appDialogsManager from "../lib/appManagers/appDialogsManager";
@ -139,6 +139,14 @@ export class AppSelectPeers { @@ -139,6 +139,14 @@ export class AppSelectPeers {
}, 0);
}
private renderSaved() {
if(!this.offsetIndex && this.folderID == 0 &&
(!this.query || 'saved messages'.includes(this.query.toLowerCase())) &&
this.peerType.includes('dialogs')) {
this.renderResultsFunc([$rootScope.myID]);
}
}
private async getMoreDialogs(): Promise<any> {
if(this.promise) return this.promise;
@ -160,20 +168,15 @@ export class AppSelectPeers { @@ -160,20 +168,15 @@ export class AppSelectPeers {
dialogs = dialogs.slice();
dialogs.findAndSplice(d => d.peerID == $rootScope.myID); // no my account
if(!this.offsetIndex && this.folderID == 0 &&
(!this.query || 'saved messages'.includes(this.query.toLowerCase())) &&
this.peerType.includes('dialogs')) {
dialogs.unshift({
peerID: $rootScope.myID,
pFlags: {}
} as any);
}
this.renderSaved();
this.offsetIndex = newOffsetIndex;
this.renderResultsFunc(dialogs.map(dialog => dialog.peerID));
} else {
if(!this.loadedWhat.dialogs) {
this.renderSaved();
this.loadedWhat.dialogs = true;
this.offsetIndex = 0;
this.folderID = 1;

2
src/components/chat/contextMenu.ts

@ -2,7 +2,7 @@ import appChatsManager from "../../lib/appManagers/appChatsManager"; @@ -2,7 +2,7 @@ import appChatsManager from "../../lib/appManagers/appChatsManager";
import appImManager from "../../lib/appManagers/appImManager";
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import appSidebarRight from "../../lib/appManagers/appSidebarRight";
import appSidebarRight from "../sidebarRight";
import $rootScope from "../../lib/rootScope";
import { findUpClassName } from "../../lib/utils";
import { parseMenuButtonsTo, attachContextMenuListener, positionMenu, openBtnMenu } from "../misc";

9
src/components/chat/input.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import Scrollable from "../scrollable_new";
import Scrollable from "../scrollable";
import { RichTextProcessor } from "../../lib/richtextprocessor";
import apiManager from "../../lib/mtproto/mtprotoworker";
import appWebPagesManager from "../../lib/appManagers/appWebPagesManager";
@ -276,7 +276,10 @@ export class ChatInput { @@ -276,7 +276,10 @@ export class ChatInput {
case 'document': {
const isPhoto = file.type.indexOf('image/') !== -1;
params.objectURL = URL.createObjectURL(file);
if(isPhoto) {
params.objectURL = URL.createObjectURL(file);
}
let docDiv = wrapDocument({
file: file,
file_name: file.name || '',
@ -784,7 +787,7 @@ export class ChatInput { @@ -784,7 +787,7 @@ export class ChatInput {
this.onMessageSent(false, true);
if(document.type == 'sticker') {
emoticonsDropdown.stickersTab.pushRecentSticker(document);
emoticonsDropdown.stickersTab?.pushRecentSticker(document);
}
return true;

243
src/components/dialogsContextMenu.ts

@ -0,0 +1,243 @@ @@ -0,0 +1,243 @@
import appChatsManager from "../lib/appManagers/appChatsManager";
import appDialogsManager from "../lib/appManagers/appDialogsManager";
import appImManager from "../lib/appManagers/appImManager";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appPeersManager from "../lib/appManagers/appPeersManager";
import $rootScope from "../lib/rootScope";
import { findUpTag } from "../lib/utils";
import { parseMenuButtonsTo, positionMenu, openBtnMenu } from "./misc";
import { PopupButton, PopupPeer } from "./popup";
export default class DialogsContextMenu {
private element = document.getElementById('dialogs-contextmenu') as HTMLDivElement;
private buttons: {
archive: HTMLButtonElement,
pin: HTMLButtonElement,
mute: HTMLButtonElement,
unread: HTMLButtonElement,
delete: HTMLButtonElement,
//clear: HTMLButtonElement,
} = {} as any;
private selectedID: number;
private peerType: 'channel' | 'chat' | 'megagroup' | 'group' | 'saved';
private filterID: number;
constructor() {
parseMenuButtonsTo(this.buttons, this.element.children);
this.buttons.archive.addEventListener('click', () => {
let dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0];
if(dialog) {
appMessagesManager.editPeerFolders([dialog.peerID], +!dialog.folder_id);
}
});
this.buttons.pin.addEventListener('click', () => {
appMessagesManager.toggleDialogPin(this.selectedID, this.filterID);
});
this.buttons.mute.addEventListener('click', () => {
appImManager.mutePeer(this.selectedID);
});
this.buttons.unread.addEventListener('click', () => {
const dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0];
if(!dialog) return;
if(dialog.unread_count) {
appMessagesManager.readHistory(this.selectedID, dialog.top_message);
appMessagesManager.markDialogUnread(this.selectedID, true);
} else {
appMessagesManager.markDialogUnread(this.selectedID);
}
});
this.buttons.delete.addEventListener('click', () => {
let firstName = appPeersManager.getPeerTitle(this.selectedID, false, true);
let callbackFlush = (justClear: boolean) => {
appMessagesManager.flushHistory(this.selectedID, justClear);
};
let callbackLeave = () => {
appChatsManager.leaveChannel(-this.selectedID);
};
let title: string, description: string, buttons: PopupButton[];
switch(this.peerType) {
case 'channel': {
title = 'Leave Channel?';
description = `Are you sure you want to leave this channel?`;
buttons = [{
text: 'LEAVE ' + firstName,
isDanger: true,
callback: callbackLeave
}];
break;
}
case 'megagroup': {
title = 'Leave Group?';
description = `Are you sure you want to leave this group?`;
buttons = [{
text: 'LEAVE ' + firstName,
isDanger: true,
callback: callbackLeave
}];
break;
}
case 'chat': {
title = 'Delete Chat?';
description = `Are you sure you want to delete chat with <b>${firstName}</b>?`;
buttons = [{
text: 'DELETE FOR ME AND ' + firstName,
isDanger: true,
callback: () => callbackFlush(false)
}, {
text: 'DELETE JUST FOR ME',
isDanger: true,
callback: () => callbackFlush(true)
}];
break;
}
case 'saved': {
title = 'Delete Saved Messages?';
description = `Are you sure you want to delete all your saved messages?`;
buttons = [{
text: 'DELETE SAVED MESSAGES',
isDanger: true,
callback: () => callbackFlush(false)
}];
break;
}
case 'group': {
title = 'Delete and leave Group?';
description = `Are you sure you want to delete all message history and leave <b>${firstName}</b>?`;
buttons = [{
text: 'DELETE AND LEAVE ' + firstName,
isDanger: true,
callback: () => callbackFlush(true)
}];
break;
}
}
buttons.push({
text: 'CANCEL',
isCancel: true
});
let popup = new PopupPeer('popup-delete-chat', {
peerID: this.selectedID,
title: title,
description: description,
buttons: buttons
});
popup.show();
});
}
onContextMenu = (e: MouseEvent | Touch) => {
let li: HTMLElement = null;
try {
li = findUpTag(e.target, 'LI');
} catch(e) {}
if(!li) return;
if(e instanceof MouseEvent) e.preventDefault();
if(this.element.classList.contains('active')) {
return false;
}
if(e instanceof MouseEvent) e.cancelBubble = true;
this.filterID = appDialogsManager.filterID;
this.selectedID = +li.getAttribute('data-peerID');
const dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0];
const notOurDialog = dialog.peerID != $rootScope.myID;
// archive button
if(notOurDialog) {
const button = this.buttons.archive;
const condition = dialog.folder_id == 1;
button.classList.toggle('flip-icon', condition);
(button.firstElementChild as HTMLElement).innerText = condition ? 'Unarchive' : 'Archive';
this.buttons.archive.style.display = '';
} else {
this.buttons.archive.style.display = 'none';
}
// pin button
{
const button = this.buttons.pin;
//const condition = !!dialog.pFlags?.pinned;
const condition = this.filterID > 1 ? appMessagesManager.filtersStorage.filters[this.filterID].pinned_peers.includes(dialog.peerID) : !!dialog.pFlags?.pinned;
button.classList.toggle('flip-icon', condition);
(button.firstElementChild as HTMLElement).innerText = condition ? 'Unpin' : 'Pin';
}
// mute button
if(notOurDialog) {
const button = this.buttons.mute;
const condition = dialog.notify_settings && dialog.notify_settings.mute_until > (Date.now() / 1000 | 0);
button.classList.toggle('flip-icon', condition);
(button.firstElementChild as HTMLElement).innerText = condition ? 'Unmute' : 'Mute';
this.buttons.mute.style.display = '';
} else {
this.buttons.mute.style.display = 'none';
}
// unread button
{
const button = this.buttons.unread;
const condition = !!(dialog.pFlags?.unread_mark || dialog.unread_count);
button.classList.toggle('flip-icon', condition);
(button.firstElementChild as HTMLElement).innerText = condition ? 'Mark as Read' : 'Mark as Unread';
}
/* // clear history button
if(appPeersManager.isChannel(this.selectedID)) {
this.buttons.clear.style.display = 'none';
} else {
this.buttons.clear.style.display = '';
} */
// delete button
let deleteButtonText = '';
if(appPeersManager.isMegagroup(this.selectedID)) {
deleteButtonText = 'Leave';
//deleteButtonText = 'Leave group';
this.peerType = 'megagroup';
} else if(appPeersManager.isChannel(this.selectedID)) {
deleteButtonText = 'Leave';
//deleteButtonText = 'Leave channel';
this.peerType = 'channel';
} else if(this.selectedID < 0) {
deleteButtonText = 'Delete';
//deleteButtonText = 'Delete and leave';
this.peerType = 'group';
} else {
deleteButtonText = 'Delete';
//deleteButtonText = 'Delete chat';
this.peerType = this.selectedID == $rootScope.myID ? 'saved' : 'chat';
}
(this.buttons.delete.firstElementChild as HTMLElement).innerText = deleteButtonText;
li.classList.add('menu-open');
positionMenu(e, this.element);
openBtnMenu(this.element, () => {
li.classList.remove('menu-open');
});
};
}

4
src/components/emoticonsDropdown/index.ts

@ -3,9 +3,9 @@ import GifsTab from "./tabs/gifs"; @@ -3,9 +3,9 @@ import GifsTab from "./tabs/gifs";
import { findUpClassName, findUpTag, whichChild } from "../../lib/utils";
import { horizontalMenu } from "../horizontalMenu";
import animationIntersector from "../animationIntersector";
import appSidebarRight from "../../lib/appManagers/appSidebarRight";
import appSidebarRight from "../sidebarRight";
import appImManager from "../../lib/appManagers/appImManager";
import Scrollable, { ScrollableX } from "../scrollable_new";
import Scrollable, { ScrollableX } from "../scrollable";
import EmojiTab from "./tabs/emoji";
import StickersTab from "./tabs/stickers";
import StickyIntersector from "../stickyIntersector";

2
src/components/emoticonsDropdown/tabs/emoji.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { EmoticonsTab, EmoticonsDropdown } from "..";
import Scrollable from "../../scrollable_new";
import Scrollable from "../../scrollable";
import Config from "../../../lib/config";
import { putPreloader } from "../../misc";
import appStateManager from "../../../lib/appManagers/appStateManager";

2
src/components/emoticonsDropdown/tabs/gifs.ts

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import emoticonsDropdown, { EmoticonsDropdown, EmoticonsTab, EMOTICONSSTICKERGROUP } from "..";
import GifsMasonry from "../../gifsMasonry";
import Scrollable from "../../scrollable_new";
import Scrollable from "../../scrollable";
import { putPreloader } from "../../misc";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import appDocsManager, {MyDocument} from "../../../lib/appManagers/appDocsManager";

2
src/components/emoticonsDropdown/tabs/stickers.ts

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import emoticonsDropdown, { EmoticonsTab, EMOTICONSSTICKERGROUP, EmoticonsDropdown } from "..";
import { StickerSet } from "../../../layer";
import Scrollable, { ScrollableX } from "../../scrollable_new";
import Scrollable, { ScrollableX } from "../../scrollable";
import { wrapSticker } from "../../wrappers";
import appStickersManager from "../../../lib/appManagers/appStickersManager";
import appDownloadManager from "../../../lib/appManagers/appDownloadManager";

2
src/components/gifsMasonry.ts

@ -4,7 +4,7 @@ import { wrapVideo } from "./wrappers"; @@ -4,7 +4,7 @@ import { wrapVideo } from "./wrappers";
import { renderImageFromUrl } from "./misc";
import { LazyLoadQueueRepeat2 } from "./lazyLoadQueue";
import animationIntersector from "./animationIntersector";
import Scrollable from "./scrollable_new";
import Scrollable from "./scrollable";
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
const width = 400;

9
src/components/horizontalMenu.ts

@ -28,6 +28,7 @@ function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, width: @@ -28,6 +28,7 @@ function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, width:
export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 250) {
const hideTimeouts: {[id: number]: number} = {};
//const deferred: (() => void)[] = [];
let transitionEndTimeout: number;
let prevTabContent: HTMLElement = null;
let prevId = -1;
@ -102,6 +103,14 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick? @@ -102,6 +103,14 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
prevId = id;
prevTabContent = tabContent;
/* if(p) {
return new Promise((resolve) => {
deferred.push(resolve);
});
} else {
return Promise.resolve();
} */
};
if(tabs) {

2
src/components/poll.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import appPollsManager, { PollResults, Poll } from "../lib/appManagers/appPollsManager";
import { RichTextProcessor } from "../lib/richtextprocessor";
import { findUpClassName, cancelEvent } from "../lib/utils";
import appSidebarRight from "../lib/appManagers/appSidebarRight";
import appSidebarRight from "./sidebarRight";
import appImManager from "../lib/appManagers/appImManager";
import serverTimeManager from "../lib/mtproto/serverTimeManager";
import { ripple } from "./ripple";

2
src/components/popupCreatePoll.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { PopupElement } from "./popup";
import Scrollable from "./scrollable_new";
import Scrollable from "./scrollable";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import $rootScope from "../lib/rootScope";
import { Poll } from "../lib/appManagers/appPollsManager";

2
src/components/popupStickers.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { PopupElement } from "./popup";
import appStickersManager from "../lib/appManagers/appStickersManager";
import { RichTextProcessor } from "../lib/richtextprocessor";
import Scrollable from "./scrollable_new";
import Scrollable from "./scrollable";
import { wrapSticker } from "./wrappers";
import LazyLoadQueue from "./lazyLoadQueue";
import { putPreloader } from "./misc";

6
src/components/preloader.ts

@ -47,7 +47,11 @@ export default class ProgressivePreloader { @@ -47,7 +47,11 @@ export default class ProgressivePreloader {
if(this.promise && this.promise.cancel) {
this.promise.cancel();
this.detach();
this.setProgress(0);
setTimeout(() => {
this.detach();
}, 100);
}
});
}

0
src/components/scrollable_new.ts → src/components/scrollable.ts

2
src/components/searchInput.ts

@ -40,7 +40,7 @@ export default class SearchInput { @@ -40,7 +40,7 @@ export default class SearchInput {
if(value != this.prevValue) {
this.prevValue = value;
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.timeout = window.setTimeout(() => {
this.onChange(value);
}, 200);
}

257
src/lib/appManagers/appSidebarLeft.ts → src/components/sidebarLeft/index.ts

@ -1,28 +1,29 @@ @@ -1,28 +1,29 @@
//import { logger } from "../polyfill";
import appDialogsManager, { AppArchivedTab, archivedTab } from "./appDialogsManager";
import { findUpTag, findUpClassName, formatNumber } from "../utils";
import appImManager from "./appImManager";
import AppSearch, { SearchGroup } from "../../components/appSearch";
import { parseMenuButtonsTo } from "../../components/misc";
import appUsersManager from "./appUsersManager";
import { ScrollableX } from "../../components/scrollable_new";
import AvatarElement from "../../components/avatar";
import AppNewChannelTab from "../../components/sidebarLeft/newChannel";
import AppAddMembersTab from "../../components/sidebarLeft/addMembers";
import AppContactsTab from "../../components/sidebarLeft/contacts";
import AppNewGroupTab from "../../components/sidebarLeft/newGroup";
import AppSettingsTab from "../../components/sidebarLeft/settings";
import AppEditProfileTab from "../../components/sidebarLeft/editProfile";
import AppChatFoldersTab from "../../components/sidebarLeft/chatFolders";
import AppEditFolderTab from "../../components/sidebarLeft/editFolder";
import AppIncludedChatsTab from "../../components/sidebarLeft/includedChats";
import SidebarSlider from "../../components/slider";
import SearchInput from "../../components/searchInput";
import appStateManager from "./appStateManager";
import appChatsManager from "./appChatsManager";
import { MOUNT_CLASS_TO } from "../mtproto/mtproto_config";
import $rootScope from "../rootScope";
import appPeersManager from "./appPeersManager";
import appDialogsManager from "../../lib/appManagers/appDialogsManager";
import { findUpTag, findUpClassName, formatNumber } from "../../lib/utils";
import appImManager from "../../lib/appManagers/appImManager";
import AppSearch, { SearchGroup } from "../appSearch";
import { parseMenuButtonsTo } from "../misc";
import appUsersManager from "../../lib/appManagers/appUsersManager";
import { ScrollableX } from "../scrollable";
import AvatarElement from "../avatar";
import AppNewChannelTab from "./tabs/newChannel";
import AppAddMembersTab from "./tabs/addMembers";
import AppContactsTab from "./tabs/contacts";
import AppNewGroupTab from "./tabs/newGroup";
import AppSettingsTab from "./tabs/settings";
import AppEditProfileTab from "./tabs/editProfile";
import AppChatFoldersTab from "./tabs/chatFolders";
import AppEditFolderTab from "./tabs/editFolder";
import AppIncludedChatsTab from "./tabs/includedChats";
import SidebarSlider from "../slider";
import SearchInput from "../searchInput";
import appStateManager from "../../lib/appManagers/appStateManager";
import appChatsManager from "../../lib/appManagers/appChatsManager";
import { MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config";
import $rootScope from "../../lib/rootScope";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import AppArchivedTab from "./tabs/archivedTab";
AvatarElement;
@ -35,6 +36,7 @@ const editProfileTab = new AppEditProfileTab(); @@ -35,6 +36,7 @@ const editProfileTab = new AppEditProfileTab();
const chatFoldersTab = new AppChatFoldersTab();
const editFolderTab = new AppEditFolderTab();
const includedChatsTab = new AppIncludedChatsTab();
const archivedTab = new AppArchivedTab();
export class AppSidebarLeft extends SidebarSlider {
public static SLIDERITEMSIDS = {
@ -87,14 +89,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -87,14 +89,7 @@ export class AppSidebarLeft extends SidebarSlider {
//private log = logger('SL');
private searchGroups = {
//saved: new SearchGroup('', 'contacts'),
contacts: new SearchGroup('Chats', 'contacts'),
globalContacts: new SearchGroup('Global Search', 'contacts'),
messages: new SearchGroup('Global Search', 'messages'),
people: new SearchGroup('People', 'contacts', false, 'search-group-people'),
recent: new SearchGroup('Recent', 'contacts', false, 'search-group-recent')
};
private searchGroups: {[k in 'contacts' | 'globalContacts' | 'messages' | 'people' | 'recent']: SearchGroup} = {} as any;
private globalSearch: AppSearch;
// peerIDs
@ -139,47 +134,117 @@ export class AppSidebarLeft extends SidebarSlider { @@ -139,47 +134,117 @@ export class AppSidebarLeft extends SidebarSlider {
this.menuEl = this.toolsBtn.querySelector('.btn-menu');
this.newBtnMenu = this.sidebarEl.querySelector('#new-menu');
this.globalSearch = new AppSearch(this.searchContainer, this.searchInput, this.searchGroups, (count) => {
if(!count && !this.searchInput.value.trim()) {
this.globalSearch.reset();
this.searchGroups.people.setActive();
this.renderRecentSearch();
}
});
this.searchContainer.addEventListener('click', (e) => {
const target = findUpTag(e.target, 'LI') as HTMLElement;
if(!target) {
return;
}
const searchGroup = findUpClassName(target, 'search-group');
if(!searchGroup || searchGroup.classList.contains('search-group-recent') || searchGroup.classList.contains('search-group-people')) {
return;
}
const peerID = +target.getAttribute('data-peerID');
if(this.recentSearch[0] != peerID) {
this.recentSearch.findAndSplice(p => p == peerID);
this.recentSearch.unshift(peerID);
if(this.recentSearch.length > 20) {
this.recentSearch.length = 20;
this.searchInput.input.addEventListener('focus', () => {
this.searchGroups = {
//saved: new SearchGroup('', 'contacts'),
contacts: new SearchGroup('Chats', 'contacts'),
globalContacts: new SearchGroup('Global Search', 'contacts'),
messages: new SearchGroup('Global Search', 'messages'),
people: new SearchGroup('People', 'contacts', false, 'search-group-people'),
recent: new SearchGroup('Recent', 'contacts', false, 'search-group-recent')
};
this.globalSearch = new AppSearch(this.searchContainer, this.searchInput, this.searchGroups, (count) => {
if(!count && !this.searchInput.value.trim()) {
this.globalSearch.reset();
this.searchGroups.people.setActive();
this.renderRecentSearch();
}
});
this.searchContainer.addEventListener('click', (e) => {
const target = findUpTag(e.target, 'LI') as HTMLElement;
if(!target) {
return;
}
const searchGroup = findUpClassName(target, 'search-group');
if(!searchGroup || searchGroup.classList.contains('search-group-recent') || searchGroup.classList.contains('search-group-people')) {
return;
}
const peerID = +target.getAttribute('data-peerID');
if(this.recentSearch[0] != peerID) {
this.recentSearch.findAndSplice(p => p == peerID);
this.recentSearch.unshift(peerID);
if(this.recentSearch.length > 20) {
this.recentSearch.length = 20;
}
this.renderRecentSearch();
appStateManager.pushToState('recentSearch', this.recentSearch);
for(const peerID of this.recentSearch) {
appStateManager.setPeer(peerID, appPeersManager.getPeer(peerID));
}
clearRecentSearchBtn.style.display = '';
}
}, {capture: true});
let peopleContainer = document.createElement('div');
peopleContainer.classList.add('search-group-scrollable');
peopleContainer.append(this.searchGroups.people.list);
this.searchGroups.people.container.append(peopleContainer);
let peopleScrollable = new ScrollableX(peopleContainer);
appUsersManager.getTopPeers().then(peers => {
//console.log('got top categories:', categories);
peers.forEach((peerID) => {
let {dialog, dom} = appDialogsManager.addDialog(peerID, this.searchGroups.people.list, false, true, true);
this.searchGroups.people.setActive();
});
});
this.renderRecentSearch();
appStateManager.pushToState('recentSearch', this.recentSearch);
for(const peerID of this.recentSearch) {
appStateManager.setPeer(peerID, appPeersManager.getPeer(peerID));
const onFocus = () => {
this.toolsBtn.classList.remove('active');
this.backBtn.classList.add('active');
this.searchContainer.classList.remove('hide');
void this.searchContainer.offsetWidth; // reflow
this.searchContainer.classList.add('active');
if(firstTime) {
this.searchGroups.people.setActive();
this.renderRecentSearch();
firstTime = false;
}
clearRecentSearchBtn.style.display = '';
}
}, {capture: true});
/* this.searchInput.addEventListener('blur', (e) => {
if(!this.searchInput.value) {
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
this.backBtn.click();
}
}, {once: true}); */
};
let firstTime = true;
this.searchInput.input.addEventListener('focus', onFocus);
onFocus();
this.backBtn.addEventListener('click', (e) => {
//appDialogsManager.chatsArchivedContainer.classList.remove('active');
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
this.searchContainer.classList.remove('active');
firstTime = true;
setTimeout(() => {
this.searchContainer.classList.add('hide');
this.globalSearch.reset();
}, 150);
});
let peopleContainer = document.createElement('div');
peopleContainer.classList.add('search-group-scrollable');
peopleContainer.append(this.searchGroups.people.list);
this.searchGroups.people.container.append(peopleContainer);
let peopleScrollable = new ScrollableX(peopleContainer);
this.renderRecentSearch();
const clearRecentSearchBtn = this.recentSearchClearBtn = document.createElement('button');
clearRecentSearchBtn.classList.add('btn-icon', 'tgico-close');
this.searchGroups.recent.nameEl.append(clearRecentSearchBtn);
clearRecentSearchBtn.addEventListener('click', () => {
this.recentSearch = [];
appStateManager.pushToState('recentSearch', this.recentSearch);
this.renderRecentSearch(false);
clearRecentSearchBtn.style.display = 'none';
});
}, {once: true});
parseMenuButtonsTo(this.buttons, this.menuEl.children);
parseMenuButtonsTo(this.newButtons, this.newBtnMenu.firstElementChild.children);
@ -206,42 +271,6 @@ export class AppSidebarLeft extends SidebarSlider { @@ -206,42 +271,6 @@ export class AppSidebarLeft extends SidebarSlider {
this.selectTab(AppSidebarLeft.SLIDERITEMSIDS.settings);
});
let firstTime = true;
this.searchInput.input.addEventListener('focus', (e) => {
this.toolsBtn.classList.remove('active');
this.backBtn.classList.add('active');
this.searchContainer.classList.remove('hide');
void this.searchContainer.offsetWidth; // reflow
this.searchContainer.classList.add('active');
if(firstTime) {
this.searchGroups.people.setActive();
this.renderRecentSearch();
firstTime = false;
}
/* this.searchInput.addEventListener('blur', (e) => {
if(!this.searchInput.value) {
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
this.backBtn.click();
}
}, {once: true}); */
});
this.backBtn.addEventListener('click', (e) => {
//appDialogsManager.chatsArchivedContainer.classList.remove('active');
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
this.searchContainer.classList.remove('active');
firstTime = true;
setTimeout(() => {
this.searchContainer.classList.add('hide');
this.globalSearch.reset();
}, 150);
});
this.newButtons.channel.addEventListener('click', (e) => {
this.selectTab(AppSidebarLeft.SLIDERITEMSIDS.newChannel);
});
@ -258,25 +287,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -258,25 +287,7 @@ export class AppSidebarLeft extends SidebarSlider {
this.archivedCount.innerText = '' + formatNumber(e.detail.count, 1);
});
appUsersManager.getTopPeers().then(peers => {
//console.log('got top categories:', categories);
peers.forEach((peerID) => {
let {dialog, dom} = appDialogsManager.addDialog(peerID, this.searchGroups.people.list, false, true, true);
this.searchGroups.people.setActive();
});
});
this.renderRecentSearch();
const clearRecentSearchBtn = this.recentSearchClearBtn = document.createElement('button');
clearRecentSearchBtn.classList.add('btn-icon', 'tgico-close');
this.searchGroups.recent.nameEl.append(clearRecentSearchBtn);
clearRecentSearchBtn.addEventListener('click', () => {
this.recentSearch = [];
appStateManager.pushToState('recentSearch', this.recentSearch);
this.renderRecentSearch(false);
clearRecentSearchBtn.style.display = 'none';
});
appUsersManager.getTopPeers();
}
public renderRecentSearch(setActive = true) {

10
src/components/sidebarLeft/addMembers.ts → src/components/sidebarLeft/tabs/addMembers.ts

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
import { SliderTab } from "../slider";
import { AppSelectPeers } from "../appSelectPeers";
import { putPreloader } from "../misc";
import appChatsManager from "../../lib/appManagers/appChatsManager";
import appSidebarLeft, { AppSidebarLeft } from "../../lib/appManagers/appSidebarLeft";
import { SliderTab } from "../../slider";
import { AppSelectPeers } from "../../appSelectPeers";
import { putPreloader } from "../../misc";
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appSidebarLeft, { AppSidebarLeft } from "..";
export default class AppAddMembersTab implements SliderTab {
private container = document.querySelector('.addmembers-container') as HTMLDivElement;

52
src/components/sidebarLeft/tabs/archivedTab.ts

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import Scrollable from "../../scrollable";
import { SliderTab } from "../../slider";
export default class AppArchivedTab implements SliderTab {
public container = document.getElementById('chats-archived-container') as HTMLDivElement;
public chatList = document.getElementById('dialogs-archived') as HTMLUListElement;
public scroll: Scrollable = null;
public loadedAll: boolean;
public loadDialogsPromise: Promise<any>;
public wasFilterID: number;
init() {
this.scroll = new Scrollable(this.container, 'CLA', this.chatList, 500);
this.scroll.setVirtualContainer(this.chatList);
this.scroll.onScrolledBottom = appDialogsManager.onChatsScroll;
///this.scroll.attachSentinels();
appDialogsManager.setListClickListener(this.chatList, null, true);
window.addEventListener('resize', () => {
setTimeout(appDialogsManager.onChatsScroll, 0);
});
}
onOpen() {
if(this.init) {
this.init();
this.init = null;
}
this.wasFilterID = appDialogsManager.filterID;
appDialogsManager.scroll = this.scroll;
appDialogsManager.filterID = 1;
appDialogsManager.onTabChange();
}
// вообще, так делать нельзя, но нет времени чтобы переделать главный чатлист на слайд...
onOpenAfterTimeout() {
appDialogsManager.chatLists[this.wasFilterID].innerHTML = '';
}
onClose() {
appDialogsManager.scroll = appDialogsManager._scroll;
appDialogsManager.filterID = this.wasFilterID;
appDialogsManager.onTabChange();
}
onCloseAfterTimeout() {
this.chatList.innerHTML = '';
}
}

24
src/components/sidebarLeft/chatFolders.ts → src/components/sidebarLeft/tabs/chatFolders.ts

@ -1,15 +1,15 @@ @@ -1,15 +1,15 @@
import { SliderTab } from "../slider";
import lottieLoader, { RLottiePlayer } from "../../lib/lottieLoader";
import apiManager from "../../lib/mtproto/mtprotoworker";
import appMessagesManager, { MyDialogFilter } from "../../lib/appManagers/appMessagesManager";
import { RichTextProcessor } from "../../lib/richtextprocessor";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import { cancelEvent } from "../../lib/utils";
import appSidebarLeft from "../../lib/appManagers/appSidebarLeft";
import { ripple } from "../ripple";
import { toast } from "../toast";
import { DialogFilterSuggested, DialogFilter } from "../../layer";
import $rootScope from "../../lib/rootScope";
import { SliderTab } from "../../slider";
import lottieLoader, { RLottiePlayer } from "../../../lib/lottieLoader";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import appMessagesManager, { MyDialogFilter } from "../../../lib/appManagers/appMessagesManager";
import { RichTextProcessor } from "../../../lib/richtextprocessor";
import appPeersManager from "../../../lib/appManagers/appPeersManager";
import { cancelEvent } from "../../../lib/utils";
import appSidebarLeft from "..";
import { ripple } from "../../ripple";
import { toast } from "../../toast";
import { DialogFilterSuggested, DialogFilter } from "../../../layer";
import $rootScope from "../../../lib/rootScope";
export default class AppChatFoldersTab implements SliderTab {
public container: HTMLElement;

30
src/components/sidebarLeft/contacts.ts → src/components/sidebarLeft/tabs/contacts.ts

@ -1,23 +1,26 @@ @@ -1,23 +1,26 @@
import { SliderTab } from "../slider";
import Scrollable from "../scrollable_new";
import appDialogsManager from "../../lib/appManagers/appDialogsManager";
import appUsersManager from "../../lib/appManagers/appUsersManager";
import appPhotosManager from "../../lib/appManagers/appPhotosManager";
import appSidebarLeft, { AppSidebarLeft } from "../../lib/appManagers/appSidebarLeft";
import $rootScope from "../../lib/rootScope";
import SearchInput from "../searchInput";
import { SliderTab } from "../../slider";
import Scrollable from "../../scrollable";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import appPhotosManager from "../../../lib/appManagers/appPhotosManager";
import appSidebarLeft, { AppSidebarLeft } from "..";
import $rootScope from "../../../lib/rootScope";
import SearchInput from "../../searchInput";
// TODO: поиск по людям глобальный, если не нашло в контактах никого
export default class AppContactsTab implements SliderTab {
private container = document.getElementById('contacts-container');
private list = this.container.querySelector('#contacts') as HTMLUListElement;
private container: HTMLElement;
private list: HTMLUListElement;
private scrollable: Scrollable;
private promise: Promise<void>;
private searchInput: SearchInput;
constructor() {
init() {
this.container = document.getElementById('contacts-container');
this.list = this.container.querySelector('#contacts');
appDialogsManager.setListClickListener(this.list);
this.scrollable = new Scrollable(this.list.parentElement);
@ -44,6 +47,11 @@ export default class AppContactsTab implements SliderTab { @@ -44,6 +47,11 @@ export default class AppContactsTab implements SliderTab {
}
public openContacts(query?: string) {
if(this.init) {
this.init();
this.init = null;
}
if(appSidebarLeft.historyTabIDs.indexOf(AppSidebarLeft.SLIDERITEMSIDS.contacts) === -1) {
appSidebarLeft.selectTab(AppSidebarLeft.SLIDERITEMSIDS.contacts);
}

18
src/components/sidebarLeft/editFolder.ts → src/components/sidebarLeft/tabs/editFolder.ts

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
import { SliderTab } from "../slider";
import appSidebarLeft, { AppSidebarLeft } from "../../lib/appManagers/appSidebarLeft";
import lottieLoader, { RLottiePlayer } from "../../lib/lottieLoader";
import appMessagesManager, { MyDialogFilter as DialogFilter } from "../../lib/appManagers/appMessagesManager";
import { parseMenuButtonsTo } from "../misc";
import appDialogsManager from "../../lib/appManagers/appDialogsManager";
import { copy, deepEqual } from "../../lib/utils";
import { toast } from "../toast";
import { ripple } from "../ripple";
import { SliderTab } from "../../slider";
import appSidebarLeft, { AppSidebarLeft } from "..";
import lottieLoader, { RLottiePlayer } from "../../../lib/lottieLoader";
import appMessagesManager, { MyDialogFilter as DialogFilter } from "../../../lib/appManagers/appMessagesManager";
import { parseMenuButtonsTo } from "../../misc";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import { copy, deepEqual } from "../../../lib/utils";
import { toast } from "../../toast";
import { ripple } from "../../ripple";
const MAX_FOLDER_NAME_LENGTH = 12;

18
src/components/sidebarLeft/editProfile.ts → src/components/sidebarLeft/tabs/editProfile.ts

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
import { SliderTab } from "../slider";
import popupAvatar from "../popupAvatar";
import apiManager from "../../lib/mtproto/mtprotoworker";
import appProfileManager from "../../lib/appManagers/appProfileManager";
import appSidebarLeft from "../../lib/appManagers/appSidebarLeft";
import Scrollable from "../scrollable_new";
import appUsersManager from "../../lib/appManagers/appUsersManager";
import $rootScope from "../../lib/rootScope";
import { InputFile } from "../../layer";
import { SliderTab } from "../../slider";
import popupAvatar from "../../popupAvatar";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import appProfileManager from "../../../lib/appManagers/appProfileManager";
import appSidebarLeft from "..";
import Scrollable from "../../scrollable";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import $rootScope from "../../../lib/rootScope";
import { InputFile } from "../../../layer";
// TODO: аватарка не поменяется в этой вкладке после изменения почему-то (если поставить в другом клиенте, и потом тут проверить, для этого ещё вышел в чатлист)

18
src/components/sidebarLeft/includedChats.ts → src/components/sidebarLeft/tabs/includedChats.ts

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
import { SliderTab } from "../slider";
import { AppSelectPeers } from "../appSelectPeers";
import appSidebarLeft, { AppSidebarLeft } from "../../lib/appManagers/appSidebarLeft";
import appDialogsManager from "../../lib/appManagers/appDialogsManager";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import appUsersManager from "../../lib/appManagers/appUsersManager";
import { copy } from "../../lib/utils";
import { MyDialogFilter as DialogFilter } from "../../lib/appManagers/appMessagesManager";
import $rootScope from "../../lib/rootScope";
import { SliderTab } from "../../slider";
import { AppSelectPeers } from "../../appSelectPeers";
import appSidebarLeft, { AppSidebarLeft } from "..";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import appPeersManager from "../../../lib/appManagers/appPeersManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import { copy } from "../../../lib/utils";
import { MyDialogFilter as DialogFilter } from "../../../lib/appManagers/appMessagesManager";
import $rootScope from "../../../lib/rootScope";
export default class AppIncludedChatsTab implements SliderTab {
public container: HTMLElement;

8
src/components/sidebarLeft/newChannel.ts → src/components/sidebarLeft/tabs/newChannel.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { SliderTab } from "../slider";
import popupAvatar from "../popupAvatar";
import appChatsManager from "../../lib/appManagers/appChatsManager";
import appSidebarLeft, { AppSidebarLeft } from "../../lib/appManagers/appSidebarLeft";
import { SliderTab } from "../../slider";
import popupAvatar from "../../popupAvatar";
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appSidebarLeft, { AppSidebarLeft } from "..";
export default class AppNewChannelTab implements SliderTab {
private container = document.querySelector('.new-channel-container') as HTMLDivElement;

16
src/components/sidebarLeft/newGroup.ts → src/components/sidebarLeft/tabs/newGroup.ts

@ -1,11 +1,11 @@ @@ -1,11 +1,11 @@
import { SliderTab } from "../slider";
import { SearchGroup } from "../appSearch";
import popupAvatar from "../popupAvatar";
import appChatsManager from "../../lib/appManagers/appChatsManager";
import appSidebarLeft, { AppSidebarLeft } from "../../lib/appManagers/appSidebarLeft";
import Scrollable from "../scrollable_new";
import appDialogsManager from "../../lib/appManagers/appDialogsManager";
import appUsersManager from "../../lib/appManagers/appUsersManager";
import { SliderTab } from "../../slider";
import { SearchGroup } from "../../appSearch";
import popupAvatar from "../../popupAvatar";
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appSidebarLeft, { AppSidebarLeft } from "..";
import Scrollable from "../../scrollable";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
export default class AppNewGroupTab implements SliderTab {
private container = document.querySelector('.new-group-container') as HTMLDivElement;

12
src/components/sidebarLeft/settings.ts → src/components/sidebarLeft/tabs/settings.ts

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
import { SliderTab } from "../slider";
import AvatarElement from "../avatar";
import { parseMenuButtonsTo } from "../misc";
import { SliderTab } from "../../slider";
import AvatarElement from "../../avatar";
import { parseMenuButtonsTo } from "../../misc";
//import $rootScope from "../../lib/rootScope";
import apiManager from "../../lib/mtproto/mtprotoworker";
import appSidebarLeft, { AppSidebarLeft } from "../../lib/appManagers/appSidebarLeft";
import appUsersManager from "../../lib/appManagers/appUsersManager";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import appSidebarLeft, { AppSidebarLeft } from "..";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
export default class AppSettingsTab implements SliderTab {
private container = document.querySelector('.settings-container') as HTMLDivElement;

18
src/lib/appManagers/appSidebarRight.ts → src/components/sidebarRight/index.ts

@ -1,13 +1,13 @@ @@ -1,13 +1,13 @@
import appImManager from "./appImManager";
import SidebarSlider from "../../components/slider";
import AppStickersTab from "../../components/sidebarRight/stickers";
import AppPollResultsTab from "../../components/sidebarRight/pollResults";
import AppGifsTab from "../../components/sidebarRight/gifs";
import appImManager from "../../lib/appManagers/appImManager";
import SidebarSlider from "../slider";
import AppStickersTab from "./tabs/stickers";
import AppPollResultsTab from "./tabs/pollResults";
import AppGifsTab from "./tabs/gifs";
import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes";
import AppPrivateSearchTab from "../../components/sidebarRight/search";
import AppSharedMediaTab from "../../components/sidebarRight/sharedMedia";
import AppForwardTab from "../../components/sidebarRight/forward";
import { MOUNT_CLASS_TO } from "../mtproto/mtproto_config";
import AppPrivateSearchTab from "./tabs/search";
import AppSharedMediaTab from "./tabs/sharedMedia";
import AppForwardTab from "./tabs/forward";
import { MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config";
export const RIGHT_COLUMN_ACTIVE_CLASSNAME = 'is-right-column-shown';

10
src/components/sidebarRight/forward.ts → src/components/sidebarRight/tabs/forward.ts

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
import appSidebarRight, { AppSidebarRight } from "../../lib/appManagers/appSidebarRight";
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import { putPreloader } from "../misc";
import { AppSelectPeers } from "../appSelectPeers";
import { SliderTab } from "../slider";
import appSidebarRight, { AppSidebarRight } from "..";
import appMessagesManager from "../../../lib/appManagers/appMessagesManager";
import { putPreloader } from "../../misc";
import { AppSelectPeers } from "../../appSelectPeers";
import { SliderTab } from "../../slider";
export default class AppForwardTab implements SliderTab {
public container: HTMLElement;

28
src/components/sidebarRight/gifs.ts → src/components/sidebarRight/tabs/gifs.ts

@ -1,15 +1,15 @@ @@ -1,15 +1,15 @@
import { SliderTab } from "../slider";
import SearchInput from "../searchInput";
import Scrollable from "../scrollable_new";
import LazyLoadQueue from "../lazyLoadQueue";
import animationIntersector from "../animationIntersector";
import appSidebarRight, { AppSidebarRight } from "../../lib/appManagers/appSidebarRight";
import appUsersManager, { User } from "../../lib/appManagers/appUsersManager";
import appInlineBotsManager, { AppInlineBotsManager } from "../../lib/appManagers/AppInlineBotsManager";
import GifsMasonry from "../gifsMasonry";
import { findUpClassName } from "../../lib/utils";
import appImManager from "../../lib/appManagers/appImManager";
import type { MyDocument } from "../../lib/appManagers/appDocsManager";
import { SliderTab } from "../../slider";
import SearchInput from "../../searchInput";
import Scrollable from "../../scrollable";
import animationIntersector from "../../animationIntersector";
import appSidebarRight, { AppSidebarRight } from "..";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import appInlineBotsManager, { AppInlineBotsManager } from "../../../lib/appManagers/AppInlineBotsManager";
import GifsMasonry from "../../gifsMasonry";
import { findUpClassName } from "../../../lib/utils";
import appImManager from "../../../lib/appManagers/appImManager";
import type { MyDocument } from "../../../lib/appManagers/appDocsManager";
import mediaSizes from "../../../helpers/mediaSizes";
const ANIMATIONGROUP = 'GIFS-SEARCH';
@ -52,7 +52,9 @@ export default class AppGifsTab implements SliderTab { @@ -52,7 +52,9 @@ export default class AppGifsTab implements SliderTab {
const fileID = target.dataset.docID;
if(appImManager.chatInputC.sendMessageWithDocument(fileID)) {
//this.closeBtn.click();
if(mediaSizes.isMobile) {
this.backBtn.click();
}
} else {
console.warn('got no doc by id:', fileID);
}

16
src/components/sidebarRight/pollResults.ts → src/components/sidebarRight/tabs/pollResults.ts

@ -1,11 +1,11 @@ @@ -1,11 +1,11 @@
import { SliderTab } from "../slider";
import Scrollable from "../scrollable_new";
import appSidebarRight, { AppSidebarRight } from "../../lib/appManagers/appSidebarRight";
import appPollsManager from "../../lib/appManagers/appPollsManager";
import { roundPercents } from "../poll";
import { RichTextProcessor } from "../../lib/richtextprocessor";
import appDialogsManager from "../../lib/appManagers/appDialogsManager";
import { ripple } from "../ripple";
import { SliderTab } from "../../slider";
import Scrollable from "../../scrollable";
import appSidebarRight, { AppSidebarRight } from "..";
import appPollsManager from "../../../lib/appManagers/appPollsManager";
import { roundPercents } from "../../poll";
import { RichTextProcessor } from "../../../lib/richtextprocessor";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import { ripple } from "../../ripple";
export default class AppPollResultsTab implements SliderTab {
private container = document.getElementById('poll-results-container') as HTMLDivElement;

8
src/components/sidebarRight/search.ts → src/components/sidebarRight/tabs/search.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import appSidebarRight, { AppSidebarRight } from "../../lib/appManagers/appSidebarRight";
import AppSearch, { SearchGroup } from "../appSearch";
import SearchInput from "../searchInput";
import { SliderTab } from "../slider";
import appSidebarRight, { AppSidebarRight } from "..";
import AppSearch, { SearchGroup } from "../../appSearch";
import SearchInput from "../../searchInput";
import { SliderTab } from "../../slider";
export default class AppPrivateSearchTab implements SliderTab {
public container: HTMLElement;

36
src/components/sidebarRight/sharedMedia.ts → src/components/sidebarRight/tabs/sharedMedia.ts

@ -1,21 +1,21 @@ @@ -1,21 +1,21 @@
import appImManager from "../../lib/appManagers/appImManager";
import appMediaViewer from "../../lib/appManagers/appMediaViewer";
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import appPhotosManager from "../../lib/appManagers/appPhotosManager";
import appProfileManager from "../../lib/appManagers/appProfileManager";
import appUsersManager from "../../lib/appManagers/appUsersManager";
import { logger, LogLevels } from "../../lib/logger";
import { RichTextProcessor } from "../../lib/richtextprocessor";
import $rootScope from "../../lib/rootScope";
import { getAbbreviation, limitSymbols } from "../../lib/utils";
import AvatarElement from "../avatar";
import { horizontalMenu } from "../horizontalMenu";
import LazyLoadQueue from "../lazyLoadQueue";
import { renderImageFromUrl, putPreloader } from "../misc";
import Scrollable from "../scrollable_new";
import { SliderTab } from "../slider";
import { wrapDocument, wrapAudio } from "../wrappers";
import appImManager from "../../../lib/appManagers/appImManager";
import appMediaViewer from "../../../lib/appManagers/appMediaViewer";
import appMessagesManager from "../../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../../lib/appManagers/appPeersManager";
import appPhotosManager from "../../../lib/appManagers/appPhotosManager";
import appProfileManager from "../../../lib/appManagers/appProfileManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import { logger, LogLevels } from "../../../lib/logger";
import { RichTextProcessor } from "../../../lib/richtextprocessor";
import $rootScope from "../../../lib/rootScope";
import { getAbbreviation, limitSymbols } from "../../../lib/utils";
import AvatarElement from "../../avatar";
import { horizontalMenu } from "../../horizontalMenu";
import LazyLoadQueue from "../../lazyLoadQueue";
import { renderImageFromUrl, putPreloader } from "../../misc";
import Scrollable from "../../scrollable";
import { SliderTab } from "../../slider";
import { wrapDocument, wrapAudio } from "../../wrappers";
const testScroll = false;

26
src/components/sidebarRight/stickers.ts → src/components/sidebarRight/tabs/stickers.ts

@ -1,16 +1,16 @@ @@ -1,16 +1,16 @@
import { SliderTab } from "../slider";
import SearchInput from "../searchInput";
import Scrollable from "../scrollable_new";
import LazyLoadQueue from "../lazyLoadQueue";
import { findUpClassName } from "../../lib/utils";
import appImManager from "../../lib/appManagers/appImManager";
import appStickersManager from "../../lib/appManagers/appStickersManager";
import PopupStickers from "../popupStickers";
import animationIntersector from "../animationIntersector";
import { RichTextProcessor } from "../../lib/richtextprocessor";
import { wrapSticker } from "../wrappers";
import appSidebarRight, { AppSidebarRight } from "../../lib/appManagers/appSidebarRight";
import { StickerSet, StickerSetCovered } from "../../layer";
import { SliderTab } from "../../slider";
import SearchInput from "../../searchInput";
import Scrollable from "../../scrollable";
import LazyLoadQueue from "../../lazyLoadQueue";
import { findUpClassName } from "../../../lib/utils";
import appImManager from "../../../lib/appManagers/appImManager";
import appStickersManager from "../../../lib/appManagers/appStickersManager";
import PopupStickers from "../../popupStickers";
import animationIntersector from "../../animationIntersector";
import { RichTextProcessor } from "../../../lib/richtextprocessor";
import { wrapSticker } from "../../wrappers";
import appSidebarRight, { AppSidebarRight } from "..";
import { StickerSet, StickerSetCovered } from "../../../layer";
export default class AppStickersTab implements SliderTab {
private container = document.getElementById('stickers-container') as HTMLDivElement;

2
src/components/wrappers.ts

@ -316,7 +316,7 @@ export function wrapDocument(doc: MyDocument, withTime = false, uploading = fals @@ -316,7 +316,7 @@ export function wrapDocument(doc: MyDocument, withTime = false, uploading = fals
const icoDiv = document.createElement('div');
icoDiv.classList.add('document-ico');
if(doc.thumbs?.length || uploading) {
if(doc.thumbs?.length || (uploading && doc.url)) {
docDiv.classList.add('photo');
if(uploading) {

303
src/lib/appManagers/appDialogsManager.ts

@ -1,17 +1,15 @@ @@ -1,17 +1,15 @@
import { findUpClassName, escapeRegExp, findUpTag, cancelEvent, positionElementByIndex } from "../utils";
import { findUpClassName, escapeRegExp, cancelEvent, positionElementByIndex } from "../utils";
import appImManager, { AppImManager } from "./appImManager";
import appPeersManager from './appPeersManager';
import appMessagesManager, { Dialog, MyDialogFilter as DialogFilter } from "./appMessagesManager";
import appUsersManager, { User } from "./appUsersManager";
import { RichTextProcessor } from "../richtextprocessor";
import { putPreloader, positionMenu, openBtnMenu, parseMenuButtonsTo, attachContextMenuListener } from "../../components/misc";
import { putPreloader, attachContextMenuListener } from "../../components/misc";
//import Scrollable from "../../components/scrollable";
import Scrollable, { ScrollableX } from "../../components/scrollable_new";
import Scrollable, { ScrollableX } from "../../components/scrollable";
import { logger, LogLevels } from "../logger";
import appChatsManager from "./appChatsManager";
import AvatarElement from "../../components/avatar";
import { PopupButton, PopupPeer } from "../../components/popup";
import { SliderTab } from "../../components/slider";
import appStateManager from "./appStateManager";
import { horizontalMenu } from "../../components/horizontalMenu";
import { ripple } from "../../components/ripple";
@ -19,6 +17,8 @@ import { isSafari } from "../../helpers/userAgent"; @@ -19,6 +17,8 @@ import { isSafari } from "../../helpers/userAgent";
import { formatDateAccordingToToday } from "../../helpers/date";
import $rootScope from "../rootScope";
import { isTouchSupported } from "../../helpers/touchSupport";
import DialogsContextMenu from "../../components/dialogsContextMenu";
import appSidebarLeft from "../../components/sidebarLeft";
type DialogDom = {
avatarEl: AvatarElement,
@ -35,291 +35,6 @@ type DialogDom = { @@ -35,291 +35,6 @@ type DialogDom = {
const testScroll = false;
//const USEPINNEDDELIMITER = false;
class DialogsContextMenu {
private element = document.getElementById('dialogs-contextmenu') as HTMLDivElement;
private buttons: {
archive: HTMLButtonElement,
pin: HTMLButtonElement,
mute: HTMLButtonElement,
unread: HTMLButtonElement,
delete: HTMLButtonElement,
//clear: HTMLButtonElement,
} = {} as any;
private selectedID: number;
private peerType: 'channel' | 'chat' | 'megagroup' | 'group' | 'saved';
private filterID: number;
constructor() {
parseMenuButtonsTo(this.buttons, this.element.children);
this.buttons.archive.addEventListener('click', () => {
let dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0];
if(dialog) {
appMessagesManager.editPeerFolders([dialog.peerID], +!dialog.folder_id);
}
});
this.buttons.pin.addEventListener('click', () => {
appMessagesManager.toggleDialogPin(this.selectedID, this.filterID);
});
this.buttons.mute.addEventListener('click', () => {
appImManager.mutePeer(this.selectedID);
});
this.buttons.unread.addEventListener('click', () => {
const dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0];
if(!dialog) return;
if(dialog.unread_count) {
appMessagesManager.readHistory(this.selectedID, dialog.top_message);
appMessagesManager.markDialogUnread(this.selectedID, true);
} else {
appMessagesManager.markDialogUnread(this.selectedID);
}
});
this.buttons.delete.addEventListener('click', () => {
let firstName = appPeersManager.getPeerTitle(this.selectedID, false, true);
let callbackFlush = (justClear: boolean) => {
appMessagesManager.flushHistory(this.selectedID, justClear);
};
let callbackLeave = () => {
appChatsManager.leaveChannel(-this.selectedID);
};
let title: string, description: string, buttons: PopupButton[];
switch(this.peerType) {
case 'channel': {
title = 'Leave Channel?';
description = `Are you sure you want to leave this channel?`;
buttons = [{
text: 'LEAVE ' + firstName,
isDanger: true,
callback: callbackLeave
}];
break;
}
case 'megagroup': {
title = 'Leave Group?';
description = `Are you sure you want to leave this group?`;
buttons = [{
text: 'LEAVE ' + firstName,
isDanger: true,
callback: callbackLeave
}];
break;
}
case 'chat': {
title = 'Delete Chat?';
description = `Are you sure you want to delete chat with <b>${firstName}</b>?`;
buttons = [{
text: 'DELETE FOR ME AND ' + firstName,
isDanger: true,
callback: () => callbackFlush(false)
}, {
text: 'DELETE JUST FOR ME',
isDanger: true,
callback: () => callbackFlush(true)
}];
break;
}
case 'saved': {
title = 'Delete Saved Messages?';
description = `Are you sure you want to delete all your saved messages?`;
buttons = [{
text: 'DELETE SAVED MESSAGES',
isDanger: true,
callback: () => callbackFlush(false)
}];
break;
}
case 'group': {
title = 'Delete and leave Group?';
description = `Are you sure you want to delete all message history and leave <b>${firstName}</b>?`;
buttons = [{
text: 'DELETE AND LEAVE ' + firstName,
isDanger: true,
callback: () => callbackFlush(true)
}];
break;
}
}
buttons.push({
text: 'CANCEL',
isCancel: true
});
let popup = new PopupPeer('popup-delete-chat', {
peerID: this.selectedID,
title: title,
description: description,
buttons: buttons
});
popup.show();
});
}
onContextMenu = (e: MouseEvent | Touch) => {
let li: HTMLElement = null;
try {
li = findUpTag(e.target, 'LI');
} catch(e) {}
if(!li) return;
if(e instanceof MouseEvent) e.preventDefault();
if(this.element.classList.contains('active')) {
return false;
}
if(e instanceof MouseEvent) e.cancelBubble = true;
this.filterID = appDialogsManager.filterID;
this.selectedID = +li.getAttribute('data-peerID');
const dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0];
const notOurDialog = dialog.peerID != $rootScope.myID;
// archive button
if(notOurDialog) {
const button = this.buttons.archive;
const condition = dialog.folder_id == 1;
button.classList.toggle('flip-icon', condition);
(button.firstElementChild as HTMLElement).innerText = condition ? 'Unarchive' : 'Archive';
this.buttons.archive.style.display = '';
} else {
this.buttons.archive.style.display = 'none';
}
// pin button
{
const button = this.buttons.pin;
//const condition = !!dialog.pFlags?.pinned;
const condition = this.filterID > 1 ? appMessagesManager.filtersStorage.filters[this.filterID].pinned_peers.includes(dialog.peerID) : !!dialog.pFlags?.pinned;
button.classList.toggle('flip-icon', condition);
(button.firstElementChild as HTMLElement).innerText = condition ? 'Unpin' : 'Pin';
}
// mute button
if(notOurDialog) {
const button = this.buttons.mute;
const condition = dialog.notify_settings && dialog.notify_settings.mute_until > (Date.now() / 1000 | 0);
button.classList.toggle('flip-icon', condition);
(button.firstElementChild as HTMLElement).innerText = condition ? 'Unmute' : 'Mute';
this.buttons.mute.style.display = '';
} else {
this.buttons.mute.style.display = 'none';
}
// unread button
{
const button = this.buttons.unread;
const condition = !!(dialog.pFlags?.unread_mark || dialog.unread_count);
button.classList.toggle('flip-icon', condition);
(button.firstElementChild as HTMLElement).innerText = condition ? 'Mark as Read' : 'Mark as Unread';
}
/* // clear history button
if(appPeersManager.isChannel(this.selectedID)) {
this.buttons.clear.style.display = 'none';
} else {
this.buttons.clear.style.display = '';
} */
// delete button
let deleteButtonText = '';
if(appPeersManager.isMegagroup(this.selectedID)) {
deleteButtonText = 'Leave';
//deleteButtonText = 'Leave group';
this.peerType = 'megagroup';
} else if(appPeersManager.isChannel(this.selectedID)) {
deleteButtonText = 'Leave';
//deleteButtonText = 'Leave channel';
this.peerType = 'channel';
} else if(this.selectedID < 0) {
deleteButtonText = 'Delete';
//deleteButtonText = 'Delete and leave';
this.peerType = 'group';
} else {
deleteButtonText = 'Delete';
//deleteButtonText = 'Delete chat';
this.peerType = this.selectedID == $rootScope.myID ? 'saved' : 'chat';
}
(this.buttons.delete.firstElementChild as HTMLElement).innerText = deleteButtonText;
li.classList.add('menu-open');
positionMenu(e, this.element);
openBtnMenu(this.element, () => {
li.classList.remove('menu-open');
});
};
}
export class AppArchivedTab implements SliderTab {
public container = document.getElementById('chats-archived-container') as HTMLDivElement;
public chatList = document.getElementById('dialogs-archived') as HTMLUListElement;
public scroll: Scrollable = null;
public loadedAll: boolean;
public loadDialogsPromise: Promise<any>;
public wasFilterID: number;
init() {
this.scroll = new Scrollable(this.container, 'CLA', this.chatList, 500);
this.scroll.setVirtualContainer(this.chatList);
this.scroll.onScrolledBottom = appDialogsManager.onChatsScroll;
///this.scroll.attachSentinels();
appDialogsManager.setListClickListener(this.chatList, null, true);
window.addEventListener('resize', () => {
setTimeout(appDialogsManager.onChatsScroll, 0);
});
}
onOpen() {
if(this.init) {
this.init();
this.init = null;
}
this.wasFilterID = appDialogsManager.filterID;
appDialogsManager.scroll = this.scroll;
appDialogsManager.filterID = 1;
appDialogsManager.onTabChange();
}
// вообще, так делать нельзя, но нет времени чтобы переделать главный чатлист на слайд...
onOpenAfterTimeout() {
appDialogsManager.chatLists[this.wasFilterID].innerHTML = '';
}
onClose() {
appDialogsManager.scroll = appDialogsManager._scroll;
appDialogsManager.filterID = this.wasFilterID;
appDialogsManager.onTabChange();
}
onCloseAfterTimeout() {
this.chatList.innerHTML = '';
}
}
export const archivedTab = new AppArchivedTab();
export class AppDialogsManager {
public _chatList = document.getElementById('dialogs') as HTMLUListElement;
public chatList = this._chatList;
@ -348,7 +63,7 @@ export class AppDialogsManager { @@ -348,7 +63,7 @@ export class AppDialogsManager {
public chatLists: {[filterID: number]: HTMLUListElement} = {
0: this.chatList,
1: archivedTab.chatList
1: appSidebarLeft.archivedTab.chatList
};
public filterID = 0;
private folders: {[k in 'menu' | 'container' | 'menuScrollContainer']: HTMLElement} = {
@ -440,11 +155,7 @@ export class AppDialogsManager { @@ -440,11 +155,7 @@ export class AppDialogsManager {
let dom = this.getDialogDom(dialog.peerID);
if(dom) {
if(online) {
dom.avatarEl.classList.add('is-online');
} else {
dom.avatarEl.classList.remove('is-online');
}
dom.avatarEl.classList.toggle('is-online', online);
}
}

12
src/lib/appManagers/appDownloadManager.ts

@ -6,6 +6,7 @@ import { getFileNameByLocation } from "../bin_utils"; @@ -6,6 +6,7 @@ import { getFileNameByLocation } from "../bin_utils";
import { InputFile } from "../../layer";
import referenceDatabase, {ReferenceBytes} from "../mtproto/referenceDatabase";
import type { ApiError } from "../mtproto/apiManager";
import cacheStorage from "../cacheStorage";
export type ResponseMethodBlob = 'blob';
export type ResponseMethodJson = 'json';
@ -97,7 +98,16 @@ export class AppDownloadManager { @@ -97,7 +98,16 @@ export class AppDownloadManager {
};
const tryDownload = (): Promise<unknown> => {
return apiManager.downloadFile(options).then(deferred.resolve, onError);
if(!apiManager.worker) {
return cacheStorage.getFile(fileName).then((blob) => {
if(blob.size < options.size) throw 'wrong size';
else deferred.resolve(blob);
}).catch(() => {
return apiManager.downloadFile(options).then(deferred.resolve, onError);
});
} else {
return apiManager.downloadFile(options).then(deferred.resolve, onError);
}
};
tryDownload();

6
src/lib/appManagers/appImManager.ts

@ -8,18 +8,18 @@ import appProfileManager from "./appProfileManager"; @@ -8,18 +8,18 @@ import appProfileManager from "./appProfileManager";
import appDialogsManager from "./appDialogsManager";
import { RichTextProcessor } from "../richtextprocessor";
import appPhotosManager from "./appPhotosManager";
import appSidebarRight, { AppSidebarRight, RIGHT_COLUMN_ACTIVE_CLASSNAME } from './appSidebarRight';
import appSidebarRight, { AppSidebarRight, RIGHT_COLUMN_ACTIVE_CLASSNAME } from '../../components/sidebarRight';
import { logger, LogLevels } from "../logger";
import appMediaViewer from "./appMediaViewer";
import appSidebarLeft from "./appSidebarLeft";
import appSidebarLeft from "../../components/sidebarLeft";
import appChatsManager, { Channel, Chat } from "./appChatsManager";
import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum, wrapPoll } from '../../components/wrappers';
import ProgressivePreloader from '../../components/preloader';
import { formatPhoneNumber, parseMenuButtonsTo } from '../../components/misc';
import { ChatInput } from '../../components/chat/input';
//import Scrollable from '../../components/scrollable';
import Scrollable from '../../components/scrollable_new';
import Scrollable from '../../components/scrollable';
import BubbleGroups from '../../components/bubbleGroups';
import LazyLoadQueue from '../../components/lazyLoadQueue';
import appDocsManager from './appDocsManager';

20
src/lib/appManagers/appMediaViewer.ts

@ -14,7 +14,7 @@ import appMediaPlaybackController from "../../components/appMediaPlaybackControl @@ -14,7 +14,7 @@ import appMediaPlaybackController from "../../components/appMediaPlaybackControl
import { deferredPromise } from "../../helpers/cancellablePromise";
import mediaSizes from "../../helpers/mediaSizes";
import { isSafari } from "../../helpers/userAgent";
import appSidebarRight, { AppSidebarRight } from "./appSidebarRight";
import appSidebarRight, { AppSidebarRight } from "../../components/sidebarRight";
import $rootScope from "../rootScope";
import { isTouchSupported } from "../../helpers/touchSupport";
@ -137,6 +137,7 @@ export class AppMediaViewer { @@ -137,6 +137,7 @@ export class AppMediaViewer {
const close = (e: MouseEvent) => {
cancelEvent(e);
if(this.setMoverAnimationPromise) return;
//this.overlaysDiv.classList.remove('active');
this.content.container.innerHTML = '';
/* if(this.content.container.firstElementChild) {
@ -393,7 +394,7 @@ export class AppMediaViewer { @@ -393,7 +394,7 @@ export class AppMediaViewer {
mover.prepend(aspecter);
}
aspecter.style.cssText = `width: ${rect.width}px; height: ${rect.height}px; transform: scale(${containerRect.width / rect.width}, ${containerRect.height / rect.height});`;
aspecter.style.cssText = `width: ${rect.width}px; height: ${rect.height}px; transform: scale3d(${containerRect.width / rect.width}, ${containerRect.height / rect.height}, 1);`;
}
mover.style.width = containerRect.width + 'px';
@ -402,7 +403,7 @@ export class AppMediaViewer { @@ -402,7 +403,7 @@ export class AppMediaViewer {
const scaleX = rect.width / containerRect.width;
const scaleY = rect.height / containerRect.height;
if(!wasActive) {
transform += `scale(${scaleX},${scaleY}) `;
transform += `scale3d(${scaleX},${scaleY},1) `;
}
let borderRadius = window.getComputedStyle(realParent).getPropertyValue('border-radius');
@ -411,6 +412,7 @@ export class AppMediaViewer { @@ -411,6 +412,7 @@ export class AppMediaViewer {
if(!wasActive) {
mover.style.borderRadius = borderRadius;
}
//let borderRadius = '0px 0px 0px 0px';
mover.style.transform = transform;
@ -583,18 +585,24 @@ export class AppMediaViewer { @@ -583,18 +585,24 @@ export class AppMediaViewer {
}
//await new Promise((resolve) => setTimeout(resolve, 0));
await new Promise((resolve) => window.requestAnimationFrame(resolve));
//await new Promise((resolve) => window.requestAnimationFrame(resolve));
// * одного RAF'а недостаточно, иногда анимация с одним не срабатывает (преимущественно на мобильных)
await new Promise((resolve) => window.requestAnimationFrame(() => window.requestAnimationFrame(resolve)));
// чтобы проверить установленную позицию - раскомментировать
//throw '';
mover.style.transform = `translate3d(${containerRect.left}px,${containerRect.top}px,0) scale(1,1)`;
//await new Promise((resolve) => setTimeout(resolve, 5e3));
mover.style.transform = `translate3d(${containerRect.left}px,${containerRect.top}px,0) scale3d(1,1,1)`;
//mover.style.transform = `translate(-50%,-50%) scale(1,1)`;
if(aspecter) {
this.setFullAspect(aspecter, containerRect, rect);
}
//throw '';
setTimeout(() => {
mover.style.borderRadius = '';
@ -658,7 +666,7 @@ export class AppMediaViewer { @@ -658,7 +666,7 @@ export class AppMediaViewer {
//this.log('will set style aspecter:', `width: ${width}px; height: ${height}px; transform: scale(${containerRect.width / width}, ${containerRect.height / height});`);
aspecter.style.cssText = `width: ${width}px; height: ${height}px; transform: scale(${containerRect.width / width}, ${containerRect.height / height});`;
aspecter.style.cssText = `width: ${width}px; height: ${height}px; transform: scale3d(${containerRect.width / width}, ${containerRect.height / height}, 1);`;
//}
}

41
src/lib/bin_utils.ts

@ -30,7 +30,7 @@ export function bigStringInt(strNum: string) { @@ -30,7 +30,7 @@ export function bigStringInt(strNum: string) {
return new BigInteger(strNum, 10);
}
export function bytesToHex(bytes: any) {
export function bytesToHex(bytes: ArrayLike<number>) {
bytes = bytes || [];
var arr = [];
for(var i = 0; i < bytes.length; i++) {
@ -92,7 +92,7 @@ export function uint6ToBase64(nUint6: number) { @@ -92,7 +92,7 @@ export function uint6ToBase64(nUint6: number) {
: 65
}
export function base64ToBlob(base64str: string, mimeType: string) {
/* export function base64ToBlob(base64str: string, mimeType: string) {
var sliceSize = 1024;
var byteCharacters = atob(base64str);
var bytesLength = byteCharacters.length;
@ -122,7 +122,7 @@ export function dataUrlToBlob(url: string) { @@ -122,7 +122,7 @@ export function dataUrlToBlob(url: string) {
var blob = base64ToBlob(base64str, mimeType);
// console.timeEnd(name)
return blob;
}
} */
export function blobConstruct(blobParts: any, mimeType: string = ''): Blob {
let blob;
@ -209,7 +209,7 @@ export function bytesFromBigInt(bigInt: BigInteger, len?: number) { @@ -209,7 +209,7 @@ export function bytesFromBigInt(bigInt: BigInteger, len?: number) {
return bytes;
}
export function bytesToArrayBuffer(b: Iterable<number>) {
export function bytesToArrayBuffer(b: number[]) {
return (new Uint8Array(b)).buffer;
}
@ -225,28 +225,14 @@ export function convertToArrayBuffer(bytes: any | ArrayBuffer | Uint8Array) { @@ -225,28 +225,14 @@ export function convertToArrayBuffer(bytes: any | ArrayBuffer | Uint8Array) {
return bytesToArrayBuffer(bytes);
}
export function convertToUint8Array(bytes: any) {
if(bytes.buffer !== undefined) {
return bytes;
export function convertToUint8Array(bytes: Uint8Array | number[]): Uint8Array {
if((bytes as Uint8Array).buffer !== undefined) {
return bytes as Uint8Array;
}
return new Uint8Array(bytes);
}
export function convertToByteArray(bytes: any) {
if(Array.isArray(bytes)) {
return bytes;
}
bytes = convertToUint8Array(bytes);
var newBytes = [];
for(var i = 0, len = bytes.length; i < len; i++) {
newBytes.push(bytes[i]);
}
return newBytes;
}
export function bytesFromArrayBuffer(buffer: ArrayBuffer) {
var len = buffer.byteLength;
var byteView = new Uint8Array(buffer);
@ -327,18 +313,9 @@ export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean, @@ -327,18 +313,9 @@ export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean,
}
if(bytes instanceof ArrayBuffer) {
bytes = prepend ? bufferConcat(padding, bytes) : bufferConcat(bytes, padding);
bytes = (prepend ? bufferConcats(padding, bytes) : bufferConcats(bytes, padding)).buffer;
} else if(bytes instanceof Uint8Array) {
let _bytes = new Uint8Array(bytes.length + padding.length);
if(prepend) {
_bytes.set(padding);
_bytes.set(bytes, padding.length);
} else {
_bytes.set(bytes);
_bytes.set(padding, bytes.length);
}
bytes = _bytes;
bytes = prepend ? bufferConcats(padding, bytes) : bufferConcats(bytes, padding);
} else {
bytes = prepend ? padding.concat(bytes) : bytes.concat(padding);
}

11
src/lib/crypto/crypto_methods.ts

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
import { convertToArrayBuffer, convertToByteArray } from "../bin_utils";
import { convertToArrayBuffer } from "../bin_utils";
import type { InputCheckPasswordSRP } from "../../layer";
import type { aesEncryptSync } from "./crypto_utils";
export default abstract class CryptoWorkerMethods {
abstract performTaskWorker<T>(task: string, ...args: any[]): Promise<T>;
@ -17,7 +18,7 @@ export default abstract class CryptoWorkerMethods { @@ -17,7 +18,7 @@ export default abstract class CryptoWorkerMethods {
}
public aesEncrypt(bytes: any, keyBytes: any, ivBytes: any) {
return this.performTaskWorker<ArrayBuffer>('aes-encrypt', convertToArrayBuffer(bytes),
return this.performTaskWorker<ReturnType<typeof aesEncryptSync>>('aes-encrypt', convertToArrayBuffer(bytes),
convertToArrayBuffer(keyBytes), convertToArrayBuffer(ivBytes));
}
@ -31,10 +32,8 @@ export default abstract class CryptoWorkerMethods { @@ -31,10 +32,8 @@ export default abstract class CryptoWorkerMethods {
return this.performTaskWorker<number[]>('rsa-encrypt', publicKey, bytes);
}
public factorize(bytes: any) {
bytes = convertToByteArray(bytes);
return this.performTaskWorker<[number[], number[], number]>('factorize', bytes);
public factorize(bytes: Uint8Array) {
return this.performTaskWorker<[number[], number[], number]>('factorize', [...bytes]);
}
public modPow(x: any, y: any, m: any) {

20
src/lib/logger.ts

@ -7,9 +7,9 @@ export enum LogLevels { @@ -7,9 +7,9 @@ export enum LogLevels {
debug = 8
};
var _logTimer = Date.now();
const _logTimer = Date.now();
function dT() {
return '[' + ((Date.now() - _logTimer) / 1000).toFixed(3) + ']'
return '[' + ((Date.now() - _logTimer) / 1000).toFixed(3) + ']';
}
export function logger(prefix: string, level = LogLevels.log | LogLevels.warn | LogLevels.error) {
@ -19,32 +19,34 @@ export function logger(prefix: string, level = LogLevels.log | LogLevels.warn | @@ -19,32 +19,34 @@ export function logger(prefix: string, level = LogLevels.log | LogLevels.warn |
//level = LogLevels.log | LogLevels.warn | LogLevels.error | LogLevels.debug
prefix = '[' + prefix + ']:';
function Log(...args: any[]) {
return level & LogLevels.log && console.log(dT(), '[' + prefix + ']:', ...args);
return level & LogLevels.log && console.log(dT(), prefix, ...args);
}
Log.warn = function(...args: any[]) {
return level & LogLevels.warn && console.warn(dT(), '[' + prefix + ']:', ...args);
return level & LogLevels.warn && console.warn(dT(), prefix, ...args);
};
Log.info = function(...args: any[]) {
return level & LogLevels.log && console.info(dT(), '[' + prefix + ']:', ...args);
return level & LogLevels.log && console.info(dT(), prefix, ...args);
};
Log.error = function(...args: any[]) {
return level & LogLevels.error && console.error(dT(), '[' + prefix + ']:', ...args);
return level & LogLevels.error && console.error(dT(), prefix, ...args);
};
Log.trace = function(...args: any[]) {
return level & LogLevels.log && console.trace(dT(), '[' + prefix + ']:', ...args);
return level & LogLevels.log && console.trace(dT(), prefix, ...args);
};
/* Log.debug = function(...args: any[]) {
return level & LogLevels.debug && console.log(dT(), '[' + prefix + ']:', ...args);
return level & LogLevels.debug && console.log(dT(), prefix, ...args);
}; */
Log.debug = function(...args: any[]) {
return level & LogLevels.debug && console.debug(dT(), '[' + prefix + ']:', ...args);
return level & LogLevels.debug && console.debug(dT(), prefix, ...args);
};
return Log;

115
src/lib/mtproto/apiFileManager.ts

@ -81,7 +81,7 @@ export class ApiFileManager { @@ -81,7 +81,7 @@ export class ApiFileManager {
const downloadPull = this.downloadPulls[dcID];
//const downloadLimit = dcID == 'upload' ? 11 : 5;
//const downloadLimit = 24;
const downloadLimit = dcID == 'upload' ? 11 : 48;
const downloadLimit = dcID == 'upload' ? 48 : 48;
if(this.downloadActives[dcID] >= downloadLimit || !downloadPull || !downloadPull.length) {
return false;
@ -134,7 +134,7 @@ export class ApiFileManager { @@ -134,7 +134,7 @@ export class ApiFileManager {
location,
offset,
limit
}, {
} as any, {
dcID,
fileDownload: true/* ,
singleInRequest: 'safari' in window */
@ -421,7 +421,10 @@ export class ApiFileManager { @@ -421,7 +421,10 @@ export class ApiFileManager {
partSize = 262144, // 256 Kb
activeDelta = 2;
if(fileSize > 67108864) {
if(fileSize > (524288 * 3000)) {
partSize = 1024 * 1024;
activeDelta = 8;
} else if(fileSize > 67108864) {
partSize = 524288;
activeDelta = 4;
} else if(fileSize < 102400) {
@ -471,54 +474,70 @@ export class ApiFileManager { @@ -471,54 +474,70 @@ export class ApiFileManager {
};
const method = isBigFile ? 'upload.saveBigFilePart' : 'upload.saveFilePart';
for(let offset = 0; offset < fileSize; offset += partSize) {
const part = _part++; // 0, 1
this.downloadRequest('upload', () => {
return new Promise<void>((uploadResolve, uploadReject) => {
const reader = new FileReader();
const blob = file.slice(offset, offset + partSize);
reader.onloadend = (e) => {
if(canceled) {
uploadReject();
return;
}
if(e.target.readyState != FileReader.DONE) {
this.log.error('wrong readyState!');
uploadReject();
return;
}
//////this.log('Starting to upload file, isBig:', isBigFile, fileID, part, e.target.result);
apiManager.invokeApi(method, {
file_id: fileID,
file_part: part,
file_total_parts: totalParts,
bytes: e.target.result
}, {
startMaxLength: partSize + 256,
fileUpload: true,
singleInRequest: true
}).then((result) => {
doneParts++;
uploadResolve();
//////this.log('Progress', doneParts * partSize / fileSize);
deferred.notify({done: doneParts * partSize, total: fileSize});
if(doneParts >= totalParts) {
deferred.resolve(resultInputFile);
resolved = true;
const self = this;
function* generator() {
for(let offset = 0; offset < fileSize; offset += partSize) {
const part = _part++; // 0, 1
yield self.downloadRequest('upload', () => {
return new Promise<void>((uploadResolve, uploadReject) => {
const reader = new FileReader();
const blob = file.slice(offset, offset + partSize);
reader.onloadend = (e) => {
if(canceled) {
uploadReject();
return;
}
if(e.target.readyState != FileReader.DONE) {
self.log.error('wrong readyState!');
uploadReject();
return;
}
}, errorHandler);
};
reader.readAsArrayBuffer(blob);
});
}, activeDelta).catch(errorHandler);
//////this.log('Starting to upload file, isBig:', isBigFile, fileID, part, e.target.result);
apiManager.invokeApi(method, {
file_id: fileID,
file_part: part,
file_total_parts: totalParts,
bytes: e.target.result/* new Uint8Array(e.target.result as ArrayBuffer) */
} as any, {
//startMaxLength: partSize + 256,
fileUpload: true,
//singleInRequest: true
}).then((result) => {
doneParts++;
uploadResolve();
//////this.log('Progress', doneParts * partSize / fileSize);
deferred.notify({done: doneParts * partSize, total: fileSize});
if(doneParts >= totalParts) {
deferred.resolve(resultInputFile);
resolved = true;
}
}, errorHandler);
};
reader.readAsArrayBuffer(blob);
});
}, activeDelta).catch(errorHandler);
}
}
const it = generator();
const process = () => {
if(canceled) return;
const r = it.next();
if(r.done || canceled) return;
(r.value as Promise<void>).then(process);
};
for(let i = 0; i < 10; ++i) {
process();
}
deferred.cancel = () => {

261
src/lib/mtproto/apiManager.ts

@ -5,9 +5,12 @@ import { bytesFromHex, bytesToHex, isObject } from '../bin_utils'; @@ -5,9 +5,12 @@ import { bytesFromHex, bytesToHex, isObject } from '../bin_utils';
import networkerFactory from './networkerFactory';
//import { telegramMeWebService } from './mtproto';
import authorizer from './authorizer';
import {App, Modes} from './mtproto_config';
import {App, Modes, MOUNT_CLASS_TO} from './mtproto_config';
import dcConfigurator from './dcConfigurator';
import { logger } from '../logger';
import type { InvokeApiOptions } from '../../types';
import type { MethodDeclMap } from '../../layer';
import { CancellablePromise, deferredPromise } from '../../helpers/cancellablePromise';
/// #if MTPROTO_HTTP
import HTTP from './transports/http';
@ -15,7 +18,6 @@ import HTTP from './transports/http'; @@ -15,7 +18,6 @@ import HTTP from './transports/http';
/// #if !MTPROTO_WORKER
import $rootScope from '../rootScope';
import { InvokeApiOptions } from '../../types';
/// #endif
//console.error('apiManager included!');
@ -113,18 +115,16 @@ export class ApiManager { @@ -113,18 +115,16 @@ export class ApiManager {
}
// mtpGetNetworker
public async getNetworker(dcID: number, options: InvokeApiOptions): Promise<MTPNetworker> {
const transport = dcConfigurator.chooseServer(dcID, true);
public async getNetworker(dcID: number, options: InvokeApiOptions = {}): Promise<MTPNetworker> {
/// #if MTPROTO_HTTP
// @ts-ignore
const upload = (options.fileUpload || options.fileDownload)
&& (transport instanceof HTTP || Modes.multipleConnections);
const upload = (options.fileUpload || options.fileDownload);
/// #else
// @ts-ignore
const upload = (options.fileUpload || options.fileDownload) && Modes.multipleConnections;
/// #endif
const transport = dcConfigurator.chooseServer(dcID, upload);
const cache = upload ? this.cachedUploadNetworkers : this.cachedNetworkers;
if(!dcID) {
@ -189,134 +189,149 @@ export class ApiManager { @@ -189,134 +189,149 @@ export class ApiManager {
}
// mtpInvokeApi
public invokeApi(method: string, params: any = {}, options: InvokeApiOptions = {}) {
public invokeApi<T extends keyof MethodDeclMap>(method: T, params: MethodDeclMap[T]['req'] = {}, options: InvokeApiOptions = {}): CancellablePromise<MethodDeclMap[T]["res"]> {
///////this.log('Invoke api', method, params, options);
return new Promise((resolve, reject) => {
let rejectPromise = (error: ApiError) => {
if(!error) {
error = {type: 'ERROR_EMPTY'};
} else if(!isObject(error)) {
error = {message: error};
}
reject(error);
if(error.code == 401 && error.type == 'SESSION_REVOKED') {
this.logOut();
}
const deferred = deferredPromise<MethodDeclMap[T]['res']>();
if(options.ignoreErrors) {
return;
}
if(error.code == 406) {
error.handled = true;
}
if(!options.noErrorBox) {
error.input = method;
error.stack = stack || (error.originalError && error.originalError.stack) || error.stack || (new Error()).stack;
setTimeout(() => {
if(!error.handled) {
if(error.code == 401) {
this.logOut();
} else {
// ErrorService.show({error: error}); // WARNING
}
error.handled = true;
}
}, 100);
}
};
if(MOUNT_CLASS_TO) {
deferred.finally(() => {
clearInterval(interval);
});
const startTime = Date.now();
const interval = MOUNT_CLASS_TO.setInterval(() => {
this.log.error('Request is still processing:', method, params, options, 'time:', (Date.now() - startTime) / 1000);
//this.cachedUploadNetworkers[2].requestMessageStatus();
}, 30e3);
}
const rejectPromise = (error: ApiError) => {
if(!error) {
error = {type: 'ERROR_EMPTY'};
} else if(!isObject(error)) {
error = {message: error};
}
deferred.reject(error);
if(error.code == 401 && error.type == 'SESSION_REVOKED') {
this.logOut();
}
if(options.ignoreErrors) {
return;
}
var dcID: number;
if(error.code == 406) {
error.handled = true;
}
var cachedNetworker: MTPNetworker;
var stack = (new Error()).stack || 'empty stack';
var performRequest = (networker: MTPNetworker) => {
return (cachedNetworker = networker)
.wrapApiCall(method, params, options)
.then(resolve, (error: ApiError) => {
//if(!options.ignoreErrors) {
if(error.type != 'FILE_REFERENCE_EXPIRED') {
this.log.error('Error', error.code, error.type, this.baseDcID, dcID, method, params);
if(!options.noErrorBox) {
error.input = method;
error.stack = stack || (error.originalError && error.originalError.stack) || error.stack || (new Error()).stack;
setTimeout(() => {
if(!error.handled) {
if(error.code == 401) {
this.logOut();
} else {
// ErrorService.show({error: error}); // WARNING
}
error.handled = true;
}
}, 100);
}
};
var dcID: number;
var cachedNetworker: MTPNetworker;
var stack = (new Error()).stack || 'empty stack';
var performRequest = (networker: MTPNetworker) => {
return (cachedNetworker = networker)
.wrapApiCall(method, params, options)
.then(deferred.resolve, (error: ApiError) => {
//if(!options.ignoreErrors) {
if(error.type != 'FILE_REFERENCE_EXPIRED') {
this.log.error('Error', error.code, error.type, this.baseDcID, dcID, method, params);
}
if(error.code == 401 && this.baseDcID == dcID) {
AppStorage.remove('dc', 'user_auth');
this.telegramMeNotify(false);
rejectPromise(error);
} else if(error.code == 401 && this.baseDcID && dcID != this.baseDcID) {
if(this.cachedExportPromise[dcID] === undefined) {
let promise = new Promise((exportResolve, exportReject) => {
this.invokeApi('auth.exportAuthorization', {dc_id: dcID}, {noErrorBox: true}).then((exportedAuth) => {
this.invokeApi('auth.importAuthorization', {
id: exportedAuth.id,
bytes: exportedAuth.bytes
}, {dcID: dcID, noErrorBox: true}).then(exportResolve, exportReject);
}, exportReject);
});
this.cachedExportPromise[dcID] = promise;
}
if(error.code == 401 && this.baseDcID == dcID) {
AppStorage.remove('dc', 'user_auth');
this.telegramMeNotify(false);
rejectPromise(error);
} else if(error.code == 401 && this.baseDcID && dcID != this.baseDcID) {
if(this.cachedExportPromise[dcID] === undefined) {
let promise = new Promise((exportResolve, exportReject) => {
this.invokeApi('auth.exportAuthorization', {dc_id: dcID}, {noErrorBox: true}).then((exportedAuth: any) => {
this.invokeApi('auth.importAuthorization', {
id: exportedAuth.id,
bytes: exportedAuth.bytes
}, {dcID: dcID, noErrorBox: true}).then(exportResolve, exportReject);
}, exportReject);
});
this.cachedExportPromise[dcID] = promise;
this.cachedExportPromise[dcID].then(() => {
//(cachedNetworker = networker).wrapApiCall(method, params, options).then(deferred.resolve, rejectPromise);
this.invokeApi(method, params, options).then(deferred.resolve, rejectPromise);
}, rejectPromise);
} else if(error.code == 303) {
var newDcID = +error.type.match(/^(PHONE_MIGRATE_|NETWORK_MIGRATE_|USER_MIGRATE_)(\d+)/)[2];
if(newDcID != dcID) {
if(options.dcID) {
options.dcID = newDcID;
} else {
AppStorage.set({dc: this.baseDcID = newDcID});
}
this.cachedExportPromise[dcID].then(() => {
(cachedNetworker = networker).wrapApiCall(method, params, options).then(resolve, rejectPromise);
this.getNetworker(newDcID, options).then((networker) => {
networker.wrapApiCall(method, params, options).then(deferred.resolve, rejectPromise);
}, rejectPromise);
} else if(error.code == 303) {
var newDcID = +error.type.match(/^(PHONE_MIGRATE_|NETWORK_MIGRATE_|USER_MIGRATE_)(\d+)/)[2];
if(newDcID != dcID) {
if(options.dcID) {
options.dcID = newDcID;
} else {
AppStorage.set({dc: this.baseDcID = newDcID});
}
this.getNetworker(newDcID, options).then((networker) => {
networker.wrapApiCall(method, params, options).then(resolve, rejectPromise);
}, rejectPromise);
}
} else if(!options.rawError && error.code == 420) {
var waitTime = +error.type.match(/^FLOOD_WAIT_(\d+)/)[1] || 10;
if(waitTime > (options.timeout !== undefined ? options.timeout : 60)) {
}
} else if(!options.rawError && error.code == 420) {
var waitTime = +error.type.match(/^FLOOD_WAIT_(\d+)/)[1] || 10;
if(waitTime > (options.floodMaxTimeout !== undefined ? options.floodMaxTimeout : 60)) {
return rejectPromise(error);
}
setTimeout(() => {
performRequest(cachedNetworker);
}, waitTime/* (waitTime + 5) */ * 1000); // 03.02.2020
} else if(!options.rawError && (error.code == 500 || error.type == 'MSG_WAIT_FAILED')) {
var now = Date.now();
if(options.stopTime) {
if(now >= options.stopTime) {
return rejectPromise(error);
}
setTimeout(() => {
performRequest(cachedNetworker);
}, waitTime/* (waitTime + 5) */ * 1000); // 03.02.2020
} else if(!options.rawError && (error.code == 500 || error.type == 'MSG_WAIT_FAILED')) {
var now = Date.now();
if(options.stopTime) {
if(now >= options.stopTime) {
return rejectPromise(error);
}
} else {
options.stopTime = now + (options.timeout !== undefined ? options.timeout : 10) * 1000;
}
options.waitTime = options.waitTime ? Math.min(60, options.waitTime * 1.5) : 1;
setTimeout(() => {
performRequest(cachedNetworker);
}, options.waitTime * 1000);
} else {
rejectPromise(error);
}
});
}
if(dcID = (options.dcID || this.baseDcID)) {
this.getNetworker(dcID, options).then(performRequest, rejectPromise);
} else {
AppStorage.get<number>('dc').then((baseDcID) => {
this.getNetworker(this.baseDcID = dcID = baseDcID || App.baseDcID, options).then(performRequest, rejectPromise);
});
}
});
options.waitTime = options.waitTime ? Math.min(60, options.waitTime * 1.5) : 1;
setTimeout(() => {
performRequest(cachedNetworker);
}, options.waitTime * 1000);
} else {
rejectPromise(error);
}
});
}
if(dcID = (options.dcID || this.baseDcID)) {
this.getNetworker(dcID, options).then(performRequest, rejectPromise);
} else {
AppStorage.get<number>('dc').then((baseDcID) => {
this.getNetworker(this.baseDcID = dcID = baseDcID || App.baseDcID, options).then(performRequest, rejectPromise);
});
}
return deferred;
}
}
export default new ApiManager();
const apiManager = new ApiManager();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.apiManager = apiManager);
export default apiManager;

2
src/lib/mtproto/authorizer.ts

@ -25,7 +25,7 @@ type AuthOptions = { @@ -25,7 +25,7 @@ type AuthOptions = {
nonce: Uint8Array,
serverNonce?: Uint8Array,
pq?: any,
pq?: Uint8Array,
fingerprints?: string[],
publicKey?: {
modulus: string,

46
src/lib/mtproto/dcConfigurator.ts

@ -3,17 +3,11 @@ import { Modes } from './mtproto_config'; @@ -3,17 +3,11 @@ import { Modes } from './mtproto_config';
/// #if !MTPROTO_HTTP
import Socket from './transports/websocket';
// @ts-ignore
type TransportTypes = 'websocket';
/// #else
import HTTP from './transports/http';
// @ts-ignore
type TransportTypes = 'https' | 'http';
/// #endif
type Servers = {
[transportType in TransportTypes]: {
[dcID: number]: MTTransport[]
}
[dcID: number]: MTTransport[]
};
export class DcConfigurator {
@ -33,35 +27,13 @@ export class DcConfigurator { @@ -33,35 +27,13 @@ export class DcConfigurator {
{id: 5, host: '149.154.171.5', port: 80}
];
/// #if !MTPROTO_HTTP
private chosenServers: Servers = {
websocket: {}
};
private chosenUploadServers: Servers = {
websocket: {}
};
/// #else
// @ts-ignore
private chosenServers: Servers = {
// @ts-ignore
https: {},
http: {}
};
// @ts-ignore
private chosenUploadServers: Servers = {
// @ts-ignore
https: {},
http: {}
};
/// #endif
private chosenServers: Servers = {};
private chosenUploadServers: Servers = {};
public chooseServer(dcID: number, upload?: boolean, transportType: TransportTypes = 'websocket') {
const servers = upload && (transportType != 'websocket' || Modes.multipleConnections)
? this.chosenUploadServers[transportType]
: this.chosenServers[transportType];
public chooseServer(dcID: number, upload?: boolean) {
const servers = upload && Modes.multipleConnections
? this.chosenUploadServers
: this.chosenServers;
if(!(dcID in servers)) {
servers[dcID] = [];
@ -77,11 +49,11 @@ export class DcConfigurator { @@ -77,11 +49,11 @@ export class DcConfigurator {
const subdomain = this.sslSubdomains[dcID - 1];
const path = Modes.test ? 'apiws_test' : 'apiws';
const chosenServer = 'wss://' + subdomain + '.web.telegram.org/' + path;
transport = new Socket(dcID, chosenServer);
transport = new Socket(dcID, chosenServer, upload ? '-U' : '');
//} else
/// #else
// @ts-ignore
if(Modes.ssl || !Modes.http || transportType == 'https') {
if(Modes.ssl || !Modes.http) {
const subdomain = this.sslSubdomains[dcID - 1] + (upload ? '-1' : '');
const path = Modes.test ? 'apiw_test1' : 'apiw1';
const chosenServer = 'https://' + subdomain + '.web.telegram.org/' + path;

8
src/lib/mtproto/mtproto.worker.ts

@ -124,6 +124,13 @@ ctx.addEventListener('message', async(e) => { @@ -124,6 +124,13 @@ ctx.addEventListener('message', async(e) => {
respond({taskID: taskID, error: err});
}
}
case 'getNetworker': {
// @ts-ignore
apiManager[task.task].apply(apiManager, task.args);
respond({taskID: taskID, result: null});
break;
}
default: {
try {
@ -147,4 +154,5 @@ ctx.addEventListener('message', async(e) => { @@ -147,4 +154,5 @@ ctx.addEventListener('message', async(e) => {
}
});
//console.log('[WORKER] Will send ready', Date.now() / 1000);
ctx.postMessage('ready');

7
src/lib/mtproto/mtprotoworker.ts

@ -11,6 +11,7 @@ import { MOUNT_CLASS_TO } from './mtproto_config'; @@ -11,6 +11,7 @@ import { MOUNT_CLASS_TO } from './mtproto_config';
import $rootScope from '../rootScope';
import referenceDatabase from './referenceDatabase';
import { ApiError } from './apiManager';
import { InvokeApiOptions } from '../../types';
type Task = {
taskID: number,
@ -136,6 +137,8 @@ class ApiManagerProxy extends CryptoWorkerMethods { @@ -136,6 +137,8 @@ class ApiManagerProxy extends CryptoWorkerMethods {
// @ts-ignore
const bytes = _task.originalPayload[1].file_reference;
referenceDatabase.refreshReference(bytes).then(() => {
// @ts-ignore
_task.originalPayload[1].file_reference = referenceDatabase.getReferenceByLink(bytes);
const newTask: ServiceWorkerTask = {
type: _task.type,
id: _task.id,
@ -231,8 +234,8 @@ class ApiManagerProxy extends CryptoWorkerMethods { @@ -231,8 +234,8 @@ class ApiManagerProxy extends CryptoWorkerMethods {
return this.performTaskWorker('setUserAuth', userAuth);
}
public getNetworker(dc_id: number) {
return this.performTaskWorker('getNetworker', dc_id);
public getNetworker(dc_id: number, options?: InvokeApiOptions) {
return this.performTaskWorker('getNetworker', dc_id, options);
}
public logOut(): Promise<void> {

313
src/lib/mtproto/networker.ts

@ -24,11 +24,21 @@ import type Socket from './transports/websocket'; @@ -24,11 +24,21 @@ import type Socket from './transports/websocket';
//console.error('networker included!', new Error().stack);
type Message = {
type MessageOptions = InvokeApiOptions & Partial<{
noResponse: true,
longPoll: true,
notContentRelated: true,
noSchedule: true,
messageID: string,
}>;
type Message = InvokeApiOptions & MessageOptions & {
msg_id: string,
seq_no: number,
body?: Uint8Array | number[],
isAPI?: boolean,
// only these four are important
acked?: boolean,
@ -40,14 +50,16 @@ type Message = { @@ -40,14 +50,16 @@ type Message = {
container?: boolean,
inner?: string[],
notContentRelated?: boolean,
noSchedule?: boolean,
// below - options
notContentRelated?: true,
noSchedule?: true,
resultType?: string,
singleInRequest?: boolean,
longPoll?: boolean,
noResponse?: boolean, // only with http (http_wait for longPoll)
singleInRequest?: true,
longPoll?: true,
noResponse?: true, // only with http (http_wait for longPoll)
};
class MTPNetworker {
@ -75,14 +87,13 @@ class MTPNetworker { @@ -75,14 +87,13 @@ class MTPNetworker {
private checkConnectionPeriod = 0;
private onOnlineCb = this.checkConnection.bind(this);
private sleepAfter = 0;
private offline = false;
/// #endif
private seqNo: number = 0;
private prevSessionID: Array<number> = [];
private sessionID: Array<number> = [];
private offline = false;
private lastResendReq: {
req_msg_id: string,
resend_msg_ids: Array<string>
@ -97,8 +108,10 @@ class MTPNetworker { @@ -97,8 +108,10 @@ class MTPNetworker {
this.authKeyUint8 = convertToUint8Array(this.authKey);
//this.authKeyID = sha1BytesSync(this.authKey).slice(-8);
this.upload = this.options.fileUpload || this.options.fileDownload || false;
this.log = logger('NET-' + dcID + (this.upload ? '-U' : ''));
//console.trace('Create', dcID, options);
this.upload = this.options.fileUpload || this.options.fileDownload;
this.log = logger('NET-' + dcID + (this.upload ? '-U' : ''), this.upload && this.dcID == 2 ? LogLevels.debug | LogLevels.warn | LogLevels.log | LogLevels.error : LogLevels.error);
this.log('constructor'/* , this.authKey, this.authKeyID, this.serverSalt */);
/* // Test resend after bad_server_salt
@ -130,20 +143,18 @@ class MTPNetworker { @@ -130,20 +143,18 @@ class MTPNetworker {
public updateSession() {
this.seqNo = 0;
this.prevSessionID = this.sessionID;
this.sessionID = new Array(8);
this.sessionID = [...new Uint8Array(this.sessionID.length).randomize()];
this.sessionID = [...new Uint8Array(8).randomize()];
}
public updateSentMessage(sentMessageID: any) {
var sentMessage = this.sentMessages[sentMessageID];
public updateSentMessage(sentMessageID: string) {
const sentMessage = this.sentMessages[sentMessageID];
if(!sentMessage) {
return false;
}
var self = this;
if(sentMessage.container) {
var newInner: any = [];
sentMessage.inner.forEach((innerSentMessageID: any) => {
var innerSentMessage = self.updateSentMessage(innerSentMessageID);
const newInner: string[] = [];
sentMessage.inner.forEach((innerSentMessageID) => {
const innerSentMessage = this.updateSentMessage(innerSentMessageID);
if(innerSentMessage) {
newInner.push(innerSentMessage.msg_id);
}
@ -157,13 +168,13 @@ class MTPNetworker { @@ -157,13 +168,13 @@ class MTPNetworker {
sentMessage.container
);
this.sentMessages[sentMessage.msg_id] = sentMessage;
delete self.sentMessages[sentMessageID];
delete this.sentMessages[sentMessageID];
return sentMessage;
}
public generateSeqNo(notContentRelated?: boolean) {
var seqNo = this.seqNo * 2;
let seqNo = this.seqNo * 2;
if(!notContentRelated) {
seqNo++;
@ -173,14 +184,14 @@ class MTPNetworker { @@ -173,14 +184,14 @@ class MTPNetworker {
return seqNo;
}
public wrapMtpCall(method: string, params: any = {}, options: any = {}) {
var serializer = new TLSerialization({mtproto: true});
public wrapMtpCall(method: string, params: any = {}, options: MessageOptions = {}) {
const serializer = new TLSerialization({mtproto: true});
serializer.storeMethod(method, params);
var messageID = timeManager.generateID();
var seqNo = this.generateSeqNo();
var message = {
const messageID = timeManager.generateID();
const seqNo = this.generateSeqNo();
const message = {
msg_id: messageID,
seq_no: seqNo,
body: serializer.getBytes()
@ -193,13 +204,13 @@ class MTPNetworker { @@ -193,13 +204,13 @@ class MTPNetworker {
return this.pushMessage(message, options);
}
public wrapMtpMessage(object: any = {}, options: any = {}) {
var serializer = new TLSerialization({mtproto: true});
public wrapMtpMessage(object: any = {}, options: MessageOptions = {}) {
const serializer = new TLSerialization({mtproto: true});
serializer.storeObject(object, 'Object');
var messageID = timeManager.generateID();
var seqNo = this.generateSeqNo(options.notContentRelated);
var message = {
const messageID = timeManager.generateID();
const seqNo = this.generateSeqNo(options.notContentRelated);
const message = {
msg_id: messageID,
seq_no: seqNo,
body: serializer.getBytes()
@ -327,6 +338,80 @@ class MTPNetworker { @@ -327,6 +338,80 @@ class MTPNetworker {
this.log('Long-poll failed', error);
});
}
public checkConnection(event: Event | string) {
/* $rootScope.offlineConnecting = true */
this.log('Check connection', event);
clearTimeout(this.checkConnectionTimeout);
this.checkConnectionTimeout = 0;
var serializer = new TLSerialization({mtproto: true});
var pingID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)];
serializer.storeMethod('ping', {
ping_id: pingID
});
var pingMessage = {
msg_id: timeManager.generateID(),
seq_no: this.generateSeqNo(true),
body: serializer.getBytes()
};
var self = this;
this.sendEncryptedRequest(pingMessage, {
timeout: 15000
}).then((result) => {
/* delete $rootScope.offlineConnecting */
self.toggleOffline(false);
}, () => {
this.log('Delay ', self.checkConnectionPeriod * 1000);
self.checkConnectionTimeout = setTimeout(self.checkConnection.bind(self), self.checkConnectionPeriod * 1000 | 0);
self.checkConnectionPeriod = Math.min(60, self.checkConnectionPeriod * 1.5);
/* setTimeout(function() {
delete $rootScope.offlineConnecting
}, 1000); */
});
}
public toggleOffline(enabled: boolean) {
// this.log('toggle ', enabled, this.dcID, this.iii)
if(this.offline !== undefined && this.offline == enabled) {
return false;
}
this.offline = enabled;
/* $rootScope.offline = enabled;
$rootScope.offlineConnecting = false; */
if(this.offline) {
clearTimeout(this.nextReqTimeout);
this.nextReqTimeout = 0;
this.nextReq = 0;
if(this.checkConnectionPeriod < 1.5) {
this.checkConnectionPeriod = 0;
}
this.checkConnectionTimeout = setTimeout(this.checkConnection.bind(this), this.checkConnectionPeriod * 1000 | 0);
this.checkConnectionPeriod = Math.min(30, (1 + this.checkConnectionPeriod) * 1.5);
document.body.addEventListener('online', this.onOnlineCb, false);
document.body.addEventListener('focus', this.onOnlineCb, false);
} else {
this.checkLongPoll();
this.scheduleRequest();
document.body.removeEventListener('online', this.onOnlineCb);
document.body.removeEventListener('focus', this.onOnlineCb);
clearTimeout(this.checkConnectionTimeout);
this.checkConnectionTimeout = 0;
}
}
/// #endif
// тут можно сделать таймаут и выводить дисконнект
@ -335,7 +420,7 @@ class MTPNetworker { @@ -335,7 +420,7 @@ class MTPNetworker {
seq_no: number,
body: Uint8Array | number[],
isAPI?: boolean
}, options: any = {}) {
}, options: MessageOptions = {}) {
return new Promise((resolve, reject) => {
this.sentMessages[message.msg_id] = Object.assign(message, options, {
deferred: {resolve, reject}
@ -414,102 +499,21 @@ class MTPNetworker { @@ -414,102 +499,21 @@ class MTPNetworker {
});
}
/// #if MTPROTO_HTTP
public checkConnection(event: Event | string) {
/* $rootScope.offlineConnecting = true */
this.log('Check connection', event);
clearTimeout(this.checkConnectionTimeout);
this.checkConnectionTimeout = 0;
var serializer = new TLSerialization({mtproto: true});
var pingID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)];
serializer.storeMethod('ping', {
ping_id: pingID
});
var pingMessage = {
msg_id: timeManager.generateID(),
seq_no: this.generateSeqNo(true),
body: serializer.getBytes()
};
var self = this;
this.sendEncryptedRequest(pingMessage, {
timeout: 15000
}).then((result) => {
/* delete $rootScope.offlineConnecting */
self.toggleOffline(false);
}, () => {
this.log('Delay ', self.checkConnectionPeriod * 1000);
self.checkConnectionTimeout = setTimeout(self.checkConnection.bind(self), self.checkConnectionPeriod * 1000 | 0);
self.checkConnectionPeriod = Math.min(60, self.checkConnectionPeriod * 1.5);
/* setTimeout(function() {
delete $rootScope.offlineConnecting
}, 1000); */
});
}
/// #endif
public toggleOffline(enabled: boolean) {
// this.log('toggle ', enabled, this.dcID, this.iii)
if(this.offline !== undefined && this.offline == enabled) {
return false;
}
this.offline = enabled;
/* $rootScope.offline = enabled;
$rootScope.offlineConnecting = false; */
/// #if !MTPROTO_HTTP
//if(!(this.transport instanceof HTTP)) {
this.log('toggle ', enabled, this.dcID);
return;
//}
/// #else
if(this.offline) {
clearTimeout(this.nextReqTimeout);
this.nextReqTimeout = 0;
this.nextReq = 0;
if(this.checkConnectionPeriod < 1.5) {
this.checkConnectionPeriod = 0;
}
this.checkConnectionTimeout = setTimeout(this.checkConnection.bind(this), this.checkConnectionPeriod * 1000 | 0);
this.checkConnectionPeriod = Math.min(30, (1 + this.checkConnectionPeriod) * 1.5);
document.body.addEventListener('online', this.onOnlineCb, false);
document.body.addEventListener('focus', this.onOnlineCb, false);
} else {
this.checkLongPoll();
this.scheduleRequest();
document.body.removeEventListener('online', this.onOnlineCb);
document.body.removeEventListener('focus', this.onOnlineCb);
clearTimeout(this.checkConnectionTimeout);
this.checkConnectionTimeout = 0;
}
/// #endif
}
public performScheduledRequest() {
// this.log('scheduled', this.dcID, this.iii)
/// #if MTPROTO_HTTP
if(this.offline) {
this.log('Cancel scheduled');
return false;
}
this.nextReq = 0;
/// #endif
if(this.pendingAcks.length) {
var ackMsgIDs: Array<string> = this.pendingAcks.slice();
/* for(var i = 0; i < this.pendingAcks.length; i++) {
ackMsgIDs.push(this.pendingAcks[i]);
} */
const ackMsgIDs: Array<string> = this.pendingAcks.slice();
// this.log('acking messages', ackMsgIDs)
this.wrapMtpMessage({
_: 'msgs_ack',
@ -521,15 +525,13 @@ class MTPNetworker { @@ -521,15 +525,13 @@ class MTPNetworker {
}
if(this.pendingResends.length) {
var resendMsgIDs: Array<string> = this.pendingResends.slice();
var resendOpts = {
const resendMsgIDs: Array<string> = this.pendingResends.slice();
const resendOpts: MessageOptions = {
noSchedule: true,
notContentRelated: true,
messageID: '' // will set in wrapMtpMessage->pushMessage
};
/* for(var i = 0; i < this.pendingResends.length; i++) {
resendMsgIDs.push(this.pendingResends[i]);
} */
this.log('resendReq messages', resendMsgIDs);
this.wrapMtpMessage({
_: 'msg_resend_req',
@ -542,8 +544,9 @@ class MTPNetworker { @@ -542,8 +544,9 @@ class MTPNetworker {
};
}
var messages: Message[] = [],
message: Message;
var message: MTPNetworker['sentMessages'][keyof MTPNetworker['sentMessages']];
var messages: typeof message[] = [];
var messagesByteLen = 0;
var currentTime = Date.now();
var hasApiCall = false;
@ -748,15 +751,15 @@ class MTPNetworker { @@ -748,15 +751,15 @@ class MTPNetworker {
});
}
public sendEncryptedRequest(message: any, options: any = {}) {
var self = this;
public sendEncryptedRequest(message: Message, options: any = {}) {
const self = this;
this.log.debug('Send encrypted', message, options, this.authKeyID);
// console.trace()
var data = new TLSerialization({
const data = new TLSerialization({
startMaxLength: message.body.length + 2048
});
data.storeIntBytes(this.serverSalt, 64, 'salt');
data.storeIntBytes(this.sessionID, 64, 'session_id');
@ -766,37 +769,44 @@ class MTPNetworker { @@ -766,37 +769,44 @@ class MTPNetworker {
data.storeInt(message.body.length, 'message_data_length');
data.storeRawBytes(message.body, 'message_data');
var dataBuffer = data.getBuffer();
const dataBuffer = data.getBuffer();
var paddingLength = (16 - (data.offset % 16)) + 16 * (1 + nextRandomInt(5));
var padding = new Array(paddingLength);
padding = [...new Uint8Array(padding.length).randomize()];
const paddingLength = (16 - (data.offset % 16)) + 16 * (1 + nextRandomInt(5));
const padding = [...new Uint8Array(paddingLength).randomize()];
//MTProto.secureRandom.nextBytes(padding);
var dataWithPadding = bufferConcat(dataBuffer, padding);
const dataWithPadding = bufferConcat(dataBuffer, padding);
// this.log('Adding padding', dataBuffer, padding, dataWithPadding)
// this.log('auth_key_id', bytesToHex(self.authKeyID))
/* if(message.fileUpload) {
this.log('Send encrypted: body length:', (message.body as ArrayBuffer).byteLength, paddingLength, dataWithPadding);
} */
return this.getEncryptedMessage(dataWithPadding).then((encryptedResult) => {
this.log.debug('Got encrypted out message', encryptedResult);
let request = new TLSerialization({
startMaxLength: encryptedResult.bytes.byteLength + 256
const request = new TLSerialization({
startMaxLength: encryptedResult.bytes.length + 256
});
request.storeIntBytes(self.authKeyID, 64, 'auth_key_id');
request.storeIntBytes(encryptedResult.msgKey, 128, 'msg_key');
request.storeRawBytes(encryptedResult.bytes, 'encrypted_data');
//var requestData = xhrSendBuffer ? request.getBuffer() : request.getBytes(true) as Uint8Array;
let requestData = request.getBytes(true);
const requestData = request.getBytes(true);
let baseError = {
const baseError = {
code: 406,
type: 'NETWORK_BAD_RESPONSE',
transport: this.transport
};
let promise = this.transport.send(requestData);
if(message.fileUpload) {
this.log('Send encrypted: requestData length:', requestData.length, requestData.length % 16, paddingLength % 16, paddingLength, data.offset);
}
const promise = this.transport.send(requestData);
/// #if !MTPROTO_HTTP
/* if(!(this.transport instanceof HTTP)) */ return promise;
/// #else
@ -946,6 +956,9 @@ class MTPNetworker { @@ -946,6 +956,9 @@ class MTPNetworker {
public scheduleRequest(delay = 0) {
/// #if !MTPROTO_HTTP
/* clearTimeout(this.nextReqTimeout);
this.nextReqTimeout = self.setTimeout(this.performScheduledRequest.bind(this), delay || 0);
return; */
return this.performScheduledRequest();
/// #else
if(this.offline/* && this.transport instanceof HTTP */) {
@ -1053,6 +1066,26 @@ class MTPNetworker { @@ -1053,6 +1066,26 @@ class MTPNetworker {
}
}
public requestMessageStatus() {
const ids: string[] = [];
for(const id in this.sentMessages) {
const message = this.sentMessages[id];
if(message.isAPI && message.fileUpload) {
ids.push(message.msg_id);
}
}
this.wrapMtpMessage({
_: 'msgs_state_req',
msg_ids: ids
}, {
notContentRelated: true
}).then(res => {
this.log('status', res);
});
}
// * https://core.telegram.org/mtproto/service_messages_about_messages#notice-of-ignored-error-message
public processMessage(message: any, messageID: string, sessionID: Uint8Array | number[]) {
var msgidInt = parseInt(messageID/* .toString(10) */.substr(0, -10), 10);
if(msgidInt % 2) {

42
src/lib/mtproto/referenceDatabase.ts

@ -2,6 +2,7 @@ import appMessagesManager from "../appManagers/appMessagesManager"; @@ -2,6 +2,7 @@ import appMessagesManager from "../appManagers/appMessagesManager";
import { Photo } from "../../layer";
import { deepEqual } from "../utils";
import { MOUNT_CLASS_TO } from "./mtproto_config";
import { bytesToHex } from "../bin_utils";
export type ReferenceContext = ReferenceContext.referenceContextProfilePhoto | ReferenceContext.referenceContextMessage;
export namespace ReferenceContext {
@ -17,14 +18,22 @@ export namespace ReferenceContext { @@ -17,14 +18,22 @@ export namespace ReferenceContext {
}
export type ReferenceBytes = Photo.photo['file_reference'];
export type ReferenceContexts = Set<ReferenceContext>;
//type ReferenceBytes = Uint8Array;
class ReferenceDatabase {
private contexts: Map<ReferenceBytes, Set<ReferenceContext>> = new Map();
private contexts: Map<ReferenceBytes, ReferenceContexts> = new Map();
//private references: Map<ReferenceBytes, number[]> = new Map();
public saveContext(reference: ReferenceBytes, context: ReferenceContext) {
const contexts = this.contexts.get(reference) ?? new Set();
private links: {[hex: string]: ReferenceBytes} = {};
public saveContext(reference: ReferenceBytes, context: ReferenceContext, contexts?: ReferenceContexts) {
[contexts, reference] = this.getContexts(reference);
if(!contexts) {
contexts = new Set();
this.contexts.set(reference, contexts);
this.links[bytesToHex(reference)] = reference;
}
for(const _context of contexts) {
if(deepEqual(_context, context)) {
@ -33,22 +42,31 @@ class ReferenceDatabase { @@ -33,22 +42,31 @@ class ReferenceDatabase {
}
contexts.add(context);
this.contexts.set(reference, contexts);
}
public getContext(reference: ReferenceBytes): ReferenceContext {
const contexts = this.contexts.get(reference);
return contexts ? contexts.values().next().value : undefined;
public getReferenceByLink(reference: ReferenceBytes) {
return this.links[bytesToHex(reference)];
}
public getContexts(reference: ReferenceBytes): [ReferenceContexts, ReferenceBytes] {
const contexts = this.contexts.get(reference) || (reference = this.getReferenceByLink(reference), this.contexts.get(reference));
return [contexts, reference];
}
public getContext(reference: ReferenceBytes): [ReferenceContext, ReferenceBytes] {
const contexts = this.getContexts(reference);
return contexts ? [contexts[0].values().next().value, contexts[1]] : undefined;
}
public deleteContext(reference: ReferenceBytes, context: ReferenceContext) {
const contexts = this.contexts.get(reference);
public deleteContext(reference: ReferenceBytes, context: ReferenceContext, contexts?: ReferenceContexts) {
[contexts, reference] = this.getContexts(reference);
if(contexts) {
for(const _context of contexts) {
if(deepEqual(_context, context)) {
contexts.delete(_context);
if(!contexts.size) {
this.contexts.delete(reference);
delete this.links[bytesToHex(reference)];
}
return true;
}
@ -58,8 +76,8 @@ class ReferenceDatabase { @@ -58,8 +76,8 @@ class ReferenceDatabase {
return false;
}
public refreshReference(reference: ReferenceBytes): Promise<void> {
const context = this.getContext(reference);
public refreshReference(reference: ReferenceBytes, context?: ReferenceContext): Promise<void> {
[context, reference] = this.getContext(reference);
switch(context?.type) {
case 'message': {
return appMessagesManager.wrapSingleMessage(context.messageID, true);

31
src/lib/mtproto/tl_utils.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import {bigint, bigStringInt, bytesToHex, isObject} from '../bin_utils';
import {bigint, bigStringInt, bytesCmp, bytesToHex, isObject} from '../bin_utils';
import Schema from './schema';
/// #if MTPROTO_WORKER
@ -26,7 +26,7 @@ class TLSerialization { @@ -26,7 +26,7 @@ class TLSerialization {
public intView: Int32Array;
public byteView: Uint8Array;
constructor(options: any = {}) {
constructor(options: Partial<{startMaxLength: number, mtproto: true}> = {}) {
this.maxLength = options.startMaxLength || 2048 // 2Kb
this.mtproto = options.mtproto || false;
this.createBuffer();
@ -75,14 +75,17 @@ class TLSerialization { @@ -75,14 +75,17 @@ class TLSerialization {
return;
}
///console.trace('Increase buffer', this.offset, needBytes, this.maxLength);
//console.log('Increase buffer start', this.offset, needBytes, this.maxLength, this.byteView.slice(0, 32));
this.maxLength = Math.ceil(Math.max(this.maxLength * 2, this.offset + needBytes + 16) / 4) * 4;
var previousBuffer = this.buffer;
var previousArray = new Int32Array(previousBuffer);
const previousBuffer = this.buffer;
//const previousByteView = this.byteView;
const previousArray = new Int32Array(previousBuffer);
this.createBuffer();
new Int32Array(this.buffer).set(previousArray);
/* console.log('Increase buffer end', this.offset, needBytes, this.maxLength, this.byteView.slice(0, 32),
bytesCmp(previousByteView, this.byteView.slice(0, previousByteView.length))); */
}
public writeInt(i: number, field: string) {
@ -168,17 +171,17 @@ class TLSerialization { @@ -168,17 +171,17 @@ class TLSerialization {
}
}
public storeBytes(bytes: any, field?: string) {
public storeBytes(bytes: ArrayBuffer | Uint8Array | number[], field?: string) {
if(bytes instanceof ArrayBuffer) {
bytes = new Uint8Array(bytes);
} else if(bytes === undefined) {
bytes = [];
}
this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':bytes');
this.debug && console.log('>>>', bytesToHex(bytes as number[]), (field || '') + ':bytes');
// if uint8array were json.stringified, then will be: {'0': 123, '1': 123}
var len = bytes.byteLength || bytes.length;
this.checkLength(len + 8)
var len = (bytes as ArrayBuffer).byteLength || (bytes as Uint8Array).length;
this.checkLength(len + 8);
if(len <= 253) {
this.byteView[this.offset++] = len;
} else {
@ -188,7 +191,7 @@ class TLSerialization { @@ -188,7 +191,7 @@ class TLSerialization {
this.byteView[this.offset++] = (len & 0xFF0000) >> 16;
}
this.byteView.set(bytes, this.offset);
this.byteView.set(bytes as Uint8Array, this.offset);
this.offset += len;
// Padding
@ -216,7 +219,7 @@ class TLSerialization { @@ -216,7 +219,7 @@ class TLSerialization {
this.offset += len;
}
public storeRawBytes(bytes: any, field?: string) {
public storeRawBytes(bytes: ArrayLike<number>, field?: string) {
if(bytes instanceof ArrayBuffer) {
bytes = new Uint8Array(bytes);
}
@ -385,7 +388,7 @@ class TLDeserialization { @@ -385,7 +388,7 @@ class TLDeserialization {
public mtproto: boolean = false;
private debug: boolean;
constructor(buffer: ArrayBuffer | Uint8Array, options: any = {}) {
constructor(buffer: ArrayBuffer | Uint8Array, options: Partial<{override: any, mtproto: true, debug: true}> = {}) {
//buffer = addPadding(buffer, 4, true); // fix 21.01.2020 for wss
if(buffer instanceof ArrayBuffer) {
this.buffer = buffer;

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

@ -6,7 +6,7 @@ class AbridgedPacketCodec implements Codec { @@ -6,7 +6,7 @@ class AbridgedPacketCodec implements Codec {
public obfuscateTag = new Uint8Array([this.tag, this.tag, this.tag, this.tag]);
public encodePacket(data: Uint8Array) {
let len = data.byteLength >> 2;
const len = data.byteLength >> 2;
let header: Uint8Array;
if(len < 127) {
header = new Uint8Array([len]);

41
src/lib/mtproto/transports/intermediate.ts

@ -1,49 +1,26 @@ @@ -1,49 +1,26 @@
import { nextRandomInt } from "../../bin_utils";
import { Codec } from "./codec";
class IntermediatePacketCodec implements Codec {
export class IntermediatePacketCodec implements Codec {
public tag = 0xee;
public obfuscateTag = new Uint8Array([this.tag, this.tag, this.tag, this.tag]);
public encodePacket(data: Uint8Array) {
let len = data.byteLength;
let header = new Uint8Array(new Uint32Array([len]).buffer);
if((data.length % 4) != 0) {
console.error('Encode error!', data.length, data);
}
const len = data.length;
const header = new Uint8Array(new Uint32Array([len]).buffer);
//console.log('got nobody cause im braindead', header, len, /* data, */data.buffer.byteLength == data.length);
return header.concat(data);
}
public readPacket(data: Uint8Array) {
let length = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
const length = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
return data.slice(4, 4 + length);
}
}
/* Data packets are aligned to 4bytes. This codec adds random bytes of size
from 0 to 3 bytes, which are ignored by decoder. */
class PaddedIntermediatePacketCodec extends IntermediatePacketCodec {
public tag = 0xdd;
public obfuscateTag = new Uint8Array([this.tag, this.tag, this.tag, this.tag]);
public encodePacket(data: Uint8Array) {
let padding = new Uint8Array(nextRandomInt(3)).randomize();
let len = data.byteLength + padding.byteLength;
let header = new Uint8Array(new Uint32Array([len]).buffer);
console.log('encodePacket', padding, len, data, header);
return header.concat(data, padding);
}
public readPacket(data: Uint8Array) {
let padLength = data.byteLength % 4;
if(padLength > 0) {
return data.slice(4, -padLength);
}
return data.slice(4);
}
}
export default new IntermediatePacketCodec();
//export default new PaddedIntermediatePacketCodec();

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

@ -0,0 +1,106 @@ @@ -0,0 +1,106 @@
//import aesjs from 'aes-js';
import { CTR } from "@cryptography/aes";
import { bytesFromWordss } from "../../bin_utils";
import { Codec } from "./codec";
/*
@cryptography/aes не работает с массивами которые не кратны 4, поэтому использую intermediate а не abridged
*/
export default class Obfuscation {
/* public enc: aesjs.ModeOfOperation.ModeOfOperationCTR;
public dec: aesjs.ModeOfOperation.ModeOfOperationCTR; */
public encNew: CTR;
public decNew: CTR;
public init(codec: Codec) {
const initPayload = new Uint8Array(64);
initPayload.randomize();
while(true) {
let val = (initPayload[3] << 24) | (initPayload[2] << 16) | (initPayload[1] << 8) | (initPayload[0]);
let val2 = (initPayload[7] << 24) | (initPayload[6] << 16) | (initPayload[5] << 8) | (initPayload[4]);
if(initPayload[0] != 0xef &&
val != 0x44414548 &&
val != 0x54534f50 &&
val != 0x20544547 &&
val != 0x4954504f &&
val != 0xeeeeeeee &&
val != 0xdddddddd &&
val2 != 0x00000000) {
//initPayload[56] = initPayload[57] = initPayload[58] = initPayload[59] = transport;
break;
}
initPayload.randomize();
}
////////////////////////initPayload.subarray(60, 62).hex = dcID;
const reversedPayload = initPayload.slice().reverse();
let encKey = initPayload.slice(8, 40);
let encIv = initPayload.slice(40, 56);
let decKey = reversedPayload.slice(8, 40);
let decIv = reversedPayload.slice(40, 56);
/* this.enc = new aesjs.ModeOfOperation.ctr(encKey, new aesjs.Counter(encIv as any));
this.dec = new aesjs.ModeOfOperation.ctr(decKey, new aesjs.Counter(decIv as any)); */
this.encNew = new CTR(encKey, encIv);
this.decNew = new CTR(decKey, decIv);
initPayload.set(codec.obfuscateTag, 56);
const encrypted = this.encode(initPayload);
initPayload.set(encrypted.slice(56, 64), 56);
return initPayload;
}
/* public encode(payload: Uint8Array) {
let startTime = performance.now();
let res = this.enc.encrypt(payload);
let time = performance.now() - startTime;
try {
startTime = performance.now();
let arr = this.encNew.encrypt(payload);
//let resNew = bytesFromWords({words: arr, sigBytes: arr.length});
let resNew = new Uint8Array(bytesFromWordss(arr));
let time2 = performance.now() - startTime;
console.log('Obfuscation: encode comparison:', res, arr, resNew, res.hex == resNew.hex, time2 < time);
} catch(err) {
console.error('Obfuscation: error:', err);
}
return res;
}
public decode(payload: Uint8Array) {
let res = this.dec.encrypt(payload);
try {
let arr = this.decNew.decrypt(payload);
//let resNew = bytesFromWords({words: arr, sigBytes: arr.length});
let resNew = new Uint8Array(bytesFromWordss(arr));
console.log('Obfuscation: decode comparison:', res, arr, resNew, res.hex == resNew.hex);
} catch(err) {
console.error('Obfuscation: error:', err);
}
return res;
} */
public encode(payload: Uint8Array) {
let res = this.encNew.encrypt(payload);
let bytes = new Uint8Array(bytesFromWordss(res));
return bytes;
}
public decode(payload: Uint8Array) {
let res = this.decNew.decrypt(payload);
let bytes = new Uint8Array(bytesFromWordss(res));
return bytes;
}
}

29
src/lib/mtproto/transports/padded.ts

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
import { nextRandomInt } from "../../bin_utils";
import { IntermediatePacketCodec } from "./intermediate";
/* Data packets are aligned to 4bytes. This codec adds random bytes of size
from 0 to 3 bytes, which are ignored by decoder. */
class PaddedIntermediatePacketCodec extends IntermediatePacketCodec {
public tag = 0xdd;
public obfuscateTag = new Uint8Array([this.tag, this.tag, this.tag, this.tag]);
public encodePacket(data: Uint8Array) {
let padding = new Uint8Array(nextRandomInt(3)).randomize();
let len = data.byteLength + padding.byteLength;
let header = new Uint8Array(new Uint32Array([len]).buffer);
console.log('encodePacket', padding, len, data, header);
return header.concat(data, padding);
}
public readPacket(data: Uint8Array) {
let padLength = data.byteLength % 4;
if(padLength > 0) {
return data.slice(4, -padLength);
}
return data.slice(4);
}
}
export default new PaddedIntermediatePacketCodec();

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

@ -1,111 +1,10 @@ @@ -1,111 +1,10 @@
import MTTransport from './transport';
//import aesjs from 'aes-js';
import {CTR} from '@cryptography/aes';
//import abridgetPacketCodec from './abridged';
import intermediatePacketCodec from './intermediate';
import {MTPNetworker} from '../networker';
import { logger, LogLevels } from '../../logger';
import { bytesFromWordss } from '../../bin_utils';
import { Codec } from './codec';
/*
@cryptography/aes не работает с массивами которые не кратны 4, поэтому использую intermediate а не abridged
*/
export class Obfuscation {
/* public enc: aesjs.ModeOfOperation.ModeOfOperationCTR;
public dec: aesjs.ModeOfOperation.ModeOfOperationCTR; */
public encNew: CTR;
public decNew: CTR;
public init(codec: Codec) {
const initPayload = new Uint8Array(64);
initPayload.randomize();
while(true) {
let val = (initPayload[3] << 24) | (initPayload[2] << 16) | (initPayload[1] << 8) | (initPayload[0]);
let val2 = (initPayload[7] << 24) | (initPayload[6] << 16) | (initPayload[5] << 8) | (initPayload[4]);
if(initPayload[0] != 0xef &&
val != 0x44414548 &&
val != 0x54534f50 &&
val != 0x20544547 &&
val != 0x4954504f &&
val != 0xeeeeeeee &&
val != 0xdddddddd &&
val2 != 0x00000000) {
//initPayload[56] = initPayload[57] = initPayload[58] = initPayload[59] = transport;
break;
}
initPayload.randomize();
}
////////////////////////initPayload.subarray(60, 62).hex = dcID;
const reversedPayload = initPayload.slice().reverse();
let encKey = initPayload.slice(8, 40);
let encIv = initPayload.slice(40, 56);
let decKey = reversedPayload.slice(8, 40);
let decIv = reversedPayload.slice(40, 56);
/* this.enc = new aesjs.ModeOfOperation.ctr(encKey, new aesjs.Counter(encIv as any));
this.dec = new aesjs.ModeOfOperation.ctr(decKey, new aesjs.Counter(decIv as any)); */
this.encNew = new CTR(encKey, encIv);
this.decNew = new CTR(decKey, decIv);
initPayload.set(codec.obfuscateTag, 56);
const encrypted = this.encode(initPayload);
initPayload.set(encrypted.slice(56, 64), 56);
return initPayload;
}
/* public encode(payload: Uint8Array) {
let res = this.enc.encrypt(payload);
try {
let arr = this.encNew.encrypt(payload);
//let resNew = bytesFromWords({words: arr, sigBytes: arr.length});
let resNew = new Uint8Array(bytesFromWordss(arr));
console.log('Obfuscation: encode comparison:', res, arr, resNew, res.hex == resNew.hex);
} catch(err) {
console.error('Obfuscation: error:', err);
}
return res;
}
public decode(payload: Uint8Array) {
let res = this.dec.encrypt(payload);
try {
let arr = this.decNew.decrypt(payload);
//let resNew = bytesFromWords({words: arr, sigBytes: arr.length});
let resNew = new Uint8Array(bytesFromWordss(arr));
console.log('Obfuscation: decode comparison:', res, arr, resNew, res.hex == resNew.hex);
} catch(err) {
console.error('Obfuscation: error:', err);
}
return res;
} */
public encode(payload: Uint8Array) {
let res = this.encNew.encrypt(payload);
let bytes = new Uint8Array(bytesFromWordss(res));
return bytes;
}
public decode(payload: Uint8Array) {
let res = this.decNew.decrypt(payload);
let bytes = new Uint8Array(bytesFromWordss(res));
return bytes;
}
}
import Obfuscation from './obfuscation';
const CONNECTION_RETRY_TIMEOUT = 30000;
@ -133,10 +32,10 @@ export default class Socket extends MTTransport { @@ -133,10 +32,10 @@ export default class Socket extends MTTransport {
lastCloseTime: number;
constructor(dcID: number, url: string) {
constructor(dcID: number, url: string, logSuffix: string) {
super(dcID, url);
this.log = logger(`WS-${dcID}`, LogLevels.log/* | LogLevels.error | LogLevels.debug */);
this.log = logger(`WS-${dcID}` + logSuffix, LogLevels.error | LogLevels.log/* | LogLevels.debug */);
this.log('constructor');
this.connect();
}
@ -145,6 +44,7 @@ export default class Socket extends MTTransport { @@ -145,6 +44,7 @@ export default class Socket extends MTTransport {
if(this.ws) {
this.ws.removeEventListener('open', this.handleOpen);
this.ws.removeEventListener('close', this.handleClose);
this.ws.removeEventListener('error', this.handleError);
this.ws.removeEventListener('message', this.handleMessage);
this.ws.close(1000);
}
@ -153,6 +53,7 @@ export default class Socket extends MTTransport { @@ -153,6 +53,7 @@ export default class Socket extends MTTransport {
this.ws.binaryType = 'arraybuffer';
this.ws.addEventListener('open', this.handleOpen);
this.ws.addEventListener('close', this.handleClose);
this.ws.addEventListener('error', this.handleError);
this.ws.addEventListener('message', this.handleMessage);
};
@ -168,7 +69,11 @@ export default class Socket extends MTTransport { @@ -168,7 +69,11 @@ export default class Socket extends MTTransport {
this.releasePending();
//}, 3e3);
};
handleError = (e: Event) => {
this.log.error(e);
};
handleClose = (event: CloseEvent) => {
this.log('closed', event, this.pending);
this.connected = false;
@ -238,7 +143,7 @@ export default class Socket extends MTTransport { @@ -238,7 +143,7 @@ export default class Socket extends MTTransport {
this.pending.push({body});
this.releasePending();
} else {
let promise = new Promise<Uint8Array>((resolve, reject) => {
const promise = new Promise<Uint8Array>((resolve, reject) => {
this.pending.push({resolve, reject, body});
});
@ -247,7 +152,7 @@ export default class Socket extends MTTransport { @@ -247,7 +152,7 @@ export default class Socket extends MTTransport {
return promise;
}
}
releasePending() {
if(!this.connected) {
//this.connect();
@ -255,8 +160,9 @@ export default class Socket extends MTTransport { @@ -255,8 +160,9 @@ export default class Socket extends MTTransport {
}
//this.log.error('Pending length:', this.pending.length);
const length = this.pending.length;
for(let i = length - 1; i >= 0; --i) {
let length = this.pending.length;
//for(let i = length - 1; i >= 0; --i) {
for(let i = 0; i < length; ++i) {
const pending = this.pending[i];
const {body, bodySent} = pending;
if(body && !bodySent) {
@ -267,10 +173,17 @@ export default class Socket extends MTTransport { @@ -267,10 +173,17 @@ export default class Socket extends MTTransport {
//this.log('send after obf:', enc.hex);
this.log.debug('-> body length to send:', enc.length);
this.ws.send(enc);
/* if(this.ws.bufferedAmount) {
this.log.error('bufferedAmount:', this.ws.bufferedAmount);
} */
//setTimeout(() => {
this.ws.send(enc);
//}, 500);
if(!pending.resolve) { // remove if no response needed
this.pending.splice(i, 1);
this.pending.splice(i--, 1);
length--;
} else {
pending.bodySent = true;
}

4
src/lib/services.ts

@ -12,8 +12,8 @@ import AppImManager from './appManagers/appImManager'; @@ -12,8 +12,8 @@ import AppImManager from './appManagers/appImManager';
import AppPeersManager from './appManagers/appPeersManager';
import AppStickersManager from './appManagers/appStickersManager';
import AppDocsManager from './appManagers/appDocsManager';
import AppSidebarRight from './appManagers/appSidebarRight';
import AppSidebarLeft from './appManagers/appSidebarLeft';
import AppSidebarRight from '../components/sidebarRight';
import AppSidebarLeft from '../components/sidebarLeft';
import AppMediaViewer from './appManagers/appMediaViewer';
//import AppSharedMediaManager from './appManagers/appSharedMediaManager';

2
src/pages/page.ts

@ -4,7 +4,7 @@ export default class Page { @@ -4,7 +4,7 @@ export default class Page {
public pageEl: HTMLDivElement;
private installed = false;
constructor(className: string, public isAuthPage: boolean, private onFirstMount?: (...args: any[]) => Promise<any> | void, private onMount?: (...args: any[]) => void) {
constructor(className: string, public isAuthPage: boolean, private onFirstMount?: (...args: any[]) => Promise<any> | void, private onMount?: (...args: any[]) => void, public onShown?: () => void) {
this.pageEl = document.body.getElementsByClassName(className)[0] as HTMLDivElement;
}

7
src/pages/pageAuthCode.ts

@ -26,6 +26,7 @@ const EDITONSAMEPAGE = false; @@ -26,6 +26,7 @@ const EDITONSAMEPAGE = false;
let headerElement: HTMLHeadElement = null;
let sentTypeElement: HTMLParagraphElement = null;
let codeInput: HTMLInputElement;
let onFirstMount = (): Promise<any> => {
let needFrame = 0, lastLength = 0;
@ -35,12 +36,10 @@ let onFirstMount = (): Promise<any> => { @@ -35,12 +36,10 @@ let onFirstMount = (): Promise<any> => {
const CODELENGTH = authCode.type.length;
const codeInput = page.pageEl.querySelector('#code') as HTMLInputElement;
codeInput = page.pageEl.querySelector('#code') as HTMLInputElement;
const codeInputLabel = codeInput.nextElementSibling as HTMLLabelElement;
const editButton = page.pageEl.querySelector('.phone-edit') as HTMLElement;
codeInput.focus();
if(EDITONSAMEPAGE) {
let editable = false;
let changePhonePromise: Promise<unknown>;
@ -311,6 +310,8 @@ const page = new Page('page-authCode', true, onFirstMount, (_authCode: typeof au @@ -311,6 +310,8 @@ const page = new Page('page-authCode', true, onFirstMount, (_authCode: typeof au
sentTypeElement.innerHTML = `Please check everything<br>for a code (type: ${authCode.type._})`;
break;
}
}, () => {
codeInput.focus();
});
export default page;

11
src/pages/pageIm.ts

@ -1,14 +1,12 @@ @@ -1,14 +1,12 @@
import { openBtnMenu/* , ripple */ } from "../components/misc";
//import {stackBlurImage} from '../lib/StackBlur';
import Page from "./page";
import { cancelEvent } from "../lib/utils";
import { DEBUG } from "../lib/mtproto/mtproto_config";
let onFirstMount = () => {
//return;
const promise = import('../lib/appManagers/appImManager');
promise.finally(() => {
const promise = import('../lib/appManagers/appDialogsManager');
promise.finally(async() => {
//alert('pageIm!');
//AudioContext && global.navigator && global.navigator.mediaDevices && global.navigator.mediaDevices.getUserMedia && global.WebAssembly;
@ -27,7 +25,8 @@ let onFirstMount = () => { @@ -27,7 +25,8 @@ let onFirstMount = () => {
}
//(Array.from(document.getElementsByClassName('rp')) as HTMLElement[]).forEach(el => ripple(el));
const misc = await import("../components/misc");
Array.from(document.getElementsByClassName('btn-menu-toggle')).forEach((el) => {
(el as HTMLElement).addEventListener('click', (e) => {
//console.log('click pageIm');
@ -42,7 +41,7 @@ let onFirstMount = () => { @@ -42,7 +41,7 @@ let onFirstMount = () => {
el.classList.remove('menu-open');
openedMenu.classList.remove('active');
} else {
openBtnMenu(openedMenu);
misc.openBtnMenu(openedMenu);
}
});
});

8
src/pages/pagePassword.ts

@ -12,6 +12,8 @@ import { cancelEvent } from '../lib/utils'; @@ -12,6 +12,8 @@ import { cancelEvent } from '../lib/utils';
import { AccountPassword } from '../layer';
import mediaSizes from '../helpers/mediaSizes';
let passwordInput: HTMLInputElement;
let onFirstMount = (): Promise<any> => {
let needFrame = 0;
let animation: RLottiePlayer;
@ -19,7 +21,7 @@ let onFirstMount = (): Promise<any> => { @@ -19,7 +21,7 @@ let onFirstMount = (): Promise<any> => {
let passwordVisible = false;
const btnNext = page.pageEl.querySelector('button') as HTMLButtonElement;
const passwordInput = document.getElementById('password') as HTMLInputElement;
passwordInput = document.getElementById('password') as HTMLInputElement;
const passwordInputLabel = passwordInput.nextElementSibling as HTMLLabelElement;
const toggleVisible = page.pageEl.querySelector('.toggle-visible') as HTMLSpanElement;
@ -142,6 +144,8 @@ let onFirstMount = (): Promise<any> => { @@ -142,6 +144,8 @@ let onFirstMount = (): Promise<any> => {
]);
};
const page = new Page('page-password', true, onFirstMount);
const page = new Page('page-password', true, onFirstMount, null, () => {
passwordInput.focus();
});
export default page;

19
src/pages/pageSignIn.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { putPreloader, formatPhoneNumber } from "../components/misc";
import Scrollable from '../components/scrollable_new';
import Scrollable from '../components/scrollable';
import {RichTextProcessor} from '../lib/richtextprocessor';
import Config from '../lib/config';
@ -268,11 +268,24 @@ let onFirstMount = () => { @@ -268,11 +268,24 @@ let onFirstMount = () => {
});
let tryAgain = () => {
apiManager.invokeApi('help.getNearestDc').then((nearestDcResult: any) => {
apiManager.invokeApi('help.getNearestDc').then((nearestDcResult) => {
const dcs = [1, 2, 3, 4, 5];
//dcs.splice(nearestDcResult.this_dc - 1, 1);
let promise: Promise<any>;
if(nearestDcResult.nearest_dc != nearestDcResult.this_dc) {
//MTProto.apiManager.baseDcID = nearestDcResult.nearest_dc;
apiManager.getNetworker(nearestDcResult.nearest_dc);
promise = apiManager.getNetworker(nearestDcResult.nearest_dc).then(() => {
});
}
(promise || Promise.resolve()).then(() => {
dcs.forEach(dcID => {
apiManager.getNetworker(dcID, {fileDownload: true});
});
});
return nearestDcResult;
}).then((nearestDcResult: any) => {

9
src/pages/pagesManager.ts

@ -6,13 +6,18 @@ import { MOUNT_CLASS_TO } from "../lib/mtproto/mtproto_config"; @@ -6,13 +6,18 @@ import { MOUNT_CLASS_TO } from "../lib/mtproto/mtproto_config";
class PagesManager {
private pageID = -1;
private page: Page;
private selectTab: ReturnType<typeof horizontalMenu>;
public pagesDiv: HTMLDivElement;
constructor() {
this.pagesDiv = document.getElementById('auth-pages') as HTMLDivElement;
this.selectTab = horizontalMenu(null, this.pagesDiv.firstElementChild as HTMLDivElement, null, null);
this.selectTab = horizontalMenu(null, this.pagesDiv.firstElementChild as HTMLDivElement, null, () => {
if(this.page.onShown) {
this.page.onShown();
}
});
}
public setPage(page: Page) {
@ -35,6 +40,8 @@ class PagesManager { @@ -35,6 +40,8 @@ class PagesManager {
this.pageID = -1;
}
this.page = page;
}
}

16
src/types.d.ts vendored

@ -2,17 +2,17 @@ import type { ApiError } from "./lib/mtproto/apiManager"; @@ -2,17 +2,17 @@ import type { ApiError } from "./lib/mtproto/apiManager";
export type InvokeApiOptions = Partial<{
dcID: number,
timeout: number,
noErrorBox: boolean,
fileUpload: boolean,
ignoreErrors: boolean,
fileDownload: boolean,
createNetworker: boolean,
singleInRequest: boolean,
floodMaxTimeout: number,
noErrorBox: true,
fileUpload: true,
ignoreErrors: true,
fileDownload: true,
createNetworker: true,
singleInRequest: true,
startMaxLength: number,
afterMessageID: string,
resultType: boolean,
resultType: string,
waitTime: number,
stopTime: number,

7
webpack.common.js

@ -10,13 +10,16 @@ const allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31. @@ -10,13 +10,16 @@ const allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.
const devMode = process.env.NODE_ENV !== 'production';
const useLocal = false;
console.log('DEVMODE:', devMode);
if(devMode) {
console.log('DEVMODE IS ON!');
}
const opts = {
MTPROTO_WORKER: true,
MTPROTO_HTTP: false,
DEBUG: devMode,
version: 3,
"ifdef-verbose": true, // add this for verbose output
"ifdef-verbose": devMode, // add this for verbose output
"ifdef-triple-slash": true // add this to use double slash comment instead of default triple slash
};

Loading…
Cancel
Save