diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..869a58c5 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,333 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import App from './config/app'; +import findUpClassName from './helpers/dom/findUpClassName'; +import fixSafariStickyInput from './helpers/dom/fixSafariStickyInput'; +import './materialize.scss'; +import './scss/style.scss'; +import './scss/tgico.scss'; +/* import { computeCheck } from './lib/crypto/srp'; +import { salt1, salt2, g, p, srp_id, secure_random, srp_B, password } from './mock/srp'; */ + +//console.log('pineapples are in my head'); + +/* console.time('get storage1'); +import * as a from './lib/config'; +import * as b from './lib/mtproto/mtproto_config'; +import * as c from './helpers/userAgent'; +import * as d from './lib/mtproto/mtprotoworker'; +import * as e from './lib/polyfill'; +import * as f from './lib/storage'; +a && b && c && d && e && f; +console.timeEnd('get storage1'); */ + +/* Promise.all([ + import('./components/pageIm'), + import('./components/pageSignIn'), + import('./components/misc'), + import('./lib/storage') +]).then(imports => { + let [pageIm, pageSignIn, misc, AppStorage] = imports; */ + + document.addEventListener('DOMContentLoaded', async() => { + //let socket = new Socket(2); + + if(!Element.prototype.toggleAttribute) { + Element.prototype.toggleAttribute = function(name, force) { + if(force !== void 0) force = !!force; + + if(this.hasAttribute(name)) { + if(force) return true; + + this.removeAttribute(name); + return false; + } + if(force === false) return false; + + this.setAttribute(name, ""); + return true; + }; + } + + // We listen to the resize event (https://css-tricks.com/the-trick-to-viewport-units-on-mobile/) + // @ts-ignore + const w = window.visualViewport || window; // * handle iOS keyboard + let setViewportVH = false; + const setVH = () => { + // @ts-ignore + const vh = (setViewportVH && !rootScope.default.overlayIsActive ? w.height || w.innerHeight : window.innerHeight) * 0.01; + //const vh = document.documentElement.scrollHeight * 0.01; + document.documentElement.style.setProperty('--vh', `${vh}px`); + + //console.log('setVH', vh, setViewportVH ? w : window); + + /* if(setViewportVH && userAgent.isSafari && touchSupport.isTouchSupported && document.activeElement && (document.activeElement as HTMLElement).blur) { + const rect = document.activeElement.getBoundingClientRect(); + if(rect.top < 0 || rect.bottom >= (w as any).height) { + fastSmoothScroll(findUpClassName(document.activeElement, 'scrollable-y') || window as any, document.activeElement as HTMLElement, 'center', 4, undefined, FocusDirection.Static); + } + } */ + }; + + window.addEventListener('resize', setVH); + setVH(); + + // * hook worker constructor to set search parameters (test, debug, etc) + const workerHandler = { + construct(target: any, args: any) { + //console.log(target, args); + const url = args[0] + location.search; + + return new target(url); + } + }; + + const workerProxy = new Proxy(Worker, workerHandler); + Worker = workerProxy; + + const [_, touchSupport, userAgent, rootScope, appStateManager, I18n] = await Promise.all([ + import('./lib/polyfill'), + import('./helpers/touchSupport'), + import('./helpers/userAgent'), + import('./lib/rootScope'), + import('./lib/appManagers/appStateManager'), + import('./lib/langPack'), + ]) + //console.timeEnd('get storage'); + + //console.log(new Uint8Array([255, 200, 145]).hex); + + const toggleResizeMode = () => { + setViewportVH = tabId === 1 && userAgent.isSafari && touchSupport.isTouchSupported && !rootScope.default.overlayIsActive; + setVH(); + + if(w !== window) { + if(setViewportVH) { + window.removeEventListener('resize', setVH); + w.addEventListener('resize', setVH); + } else { + w.removeEventListener('resize', setVH); + window.addEventListener('resize', setVH); + } + } + }; + + let tabId: number; + rootScope.default.on('im_tab_change', (id) => { + const wasTabId = tabId !== undefined; + tabId = id; + + if(wasTabId || tabId === 1) { + toggleResizeMode(); + } + }); + + rootScope.default.on('overlay_toggle', () => { + toggleResizeMode(); + }); + + if(userAgent.isApple) { + if(userAgent.isSafari) { + document.documentElement.classList.add('is-safari'); + + if(touchSupport.isTouchSupported) { + let key: 'clientY' | 'pageY' = 'clientY'; + let startY = 0; + const o = {capture: true, passive: false}; + const onTouchMove = (e: TouchEvent) => { + const touch = e.touches[0]; + + //console.log('touchmove y', touch[key], startY); + + const scrollable = findUpClassName(touch.target, 'scrollable-y'); + if(scrollable) { + const y = touch[key]; + const scrolled = startY - y; + + /* if(y < startY) { + startY = y; + } */ + + const scrollTop = scrollable.scrollTop; + const scrollHeight = scrollable.scrollHeight; + const clientHeight = scrollable.clientHeight; + const nextScrollTop = scrollTop ? Math.round(scrollTop + scrollable.clientHeight + scrolled) : scrollTop + scrolled; + //const needCancel = scrollHeight !== clientHeight ? (scrollTop && diff <= 1) || (scrollTop - diff) < 0 : true; + const needCancel = scrollHeight === clientHeight || nextScrollTop >= scrollHeight || nextScrollTop <= 0; + if(needCancel) { + e.preventDefault(); + } + + //console.log('touchmove with scrollable', scrollTop, startY, scrolled, nextScrollTop, needCancel, e.cancelable); + } else { + e.preventDefault(); + + //console.log('touchmove no scrollable', e, touch); + } + + //if(e.target === document.documentElement || e.target === document.body) e.preventDefault(); + }; + + document.addEventListener('focusin', (e) => { + if(!setViewportVH) return; + //console.log('focusin'); + + fixSafariStickyInput(e.target as HTMLElement); + + document.addEventListener('touchmove', onTouchMove, o); + document.addEventListener('touchstart', (e) => { + if(e.touches.length > 1) return; + const touchStart = e.touches[0]; + + startY = touchStart[key]; + }); + }); + + document.addEventListener('focusout', () => { + document.removeEventListener('touchmove', onTouchMove, o); + }); + + document.addEventListener('visibilitychange', () => { + if(!setViewportVH) return; + //console.log('window visibilitychange'); + if(document.activeElement && (document.activeElement as HTMLElement).blur) { + fixSafariStickyInput(document.activeElement as HTMLElement); + } + + /* blurActiveElement(); + window.scrollTo(0, 0); + setVH(); */ + }); + } + } + + document.documentElement.classList.add('is-mac', 'emoji-supported'); + + if(userAgent.isAppleMobile) { + document.documentElement.classList.add('is-ios'); + } + } else if(userAgent.isAndroid) { + document.documentElement.classList.add('is-android'); + } + + if(!touchSupport.isTouchSupported) { + document.documentElement.classList.add('no-touch'); + } else { + document.documentElement.classList.add('is-touch'); + /* document.addEventListener('touchmove', (event: any) => { + event = event.originalEvent || event; + if(event.scale && event.scale !== 1) { + event.preventDefault(); + } + }, {capture: true, passive: false}); */ + } + + /* if(config.isServiceWorkerSupported) { + await navigator.serviceWorker.ready; + navigator.serviceWorker.controller ? true : await new Promise((resolve, reject) => { + navigator.serviceWorker.addEventListener('controllerchange', resolve); + }); + } */ + + //console.time('get storage'); + + const perf = performance.now(); + + //import('./vendor/dateFormat'); + + const langPromise = I18n.default.getCacheLangPack(); + + const [state, langPack] = await Promise.all([ + appStateManager.default.getState(), + langPromise + ]); + //I18n.getCacheLangPack(); + //console.log('got auth:', auth); + //console.timeEnd('get storage'); + + if(langPack.appVersion !== App.langPackVersion) { + I18n.default.getLangPack(langPack.lang_code); + } + + console.log('got state, time:', performance.now() - perf); + + const authState = state.authState; + if(authState._ !== 'authStateSignedIn'/* || 1 === 1 */) { + console.log('Will mount auth page:', authState._, Date.now() / 1000); + + //langPromise.then(async() => { + switch(authState._) { + case 'authStateSignIn': + (await import('./pages/pageSignIn')).default.mount(); + break; + case 'authStateAuthCode': + (await import('./pages/pageAuthCode')).default.mount(authState.sentCode); + break; + case 'authStatePassword': + (await import('./pages/pagePassword')).default.mount(); + break; + case 'authStateSignUp': + (await import('./pages/pageSignUp')).default.mount(authState.authCode); + break; + } + //}); + + /* computeCheck(password, { + current_algo: { + salt1, + salt2, + p, + g + }, + srp_id, + srp_B, + secure_random, + }).then(res => { + console.log(res); + }); */ + + /* setTimeout(async() => { + (await import('./pages/pageAuthCode')).default.mount({ + "_": "auth.sentCode", + "pFlags": {}, + "flags": 6, + "type": { + "_": "auth.sentCodeTypeSms", + "length": 5 + }, + "phone_code_hash": "", + "next_type": { + "_": "auth.codeTypeCall" + }, + "timeout": 120, + "phone_number": "" + }); + }, 500); */ + /* setTimeout(async() => { + (await import('./pages/pageSignQR')).default.mount(); + }, 500); */ + /* setTimeout(async() => { + (await import('./pages/pagePassword')).default.mount(); + }, 500); */ + /* setTimeout(async() => { + (await import('./pages/pageSignUp')).default.mount({ + "phone_code_hash": "", + "phone_number": "" + }); + }, 500); */ + } else { + console.log('Will mount IM page:', Date.now() / 1000); + (await import('./pages/pageIm')).default.mount(); + //getNearestDc(); + } + + const ripple = (await import('./components/ripple')).ripple; + (Array.from(document.getElementsByClassName('rp')) as HTMLElement[]).forEach(el => ripple(el)); + }); +//}); + + diff --git a/src/lib/appManagers/appProfileManager.ts b/src/lib/appManagers/appProfileManager.ts new file mode 100644 index 00000000..b95e9694 --- /dev/null +++ b/src/lib/appManagers/appProfileManager.ts @@ -0,0 +1,604 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + * + * Originally from: + * https://github.com/zhukov/webogram + * Copyright (C) 2014 Igor Zhukov + * https://github.com/zhukov/webogram/blob/master/LICENSE + */ + +import { MOUNT_CLASS_TO } from "../../config/debug"; +import { tsNow } from "../../helpers/date"; +import renderImageFromUrl from "../../helpers/dom/renderImageFromUrl"; +import { ChannelParticipantsFilter, ChannelsChannelParticipants, ChatFull, ChatParticipants, ChatPhoto, ExportedChatInvite, InputChannel, InputFile, InputFileLocation, PhotoSize, UserFull, UserProfilePhoto } from "../../layer"; +//import apiManager from '../mtproto/apiManager'; +import apiManager from '../mtproto/mtprotoworker'; +import { RichTextProcessor } from "../richtextprocessor"; +import rootScope from "../rootScope"; +import apiUpdatesManager from "./apiUpdatesManager"; +import appChatsManager from "./appChatsManager"; +import appDownloadManager from "./appDownloadManager"; +import appNotificationsManager from "./appNotificationsManager"; +import appPeersManager from "./appPeersManager"; +import appPhotosManager, { MyPhoto } from "./appPhotosManager"; +import appUsersManager, { User } from "./appUsersManager"; + +type PeerPhotoSize = 'photo_small' | 'photo_big'; + +export class AppProfileManager { + private botInfos: any = {}; + private usersFull: {[id: string]: UserFull.userFull} = {}; + public chatsFull: {[id: string]: ChatFull} = {}; + private fullPromises: {[peerId: string]: Promise} = {}; + + private savedAvatarURLs: { + [peerId: number]: { + [size in PeerPhotoSize]?: string | Promise + } + } = {}; + + constructor() { + rootScope.on('apiUpdate', (update) => { + switch(update._) { + case 'updateChatParticipants': { + const participants = update.participants; + if(participants._ === 'chatParticipants') { + const chatId = participants.chat_id; + const chatFull = this.chatsFull[chatId] as ChatFull.chatFull; + if(chatFull !== undefined) { + chatFull.participants = participants; + rootScope.broadcast('chat_full_update', chatId); + } + } + + break; + } + + case 'updateChatParticipantAdd': { + const chatFull = this.chatsFull[update.chat_id] as ChatFull.chatFull; + if(chatFull !== undefined) { + const _participants = chatFull.participants as ChatParticipants.chatParticipants; + const participants = _participants.participants || []; + for(let i = 0, length = participants.length; i < length; i++) { + if(participants[i].user_id === update.user_id) { + return; + } + } + + participants.push({ + _: 'chatParticipant', + user_id: update.user_id, + inviter_id: update.inviter_id, + date: tsNow(true) + }); + + _participants.version = update.version; + rootScope.broadcast('chat_full_update', update.chat_id); + } + + break; + } + + case 'updateChatParticipantDelete': { + const chatFull = this.chatsFull[update.chat_id] as ChatFull.chatFull; + if(chatFull !== undefined) { + const _participants = chatFull.participants as ChatParticipants.chatParticipants; + const participants = _participants.participants || []; + for(let i = 0, length = participants.length; i < length; i++) { + if(participants[i].user_id === update.user_id) { + participants.splice(i, 1); + _participants.version = update.version; + rootScope.broadcast('chat_full_update', update.chat_id); + return; + } + } + } + + break; + } + } + }); + + rootScope.on('chat_update', (chatId) => { + const fullChat = this.chatsFull[chatId]; + const chat = appChatsManager.getChat(chatId); + if(!chat.photo || !fullChat) { + return; + } + const emptyPhoto = chat.photo._ === 'chatPhotoEmpty'; + //////console.log('chat_update:', fullChat); + if(fullChat.chat_photo && emptyPhoto !== (fullChat.chat_photo._ === 'photoEmpty')) { + delete this.chatsFull[chatId]; + rootScope.broadcast('chat_full_update', chatId); + return; + } + if(emptyPhoto) { + return; + } + + const smallUserpic = chat.photo.photo_small; + const smallPhotoSize = fullChat.chat_photo ? appPhotosManager.choosePhotoSize(fullChat.chat_photo as MyPhoto, 0, 0) : undefined; + if(!smallPhotoSize || JSON.stringify(smallUserpic) !== JSON.stringify((smallPhotoSize as PhotoSize.photoSize).location)) { + delete this.chatsFull[chatId]; + rootScope.broadcast('chat_full_update', chatId); + } + }); + } + + public saveBotInfo(botInfo: any) { + const botId = botInfo && botInfo.user_id; + if(!botId) { + return null; + } + + const commands: any = {}; + botInfo.commands.forEach((botCommand: any) => { + commands[botCommand.command] = botCommand.description; + }); + + return this.botInfos[botId] = { + id: botId, + version: botInfo.version, + shareText: botInfo.share_text, + description: botInfo.description, + commands: commands + }; + } + + public getProfile(id: number, override?: true): Promise { + if(this.usersFull[id] && !override) { + return Promise.resolve(this.usersFull[id]); + } + + if(this.fullPromises[id]) { + return this.fullPromises[id] as any; + } + + return this.fullPromises[id] = apiManager.invokeApi('users.getFullUser', { + id: appUsersManager.getUserInput(id) + }).then((userFull) => { + const user = userFull.user as User; + /* if(override && isObject(override) && override.phone_number) { + user.phone = override.phone_number; + if(override.first_name || override.last_name) { + user.first_name = override.first_name; + user.last_name = override.last_name; + } + + appUsersManager.saveApiUser(user); + } else { */ + appUsersManager.saveApiUser(user, true); + //} + + if(userFull.profile_photo) { + userFull.profile_photo = appPhotosManager.savePhoto(userFull.profile_photo, {type: 'profilePhoto', peerId: id}); + /* appPhotosManager.savePhoto(userFull.profile_photo, {user_id: id}); */ + } + + if(userFull.about !== undefined) { + userFull.rAbout = RichTextProcessor.wrapRichText(userFull.about, {noLinebreaks: true}); + } + + appNotificationsManager.savePeerSettings(id, userFull.notify_settings); + + if(userFull.bot_info) { + userFull.bot_info = this.saveBotInfo(userFull.bot_info) as any; + } + + //appMessagesManager.savePinnedMessage(id, userFull.pinned_msg_id); + + delete this.fullPromises[id]; + + return this.usersFull[id] = userFull; + }) as any; + } + + public getProfileByPeerId(peerId: number, override?: true): Promise { + if(peerId < 0) return this.getChatFull(-peerId, override); + else return this.getProfile(peerId, override); + } + + public getFullPhoto(peerId: number) { + return this.getProfileByPeerId(peerId).then(profile => { + switch(profile._) { + case 'userFull': + return profile.profile_photo; + case 'channelFull': + case 'chatFull': + return profile.chat_photo; + } + }); + } + + /* public getPeerBots(peerId: number) { + var peerBots: any[] = []; + if(peerId >= 0 && !appUsersManager.isBot(peerId) || + (appPeersManager.isChannel(peerId) && !appPeersManager.isMegagroup(peerId))) { + return Promise.resolve(peerBots); + } + if(peerId >= 0) { + return this.getProfile(peerId).then((userFull: any) => { + var botInfo = userFull.bot_info; + if(botInfo && botInfo._ !== 'botInfoEmpty') { + peerBots.push(botInfo); + } + return peerBots; + }); + } + + return this.getChatFull(-peerId).then((chatFull: any) => { + chatFull.bot_info.forEach((botInfo: any) => { + peerBots.push(this.saveBotInfo(botInfo)) + }); + return peerBots; + }); + } */ + + public getChatFull(id: number, override?: true): Promise { + if(appChatsManager.isChannel(id)) { + return this.getChannelFull(id, override); + } + + const fullChat = this.chatsFull[id] as ChatFull.chatFull; + if(fullChat && !override) { + const chat = appChatsManager.getChat(id); + if(chat.version === (fullChat.participants as ChatParticipants.chatParticipants).version || + chat.pFlags.left) { + return Promise.resolve(fullChat); + } + } + + const peerId = -id; + if(this.fullPromises[peerId] !== undefined) { + return this.fullPromises[peerId] as any; + } + + // console.trace(dT(), 'Get chat full', id, appChatsManager.getChat(id)) + return this.fullPromises[peerId] = apiManager.invokeApi('messages.getFullChat', { + chat_id: id + }).then((result) => { + appChatsManager.saveApiChats(result.chats); + appUsersManager.saveApiUsers(result.users); + const fullChat = result.full_chat as ChatFull.chatFull; + if(fullChat && fullChat.chat_photo && fullChat.chat_photo.id) { + fullChat.chat_photo = appPhotosManager.savePhoto(fullChat.chat_photo, {type: 'profilePhoto', peerId: peerId}); + } + + //appMessagesManager.savePinnedMessage(peerId, fullChat.pinned_msg_id); + appNotificationsManager.savePeerSettings(peerId, fullChat.notify_settings); + delete this.fullPromises[peerId]; + this.chatsFull[id] = fullChat; + rootScope.broadcast('chat_full_update', id); + + return fullChat; + }) as any; + } + + public getChatInviteLink(id: number, force?: boolean) { + return this.getChatFull(id).then((chatFull) => { + if(!force && + chatFull.exported_invite && + chatFull.exported_invite._ == 'chatInviteExported') { + return chatFull.exported_invite.link; + } + + return apiManager.invokeApi('messages.exportChatInvite', { + peer: appPeersManager.getInputPeerById(-id) + }).then((exportedInvite) => { + if(this.chatsFull[id] !== undefined) { + this.chatsFull[id].exported_invite = exportedInvite; + } + + return (exportedInvite as ExportedChatInvite.chatInviteExported).link; + }); + }); + } + + public getChannelParticipants(id: number, filter: ChannelParticipantsFilter = {_: 'channelParticipantsRecent'}, limit = 200, offset = 0) { + if(filter._ === 'channelParticipantsRecent') { + const chat = appChatsManager.getChat(id); + if(chat && + chat.pFlags && ( + chat.pFlags.kicked || + chat.pFlags.broadcast && !chat.pFlags.creator && !chat.admin_rights + )) { + return Promise.reject(); + } + } + + return apiManager.invokeApiCacheable('channels.getParticipants', { + channel: appChatsManager.getChannelInput(id), + filter, + offset, + limit, + hash: 0 + }, {cacheSeconds: 60}).then(result => { + appUsersManager.saveApiUsers((result as ChannelsChannelParticipants.channelsChannelParticipants).users); + return result as ChannelsChannelParticipants.channelsChannelParticipants; + }); + /* let maybeAddSelf = (participants: any[]) => { + let chat = appChatsManager.getChat(id); + let selfMustBeFirst = filter._ === 'channelParticipantsRecent' && + !offset && + !chat.pFlags.kicked && + !chat.pFlags.left; + + if(selfMustBeFirst) { + participants = copy(participants); + let myID = appUsersManager.getSelf().id; + let myIndex = participants.findIndex(p => p.user_id === myID); + let myParticipant; + + if(myIndex !== -1) { + myParticipant = participants[myIndex]; + participants.splice(myIndex, 1); + } else { + myParticipant = {_: 'channelParticipantSelf', user_id: myID}; + } + + participants.unshift(myParticipant); + } + + return participants; + } */ + } + + public getChannelParticipant(id: number, userId: number) { + return apiManager.invokeApiSingle('channels.getParticipant', { + channel: appChatsManager.getChannelInput(id), + user_id: appUsersManager.getUserInput(userId) + }).then(channelParticipant => { + appUsersManager.saveApiUsers(channelParticipant.users); + return channelParticipant.participant; + }); + } + + public getChannelFull(id: number, override?: true): Promise { + if(this.chatsFull[id] !== undefined && !override) { + return Promise.resolve(this.chatsFull[id] as ChatFull.channelFull); + } + + const peerId = -id; + if(this.fullPromises[peerId] !== undefined) { + return this.fullPromises[peerId] as any; + } + + return this.fullPromises[peerId] = apiManager.invokeApi('channels.getFullChannel', { + channel: appChatsManager.getChannelInput(id) + }).then((result) => { + appChatsManager.saveApiChats(result.chats); + appUsersManager.saveApiUsers(result.users); + const fullChannel = result.full_chat as ChatFull.channelFull; + if(fullChannel && fullChannel.chat_photo.id) { + fullChannel.chat_photo = appPhotosManager.savePhoto(fullChannel.chat_photo, {type: 'profilePhoto', peerId}); + //appPhotosManager.savePhoto(fullChannel.chat_photo); + } + appNotificationsManager.savePeerSettings(peerId, fullChannel.notify_settings); + + delete this.fullPromises[peerId]; + this.chatsFull[id] = fullChannel; + rootScope.broadcast('chat_full_update', id); + + return fullChannel; + }, (error) => { + switch (error.type) { + case 'CHANNEL_PRIVATE': + let channel = appChatsManager.getChat(id); + channel = {_: 'channelForbidden', access_hash: channel.access_hash, title: channel.title}; + apiUpdatesManager.processUpdateMessage({ + _: 'updates', + updates: [{ + _: 'updateChannel', + channel_id: id + }], + chats: [channel], + users: [] + }); + break; + } + + return Promise.reject(error); + }) as any; + } + + public invalidateChannelParticipants(id: number) { + delete this.chatsFull[id]; + delete this.fullPromises[-id]; + apiManager.clearCache('channels.getParticipants', (params) => (params.channel as InputChannel.inputChannel).channel_id === id); + rootScope.broadcast('chat_full_update', id); + } + + public updateProfile(first_name: string, last_name: string, about: string) { + return apiManager.invokeApi('account.updateProfile', { + first_name, + last_name, + about + }).then(user => { + appUsersManager.saveApiUser(user); + + return this.getProfile(rootScope.myId, true); + }); + } + + public uploadProfilePhoto(inputFile: InputFile) { + return apiManager.invokeApi('photos.uploadProfilePhoto', { + file: inputFile + }).then((updateResult) => { + appUsersManager.saveApiUsers(updateResult.users); + + const myId = rootScope.myId; + appPhotosManager.savePhoto(updateResult.photo, { + type: 'profilePhoto', + peerId: myId + }); + + apiUpdatesManager.processUpdateMessage({ + _: 'updateShort', + update: { + _: 'updateUserPhoto', + user_id: myId, + date: tsNow(true), + photo: appUsersManager.getUser(myId).photo, + previous: true + } + }); + }); + } + + public removeFromAvatarsCache(peerId: number) { + if(this.savedAvatarURLs[peerId]) { + delete this.savedAvatarURLs[peerId]; + } + } + + public putAvatar(div: HTMLElement, peerId: number, photo: UserProfilePhoto.userProfilePhoto | ChatPhoto.chatPhoto, size: PeerPhotoSize, img = new Image()) { + const inputPeer = appPeersManager.getInputPeerById(peerId); + + let needFadeIn = true; + let getAvatarPromise: Promise; + let saved = this.savedAvatarURLs[peerId]; + if(!saved || !saved[size]) { + if(!saved) { + saved = this.savedAvatarURLs[peerId] = {}; + } + + //console.warn('will invoke downloadSmallFile:', peerId); + const peerPhotoFileLocation: InputFileLocation.inputPeerPhotoFileLocation = { + _: 'inputPeerPhotoFileLocation', + pFlags: {}, + peer: inputPeer, + volume_id: photo[size].volume_id, + local_id: photo[size].local_id + }; + + if(size === 'photo_big') { + peerPhotoFileLocation.pFlags.big = true; + } + + const downloadOptions = {dcId: photo.dc_id, location: peerPhotoFileLocation}; + + /* let str: string; + const time = Date.now(); + if(peerId === 0) { + str = `download avatar ${peerId}`; + } */ + + const promise = appDownloadManager.download(downloadOptions); + getAvatarPromise = saved[size] = promise.then(blob => { + saved[size] = URL.createObjectURL(blob); + + /* if(str) { + console.log(str, Date.now() / 1000, Date.now() - time); + } */ + }); + } else if(typeof(saved[size]) !== 'string') { + getAvatarPromise = saved[size] as Promise; + } else { + getAvatarPromise = Promise.resolve(); + needFadeIn = false; + } + + let callback: () => void; + if(!needFadeIn) { + // смотри в misc.ts: renderImageFromUrl + callback = () => { + div.innerHTML = ''; + div.append(img); + div.dataset.color = ''; + }; + } else { + const animate = rootScope.settings.animationsEnabled; + if(animate) { + img.classList.add('fade-in'); + } + + callback = () => { + div.innerHTML = ''; + div.append(img); + + setTimeout(() => { + if(div.childElementCount) { + div.dataset.color = ''; + + if(animate) { + img.classList.remove('fade-in'); + } + } + }, animate ? 200 : 0); + }; + } + + const loadPromise = getAvatarPromise.then(() => { + return new Promise((resolve) => { + renderImageFromUrl(img, saved[size] as string, () => { + callback(); + resolve(); + }, false); + }); + }); + + return {cached: !needFadeIn, loadPromise}; + } + + // peerId === peerId || title + public putPhoto(div: HTMLElement, peerId: number, isDialog = false, title = '') { + const photo = appPeersManager.getPeerPhoto(peerId); + + const size: PeerPhotoSize = 'photo_small'; + const avatarAvailable = photo && photo[size]; + const avatarRendered = !!div.firstElementChild; + + const myId = rootScope.myId; + + //console.log('loadDialogPhoto location:', location, inputPeer); + if(peerId === myId && isDialog) { + div.innerHTML = ''; + div.dataset.color = ''; + div.classList.add('tgico-savedmessages'); + div.classList.remove('tgico-avatar_deletedaccount'); + return; + } + + if(peerId > 0) { + const user = appUsersManager.getUser(peerId); + if(user && user.pFlags && user.pFlags.deleted) { + div.innerHTML = ''; + div.dataset.color = appPeersManager.getPeerColorById(peerId); + div.classList.add('tgico-avatar_deletedaccount'); + div.classList.remove('tgico-savedmessages'); + return; + } + } + + if(!avatarAvailable || !avatarRendered || !this.savedAvatarURLs[peerId]) { + let color = ''; + if(peerId && (peerId !== myId || !isDialog)) { + color = appPeersManager.getPeerColorById(peerId); + } + + div.innerHTML = ''; + div.classList.remove('tgico-savedmessages', 'tgico-avatar_deletedaccount'); + div.dataset.color = color; + + let abbr: string; + if(!title) { + abbr = appPeersManager.getPeer(peerId).initials ?? ''; + } else { + abbr = RichTextProcessor.getAbbreviation(title); + } + + div.innerHTML = abbr; + //return Promise.resolve(true); + } + + if(avatarAvailable/* && false */) { + return this.putAvatar(div, peerId, photo, size); + } + } +} + +const appProfileManager = new AppProfileManager(); +MOUNT_CLASS_TO.appProfileManager = appProfileManager; +export default appProfileManager; diff --git a/src/mock/srp.ts b/src/mock/srp.ts new file mode 100644 index 00000000..53747b7d --- /dev/null +++ b/src/mock/srp.ts @@ -0,0 +1,31 @@ +export const salt1 = new Uint8Array([114, 47, 217, 190, 196, 217, 91, 15, 205, 209, 189, 57, 98, 19, 110, 40, 47, 211, 245, 29, 58, 194, 205, 57, 205, 200, 225, 139, 244, 230, 206, 138, 1, 216, 18, 99, 130, 133, 226, 167]); + +export const salt2 = new Uint8Array([124, 249, 139, 209, 240, 124, 2, 97, 93, 249, 218, 19, 86, 31, 57, 215]); + +export const g = 3; + +export const p = new Uint8Array([ + 199, 28, 174, 185, 198, 177, 201, 4, 142, 108, 82, 47, 112, 241, 63, 115, 152, 13, 64, 35, 142, 62, 33, 193, 73, 52, 208, 55, 86, 61, 147, 15, 72, 25, 138, 10, 167, 193, 64, 88, 34, 148, 147, 210, 37, 48, 244, 219, 250, 51, 111, 110, 10, 201, 37, 19, 149, 67, 174, 212, 76, 206, 124, 55, 32, 253, 81, 246, 148, 88, 112, 90, 198, 140, 212, 254, 107, 107, 19, 171, 220, 151, 70, 81, 41, 105, 50, 132, 84, 241, 143, 175, 140, 89, 95, 100, 36, 119, 254, 150, 187, 42, 148, 29, 91, 205, 29, 74, 200, 204, 73, 136, 7, 8, 250, 155, 55, 142, 60, 79, 58, 144, 96, 190, 230, 124, 249, 164, 164, 166, 149, 129, 16, 81, 144, 126, 22, 39, 83, 181, 107, 15, 107, 65, 13, 186, 116, 216, 168, 75, 42, 20, 179, 20, 78, 14, 241, 40, 71, 84, 253, 23, 237, 149, 13, 89, 101, 180, 185, 221, 70, 88, 45, 177, 23, 141, 22, 156, 107, 196, 101, 176, 214, 255, 156, 163, 146, 143, 239, 91, 154, 228, 228, 24, 252, 21, 232, 62, 190, 160, 248, 127, 169, 255, 94, 237, 112, 5, 13, 237, 40, 73, 244, 123, 249, 89, 217, 86, 133, 12, 233, 41, 133, 31, 13, 129, 21, 246, 53, 177, 5, 238, 46, 78, 21, 208, 75, 36, 84, 191, 111, 79, 173, 240, 52, 177, 4, 3, 17, 156, 216, 227, 185, 47, 204, 91 +]); + +export const srp_id = "14665952836034598759"; + +export const srp_B = new Uint8Array([ + 36, 159, 225, 252, 202, 217, 105, 129, 128, 119, 70, 179, 96, 132, 49, 65, 196, 78, 171, 70, 83, 220, 92, 107, 121, 102, 245, 138, 85, 250, 184, 110, 175, 149, 165, 176, 128, 124, 14, 106, 253, 169, 235, 158, 234, 161, 245, 130, 11, 222, 7, 232, 201, 127, 69, 114, 135, 43, 245, 203, 106, 148, 195, 0, 29, 90, 59, 134, 216, 164, 155, 201, 92, 103, 211, 125, 242, 71, 143, 111, 9, 56, 16, 36, 104, 221, 145, 114, 164, 101, 253, 188, 203, 187, 162, 205, 200, 235, 83, 109, 52, 189, 78, 227, 132, 218, 57, 211, 196, 186, 156, 45, 126, 245, 211, 23, 4, 245, 231, 57, 2, 177, 227, 131, 180, 8, 48, 114, 127, 184, 133, 74, 118, 248, 0, 245, 224, 160, 109, 160, 254, 196, 108, 33, 74, 127, 26, 189, 74, 76, 63, 81, 131, 169, 139, 12, 134, 67, 129, 86, 10, 109, 90, 91, 161, 130, 56, 123, 173, 214, 236, 124, 16, 248, 232, 4, 196, 5, 191, 182, 81, 64, 165, 59, 87, 251, 76, 51, 96, 2, 34, 93, 47, 75, 179, 26, 217, 26, 152, 245, 100, 255, 91, 118, 44, 156, 90, 18, 232, 172, 101, 175, 8, 31, 215, 121, 74, 133, 243, 50, 19, 179, 239, 5, 174, 231, 146, 215, 56, 225, 154, 207, 7, 67, 85, 177, 72, 78, 184, 206, 249, 201, 226, 15, 190, 121, 91, 6, 85, 125, 217, 53, 31, 171, 4, 88 +]); + +export const secure_random = new Uint8Array([108, 48, 160, 103, 227, 55, 213, 54, 37, 175, 157, 206, 29, 139, 226, 92, 166, 135, 70, 221, 90, 88, 48, 239, 241, 242, 113, 108, 186, 90, 227, 66, 127, 15, 24, 53, 56, 173, 46, 100, 223, 108, 73, 64, 40, 4, 193, 112, 171, 183, 201, 232, 47, 75, 114, 73, 106, 214, 244, 216, 10, 188, 16, 54, 200, 220, 164, 106, 55, 165, 115, 165, 191, 218, 186, 171, 107, 75, 124, 168, 245, 100, 50, 60, 237, 90, 224, 9, 250, 135, 40, 111, 44, 180, 159, 13, 76, 15, 122, 118, 223, 162, 5, 164, 2, 225, 90, 215, 186, 180, 20, 218, 123, 249, 208, 186, 154, 149, 246, 44, 60, 240, 191, 111, 87, 62, 175, 2, 159, 234, 46, 216, 94, 160, 166, 60, 179, 139, 101, 16, 212, 112, 95, 198, 143, 111, 236, 203, 172, 99, 58, 162, 77, 52, 87, 64, 140, 74, 165, 107, 82, 191, 221, 64, 106, 204, 79, 87, 67, 160, 22, 133, 158, 152, 231, 72, 197, 127, 184, 161, 10, 196, 205, 95, 77, 116, 177, 66, 49, 8, 65, 11, 9, 168, 109, 224, 146, 24, 48, 55, 102, 150, 153, 122, 185, 12, 51, 137, 32, 8, 44, 162, 22, 129, 133, 132, 29, 72, 1, 11, 53, 21, 222, 179, 132, 126, 182, 206, 130, 185, 68, 71, 43, 217, 12, 12, 1, 193, 23, 49, 123, 82, 221, 197, 56, 48, 90, 30, 99, 64, 126, 117, 104, 211, 80, 242]); + +export const password = '101010'; + +export const A = new Uint8Array([ + 93, 125, 125, 50, 15, 192, 164, 213, 62, 136, 32, 204, 201, 201, 203, 128, 158, 120, 110, 253, 158, 88, 176, 118, 71, 167, 172, 52, 37, 35, 71, 39, 97, 142, 196, 71, 213, 37, 182, 156, 57, 137, 14, 85, 25, 35, 110, 220, 228, 88, 41, 44, 185, 183, 127, 153, 199, 103, 238, 82, 158, 188, 32, 72, 178, 171, 66, 29, 221, 60, 169, 172, 185, 58, 95, 7, 192, 230, 36, 8, 138, 225, 242, 136, 202, 130, 49, 100, 56, 72, 209, 71, 31, 166, 112, 232, 33, 148, 114, 165, 89, 246, 36, 158, 241, 77, 179, 187, 204, 19, 5, 206, 184, 47, 122, 234, 245, 72, 226, 191, 188, 225, 236, 100, 152, 227, 130, 190, 207, 150, 110, 159, 28, 100, 189, 189, 66, 196, 250, 161, 80, 214, 140, 122, 19, 71, 76, 73, 134, 227, 197, 73, 41, 35, 123, 215, 86, 162, 11, 116, 148, 157, 216, 6, 212, 93, 52, 200, 244, 67, 29, 153, 171, 147, 12, 7, 65, 158, 17, 36, 10, 36, 90, 198, 163, 252, 194, 253, 104, 41, 137, 121, 80, 43, 181, 229, 204, 184, 227, 2, 175, 201, 250, 213, 247, 117, 196, 60, 27, 238, 240, 45, 214, 147, 94, 108, 102, 183, 129, 28, 200, 47, 1, 68, 212, 247, 73, 194, 200, 188, 33, 230, 92, 123, 83, 36, 23, 144, 185, 5, 196, 205, 33, 45, 211, 128, 234, 137, 110, 221, 214, 47, 30, 175, 14, 132 +]); + +export const M1 = new Uint8Array([ + 72, 29, 26, 252, 69, 33, 152, 165, 104, 187, 154, 206, 10, 169, 23, 103, 35, 211, 240, 73, 60, 187, 50, 212, 42, 209, 241, 100, 91, 201, 77, 7 +]); + +export const passwordHashed = [ + 66, 92, 210, 197, 237, 255, 209, 109, 38, 17, 14, 200, 177, 152, 124, 167, 92, 166, 132, 205, 195, 184, 24, 240, 111, 118, 45, 43, 66, 66, 248, 49 +];