From 344b70dc3e146d83ffba688c2163b07bbe6b00bf Mon Sep 17 00:00:00 2001 From: morethanwords Date: Fri, 24 Sep 2021 19:33:33 +0400 Subject: [PATCH] Fix avatar duplicate Fix media viewer list again Supported media browser controls --- src/components/appMediaPlaybackController.ts | 311 ++++++++++++++---- src/components/appMediaViewer.ts | 254 ++++---------- src/components/audio.ts | 28 +- src/components/chat/bubbles.ts | 5 +- src/components/lazyLoadQueue.ts | 2 +- .../sidebarRight/tabs/groupPermissions.ts | 2 +- .../sidebarRight/tabs/sharedMedia.ts | 142 ++------ src/components/wrappers.ts | 9 +- src/helpers/listLoader.ts | 202 +++++++++--- src/helpers/scrollableLoader.ts | 53 +++ src/helpers/userAgent.ts | 2 +- src/lib/appManagers/appPhotosManager.ts | 10 +- src/lib/idb.ts | 2 +- src/lib/mtproto/apiManager.ts | 2 +- src/lib/mtproto/authorizer.ts | 2 +- src/lib/mtproto/networker.ts | 2 +- src/pages/pageSignQR.ts | 5 +- 17 files changed, 580 insertions(+), 453 deletions(-) create mode 100644 src/helpers/scrollableLoader.ts diff --git a/src/components/appMediaPlaybackController.ts b/src/components/appMediaPlaybackController.ts index 063f4f92..75a77d0f 100644 --- a/src/components/appMediaPlaybackController.ts +++ b/src/components/appMediaPlaybackController.ts @@ -8,12 +8,19 @@ import rootScope from "../lib/rootScope"; import appMessagesManager from "../lib/appManagers/appMessagesManager"; import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager"; import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise"; -import { isSafari } from "../helpers/userAgent"; +import { isApple, isSafari } from "../helpers/userAgent"; import { MOUNT_CLASS_TO } from "../config/debug"; import appDownloadManager from "../lib/appManagers/appDownloadManager"; import simulateEvent from "../helpers/dom/dispatchEvent"; import type { SearchSuperContext } from "./appSearchSuper."; import { copy, deepEqual } from "../helpers/object"; +import { DocumentAttribute, Message, MessageMedia, PhotoSize } from "../layer"; +import appPhotosManager from "../lib/appManagers/appPhotosManager"; +import { isTouchSupported } from "../helpers/touchSupport"; +import appAvatarsManager from "../lib/appManagers/appAvatarsManager"; +import appPeersManager from "../lib/appManagers/appPeersManager"; +import I18n from "../lib/langPack"; +import { SearchListLoader } from "./appMediaViewer"; // TODO: если удалить сообщение, и при этом аудио будет играть - оно не остановится, и можно будет по нему перейти вникуда @@ -21,7 +28,7 @@ import { copy, deepEqual } from "../helpers/object"; // TODO: Safari: попробовать замаскировать подгрузку последнего чанка // TODO: Safari: пофиксить момент, когда заканчивается песня и пытаешься включить её заново - прогресс сразу в конце -type MediaItem = {mid: number, peerId: number}; +export type MediaItem = {mid: number, peerId: number}; type HTMLMediaElement = HTMLAudioElement | HTMLVideoElement; @@ -33,6 +40,8 @@ const SHOULD_USE_SAFARI_FIX = (() => { } })(); +const SEEK_OFFSET = 10; + class AppMediaPlaybackController { private container: HTMLElement; private media: { @@ -40,7 +49,7 @@ class AppMediaPlaybackController { [mid: string]: HTMLMediaElement } } = {}; - public playingMedia: HTMLMediaElement; + private playingMedia: HTMLMediaElement; private waitingMediaForLoad: { [peerId: string]: { @@ -49,20 +58,41 @@ class AppMediaPlaybackController { } = {}; public willBePlayedMedia: HTMLMediaElement; - public searchContext: SearchSuperContext; - - private currentPeerId: number; - private prevMid: number; - private nextMid: number; + private searchContext: SearchSuperContext; - private prev: MediaItem[] = []; - private next: MediaItem[] = []; + private listLoader: SearchListLoader; constructor() { this.container = document.createElement('div'); //this.container.style.cssText = 'position: absolute; top: -10000px; left: -10000px;'; this.container.style.cssText = 'display: none;'; document.body.append(this.container); + + if(navigator.mediaSession) { + navigator.mediaSession.setActionHandler('play', this.play); + navigator.mediaSession.setActionHandler('pause', this.pause); + navigator.mediaSession.setActionHandler('stop', this.stop); + navigator.mediaSession.setActionHandler('seekbackward', (details) => { + const media = this.playingMedia + if(media) { + media.currentTime = Math.max(0, media.currentTime - (details.seekOffset || SEEK_OFFSET)); + } + }); + navigator.mediaSession.setActionHandler('seekforward', (details) => { + const media = this.playingMedia + if(media) { + media.currentTime = Math.min(media.duration, media.currentTime + (details.seekOffset || SEEK_OFFSET)); + } + }); + navigator.mediaSession.setActionHandler('seekto', (details) => { + const media = this.playingMedia + if(media) { + media.currentTime = details.seekTime; + } + }); + navigator.mediaSession.setActionHandler('previoustrack', this.previous); + navigator.mediaSession.setActionHandler('nexttrack', this.next); + } } public addMedia(peerId: number, doc: MyDocument, mid: number, autoload = true): HTMLMediaElement { @@ -187,33 +217,126 @@ class AppMediaPlaybackController { media.safariBuffering = value; } + private async setNewMediadata(message: Message.message) { + const playingMedia = this.playingMedia; + const doc = (message.media as MessageMedia.messageMediaDocument).document as MyDocument; + + const artwork: MediaImage[] = []; + + const isVoice = doc.type === 'voice' || doc.type === 'round'; + let title = '', artist = ''; + + if(doc.thumbs?.length) { + const size = doc.thumbs[doc.thumbs.length - 1]; + if(!(size as PhotoSize.photoStrippedSize).bytes) { + const cacheContext = appDownloadManager.getCacheContext(doc, size.type); + + if(cacheContext.url) { + artwork.push({ + src: cacheContext.url, + sizes: `${(size as PhotoSize.photoSize).w}x${(size as PhotoSize.photoSize).h}`, + type: 'image/jpeg' + }); + } else { + const download = appPhotosManager.preloadPhoto(doc, size); + download.then(() => { + if(this.playingMedia !== playingMedia || !cacheContext.url) { + return; + } + + this.setNewMediadata(message); + }); + } + } + } else if(isVoice) { + const peerId = message.fromId || message.peerId; + const peerPhoto = appPeersManager.getPeerPhoto(peerId); + const result = appAvatarsManager.loadAvatar(peerId, peerPhoto, 'photo_small'); + if(result.cached) { + const url = await result.loadPromise; + artwork.push({ + src: url, + sizes: '160x160', + type: 'image/jpeg' + }); + } else { + result.loadPromise.then((url) => { + if(this.playingMedia !== playingMedia || !url) { + return; + } + + this.setNewMediadata(message); + }); + } + + title = appPeersManager.getPeerTitle(peerId, true, false); + artist = I18n.format(doc.type === 'voice' ? 'AttachAudio' : 'AttachRound', true); + } + + if(!isVoice) { + const attribute = doc.attributes.find(attribute => attribute._ === 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio; + title = attribute && attribute.title || doc.file_name; + artist = attribute && attribute.performer; + } + + if(!artwork.length) { + if(isApple) { + if(isTouchSupported) { + artwork.push({ + src: `assets/img/apple-touch-icon-precomposed.png`, + sizes: '180x180', + type: 'image/png' + }); + } else { + artwork.push({ + src: `assets/img/apple-touch-icon.png`, + sizes: '180x180', + type: 'image/png' + }); + } + } else { + [72, 96, 144, 192, 256, 384, 512].forEach(size => { + const sizes = `${size}x${size}`; + artwork.push({ + src: `assets/img/android-chrome-${sizes}.png`, + sizes, + type: 'image/png' + }); + }); + } + } + + const metadata = new MediaMetadata({ + title, + artist, + artwork + }); + + navigator.mediaSession.metadata = metadata; + } + onPlay = (e?: Event) => { const media = e.target as HTMLMediaElement; const peerId = +media.dataset.peerId; const mid = +media.dataset.mid; - this.currentPeerId = peerId; //console.log('appMediaPlaybackController: video playing', this.currentPeerId, this.playingMedia, media); + const message = appMessagesManager.getMessageByPeer(peerId, mid); + const previousMedia = this.playingMedia; if(previousMedia !== media) { - if(previousMedia) { - if(!previousMedia.paused) { - previousMedia.pause(); - } - - // reset media - previousMedia.currentTime = 0; - simulateEvent(previousMedia, 'ended'); - } + this.stop(); this.playingMedia = media; - this.loadSiblingsMedia(peerId, mid); + + if('mediaSession' in navigator) { + this.setNewMediadata(message); + } } // audio_pause не успеет сработать без таймаута setTimeout(() => { - const message = appMessagesManager.getMessageByPeer(peerId, mid); rootScope.dispatchEvent('audio_play', {peerId, doc: message.media.document, mid}); }, 0); }; @@ -238,65 +361,80 @@ class AppMediaPlaybackController { //console.log('on media end'); - if(this.nextMid) { - const media = this.media[this.currentPeerId][this.nextMid]; - - /* if(isSafari) { - media.autoplay = true; - } */ + this.next(); + }; - this.resolveWaitingForLoadMedia(this.currentPeerId, this.nextMid); + public toggle(play?: boolean) { + if(!this.playingMedia) { + return; + } - setTimeout(() => { - media.play()//.catch(() => {}); - }, 0); + if(play === undefined) { + play = this.playingMedia.paused; } - }; - private loadSiblingsMedia(offsetPeerId: number, offsetMid: number) { - const {playingMedia, searchContext} = this; - if(!searchContext) { + if(this.playingMedia.paused !== play) { return; } - return appMessagesManager.getSearch({ - ...searchContext, - maxId: offsetMid, - limit: 3, - backLimit: 2, - }).then(value => { - if(this.playingMedia !== playingMedia || this.searchContext !== searchContext) { - return; - } - - const idx = Math.max(0, value.history.findIndex(message => message.peerId === offsetPeerId && message.mid === offsetMid)); - const prev = value.history.slice(Math.max(0, idx)); - const next = value.history.slice(0, idx); - - [this.prevMid, this.nextMid].filter(Boolean).forEach(mid => { - const peerId = searchContext.peerId; - const message = appMessagesManager.getMessageByPeer(peerId, mid); - this.addMedia(peerId, message.media.document, mid, false); - }); - - //console.log('loadSiblingsAudio', audio, type, mid, value, this.prevMid, this.nextMid); - }); - } - - public toggle() { - if(!this.playingMedia) return; - - if(this.playingMedia.paused) { + if(play) { this.playingMedia.play(); } else { this.playingMedia.pause(); } } - public pause() { - if(!this.playingMedia || this.playingMedia.paused) return; - this.playingMedia.pause(); - } + public play = () => { + return this.toggle(true); + }; + + public pause = () => { + return this.toggle(false); + }; + + public stop = () => { + const media = this.playingMedia; + if(media) { + if(!media.paused) { + media.pause(); + } + + media.currentTime = 0; + simulateEvent(media, 'ended'); + + // this.playingMedia = undefined; + } + }; + + public playItem = (item: MediaItem) => { + const {peerId, mid} = item; + const media = this.media[peerId][mid]; + + /* if(isSafari) { + media.autoplay = true; + } */ + + this.resolveWaitingForLoadMedia(peerId, mid); + + setTimeout(() => { + media.play()//.catch(() => {}); + }, 0); + }; + + public next = () => { + this.listLoader.go(1); + }; + + public previous = () => { + const media = this.playingMedia; + if(media && media.currentTime > 5) { + media.currentTime = 0; + this.toggle(true); + return; + } + + this.listLoader.go(-1); + }; public willBePlayed(media: HTMLMediaElement) { this.willBePlayedMedia = media; @@ -304,12 +442,43 @@ class AppMediaPlaybackController { public setSearchContext(context: SearchSuperContext) { if(deepEqual(this.searchContext, context)) { - return; + return false; } this.searchContext = copy(context); // {_: type === 'audio' ? 'inputMessagesFilterMusic' : 'inputMessagesFilterRoundVoice'} - this.prev.length = 0; - this.next.length = 0; + return true; + } + + public setTargets(current: MediaItem, prev?: MediaItem[], next?: MediaItem[]) { + if(!this.listLoader) { + this.listLoader = new SearchListLoader({ + loadCount: 10, + loadWhenLeft: 5, + processItem: (item: Message.message) => { + const {peerId, mid} = item; + this.addMedia(peerId, (item.media as MessageMedia.messageMediaDocument).document as MyDocument, mid, false); + return {peerId, mid}; + }, + onJump: (item, older) => { + this.playItem(item); + } + }); + } else { + this.listLoader.reset(); + } + + const reverse = this.searchContext.folderId !== undefined ? false : true; + if(prev) { + this.listLoader.setTargets(prev, next, reverse); + } else { + this.listLoader.reverse = reverse; + } + + this.listLoader.setSearchContext(this.searchContext); + this.listLoader.current = current; + + this.listLoader.load(true); + this.listLoader.load(false); } } diff --git a/src/components/appMediaViewer.ts b/src/components/appMediaViewer.ts index ad9ec56b..eec0b660 100644 --- a/src/components/appMediaViewer.ts +++ b/src/components/appMediaViewer.ts @@ -30,11 +30,9 @@ import appSidebarRight from "./sidebarRight"; import SwipeHandler from "./swipeHandler"; import { ONE_DAY } from "../helpers/date"; import { SearchSuperContext } from "./appSearchSuper."; -import DEBUG from "../config/debug"; import appNavigationController from "./appNavigationController"; import { Message } from "../layer"; -import { forEachReverse } from "../helpers/array"; -import AppSharedMediaTab from "./sidebarRight/tabs/sharedMedia"; +import AppSharedMediaTab, { filterChatPhotosMessages } from "./sidebarRight/tabs/sharedMedia"; import findUpClassName from "../helpers/dom/findUpClassName"; import renderImageFromUrl, { renderImageFromUrlPromise } from "../helpers/dom/renderImageFromUrl"; import getVisibleRect from "../helpers/dom/getVisibleRect"; @@ -53,7 +51,7 @@ import { attachClickEvent } from "../helpers/dom/clickEvent"; import PopupDeleteMessages from "./popups/deleteMessages"; import RangeSelector from "./rangeSelector"; import windowSize from "../helpers/windowSize"; -import { safeAssign } from "../helpers/object"; +import ListLoader, { ListLoaderOptions } from "../helpers/listLoader"; const ZOOM_STEP = 0.5; const ZOOM_INITIAL_VALUE = 1; @@ -66,129 +64,22 @@ const ZOOM_MAX_VALUE = 4; const MEDIA_VIEWER_CLASSNAME = 'media-viewer'; -type MediaQueueLoaderOptions = { - prevTargets?: MediaQueueLoader['prevTargets'], - nextTargets?: MediaQueueLoader['nextTargets'], - onLoadedMore?: MediaQueueLoader['onLoadedMore'], - generateItem?: MediaQueueLoader['generateItem'], - getLoadPromise?: MediaQueueLoader['getLoadPromise'], - reverse?: MediaQueueLoader['reverse'] -}; - -class MediaQueueLoader { - public target: Item = false as any; - public prevTargets: Item[] = []; - public nextTargets: Item[] = []; - - public loadMediaPromiseUp: Promise = null; - public loadMediaPromiseDown: Promise = null; - public loadedAllMediaUp = false; - public loadedAllMediaDown = false; - - public reverse = false; // reverse means next = higher msgid - - protected generateItem: (item: Item) => Item = (item) => item; - protected getLoadPromise: (older: boolean, anchor: Item, loadCount: number) => Promise; - protected onLoadedMore: () => void; - - constructor(options: MediaQueueLoaderOptions = {}) { - safeAssign(this, options); - } - - public setTargets(prevTargets: Item[], nextTargets: Item[], reverse: boolean) { - this.prevTargets = prevTargets; - this.nextTargets = nextTargets; - this.reverse = reverse; - this.loadedAllMediaUp = this.loadedAllMediaDown = false; - this.loadMediaPromiseUp = this.loadMediaPromiseDown = null; - } - - public reset() { - this.prevTargets = []; - this.nextTargets = []; - this.loadedAllMediaUp = this.loadedAllMediaDown = false; - this.loadMediaPromiseUp = this.loadMediaPromiseDown = null; - } - - // нет смысла делать проверку для reverse и loadMediaPromise - public loadMoreMedia = (older = true) => { - //if(!older && this.reverse) return; - - if(older && this.loadedAllMediaDown) return Promise.resolve(); - else if(!older && this.loadedAllMediaUp) return Promise.resolve(); - - if(older && this.loadMediaPromiseDown) return this.loadMediaPromiseDown; - else if(!older && this.loadMediaPromiseUp) return this.loadMediaPromiseUp; - - const loadCount = 50; - - let anchor: Item; - if(older) { - anchor = this.reverse ? this.prevTargets[0] : this.nextTargets[this.nextTargets.length - 1]; - } else { - anchor = this.reverse ? this.nextTargets[this.nextTargets.length - 1] : this.prevTargets[0]; - } - - const promise = this.getLoadPromise(older, anchor, loadCount).then(items => { - if((older && this.loadMediaPromiseDown !== promise) || (!older && this.loadMediaPromiseUp !== promise)) { - return; - } - - if(items.length < loadCount) { - /* if(this.reverse) { - if(older) this.loadedAllMediaUp = true; - else this.loadedAllMediaDown = true; - } else { */ - if(older) this.loadedAllMediaDown = true; - else this.loadedAllMediaUp = true; - //} - } - - const method: any = older ? items.forEach.bind(items) : forEachReverse.bind(null, items); - method((item: Item) => { - const t = this.generateItem(item); - if(!t) { - return; - } - - if(older) { - if(this.reverse) this.prevTargets.unshift(t); - else this.nextTargets.push(t); - } else { - if(this.reverse) this.nextTargets.push(t); - else this.prevTargets.unshift(t); - } - }); - - this.onLoadedMore && this.onLoadedMore(); - }, () => {}).then(() => { - if(older) this.loadMediaPromiseDown = null; - else this.loadMediaPromiseUp = null; - }); - - if(older) this.loadMediaPromiseDown = promise; - else this.loadMediaPromiseUp = promise; - - return promise; - }; -} - -class MediaSearchQueueLoader extends MediaQueueLoader { +export class SearchListLoader extends ListLoader { public searchContext: SearchSuperContext; - constructor(options: Omit, 'getLoadPromise'> = {}) { + constructor(options: Omit, 'loadMore'> = {}) { super({ ...options, - getLoadPromise: (older, anchor, loadCount) => { + loadMore: (anchor, older, loadCount) => { const backLimit = older ? 0 : loadCount; - let maxId = this.target?.mid; + let maxId = this.current?.mid; if(anchor) maxId = anchor.mid; if(!older) maxId = appMessagesIdsManager.incrementMessageId(maxId, 1); return appMessagesManager.getSearch({ ...this.searchContext, - peerId: anchor?.peerId, + peerId: this.searchContext.peerId || anchor?.peerId, maxId, limit: backLimit ? 0 : loadCount, backLimit @@ -197,11 +88,15 @@ class MediaSearchQueueLoader extends this.log('loaded more media by maxId:', maxId, value, older, this.reverse); } */ + if(this.searchContext.inputFilter._ === 'inputMessagesFilterChatPhotos') { + filterChatPhotosMessages(value); + } + if(value.next_rate) { this.searchContext.nextRate = value.next_rate; } - return value.history as any; + return {count: value.count, items: value.history}; }); } }); @@ -211,37 +106,45 @@ class MediaSearchQueueLoader extends this.searchContext = context; if(this.searchContext.folderId !== undefined) { - this.loadedAllMediaUp = true; + this.loadedAllUp = true; if(this.searchContext.nextRate === undefined) { - this.loadedAllMediaDown = true; + this.loadedAllDown = true; } } + + if(this.searchContext.inputFilter._ === 'inputMessagesFilterChatPhotos') { + this.loadedAllUp = true; + } + } + + public reset() { + super.reset(); + this.searchContext = undefined; } } -class MediaAvatarQueueLoader extends MediaQueueLoader { +class AvatarListLoader extends ListLoader { private peerId: number; - constructor(options: Omit, 'getLoadPromise'> & {peerId: number}) { + constructor(options: Omit, 'loadMore'> & {peerId: number}) { super({ ...options, - getLoadPromise: (older, anchor, loadCount) => { - if(this.peerId < 0) return Promise.resolve([]); // ! это значит, что открыло аватар чата, но следующих фотографий нет. - - return appPhotosManager.getUserPhotos(this.peerId, anchor?.photoId, loadCount).then(value => { - const idx = value.photos.indexOf(this.target.photoId); - if(idx !== -1) { - value.photos.splice(idx, 1); - } + loadMore: (anchor, older, loadCount) => { + if(this.peerId < 0 || !older) return Promise.resolve({count: 0, items: []}); // ! это значит, что открыло аватар чата, но следующих фотографий нет. - return value.photos.map(photoId => { + const maxId = anchor?.photoId; + return appPhotosManager.getUserPhotos(this.peerId, maxId, loadCount).then(value => { + const items = value.photos.map(photoId => { return {element: null as HTMLElement, photoId} as any; }); + + return {count: value.count, items}; }); } }); + this.loadedAllUp = true; this.peerId = options.peerId; } } @@ -259,8 +162,6 @@ class AppMediaViewerBase void = null; @@ -268,8 +169,7 @@ class AppMediaViewerBase, + constructor(protected listLoader: ListLoader, topButtons: Array['buttons']>) { this.log = logger('AMV'); this.preloader = new ProgressivePreloader(); @@ -432,30 +332,13 @@ class AppMediaViewerBase { - cancelEvent(e); - if(this.setMoverPromise) return; - - const target = this.prevTargets.pop(); - if(target) { - this.nextTargets.unshift(this.target); - this.onPrevClick(target); - } else { - this.buttons.prev.style.display = 'none'; - } - }); - - this.buttons.next.addEventListener('click', (e) => { - cancelEvent(e); - if(this.setMoverPromise) return; - - let target = this.nextTargets.shift(); - if(target) { - this.prevTargets.push(this.target); - this.onNextClick(target); - } else { - this.buttons.next.style.display = 'none'; - } + ([[-1, this.buttons.prev], [1, this.buttons.next]] as [number, HTMLElement][]).forEach(([moveLength, button]) => { + button.addEventListener('click', (e) => { + cancelEvent(e); + if(this.setMoverPromise) return; + + this.listLoader.go(moveLength); + }); }); this.buttons.zoom.addEventListener('click', () => { @@ -467,6 +350,11 @@ class AppMediaViewerBase { + if(older) this.onNextClick(item); + else this.onPrevClick(item); + }; + if(isTouchSupported) { const swipeHandler = new SwipeHandler({ element: this.wholeDiv, @@ -606,9 +494,7 @@ class AppMediaViewerBase onAnimationEnd); - this.target = false as any; - this.prevTargets.length = 0; - this.nextTargets.length = 0; + this.listLoader.reset(); this.setMoverPromise = null; this.tempId = -1; (window as any).appMediaViewer = undefined; @@ -1353,7 +1239,7 @@ class AppMediaViewerBase { protected btnMenuDelete: HTMLElement; - - protected queueLoader: MediaSearchQueueLoader; + protected listLoader: SearchListLoader; get searchContext() { - return this.queueLoader.searchContext; + return this.listLoader.searchContext; } constructor() { - super(new MediaSearchQueueLoader({ - generateItem: (item) => { + super(new SearchListLoader({ + processItem: (item) => { const isForDocument = this.searchContext.inputFilter._ === 'inputMessagesFilterDocument'; const {mid, peerId} = item; const media: MyPhoto | MyDocument = appMessagesManager.getMediaFromMessage(item); @@ -1950,13 +1822,13 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet } public setSearchContext(context: SearchSuperContext) { - this.queueLoader.setSearchContext(context); + this.listLoader.setSearchContext(context); return this; } public async openMedia(message: any, target?: HTMLElement, fromRight = 0, reverse = false, - prevTargets: AppMediaViewer['prevTargets'] = [], nextTargets: AppMediaViewer['prevTargets'] = [], needLoadMore = true) { + prevTargets: AppMediaViewerTargetType[] = [], nextTargets: AppMediaViewerTargetType[] = []/* , needLoadMore = true */) { if(this.setMoverPromise) return this.setMoverPromise; const mid = message.mid; @@ -1971,7 +1843,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet }); this.setCaption(message); - const promise = super._openMedia(media, message.date, fromId, fromRight, target, reverse, prevTargets, nextTargets, needLoadMore); + const promise = super._openMedia(media, message.date, fromId, fromRight, target, reverse, prevTargets, nextTargets/* , needLoadMore */); this.target.mid = mid; this.target.peerId = message.peerId; @@ -1988,7 +1860,7 @@ export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMe public peerId: number; constructor(peerId: number) { - super(new MediaAvatarQueueLoader({peerId}), [/* 'delete' */]); + super(new AvatarListLoader({peerId}), [/* 'delete' */]); this.peerId = peerId; diff --git a/src/components/audio.ts b/src/components/audio.ts index e7f0e125..1b392086 100644 --- a/src/components/audio.ts +++ b/src/components/audio.ts @@ -9,7 +9,7 @@ import { RichTextProcessor } from "../lib/richtextprocessor"; import { formatDate, wrapPhoto } from "./wrappers"; import ProgressivePreloader from "./preloader"; import { MediaProgressLine } from "../lib/mediaPlayer"; -import appMediaPlaybackController from "./appMediaPlaybackController"; +import appMediaPlaybackController, { MediaItem } from "./appMediaPlaybackController"; import { DocumentAttribute } from "../layer"; import mediaSizes from "../helpers/mediaSizes"; import { isSafari } from "../helpers/userAgent"; @@ -21,13 +21,14 @@ import { formatDateAccordingToToday } from "../helpers/date"; import { cancelEvent } from "../helpers/dom/cancelEvent"; import { attachClickEvent, detachClickEvent } from "../helpers/dom/clickEvent"; import LazyLoadQueue from "./lazyLoadQueue"; -import { deferredPromise } from "../helpers/cancellablePromise"; +import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise"; import ListenerSetter, { Listener } from "../helpers/listenerSetter"; import noop from "../helpers/noop"; +import findUpClassName from "../helpers/dom/findUpClassName"; rootScope.addEventListener('messages_media_read', ({mids, peerId}) => { mids.forEach(mid => { - (Array.from(document.querySelectorAll('audio-element[message-id="' + mid + '"][peer-id="' + peerId + '"].is-unread')) as AudioElement[]).forEach(elem => { + (Array.from(document.querySelectorAll('audio-element[data-mid="' + mid + '"][data-peer-id="' + peerId + '"].is-unread')) as AudioElement[]).forEach(elem => { elem.classList.remove('is-unread'); }); }); @@ -371,7 +372,7 @@ export default class AudioElement extends HTMLElement { private listenerSetter = new ListenerSetter(); private onTypeDisconnect: () => void; public onLoad: (autoload?: boolean) => void; - readyPromise: import("/Users/kuzmenko/Documents/projects/tweb/src/helpers/cancellablePromise").CancellablePromise; + private readyPromise: CancellablePromise; public render() { this.classList.add('audio'); @@ -429,7 +430,22 @@ export default class AudioElement extends HTMLElement { e && cancelEvent(e); if(paused) { - appMediaPlaybackController.setSearchContext(this.searchContext); + if(appMediaPlaybackController.setSearchContext(this.searchContext)) { + let prev: MediaItem[], next: MediaItem[]; + const container = findUpClassName(this, this.classList.contains('search-super-item') ? 'tabs-tab' : 'bubbles-inner'); + if(container) { + const elements = Array.from(container.querySelectorAll('.audio' + (isVoice ? '.is-voice' : ''))) as AudioElement[]; + const idx = elements.indexOf(this); + + const mediaItems: MediaItem[] = elements.map(element => ({peerId: +element.dataset.peerId, mid: +element.dataset.mid})); + + prev = mediaItems.slice(0, idx); + next = mediaItems.slice(idx + 1); + } + + appMediaPlaybackController.setTargets({peerId: this.message.peerId, mid: this.message.mid}, prev, next); + } + audio.play().catch(() => {}); } else { audio.pause(); @@ -444,7 +460,7 @@ export default class AudioElement extends HTMLElement { }); this.addAudioListener('timeupdate', () => { - if(appMediaPlaybackController.playingMedia !== audio || appMediaPlaybackController.isSafariBuffering(audio)) return; + if((!audio.currentTime && audio.paused) || appMediaPlaybackController.isSafariBuffering(audio)) return; audioTimeDiv.innerText = getTimeStr(); }); diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index babf4784..5dd0ad67 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -295,11 +295,10 @@ export default class ChatBubbles { } if(message.media?.document) { - const element = bubble.querySelector(`audio-element[message-id="${tempId}"], .document[data-doc-id="${tempId}"]`) as HTMLElement; + const element = bubble.querySelector(`audio-element[data-mid="${tempId}"], .document[data-doc-id="${tempId}"]`) as HTMLElement; if(element) { if(element instanceof AudioElement) { - element.setAttribute('doc-id', message.media.document.id); - element.setAttribute('message-id', '' + mid); + element.dataset.mid = '' + mid; element.message = message; element.onLoad(true); } else { diff --git a/src/components/lazyLoadQueue.ts b/src/components/lazyLoadQueue.ts index 91767f8f..41dddb8d 100644 --- a/src/components/lazyLoadQueue.ts +++ b/src/components/lazyLoadQueue.ts @@ -87,7 +87,7 @@ export class LazyLoadQueueBase { //await item.load(item.div); await this.loadItem(item); } catch(err) { - if(!['NO_ENTRY_FOUND', 'STORAGE_OFFLINE'].includes(err)) { + if(!['NO_ENTRY_FOUND', 'STORAGE_OFFLINE'].includes(err as string)) { this.log.error('loadMediaQueue error:', err/* , item */); } } diff --git a/src/components/sidebarRight/tabs/groupPermissions.ts b/src/components/sidebarRight/tabs/groupPermissions.ts index 094fa791..f9eaeea5 100644 --- a/src/components/sidebarRight/tabs/groupPermissions.ts +++ b/src/components/sidebarRight/tabs/groupPermissions.ts @@ -8,7 +8,7 @@ import { attachClickEvent } from "../../../helpers/dom/clickEvent"; import findUpTag from "../../../helpers/dom/findUpTag"; import replaceContent from "../../../helpers/dom/replaceContent"; import ListenerSetter from "../../../helpers/listenerSetter"; -import ScrollableLoader from "../../../helpers/listLoader"; +import ScrollableLoader from "../../../helpers/scrollableLoader"; import { ChannelParticipant, Chat, ChatBannedRights, Update } from "../../../layer"; import appChatsManager, { ChatRights } from "../../../lib/appManagers/appChatsManager"; import appDialogsManager from "../../../lib/appManagers/appDialogsManager"; diff --git a/src/components/sidebarRight/tabs/sharedMedia.ts b/src/components/sidebarRight/tabs/sharedMedia.ts index b7ea6e8d..0b768a75 100644 --- a/src/components/sidebarRight/tabs/sharedMedia.ts +++ b/src/components/sidebarRight/tabs/sharedMedia.ts @@ -5,7 +5,7 @@ */ import appImManager from "../../../lib/appManagers/appImManager"; -import appMessagesManager, { AppMessagesManager } from "../../../lib/appManagers/appMessagesManager"; +import appMessagesManager, { AppMessagesManager, MyMessage } from "../../../lib/appManagers/appMessagesManager"; import appPeersManager from "../../../lib/appManagers/appPeersManager"; import appProfileManager from "../../../lib/appManagers/appProfileManager"; import appUsersManager, { User } from "../../../lib/appManagers/appUsersManager"; @@ -31,8 +31,6 @@ import Row from "../../row"; import { copyTextToClipboard } from "../../../helpers/clipboard"; import { toast, toastNew } from "../../toast"; import { fastRaf } from "../../../helpers/schedulers"; -import { safeAssign } from "../../../helpers/object"; -import { forEachReverse } from "../../../helpers/array"; import appPhotosManager from "../../../lib/appManagers/appPhotosManager"; import renderImageFromUrl from "../../../helpers/dom/renderImageFromUrl"; import SwipeHandler from "../../swipeHandler"; @@ -50,6 +48,8 @@ import { attachClickEvent } from "../../../helpers/dom/clickEvent"; import replaceContent from "../../../helpers/dom/replaceContent"; import appAvatarsManager from "../../../lib/appManagers/appAvatarsManager"; import generateVerifiedIcon from "../../generateVerifiedIcon"; +import ListLoader from "../../../helpers/listLoader"; +import { forEachReverse } from "../../../helpers/array"; let setText = (text: string, row: Row) => { //fastRaf(() => { @@ -60,115 +60,20 @@ let setText = (text: string, row: Row) => { const PARALLAX_SUPPORTED = !isFirefox; -type ListLoaderResult = {count: number, items: any[]}; -class ListLoader { - public current: T; - public previous: T[] = []; - public next: T[] = []; - public count: number; - - public tempId = 0; - public loadMore: (anchor: T, older: boolean) => Promise>; - public processItem: (item: any) => false | T; - public onJump: (item: T, older: boolean) => void; - public loadCount = 50; - public reverse = false; // reverse means next = higher msgid - - public loadedAllUp = false; - public loadedAllDown = false; - public loadPromiseUp: Promise; - public loadPromiseDown: Promise; - - constructor(options: { - loadMore: ListLoader['loadMore'], - loadCount: ListLoader['loadCount'], - processItem?: ListLoader['processItem'], - onJump: ListLoader['onJump'], - }) { - safeAssign(this, options); - - - } - - get index() { - return this.count !== undefined ? this.previous.length : -1; - } - - public go(length: number) { - let items: T[], item: T; - if(length > 0) { - items = this.next.splice(0, length); - item = items.pop(); - if(!item) { - return; - } - - this.previous.push(this.current, ...items); - } else { - items = this.previous.splice(this.previous.length + length, -length); - item = items.shift(); - if(!item) { - return; +export function filterChatPhotosMessages(value: { + count: number; + next_rate: number; + offset_id_offset: number; + history: MyMessage[]; +}) { + forEachReverse(value.history, (message, idx, arr) => { + if(!((message as Message.messageService).action as MessageAction.messageActionChatEditPhoto).photo) { + arr.splice(idx, 1); + if(value.count !== undefined) { + --value.count; } - - this.next.unshift(...items, this.current); } - - this.current = item; - this.onJump(item, length > 0); - } - - public load(older: boolean) { - if(older && this.loadedAllDown) return Promise.resolve(); - else if(!older && this.loadedAllUp) return Promise.resolve(); - - if(older && this.loadPromiseDown) return this.loadPromiseDown; - else if(!older && this.loadPromiseUp) return this.loadPromiseUp; - - /* const loadCount = 50; - const backLimit = older ? 0 : loadCount; */ - - let anchor: T; - if(older) { - anchor = this.reverse ? this.previous[0] : this.next[this.next.length - 1]; - } else { - anchor = this.reverse ? this.next[this.next.length - 1] : this.previous[0]; - } - - const promise = this.loadMore(anchor, older).then(result => { - if(result.items.length < this.loadCount) { - if(older) this.loadedAllDown = true; - else this.loadedAllUp = true; - } - - if(this.count === undefined) { - this.count = result.count || result.items.length; - } - - const method = older ? result.items.forEach.bind(result.items) : forEachReverse.bind(null, result.items); - method((item: any) => { - const processed = this.processItem ? this.processItem(item) : item; - - if(!processed) return; - - if(older) { - if(this.reverse) this.previous.unshift(processed); - else this.next.push(processed); - } else { - if(this.reverse) this.next.push(processed); - else this.previous.unshift(processed); - } - }); - }, () => {}).then(() => { - if(older) this.loadPromiseDown = null; - else this.loadPromiseUp = null; - }); - - if(older) this.loadPromiseDown = promise; - else this.loadPromiseUp = promise; - - return promise; - } + }); } class PeerProfileAvatars { @@ -361,15 +266,17 @@ class PeerProfileAvatars { return; } - const loadCount = 50; const listLoader: PeerProfileAvatars['listLoader'] = this.listLoader = new ListLoader({ - loadCount, - loadMore: (anchor, older) => { + loadCount: 50, + loadMore: (anchor, older, loadCount) => { + if(!older) return Promise.resolve({count: undefined, items: []}); + if(peerId > 0) { - return appPhotosManager.getUserPhotos(peerId, (anchor || listLoader.current) as any, loadCount).then(result => { + const maxId: string = (anchor || listLoader.current) as any; + return appPhotosManager.getUserPhotos(peerId, maxId, loadCount).then(value => { return { - count: result.count, - items: result.photos + count: value.count, + items: value.photos }; }); } else { @@ -391,6 +298,8 @@ class PeerProfileAvatars { return Promise.all(promises).then((result) => { const value = result.pop() as typeof result[1]; + filterChatPhotosMessages(value); + if(!listLoader.current) { const chatFull = result[0]; const message = value.history.findAndSplice(m => { @@ -429,6 +338,7 @@ class PeerProfileAvatars { this.processItem(listLoader.current); + // listLoader.loaded listLoader.load(true); } diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 3ee05f37..c0ba847c 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -277,7 +277,10 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai } */ if(globalVideo.paused) { - appMediaPlaybackController.setSearchContext(searchContext); + if(appMediaPlaybackController.setSearchContext(searchContext)) { + appMediaPlaybackController.setTargets({peerId: message.peerId, mid: message.mid}); + } + globalVideo.play(); } else { globalVideo.pause(); @@ -522,8 +525,8 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS const uploading = message.pFlags.is_outgoing && message.media?.preloader; if(doc.type === 'audio' || doc.type === 'voice') { const audioElement = new AudioElement(); - audioElement.setAttribute('message-id', '' + message.mid); - audioElement.setAttribute('peer-id', '' + message.peerId); + audioElement.dataset.mid = '' + message.mid; + audioElement.dataset.peerId = '' + message.peerId; audioElement.withTime = withTime; audioElement.message = message; audioElement.noAutoDownload = noAutoDownload; diff --git a/src/helpers/listLoader.ts b/src/helpers/listLoader.ts index 6f4cc84f..e990c50a 100644 --- a/src/helpers/listLoader.ts +++ b/src/helpers/listLoader.ts @@ -1,53 +1,149 @@ -/* - * https://github.com/morethanwords/tweb - * Copyright (C) 2019-2021 Eduard Kuzmenko - * https://github.com/morethanwords/tweb/blob/master/LICENSE - */ - -import Scrollable from "../components/scrollable"; -import { safeAssign } from "./object"; - -export default class ScrollableLoader { - public loading = false; - private scrollable: Scrollable; - private getPromise: () => Promise; - private promise: Promise; - private loaded = false; - - constructor(options: { - scrollable: ScrollableLoader['scrollable'], - getPromise: ScrollableLoader['getPromise'] - }) { - safeAssign(this, options); - - options.scrollable.onScrolledBottom = () => { - this.load(); - }; - } - - public load() { - if(this.loaded) { - return Promise.resolve(); - } - - if(this.loading) { - return this.promise; - } - - this.loading = true; - this.promise = this.getPromise().then(done => { - this.loading = false; - this.promise = undefined; - - if(done) { - this.loaded = true; - this.scrollable.onScrolledBottom = null; - } else { - this.scrollable.checkForTriggers(); - } - }, () => { - this.promise = undefined; - this.loading = false; - }); - } -} +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import { forEachReverse } from "./array"; +import { safeAssign } from "./object"; + +export type ListLoaderOptions = { + loadMore: ListLoader['loadMore'], + loadCount?: ListLoader['loadCount'], + loadWhenLeft?: ListLoader['loadWhenLeft'], + processItem?: ListLoader['processItem'], + onJump?: ListLoader['onJump'], + onLoadedMore?: ListLoader['onLoadedMore'] +}; + +export type ListLoaderResult = {count: number, items: any[]}; +export default class ListLoader { + public current: T; + public previous: T[] = []; + public next: T[] = []; + public count: number; + public reverse = false; // reverse means next = higher msgid + + protected loadMore: (anchor: T, older: boolean, loadCount: number) => Promise>; + protected processItem: (item: any) => T; + protected loadCount = 50; + protected loadWhenLeft = 20; + + public onJump: (item: T, older: boolean) => void; + public onLoadedMore: () => void; + + protected loadedAllUp = false; + protected loadedAllDown = false; + protected loadPromiseUp: Promise; + protected loadPromiseDown: Promise; + + constructor(options: ListLoaderOptions) { + safeAssign(this, options); + } + + public setTargets(previous: T[], next: T[], reverse: boolean) { + this.previous = previous; + this.next = next; + this.reverse = reverse; + } + + public get index() { + return this.count !== undefined ? this.previous.length : -1; + } + + public reset() { + this.current = undefined; + this.previous = []; + this.next = []; + this.loadedAllUp = this.loadedAllDown = false; + this.loadPromiseUp = this.loadPromiseDown = null; + } + + public go(length: number) { + let items: T[], item: T; + if(length > 0) { + items = this.next.splice(0, length); + item = items.pop(); + if(!item) { + return; + } + + this.previous.push(this.current, ...items); + } else { + items = this.previous.splice(this.previous.length + length, -length); + item = items.shift(); + if(!item) { + return; + } + + this.next.unshift(...items, this.current); + } + + if(this.next.length < this.loadWhenLeft) { + this.load(!this.reverse); + } + + if(this.previous.length < this.loadWhenLeft) { + this.load(this.reverse); + } + + this.current = item; + this.onJump && this.onJump(item, length > 0); + } + + // нет смысла делать проверку для reverse и loadMediaPromise + public load(older: boolean) { + if(older && this.loadedAllDown) return Promise.resolve(); + else if(!older && this.loadedAllUp) return Promise.resolve(); + + if(older && this.loadPromiseDown) return this.loadPromiseDown; + else if(!older && this.loadPromiseUp) return this.loadPromiseUp; + + let anchor: T; + if(older) { + anchor = this.reverse ? this.previous[0] : this.next[this.next.length - 1]; + } else { + anchor = this.reverse ? this.next[this.next.length - 1] : this.previous[0]; + } + + const promise = this.loadMore(anchor, older, this.loadCount).then(result => { + if((older && this.loadPromiseDown !== promise) || (!older && this.loadPromiseUp !== promise)) { + return; + } + + if(result.items.length < this.loadCount) { + if(older) this.loadedAllDown = true; + else this.loadedAllUp = true; + } + + if(this.count === undefined) { + this.count = result.count || result.items.length; + } + + const method = older ? result.items.forEach.bind(result.items) : forEachReverse.bind(null, result.items); + method((item: any) => { + const processed = this.processItem ? this.processItem(item) : item; + + if(!processed) return; + + if(older) { + if(this.reverse) this.previous.unshift(processed); + else this.next.push(processed); + } else { + if(this.reverse) this.next.push(processed); + else this.previous.unshift(processed); + } + }); + + this.onLoadedMore && this.onLoadedMore(); + }, () => {}).then(() => { + if(older) this.loadPromiseDown = null; + else this.loadPromiseUp = null; + }); + + if(older) this.loadPromiseDown = promise; + else this.loadPromiseUp = promise; + + return promise; + } +} diff --git a/src/helpers/scrollableLoader.ts b/src/helpers/scrollableLoader.ts new file mode 100644 index 00000000..6f4cc84f --- /dev/null +++ b/src/helpers/scrollableLoader.ts @@ -0,0 +1,53 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import Scrollable from "../components/scrollable"; +import { safeAssign } from "./object"; + +export default class ScrollableLoader { + public loading = false; + private scrollable: Scrollable; + private getPromise: () => Promise; + private promise: Promise; + private loaded = false; + + constructor(options: { + scrollable: ScrollableLoader['scrollable'], + getPromise: ScrollableLoader['getPromise'] + }) { + safeAssign(this, options); + + options.scrollable.onScrolledBottom = () => { + this.load(); + }; + } + + public load() { + if(this.loaded) { + return Promise.resolve(); + } + + if(this.loading) { + return this.promise; + } + + this.loading = true; + this.promise = this.getPromise().then(done => { + this.loading = false; + this.promise = undefined; + + if(done) { + this.loaded = true; + this.scrollable.onScrolledBottom = null; + } else { + this.scrollable.checkForTriggers(); + } + }, () => { + this.promise = undefined; + this.loading = false; + }); + } +} diff --git a/src/helpers/userAgent.ts b/src/helpers/userAgent.ts index 80813518..9a239545 100644 --- a/src/helpers/userAgent.ts +++ b/src/helpers/userAgent.ts @@ -14,7 +14,7 @@ export const ctx = typeof(window) !== 'undefined' ? window : self; // https://stackoverflow.com/a/58065241 export const isAppleMobile = (/iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) && - !ctx.MSStream; + !(ctx as any).MSStream; export const isSafari = !!('safari' in ctx) || !!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))))/* || true */; export const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index dd438d62..6594d933 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -137,9 +137,17 @@ export class AppPhotosManager { photosResult.photos[idx] = this.savePhoto(photo, {type: 'profilePhoto', peerId: userId}); return photo.id; }); + + // ! WARNING ! + if(maxId !== '0' && maxId) { + const idx = photoIds.indexOf(maxId); + if(idx !== -1) { + photoIds.splice(idx, 1); + } + } return { - count: (photosResult as PhotosPhotos.photosPhotosSlice).count || photosResult.photos.length, + count: (photosResult as PhotosPhotos.photosPhotosSlice).count || photoIds.length, photos: photoIds }; }); diff --git a/src/lib/idb.ts b/src/lib/idb.ts index 04d56686..1250d174 100644 --- a/src/lib/idb.ts +++ b/src/lib/idb.ts @@ -141,7 +141,7 @@ export default class IDBStorage> { return Promise.reject(); } } catch(error) { - this.log.error('error opening db', error.message) + this.log.error('error opening db', (error as Error).message); this.storageIsAvailable = false; return Promise.reject(error); } diff --git a/src/lib/mtproto/apiManager.ts b/src/lib/mtproto/apiManager.ts index 9f8065f6..e5c9fb75 100644 --- a/src/lib/mtproto/apiManager.ts +++ b/src/lib/mtproto/apiManager.ts @@ -275,7 +275,7 @@ export class ApiManager { networker = networkerFactory.getNetworker(dcId, auth.authKey, auth.authKeyId, auth.serverSalt, transport, options); } catch(error) { - this.log('Get networker error', error, error.stack); + this.log('Get networker error', error, (error as Error).stack); delete this.gettingNetworkers[getKey]; throw error; } diff --git a/src/lib/mtproto/authorizer.ts b/src/lib/mtproto/authorizer.ts index 3bd79554..6f6dadd3 100644 --- a/src/lib/mtproto/authorizer.ts +++ b/src/lib/mtproto/authorizer.ts @@ -194,7 +194,7 @@ export class Authorizer { rsaKeysManager.prepare(); deserializer = await promise; } catch(error) { - this.log.error('req_pq error', error.message); + this.log.error('req_pq error', (error as Error).message); throw error; } diff --git a/src/lib/mtproto/networker.ts b/src/lib/mtproto/networker.ts index ad678a76..483a869d 100644 --- a/src/lib/mtproto/networker.ts +++ b/src/lib/mtproto/networker.ts @@ -1262,7 +1262,7 @@ export default class MTPNetworker { try { result.body = deserializer.fetchObject('Object', field + '[body]'); } catch(e) { - this.log.error('parse error', e.message, e.stack); + this.log.error('parse error', (e as Error).message, (e as Error).stack); result.body = { _: 'parse_error', error: e diff --git a/src/pages/pageSignQR.ts b/src/pages/pageSignQR.ts index 22321a5d..e3bf2ee9 100644 --- a/src/pages/pageSignQR.ts +++ b/src/pages/pageSignQR.ts @@ -5,6 +5,7 @@ */ import type { DcId } from '../types'; +import type { ApiError } from '../lib/mtproto/apiManager'; import apiManager from '../lib/mtproto/mtprotoworker'; import Page from './page'; import serverTimeManager from '../lib/mtproto/serverTimeManager'; @@ -200,10 +201,10 @@ let onFirstMount = async() => { await pause(diff > FETCH_INTERVAL ? 1e3 * FETCH_INTERVAL : 1e3 * diff | 0); } } catch(err) { - switch(err.type) { + switch((err as ApiError).type) { case 'SESSION_PASSWORD_NEEDED': console.warn('pageSignQR: SESSION_PASSWORD_NEEDED'); - err.handled = true; + (err as ApiError).handled = true; import('./pagePassword').then(m => m.default.mount()); stop = true; cachedPromise = null;