Pause media in other tabs

Deactivate tab when new version in installed
Fix messages grouping
Fix downloading photos from media viewer
DIspatch notification to better tab
This commit is contained in:
Eduard Kuzmenko 2022-06-24 21:23:12 +04:00
parent 045199cccc
commit 353824d12e
65 changed files with 1291 additions and 919 deletions

View File

@ -191,7 +191,7 @@ export class AnimationIntersector {
this.visible.has(player) &&
animation.autoplay &&
(!this.onlyOnePlayableGroup || this.onlyOnePlayableGroup === group) &&
(!idleController.idle.isIDLE || this.overrideIdleGroups.has(player.group))
(!idleController.isIdle || this.overrideIdleGroups.has(player.group))
) {
//console.warn('play animation:', animation);
animation.play();

View File

@ -102,6 +102,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
private pip: HTMLVideoElement;
private managers: AppManagers;
private skipMediaPlayEvent: boolean;
construct(managers: AppManagers) {
this.managers = managers;
@ -140,6 +141,17 @@ export class AppMediaPlaybackController extends EventListenerBase<{
}
});
rootScope.addEventListener('media_play', () => {
if(this.skipMediaPlayEvent) {
this.skipMediaPlayEvent = false;
return;
}
if(!this.pause() && this.pip) {
this.pip.pause();
}
});
const properties: {[key: PropertyKey]: PropertyDescriptor} = {};
const keys = [
'volume' as const,
@ -602,6 +614,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
}
this.dispatchEvent('play', this.getPlayingDetails());
this.pauseMediaInOtherTabs();
}, 0);
};
@ -639,6 +652,11 @@ export class AppMediaPlaybackController extends EventListenerBase<{
}
};
public pauseMediaInOtherTabs() {
this.skipMediaPlayEvent = true;
rootScope.dispatchEvent('media_play');
}
// public get pip() {
// return document.pictureInPictureElement as HTMLVideoElement;
// }
@ -879,6 +897,8 @@ export class AppMediaPlaybackController extends EventListenerBase<{
if(pip) {
pip.pause();
}
this.pauseMediaInOtherTabs();
};
if(!media.paused) {
@ -886,6 +906,8 @@ export class AppMediaPlaybackController extends EventListenerBase<{
}
media.addEventListener('play', onPlay);
} else { // maybe it's voice recording
this.pauseMediaInOtherTabs();
}
this.willBePlayed(undefined);
@ -952,6 +974,8 @@ export class AppMediaPlaybackController extends EventListenerBase<{
if(this.playingMedia !== video) {
this.pause();
}
this.pauseMediaInOtherTabs();
// if(this.pause()) {
// listenerSetter.add(video)('pause', () => {
// this.play();

View File

@ -223,11 +223,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
const message = await this.getMessageByPeer(peerId, mid);
const media = getMediaFromMessage(message);
if(!media) return;
if(media._ === 'photo') {
appDownloadManager.downloadToDisc({media, queueId: appImManager.chat.bubbles.lazyLoadQueue.queueId});
} else {
appDownloadManager.downloadToDisc({media, queueId: appImManager.chat.bubbles.lazyLoadQueue.queueId});
}
appDownloadManager.downloadToDisc({media, queueId: appImManager.chat.bubbles.lazyLoadQueue.queueId});
};
private setCaption(message: MyMessage) {

View File

@ -213,9 +213,9 @@ class SearchContextMenu {
}
private onGotoClick = () => {
rootScope.dispatchEvent('history_focus', {
appImManager.setInnerPeer({
peerId: this.peerId,
mid: this.mid,
lastMsgId: this.mid,
threadId: this.searchSuper.searchContext.threadId
});
};
@ -597,13 +597,26 @@ export default class AppSearchSuper {
}
private processEmptyFilter({message, searchGroup}: ProcessSearchSuperResult) {
const loadPromises: Promise<any>[] = [];
const {dom} = appDialogsManager.addDialogNew({
peerId: message.peerId,
container: searchGroup.list,
avatarSize: 54
avatarSize: 54,
loadPromises
});
// appDialogsManager.setLastMessage(dialog, message, dom, this.searchContext.query);
const setLastMessagePromise = appDialogsManager.setLastMessageN({
dialog: {
_: 'dialog',
peerId: message.peerId
} as any,
lastMessage: message,
dom,
highlightWord: this.searchContext.query
});
loadPromises.push(setLastMessagePromise);
return Promise.all(loadPromises);
}
private async processPhotoVideoFilter({message, promises, middleware}: ProcessSearchSuperResult) {

View File

@ -198,7 +198,7 @@ export default class AvatarElement extends HTMLElement {
private r(onlyThumb = false) {
const promise = putPhoto(this, this.peerId, this.isDialog, this.peerTitle, onlyThumb, this.isBig);
recordPromise(promise, 'avatar putPhoto-' + this.peerId);
// recordPromise(promise, 'avatar putPhoto-' + this.peerId);
if(this.loadPromises) {
this.loadPromises.push(promise);

View File

@ -16,16 +16,19 @@ import { Message } from "../../layer";
import { NULL_PEER_ID, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
import { SERVICE_AS_REGULAR, STICKY_OFFSET } from "./bubbles";
import forEachReverse from "../../helpers/array/forEachReverse";
import partition from "../../helpers/array/partition";
type GroupItem = {
bubble: HTMLElement,
fromId: PeerId,
mid: number,
groupMid?: number,
timestamp: number,
dateTimestamp: number,
mounted: boolean,
single: boolean,
group?: BubbleGroup
group?: BubbleGroup,
message: Message.message | Message.messageService // use it only to set avatar
};
class BubbleGroup {
@ -38,6 +41,7 @@ class BubbleGroup {
avatar: AvatarElement;
mounted: boolean;
dateTimestamp: number;
offset: number;
constructor(chat: Chat, groups: BubbleGroups, dateTimestamp: number) {
this.container = document.createElement('div');
@ -46,6 +50,7 @@ class BubbleGroup {
this.groups = groups;
this.items = [];
this.dateTimestamp = dateTimestamp;
this.offset = 0;
}
createAvatar(message: Message.message | Message.messageService) {
@ -57,6 +62,7 @@ class BubbleGroup {
this.avatarContainer = document.createElement('div');
this.avatarContainer.classList.add('bubbles-group-avatar-container');
++this.offset;
const fwdFrom = message.fwd_from;
const fwdFromId = message.fwdFromId;
@ -88,17 +94,29 @@ class BubbleGroup {
return this.items[this.items.length - 1];
}
get lastMid() {
return this.lastItem.mid;
}
get lastItem() {
return this.items[0];
}
updateClassNames() {
const items = this.items;
if(!items.length) {
const length = items.length;
if(!length) {
return;
}
const length = items.length;
// const elements = Array.from(this.container.children);
// if(this.offset) elements.splice(0, this.offset);
// const length = elements.length;
// if(!length) {
// return;
// }
const first = items[length - 1].bubble;
if(items.length === 1) {
@ -111,7 +129,7 @@ class BubbleGroup {
//this.setClipIfNeeded(first, true);
}
for(let i = length - 2; i > 0; --i) {
for(let i = 1, _length = length - 1; i < _length; ++i) {
const bubble = items[i].bubble;
bubble.classList.remove('is-group-last', 'is-group-first');
//this.setClipIfNeeded(bubble, true);
@ -145,33 +163,51 @@ class BubbleGroup {
items.splice(i, 0, item);
} else {
insertInDescendSortedArray(items, item, 'mid');
// insertInDescendSortedArray(items, item, 'mid');
insertInDescendSortedArray(items, item, 'groupMid');
}
item.group = this;
if(items.length === 1) {
insertInDescendSortedArray(this.groups.groups, this, 'firstMid');
insertInDescendSortedArray(this.groups.groups, this, 'lastMid');
}
}
mount() {
if(!this.groups.groups.includes(this)) { // group can be already removed
removeItem(item: GroupItem) {
indexOfAndSplice(this.items, item);
if(!this.items.length) {
indexOfAndSplice(this.groups.groups, this);
}
item.group = undefined;
}
mount(updateClassNames?: boolean) {
if(!this.groups.groups.includes(this) || !this.items.length) { // group can be already removed
debugger;
if(this.mounted) {
this.onItemUnmount();
}
return;
}
const offset = this.avatar ? 1 : 0;
const items = this.items;
this.updateClassNames();
const {offset, items} = this;
const {length} = items;
forEachReverse(items, (item, idx) => {
this.mountItem(item, length - 1 - idx, offset);
});
if(updateClassNames) {
this.updateClassNames();
}
this.onItemMount();
}
mountItem(item: GroupItem, idx = this.items.indexOf(item), offset = this.avatar ? 1 : 0) {
mountItem(item: GroupItem, idx = this.items.indexOf(item), offset = this.offset) {
if(item.mounted) {
return;
}
@ -181,11 +217,13 @@ class BubbleGroup {
}
unmountItem(item: GroupItem) {
if(item.mounted) {
item.bubble.remove();
item.mounted = false;
this.onItemUnmount();
if(!item.mounted) {
return;
}
item.bubble.remove();
item.mounted = false;
this.onItemUnmount();
}
onItemMount() {
@ -234,7 +272,7 @@ class BubbleGroup {
// }
export default class BubbleGroups {
private itemsArr: Array<GroupItem> = []; // descend sorted
public itemsArr: Array<GroupItem> = []; // descend sorted
private itemsMap: Map<HTMLElement, GroupItem> = new Map();
public groups: Array<BubbleGroup> = []; // descend sorted
private newGroupDiff = 121; // * 121 in scheduled messages
@ -243,32 +281,69 @@ export default class BubbleGroups {
}
removeBubble(bubble: HTMLElement) {
removeItem(item: GroupItem) {
item.group.removeItem(item);
this.removeItemFromCache(item);
}
removeAndUnmountBubble(bubble: HTMLElement) {
const item = this.getItemByBubble(bubble);
if(!item) {
return;
}
const items = this.itemsArr;
const index = items.indexOf(item);
const siblings = this.getSiblingsAtIndex(index, items);
const group = item.group;
const items = group.items;
if(items.length) {
indexOfAndSplice(items, item);
this.removeItem(item);
group.unmountItem(item);
if(!items.length) {
indexOfAndSplice(this.groups, group);
}
const modifiedGroups: Set<BubbleGroup> = new Set();
modifiedGroups.add(group);
const [previousSibling, nextSibling] = siblings;
if(
previousSibling
&& nextSibling
&& this.canItemsBeGrouped(previousSibling, nextSibling)
&& previousSibling.group !== nextSibling.group
) {
const group = nextSibling.group;
this.f(nextSibling.group.items);
group.onItemUnmount();
modifiedGroups.add(previousSibling.group);
this.groupUngrouped();
}
indexOfAndSplice(this.itemsArr, item);
this.itemsMap.delete(bubble);
return item;
this.mountUnmountGroups(Array.from(modifiedGroups));
}
removeAndUnmountBubble(bubble: HTMLElement) {
const item = this.removeBubble(bubble);
if(item) {
item.group.unmountItem(item);
mountUnmountGroups(groups: BubbleGroup[]) {
// groups.sort((a, b) => (b.lastItem?.mid ?? 0) - (a.lastItem?.mid ?? 0));
const [toMount, toUnmount] = partition(groups, (group) => !!group.items.length);
toUnmount.forEach((group) => {
group.onItemUnmount();
})
toMount.forEach((group) => {
group.mount(true);
});
// toMount.forEach((group) => {
// group.updateClassNames();
// });
}
f(items: GroupItem[], index: number = 0, length = items.length) {
for(; index < length; ++index) {
const item = items[index];
item.mounted = false;
item.group.removeItem(item);
--length;
--index;
}
}
@ -280,20 +355,22 @@ export default class BubbleGroups {
return this.groups[0];
}
// changeBubbleMid(bubble: HTMLElement, mid: number) {
// const item = this.getItemByBubble(bubble);
// if(!item) {
// return;
// }
changeBubbleMid(bubble: HTMLElement, mid: number) {
const item = this.getItemByBubble(bubble);
if(!item) {
return;
}
// item.mid = mid;
// // indexOfAndSplice(item.group.items, item);
// // item.group.insertItem(item);
// indexOfAndSplice(this.itemsArr, item);
// insertInDescendSortedArray(this.itemsArr, item, 'mid');
// }
item.mid = mid;
// indexOfAndSplice(item.group.items, item);
// // const canChangeGroupMid = !item.group.items.length || item.group.items.every((item) => item.groupMid === item.mid);
// // if(canChangeGroupMid) item.groupMid = mid;
// item.group.insertItem(item);
indexOfAndSplice(this.itemsArr, item);
insertInDescendSortedArray(this.itemsArr, item, 'mid');
}
changeItemBubble(item: GroupItem, bubble: HTMLElement) {
this.itemsMap.delete(item.bubble);
@ -310,45 +387,51 @@ export default class BubbleGroups {
this.changeItemBubble(item, to);
}
/**
*
* @param item
* @param items expect descend sorted array
* @returns
*/
findIndexForItemInItems(item: GroupItem, items: GroupItem[]) {
let foundAtIndex = -1;
for(let i = 0, length = items.length; i < length; ++i) {
const _item = items[i];
const diff = Math.abs(_item.timestamp - item.timestamp);
const good = _item.fromId === item.fromId
&& diff <= this.newGroupDiff
&& item.dateTimestamp === _item.dateTimestamp
&& !item.single
&& !_item.single;
canItemsBeGrouped(item1: GroupItem, item2: GroupItem) {
return item2.fromId === item1.fromId
&& Math.abs(item2.timestamp - item1.timestamp) <= this.newGroupDiff
&& item1.dateTimestamp === item2.dateTimestamp
&& !item1.single
&& !item2.single;
}
if(good) {
foundAtIndex = i;
getSiblingsAtIndex(itemIndex: number, items: GroupItem[]) {
return [items[itemIndex - 1], items[itemIndex + 1]] as const;
}
if(this.chat.type === 'scheduled') {
break;
}
} else {
foundAtIndex = -1;
}
// findGroupSiblingInSiblings(item: GroupItem, siblings: ReturnType<BubbleGroups['getSiblingsAtIndex']>) {
// return siblings.find((sibling) => sibling && this.canItemsBeGrouped(item, sibling));
// }
if(this.chat.type !== 'scheduled') {
if(item.mid > _item.mid) {
findGroupSiblingByItem(item: GroupItem, items: GroupItem[]) {
items = items.slice();
const idx = insertInDescendSortedArray(items, item, 'mid');
// return this.findGroupSiblingInSiblings(item, this.getSiblingsAtIndex(idx, items));
return this.findGroupSiblingInItems(item, items, idx);
}
findGroupSiblingInItems(item: GroupItem, items: GroupItem[], index = items.indexOf(item), length = items.length) {
const previousItem = items[index - 1];
let siblingGroupedItem: GroupItem;
if(previousItem?.group && this.canItemsBeGrouped(item, previousItem)) {
siblingGroupedItem = previousItem;
} else {
for(let k = index + 1; k < length; ++k) {
const nextItem = items[k];
if(this.canItemsBeGrouped(item, nextItem)) {
if(nextItem.group) {
siblingGroupedItem = nextItem;
}
} else {
break;
}
}
}
return foundAtIndex;
return siblingGroupedItem;
}
addItemToGroup(item: GroupItem, group: BubbleGroup) {
item.group = group;
group.insertItem(item);
this.addItemToCache(item);
}
@ -358,6 +441,11 @@ export default class BubbleGroups {
this.itemsMap.set(item.bubble, item);
}
removeItemFromCache(item: GroupItem) {
indexOfAndSplice(this.itemsArr, item);
this.itemsMap.delete(item.bubble);
}
getMessageFromId(message: MyMessage) {
let fromId = message.viaBotId || message.fromId;
@ -374,55 +462,113 @@ export default class BubbleGroups {
const {mid, date: timestamp} = message;
const {dateTimestamp} = this.chat.bubbles.getDateForDateContainer(timestamp);
const item: GroupItem = {
bubble,
fromId: this.getMessageFromId(message),
mid,
groupMid: mid,
fromId: this.getMessageFromId(message),
bubble,
timestamp,
dateTimestamp,
mounted: false,
single: single
single,
message
};
return item;
}
// prepareForGrouping(bubble: HTMLElement, message: MyMessage) {
// const item = this.createItem(bubble, message);
// this.addItemToCache(item);
// }
splitSiblingsOnGrouping(siblings: ReturnType<BubbleGroups['getSiblingsAtIndex']>) {
const [previousSibling, nextSibling] = siblings;
const previousGroup = previousSibling?.group;
const nextGroup = nextSibling?.group;
// groupUngrouped() {
// const items = this.itemsArr;
// const length = items.length;
// for(let i = length - 1; i >= 0; --i) {
// const item = items[i];
// if(item.gr)
// }
// }
if(!previousGroup) {
return;
}
addBubble(bubble: HTMLElement, message: MyMessage, unmountIfFound?: boolean) {
const oldItem = this.getItemByBubble(bubble);
if(unmountIfFound) { // updating position
this.removeAndUnmountBubble(bubble);
} else if(oldItem) { // editing
const group = oldItem.group;
this.changeItemBubble(oldItem, bubble);
oldItem.mounted = false;
// will refresh group
// if(previousGroup === nextGroup) {
const items = previousGroup.items;
const index = items.indexOf(previousSibling) + 1;
const length = items.length;
if(index === length) {
return;
}
return {item: oldItem, group};
const modifiedGroups: BubbleGroup[] = [previousGroup];
// if(previousGroup !== nextGroup && nextGroup) {
// modifiedGroups.push(nextGroup);
// }
this.f(items, index, length);
return modifiedGroups;
// }
}
prepareForGrouping(bubble: HTMLElement, message: MyMessage) {
if(this.getItemByBubble(bubble)) {
debugger;
return;
}
const item = this.createItem(bubble, message);
const foundAtIndex = this.findIndexForItemInItems(item, this.itemsArr);
const foundItem = this.itemsArr[foundAtIndex];
const group = foundItem?.group ?? new BubbleGroup(this.chat, this, item.dateTimestamp);
this.addItemToGroup(item, group);
return {item, group};
this.addItemToCache(item);
}
groupUngrouped() {
const items = this.itemsArr;
const length = items.length;
const modifiedGroups: Set<BubbleGroup> = new Set();
// for(let i = length - 1; i >= 0; --i) {
for(let i = 0; i < length; ++i) {
const item = items[i];
if(item.group) {
continue;
}
let hadGroup = true;
const siblings = this.getSiblingsAtIndex(i, items);
const siblingGroupedItem = this.findGroupSiblingInItems(item, items, i, length);
// const foundItem = this.findGroupSiblingInSiblings(item, siblings);
const foundItem = siblingGroupedItem;
const group = foundItem?.group ?? (hadGroup = false, new BubbleGroup(this.chat, this, item.dateTimestamp));
modifiedGroups.add(group);
group.insertItem(item);
if(!hadGroup) {
const splittedGroups = this.splitSiblingsOnGrouping(siblings);
if(splittedGroups) {
splittedGroups.forEach((group) => modifiedGroups.add(group));
}
}
}
return modifiedGroups;
}
// addBubble(bubble: HTMLElement, message: MyMessage, unmountIfFound?: boolean) {
// const oldItem = this.getItemByBubble(bubble);
// if(unmountIfFound) { // updating position
// this.removeAndUnmountBubble(bubble);
// } else if(oldItem) { // editing
// const group = oldItem.group;
// this.changeItemBubble(oldItem, bubble);
// oldItem.mounted = false;
// return {item: oldItem, group};
// }
// const item = this.createItem(bubble, message);
// const foundItem = this.findSameGroupItem(item, this.itemsArr);
// const group = foundItem?.group ?? new BubbleGroup(this.chat, this, item.dateTimestamp);
// this.addItemToGroup(item, group);
// return {item, group};
// }
/* setClipIfNeeded(bubble: HTMLDivElement, remove = false) {
//console.log('setClipIfNeeded', bubble, remove);
const className = bubble.className;
@ -485,4 +631,18 @@ export default class BubbleGroups {
this.groups = [];
this.itemsMap.clear();
}
// findIncorrentPositions() {
// var bubbles = Array.from(this.chat.bubbles.chatInner.querySelectorAll('.bubbles-group .bubble')).reverse();
// var items = this.itemsArr;
// for(var i = 0, length = items.length; i < length; ++i) {
// const item = items[i];
// const foundBubble = bubbles[i];
// if(item.bubble !== foundBubble) {
// console.log('incorrect position', i, item, foundBubble);
// // debugger;
// // break;
// }
// }
// }
}

View File

@ -156,7 +156,7 @@ function getMainMidForGrouped(mids: number[]) {
export default class ChatBubbles {
public container: HTMLDivElement;
private chatInner: HTMLDivElement;
public chatInner: HTMLDivElement;
public scrollable: Scrollable;
private getHistoryTopPromise: Promise<boolean>;
@ -248,11 +248,14 @@ export default class ChatBubbles {
private attachPlaceholderOnRender: () => void;
private bubblesToEject: Set<HTMLElement> = new Set();
private bubblesToReplace: Set<HTMLElement> = new Set();
private updatePlaceholderPosition: () => void;
private setPeerOptions: {lastMsgId: number; topMessage: number;};
private setPeerTempId: number = 0;
private renderNewPromises: Set<Promise<any>> = new Set();
// private reactions: Map<number, ReactionsElement>;
constructor(
@ -280,62 +283,104 @@ export default class ChatBubbles {
// * events
// will call when sent for update pos
this.listenerSetter.add(rootScope)('history_update', ({storageKey, peerId, mid, message, sequential}) => {
this.listenerSetter.add(rootScope)('history_update', async({storageKey, mid, message}) => {
if(this.chat.messagesStorageKey !== storageKey) {
return;
}
const log = false ? this.log.bindPrefix('history_update-' + mid) : undefined;
log && log('start');
const bubble = this.bubbles[mid];
if(!bubble) return;
if(this.renderNewPromises.size) {
log && log.error('will await new messages render');
await Promise.all(Array.from(this.renderNewPromises));
}
if(this.messagesQueuePromise) {
log && log.error('messages render in process');
await this.messagesQueuePromise;
}
if(this.bubbles[mid] !== bubble) return;
// await getHeavyAnimationPromise();
const item = this.bubbleGroups.getItemByBubble(bubble);
if(!item) return; // probably a group item
if(!item) { // probably a group item
log && log.error('no item by bubble', bubble);
return;
} else if(item.mid === mid) {
log && log.warn('wow what', item, mid);
return;
}
const unmountIfFound = !sequential || this.bubbleGroups.getLastGroup() !== item.group;
if(unmountIfFound) {
const groupIndex = this.bubbleGroups.groups.indexOf(item.group);
this.bubbleGroups.removeAndUnmountBubble(bubble);
if(!item.group.items.length) { // group has collapsed, next message can have higher mid so have to reposition them too
const nearbyGroups = this.bubbleGroups.groups.slice(0, groupIndex + 1);
for(let length = nearbyGroups.length, i = length - 2; i >= 0; --i) {
const group = nearbyGroups[i];
const groupItems = group.items;
const nextGroup = nearbyGroups[i + 1];
const nextGroupItems = nextGroup.items;
let _break = false, moved = false;
for(let j = groupItems.length - 1; j >= 0; --j) {
const groupItem = groupItems[j];
const possibleIndex = this.bubbleGroups.findIndexForItemInItems(groupItem, nextGroupItems);
if(possibleIndex === -1) {
_break = true;
break;
}
this.bubbleGroups.removeAndUnmountBubble(groupItem.bubble);
this.bubbleGroups.addItemToGroup(groupItem, nextGroup);
moved = true;
}
if(moved) {
nextGroup.mount();
}
if(_break) {
break;
}
}
}
const {group} = this.setBubblePosition(bubble, message, unmountIfFound);
group.mount();
if(this.scrollingToBubble) {
this.scrollToEnd();
}
}/* else {
const group = item.group;
const newItem = this.bubbleGroups.createItem(bubble, message);
// newItem.mid = item.mid;
const _items = this.bubbleGroups.itemsArr.slice();
indexOfAndSplice(_items, item);
const foundItem = this.bubbleGroups.findGroupSiblingByItem(newItem, _items);
if(group === foundItem?.group/* && false */) {
log && log('item has correct position', item);
this.bubbleGroups.changeBubbleMid(bubble, mid);
} */
return;
}
// return;
// await fastRafPromise();
// if(this.bubbles[mid] !== bubble) return;
// const groupIndex = this.bubbleGroups.groups.indexOf(group);
this.bubbleGroups.removeAndUnmountBubble(bubble);
// if(!group.items.length) { // group has collapsed, next message can have higher mid so have to reposition them too
// log && log('group has collapsed', item);
// const siblingGroups = this.bubbleGroups.groups.slice(0, groupIndex + 1);
// for(let length = siblingGroups.length, i = length - 2; i >= 0; --i) {
// const siblingGroup = siblingGroups[i];
// const siblingItems = siblingGroup.items;
// const nextGroup = siblingGroups[i + 1];
// const nextItems = nextGroup.items;
// let _break = false, moved = false;
// for(let j = siblingItems.length - 1; j >= 0; --j) {
// const siblingItem = siblingItems[j];
// const foundItem = this.bubbleGroups.findGroupSiblingByItem(siblingItem, nextItems);
// if(!foundItem) {
// _break = true;
// break;
// }
// log('will move item', siblingItem, nextGroup);
// this.bubbleGroups.removeAndUnmountBubble(siblingItem.bubble);
// this.bubbleGroups.addItemToGroup(siblingItem, nextGroup);
// moved = true;
// }
// if(moved) {
// nextGroup.mount();
// }
// if(_break) {
// break;
// }
// }
// }
const {groups} = this.groupBubbles([{bubble, message}]);
this.bubbleGroups.mountUnmountGroups(groups);
if(this.scrollingToBubble) {
this.scrollToEnd();
}
log && log('end');
// this.bubbleGroups.findIncorrentPositions();
});
this.listenerSetter.add(rootScope)('dialog_flush', ({peerId}) => {
@ -358,6 +403,7 @@ export default class ChatBubbles {
if(_bubble) {
const bubble = bubbles[tempId];
bubbles[mid] = bubble;
bubble.dataset.mid = '' + mid;
delete bubbles[tempId];
fastRaf(() => {
@ -367,8 +413,6 @@ export default class ChatBubbles {
bubble.classList.add((this.peerId === rootScope.myId && this.chat.type !== 'scheduled') || !this.unreadOut.has(mid) ? 'is-read' : 'is-sent');
}
});
bubble.dataset.mid = '' + mid;
}
if(this.unreadOut.has(tempId)) {
@ -385,6 +429,10 @@ export default class ChatBubbles {
}
}
if(!_bubble) {
return;
}
let messages: (Message.message | Message.messageService)[], tempIds: number[];
const groupedId = (message as Message.message).grouped_id;
if(groupedId) {
@ -1261,7 +1309,7 @@ export default class ChatBubbles {
if(this.readPromise) return;
const middleware = this.getMiddleware();
this.readPromise = idleController.idle.focusPromise.then(async() => {
this.readPromise = idleController.getFocusPromise().then(async() => {
if(!middleware()) return;
let maxId = Math.max(...Array.from(this.unreadedSeen));
@ -2112,8 +2160,17 @@ export default class ChatBubbles {
} : undefined
};
}
private renderNewMessagesByIds(mids: number[], scrolledDown?: boolean) {
const promise = this._renderNewMessagesByIds(mids, scrolledDown);
this.renderNewPromises.add(promise);
promise.catch(noop).finally(() => {
this.renderNewPromises.delete(promise);
});
return promise;
}
public async renderNewMessagesByIds(mids: number[], scrolledDown?: boolean) {
private async _renderNewMessagesByIds(mids: number[], scrolledDown?: boolean) {
if(!this.scrollable.loadedAll.bottom) { // seems search active or sliced
//this.log('renderNewMessagesByIds: seems search is active, skipping render:', mids);
const setPeerPromise = this.chat.setPeerPromise;
@ -2131,7 +2188,7 @@ export default class ChatBubbles {
if(this.chat.threadId) {
mids = await filterAsync(mids, async(mid) => {
const message = await this.chat.getMessage(mid);
const replyTo = message.reply_to as MessageReplyHeader;
const replyTo = message?.reply_to as MessageReplyHeader;
return replyTo && (replyTo.reply_to_top_id || replyTo.reply_to_msg_id) === this.chat.threadId;
});
}
@ -2179,6 +2236,8 @@ export default class ChatBubbles {
}, 10); */
});
}
return promise;
}
public getLastBubble() {
@ -2194,6 +2253,10 @@ export default class ChatBubbles {
) {
const bubble = findUpClassName(element, 'bubble');
if(!element.parentElement) {
this.log.error('element is not connected', bubble);
}
let fallbackToElementStartWhenCentering: HTMLElement;
// * if it's a start, then scroll to start of the group
if(bubble && position !== 'end') {
@ -2454,6 +2517,7 @@ export default class ChatBubbles {
this.unreadOut.clear();
this.needUpdate.length = 0;
this.lazyLoadQueue.clear();
this.renderNewPromises.clear();
// clear messages
if(bubblesToo) {
@ -2501,6 +2565,7 @@ export default class ChatBubbles {
this.renderingMessages.clear();
this.bubblesToEject.clear();
this.bubblesToReplace.clear();
// this.reactions.clear();
@ -3012,7 +3077,9 @@ export default class ChatBubbles {
const possibleError = PEER_CHANGED_ERROR;
const m = middlewarePromise(middleware, possibleError);
const promise = this.messagesQueuePromise = m(pause(0)).then(async() => {
const processQueue = async(): Promise<void> => {
log('start');
const renderQueue = this.messagesQueue.slice();
this.messagesQueue.length = 0;
@ -3025,15 +3092,27 @@ export default class ChatBubbles {
return promise;
});
const loadQueue = (await m(Promise.all(renderQueuePromises))).filter(Boolean);
log.warn('messages rendered');
let loadQueue = await m(Promise.all(renderQueuePromises));
const filterQueue = (queue: typeof loadQueue) => {
return queue.filter((details) => {
// message can be deleted during rendering
return details && this.bubbles[details.bubble.dataset.mid] === details.bubble;
});
};
const groups: Set<BubbleGroups['groups'][0]> = new Set();
loadQueue = filterQueue(loadQueue);
log('messages rendered');
const reverse = loadQueue[0]?.reverse;
const {groups, avatarPromises} = this.groupBubbles(loadQueue.filter((details) => details.updatePosition));
// if(groups.length > 2 && loadQueue.length === 1) {
// debugger;
// }
const promises = loadQueue.reduce((acc, details) => {
if(!details || this.bubbles[details.message.mid] !== details.bubble) {
return acc; // message was deleted during rendering
}
const perf = performance.now();
const promises = details.promises.slice();
@ -3046,32 +3125,34 @@ export default class ChatBubbles {
log.groupEnd();
});
if(details.updatePosition) {
const res = this.setBubblePosition(details.bubble, details.message, false);
if(res) {
groups.add(res.group);
if(details.needAvatar) {
details.promises.push(res.group.createAvatar(details.message));
}
}
}
// if(details.updatePosition) {
// if(res) {
// groups.add(res.group);
// if(details.needAvatar) {
// details.promises.push(res.group.createAvatar(details.message));
// }
// }
// }
acc.push(...details.promises);
return acc;
}, [] as Promise<any>[]);
promises.push(...avatarPromises);
// promises.push(pause(200));
// * это нужно для того, чтобы если захочет подгрузить reply или какое-либо сообщение, то скролл не прервался
// * если добавить этот промис - в таком случае нужно сделать, чтобы скроллило к последнему сообщению после рендера
// promises.push(getHeavyAnimationPromise());
log.warn('media promises to call', promises, loadQueue, this.isHeavyAnimationInProgress);
log('media promises to call', promises, loadQueue, this.isHeavyAnimationInProgress);
await m(Promise.all([...promises, this.setUnreadDelimiter()])); // не нашёл места лучше
await m(fastRafPromise()); // have to be the last
log.warn('media promises end');
log('media promises end');
this.prepareToSaveScroll(loadQueue[0] && loadQueue[0].reverse);
loadQueue = filterQueue(loadQueue);
const restoreScroll = this.prepareToSaveScroll(reverse);
// if(this.messagesQueueOnRender) {
// this.messagesQueueOnRender();
// }
@ -3081,35 +3162,58 @@ export default class ChatBubbles {
}
this.ejectBubbles();
for(const bubble of this.bubblesToReplace) {
if(!loadQueue.find((details) => details.bubble === bubble)) {
continue;
}
const item = this.bubbleGroups.getItemByBubble(bubble);
item.mounted = false;
if(!groups.includes(item.group)) {
groups.push(item.group);
}
this.bubblesToReplace.delete(bubble);
}
if(this.chat.selection.isSelecting) {
loadQueue.forEach(({message, bubble}) => {
if(this.bubbles[message.mid] !== bubble) { // maybe message was deleted during rendering
return;
}
loadQueue.forEach(({bubble}) => {
this.chat.selection.toggleElementCheckbox(bubble, true);
});
}
// const sortedGroups = Array.from(groups).sort((a, b) => a.firstTimestamp - b.firstTimestamp);
// for(const group of sortedGroups) {
for(const group of groups) {
group.mount();
}
loadQueue.forEach(({message, bubble, updatePosition}) => {
if(message.pFlags.local && updatePosition) {
this.chatInner[(message as Message.message).pFlags.sponsored ? 'append' : 'prepend'](bubble);
return;
}
});
this.bubbleGroups.mountUnmountGroups(groups);
// this.bubbleGroups.findIncorrentPositions();
if(this.updatePlaceholderPosition) {
this.updatePlaceholderPosition();
}
if(restoreScroll) {
restoreScroll();
}
// this.setStickyDateManually();
}).finally(() => {
if(this.messagesQueue.length) {
log('have new messages to render');
return processQueue();
} else {
log('end');
}
};
log('setting pause');
const promise = this.messagesQueuePromise = m(pause(0)).then(processQueue).finally(() => {
if(this.messagesQueuePromise === promise) {
this.messagesQueuePromise = null;
if(this.messagesQueue.length) {
this.setMessagesQueuePromise();
}
}
});
@ -3121,16 +3225,35 @@ export default class ChatBubbles {
bubble.remove();
// this.bubbleGroups.removeAndUnmountBubble(bubble);
}
this.bubblesToEject.clear();
}
public setBubblePosition(bubble: HTMLElement, message: Message.message | Message.messageService, unmountIfFound: boolean) {
if(message.pFlags.local) {
this.chatInner[(message as Message.message).pFlags.sponsored ? 'append' : 'prepend'](bubble);
return;
}
public groupBubbles(items: Array<{
// Awaited<ReturnType<ChatBubbles['safeRenderMessage']>> &
bubble: HTMLElement,
message: Message.message | Message.messageService
}/* & {
unmountIfFound?: boolean
} */>) {
items.forEach(({bubble, message}) => {
this.bubbleGroups.prepareForGrouping(bubble, message);
});
const result = this.bubbleGroups.addBubble(bubble, message, unmountIfFound);
return result;
const groups = this.bubbleGroups.groupUngrouped();
const avatarPromises = Array.from(groups).map((group) => {
if(group.avatar) return;
const firstItem = group.firstItem;
if(this.chat.isAvatarNeeded(firstItem.message)) {
return group.createAvatar(firstItem.message);
}
}).filter(Boolean);
return {
groups: [...groups],
avatarPromises
};
}
public getMiddleware(additionalCallback?: () => boolean) {
@ -3156,6 +3279,9 @@ export default class ChatBubbles {
// const groupedId = (message as Message.message).grouped_id;
const newBubble = document.createElement('div');
newBubble.dataset.mid = '' + message.mid;
newBubble.dataset.peerId = '' + message.peerId;
newBubble.dataset.timestamp = '' + message.date;
// const bubbleNew: Bubble = this.bubblesNew[message.mid] ??= {
// bubble: newBubble,
@ -3167,7 +3293,10 @@ export default class ChatBubbles {
if(bubble) {
this.skippedMids.delete(message.mid);
this.bubblesToEject.add(bubble);
this.bubblesToReplace.delete(bubble);
this.bubblesToReplace.add(newBubble);
this.bubbleGroups.changeBubbleByBubble(bubble, newBubble);
}
@ -3215,6 +3344,8 @@ export default class ChatBubbles {
// return;
// }
// await pause(1000);
const isMessage = message._ === 'message';
const groupedId = isMessage && message.grouped_id;
let albumMids: number[], reactionsMessage: Message.message;
@ -3232,96 +3363,42 @@ export default class ChatBubbles {
reactionsMessage = groupedId ? await this.managers.appMessagesManager.getGroupsFirstMessage(message) : message;
}
const peerId = this.peerId;
// * can't use 'message.pFlags.out' here because this check will be used to define side of message (left-right)
const our = message.fromId === rootScope.myId || (message.pFlags.out && await this.managers.appPeersManager.isMegagroup(peerId));
const our = this.chat.isOurMessage(message);
const messageDiv = document.createElement('div');
messageDiv.classList.add('message');
//messageDiv.innerText = message.message;
let bubbleContainer: HTMLDivElement;
let contentWrapper: HTMLElement;
// bubble
// if(!bubble) {
contentWrapper = document.createElement('div');
contentWrapper.classList.add('bubble-content-wrapper');
bubbleContainer = document.createElement('div');
bubbleContainer.classList.add('bubble-content');
// bubble = document.createElement('div');
bubble.classList.add('bubble');
contentWrapper.appendChild(bubbleContainer);
bubble.appendChild(contentWrapper);
contentWrapper = document.createElement('div');
contentWrapper.classList.add('bubble-content-wrapper');
bubbleContainer = document.createElement('div');
bubbleContainer.classList.add('bubble-content');
bubble.classList.add('bubble');
contentWrapper.append(bubbleContainer);
bubble.append(contentWrapper);
if(!our && !message.pFlags.out && this.observer) {
//this.log('not our message', message, message.pFlags.unread);
const isUnread = message.pFlags.unread ||
isMentionUnread(message)/* ||
(this.historyStorage.readMaxId !== undefined && this.historyStorage.readMaxId < message.mid) */;
if(isUnread) {
this.observer.observe(bubble, this.unreadedObserverCallback);
this.unreaded.set(bubble, message.mid);
}
if(!our && !message.pFlags.out && this.observer) {
//this.log('not our message', message, message.pFlags.unread);
const isUnread = message.pFlags.unread ||
isMentionUnread(message)/* ||
(this.historyStorage.readMaxId !== undefined && this.historyStorage.readMaxId < message.mid) */;
if(isUnread) {
this.observer.observe(bubble, this.unreadedObserverCallback);
this.unreaded.set(bubble, message.mid);
}
// } else {
// const save = ['is-highlighted', 'is-group-first', 'is-group-last'];
// const wasClassNames = bubble.className.split(' ');
// const classNames = ['bubble'].concat(save.filter((c) => wasClassNames.includes(c)));
// bubble.className = classNames.join(' ');
// contentWrapper = bubble.lastElementChild as HTMLElement;
// if(!contentWrapper.classList.contains('bubble-content-wrapper')) {
// contentWrapper = bubble.querySelector('.bubble-content-wrapper');
// }
// bubbleContainer = contentWrapper.firstElementChild as HTMLDivElement;
// bubbleContainer.innerHTML = '';
// bubbleContainer.style.cssText = '';
// contentWrapper.innerHTML = '';
// contentWrapper.appendChild(bubbleContainer);
// //bubbleContainer.style.marginBottom = '';
// const transitionDelay = contentWrapper.style.transitionDelay;
// contentWrapper.style.cssText = '';
// contentWrapper.style.transitionDelay = transitionDelay;
// if(bubble === this.firstUnreadBubble) {
// bubble.classList.add('is-first-unread');
// }
// // * Нужно очистить прошлую информацию, полезно если удалить последний элемент из альбома в ПОСЛЕДНЕМ БАББЛЕ ГРУППЫ (видно по аватару)
// const originalMid = +bubble.dataset.mid;
// const sameMid = +message.mid === originalMid;
// /* if(updatePosition) {
// bubble.remove(); // * for positionElementByIndex
// } */
// ! WARNING
// if(!sameMid) {
// delete this.bubbles[originalMid];
// this.skippedMids.delete(originalMid);
// }
// //bubble.innerHTML = '';
// }
// ! reset due to album edit or delete item
// this.bubbles[+message.mid] = bubble;
bubble.dataset.mid = '' + message.mid;
bubble.dataset.peerId = '' + message.peerId;
bubble.dataset.timestamp = '' + message.date;
}
const loadPromises: Promise<any>[] = [];
const ret = {
bubble,
promises: loadPromises,
message,
reverse,
needAvatar: undefined as boolean,
isOut: undefined as boolean
reverse
};
if(message._ === 'messageService' && (!message.action || !SERVICE_AS_REGULAR.has(message.action._))) {
@ -3339,10 +3416,15 @@ export default class ChatBubbles {
const s = document.createElement('div');
s.classList.add('service-msg');
if(action) {
let promise: Promise<any>;
if(action._ === 'messageActionChannelMigrateFrom') {
s.append(i18n('ChatMigration.From', [new PeerTitle({peerId: action.chat_id.toPeerId(true)}).element]));
const peerTitle = new PeerTitle();
promise = peerTitle.update({peerId: action.chat_id.toPeerId(true)});
s.append(i18n('ChatMigration.From', [peerTitle.element]));
} else if(action._ === 'messageActionChatMigrateTo') {
s.append(i18n('ChatMigration.To', [new PeerTitle({peerId: action.channel_id.toPeerId(true)}).element]));
const peerTitle = new PeerTitle();
promise = peerTitle.update({peerId: action.channel_id.toPeerId(true)});
s.append(i18n('ChatMigration.To', [peerTitle.element]));
} else {
s.append(await wrapMessageActionTextNew(message));
}
@ -3524,6 +3606,7 @@ export default class ChatBubbles {
promise.then((peerId) => {
const threadId = this.peerId === peerId ? this.chat.threadId : undefined;
this.chat.appImManager.setInnerPeer({peerId});
this.managers.appInlineBotsManager.switchInlineQuery(peerId, threadId, botId, button.query);
});
});
@ -3605,7 +3688,7 @@ export default class ChatBubbles {
const fwdFrom = isMessage && message.fwd_from;
const fwdFromId = isMessage && message.fwdFromId;
const isOut = ret.isOut = our && (!fwdFrom || this.peerId !== rootScope.myId);
const isOut = this.chat.isOutMessage(message);
let nameContainer: HTMLElement = bubbleContainer;
const canHideNameIfMedia = !message.viaBotId && (message.fromId === rootScope.myId || !message.pFlags.out);
@ -3674,9 +3757,9 @@ export default class ChatBubbles {
case 'messageMediaWebPage': {
processingWebPage = true;
let webpage: WebPage = messageMedia.webpage;
let webPage: WebPage = messageMedia.webpage;
////////this.log('messageMediaWebPage', webpage);
if(webpage._ !== 'webPage') {
if(webPage._ !== 'webPage') {
break;
}
@ -3689,8 +3772,8 @@ export default class ChatBubbles {
quote.classList.add('quote');
let previewResizer: HTMLDivElement, preview: HTMLDivElement;
const photo: Photo.photo = webpage.photo as any;
if(photo || webpage.document) {
const photo: Photo.photo = webPage.photo as any;
if(photo || webPage.document) {
previewResizer = document.createElement('div');
previewResizer.classList.add('preview-resizer');
preview = document.createElement('div');
@ -3701,7 +3784,7 @@ export default class ChatBubbles {
let quoteTextDiv = document.createElement('div');
quoteTextDiv.classList.add('quote-text');
const doc = webpage.document as MyDocument;
const doc = webPage.document as MyDocument;
if(doc) {
if(doc.type === 'gif' || doc.type === 'video' || doc.type === 'round') {
//if(doc.size <= 20e6) {
@ -3754,19 +3837,19 @@ export default class ChatBubbles {
}
let t: HTMLElement;
if(webpage.site_name) {
const html = wrapRichText(webpage.url);
if(webPage.site_name) {
const html = wrapRichText(webPage.url);
const a: HTMLAnchorElement = htmlToDocumentFragment(html).firstElementChild as any;
a.classList.add('webpage-name');
const strong = document.createElement('strong');
setInnerHTML(strong, wrapEmojiText(webpage.site_name));
setInnerHTML(strong, wrapEmojiText(webPage.site_name));
a.textContent = '';
a.append(strong);
quoteTextDiv.append(a);
t = a;
}
const title = wrapWebPageTitle(webpage);
const title = wrapWebPageTitle(webPage);
if(title.textContent) {
let titleDiv = document.createElement('div');
titleDiv.classList.add('title');
@ -3777,7 +3860,7 @@ export default class ChatBubbles {
t = titleDiv;
}
const description = wrapWebPageDescription(webpage);
const description = wrapWebPageDescription(webPage);
if(description.textContent) {
let textDiv = document.createElement('div');
textDiv.classList.add('text');
@ -4098,7 +4181,7 @@ export default class ChatBubbles {
let savedFrom = '';
// const needName = ((peerId.isAnyChat() && (peerId !== message.fromId || our)) && message.fromId !== rootScope.myId) || message.viaBotId;
const needName = (message.fromId !== rootScope.myId && await this.managers.appPeersManager.isAnyGroup(peerId)) || message.viaBotId || (message as Message.message).pFlags.sponsored;
const needName = (message.fromId !== rootScope.myId && this.chat.isAnyGroup) || message.viaBotId || (message as Message.message).pFlags.sponsored;
if(needName || fwdFrom || message.reply_to_mid) { // chat
let title: HTMLElement | DocumentFragment;
let titleVia: typeof title;
@ -4200,8 +4283,6 @@ export default class ChatBubbles {
nameDiv.classList.add('name');
nameContainer.append(nameDiv);
}
ret.needAvatar = this.chat.isAnyGroup && !isOut;
} else {
bubble.classList.add('hide-name');
}
@ -4315,7 +4396,8 @@ export default class ChatBubbles {
return;
}
this.log.warn('onRender');
const log = this.log.bindPrefix('prepareToSaveScroll');
log('save');
const scrollSaver = this.createScrollSaver(reverse);
scrollSaver.save(); // * let's save scroll position by point before the slicing, not after
@ -4328,16 +4410,17 @@ export default class ChatBubbles {
// const saved = scrollSaver.getSaved();
// const hadScroll = saved.scrollHeight !== saved.clientHeight;
(this.messagesQueuePromise || Promise.resolve()).then(() => {
return () => {
log('restore');
// scrollSaver.restore(_history.length === 1 && !reverse ? false : true);
scrollSaver.restore(reverse);
this.onRenderScrollSet(scrollSaver.getSaved());
});
};
}
public async performHistoryResult(historyResult: HistoryResult | {history: (Message.message | Message.messageService | number)[]}, reverse: boolean) {
const log = this.log.bindPrefix('perform-' + (Math.random() * 1000 | 0));
log.warn('start', this.chatInner.parentElement);
const log = false ? this.log.bindPrefix('perform-' + (Math.random() * 1000 | 0)) : undefined;
log && log('start', this.chatInner.parentElement);
let history = historyResult.history;
history = history.slice(); // need
@ -4393,9 +4476,10 @@ export default class ChatBubbles {
await Promise.all(setLoadedPromises);
// ! it is important to insert bubbles to group reversed way
const length = history.length, promises: Promise<any>[] = [];
if(reverse) for(let i = 0; i < length; ++i) promises.push(cb(messages[i]));
else for(let i = length - 1; i >= 0; --i) promises.push(cb(messages[i]));
// const length = history.length, promises: Promise<any>[] = [];
// if(reverse) for(let i = 0; i < length; ++i) promises.push(cb(messages[i]));
// else for(let i = length - 1; i >= 0; --i) promises.push(cb(messages[i]));
const promises = messages.map(cb);
// cannot combine them into one promise
await Promise.all(promises);
@ -4409,7 +4493,7 @@ export default class ChatBubbles {
}
}
log.warn('performHistoryResult end');
log && log('performHistoryResult end');
}
private onRenderScrollSet(state?: {scrollHeight: number, clientHeight: number}) {
@ -4823,9 +4907,9 @@ export default class ChatBubbles {
};
} else {
callback = () => {
rootScope.dispatchEvent('history_focus', {
this.chat.appImManager.setInnerPeer({
peerId,
mid,
lastMsgId: mid,
startParam
});
};
@ -5268,7 +5352,7 @@ export default class ChatBubbles {
// * filter last album, because we don't know is it the last item
for(let i = additionMsgIds.length - 1; i >= 0; --i) {
const message = await this.chat.getMessage(additionMsgIds[i]);
if((message as Message.message).grouped_id) additionMsgIds.splice(i, 1);
if((message as Message.message)?.grouped_id) additionMsgIds.splice(i, 1);
else break;
}

View File

@ -34,6 +34,7 @@ import AppSharedMediaTab from "../sidebarRight/tabs/sharedMedia";
import noop from "../../helpers/noop";
import middlewarePromise from "../../helpers/middlewarePromise";
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
import { Message } from "../../layer";
export type ChatType = 'chat' | 'pinned' | 'replies' | 'discussion' | 'scheduled';
@ -80,6 +81,7 @@ export default class Chat extends EventListenerBase<{
// public renderDarkPattern: () => Promise<void>;
public isAnyGroup: boolean;
public isMegagroup: boolean;
constructor(
public appImManager: AppImManager,
@ -401,16 +403,18 @@ export default class Chat extends EventListenerBase<{
searchTab.close();
}
const [noForwards, isRestricted, isAnyGroup] = await m(Promise.all([
const [noForwards, isRestricted, isAnyGroup, _, isMegagroup] = await m(Promise.all([
this.managers.appPeersManager.noForwards(peerId),
this.managers.appPeersManager.isRestricted(peerId),
this._isAnyGroup(peerId),
this.setAutoDownloadMedia()
this.setAutoDownloadMedia(),
this.managers.appPeersManager.isMegagroup(peerId)
]));
this.noForwards = noForwards;
this.isRestricted = isRestricted;
this.isAnyGroup = isAnyGroup;
this.isMegagroup = isMegagroup;
this.container.classList.toggle('no-forwards', this.noForwards);
@ -588,4 +592,18 @@ export default class Chat extends EventListenerBase<{
sendAsPeerId: this.input.sendAsPeerId
};
}
public isOurMessage(message: Message.message | Message.messageService) {
return message.fromId === rootScope.myId || (message.pFlags.out && this.isMegagroup);
}
public isOutMessage(message: Message.message | Message.messageService) {
const fwdFrom = (message as Message.message).fwd_from;
const isOut = this.isOurMessage(message) && (!fwdFrom || this.peerId !== rootScope.myId);
return isOut;
}
public isAvatarNeeded(message: Message.message | Message.messageService) {
return this.isAnyGroup && !this.isOutMessage(message);
}
}

View File

@ -245,6 +245,7 @@ export default class InlineHelper extends AutocompleteHelper {
const btnSwitchToPM = Button('btn-primary btn-secondary btn-primary-transparent primary');
setInnerHTML(btnSwitchToPM, wrapEmojiText(botResults.switch_pm.text));
attachClickEvent(btnSwitchToPM, (e) => {
this.chat.appImManager.setInnerPeer({peerId});
this.managers.appInlineBotsManager.switchToPM(peerId, peer.id, botResults.switch_pm.start_param);
});
parent.append(btnSwitchToPM);

View File

@ -482,7 +482,16 @@ export default class ChatInput {
attachClickEvent(this.goMentionBtn, (e) => {
cancelEvent(e);
this.managers.appMessagesManager.goToNextMention(this.chat.peerId);
const middleware = this.chat.bubbles.getMiddleware();
this.managers.appMessagesManager.goToNextMention(this.chat.peerId).then((mid) => {
if(!middleware()) {
return;
}
if(mid) {
this.chat.setMessageId(mid);
}
});
}, {listenerSetter: this.listenerSetter});
this.btnScheduled = ButtonIcon('scheduled btn-scheduled float hide', {noRipple: true});

View File

@ -38,6 +38,7 @@ import safeAssign from "../../helpers/object/safeAssign";
import { AppManagers } from "../../lib/appManagers/managers";
import { attachContextMenuListener } from "../../helpers/dom/attachContextMenuListener";
import filterUnique from "../../helpers/array/filterUnique";
import appImManager from "../../lib/appManagers/appImManager";
const accumulateMapSet = (map: Map<any, Set<number>>) => {
return [...map.values()].reduce((acc, v) => acc + v.size, 0);
@ -627,10 +628,7 @@ export class SearchSelection extends AppSelection {
const mid = [...this.selectedMids.get(peerId)][0];
this.cancelSelection();
rootScope.dispatchEvent('history_focus', {
peerId,
mid
});
appImManager.setInnerPeer({peerId, lastMsgId: mid});
}, attachClickOptions);
this.selectionForwardBtn = ButtonIcon(`forward ${BASE_CLASS}-forward`);

View File

@ -18,6 +18,7 @@ import { ConnectionStatus } from "../lib/mtproto/connectionStatus";
import cancelEvent from "../helpers/dom/cancelEvent";
import { attachClickEvent } from "../helpers/dom/clickEvent";
import { AppManagers } from "../lib/appManagers/managers";
import singleInstance from "../lib/mtproto/singleInstance";
export default class ConnectionStatusComponent {
public static CHANGE_STATE_DELAY = 1000;
@ -145,6 +146,10 @@ export default class ConnectionStatusComponent {
}
private setState = () => {
if(singleInstance.deactivatedReason) {
return;
}
const timeout = ConnectionStatusComponent.CHANGE_STATE_DELAY;
if(this.connecting) {
if(this.timedOut) {

View File

@ -19,6 +19,7 @@ import ScrollableLoader from "../../helpers/scrollableLoader";
import { GroupCallParticipant } from "../../layer";
import type { AppChatsManager } from "../../lib/appManagers/appChatsManager";
import type { AppGroupCallsManager } from "../../lib/appManagers/appGroupCallsManager";
import appImManager from "../../lib/appManagers/appImManager";
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
import { AppManagers } from "../../lib/appManagers/managers";
import getPeerId from "../../lib/appManagers/utils/peers/getPeerId";
@ -157,9 +158,7 @@ export class GroupCallParticipantContextMenu {
popup.hide();
}
rootScope.dispatchEvent('history_focus', {
peerId: this.targetPeerId
});
appImManager.setInnerPeer({peerId: this.targetPeerId});
};
private toggleParticipantMuted = (muted: boolean) => {

View File

@ -32,10 +32,10 @@ export type PopupButton = {
};
export type PopupOptions = Partial<{
closable: true,
overlayClosable: true,
withConfirm: LangPackKey | true,
body: true,
closable: boolean,
overlayClosable: boolean,
withConfirm: LangPackKey | boolean,
body: boolean,
confirmShortcutIsSendShortcut: boolean,
withoutOverlay: boolean
}>;

View File

@ -8,11 +8,10 @@ import PopupElement, { addCancelButton } from ".";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
import numberThousandSplitter from "../../helpers/number/numberThousandSplitter";
import { ChatInvite } from "../../layer";
import { AppManagers } from "../../lib/appManagers/managers";
import appImManager from "../../lib/appManagers/appImManager";
import { i18n, _i18n } from "../../lib/langPack";
import { NULL_PEER_ID } from "../../lib/mtproto/mtproto_config";
import wrapEmojiText from "../../lib/richTextProcessor/wrapEmojiText";
import rootScope from "../../lib/rootScope";
import AvatarElement from "../avatar";
import putPhoto from "../putPhoto";
import { toastNew } from "../toast";
@ -31,7 +30,7 @@ export default class PopupJoinChatInvite extends PopupElement {
this.managers.appChatsManager.importChatInvite(hash)
.then((chatId) => {
const peerId = chatId.toPeerId(true);
rootScope.dispatchEvent('history_focus', {peerId});
appImManager.setInnerPeer({peerId});
}, (error) => {
if(error.type === 'INVITE_REQUEST_SENT') {
toastNew({langPackKey: 'RequestToJoinSent'});

View File

@ -94,7 +94,7 @@ export async function putAvatar(
}
const renderPromise = loadPromise
.then((url) => renderImageFromUrlPromise(img, url, !cached))
.then((url) => renderImageFromUrlPromise(img, url/* , !cached */))
.then(callback);
await (renderThumbPromise || renderPromise);
@ -166,7 +166,7 @@ export default async function putPhoto(
if(avatarAvailable/* && false */) {
const promise = putAvatar(div, peerId, photo, size, undefined, onlyThumb);
recordPromise(promise, 'putAvatar-' + peerId);
// recordPromise(promise, 'putAvatar-' + peerId);
return promise;
}
}

View File

@ -282,6 +282,10 @@ export class AppSidebarLeft extends SidebarSlider {
// this.updateBtn.prepend(weaveContainer);
attachClickEvent(this.updateBtn, () => {
if(this.updateBtn.classList.contains('is-hidden')) {
return;
}
location.reload();
});

View File

@ -12,6 +12,7 @@ import AvatarEdit from "../../avatarEdit";
import AppAddMembersTab from "./addMembers";
import { _i18n } from "../../../lib/langPack";
import ButtonCorner from "../../buttonCorner";
import appImManager from "../../../lib/appManagers/appImManager";
export default class AppNewChannelTab extends SliderSuperTab {
private uploadAvatar: () => Promise<InputFile> = null;
@ -74,6 +75,8 @@ export default class AppNewChannelTab extends SliderSuperTab {
this.managers.appChatsManager.editPhoto(channelId, inputFile);
});
}
appImManager.setInnerPeer({peerId: channelId.toPeerId(true)});
appSidebarLeft.removeTabFromHistory(this);
this.slider.createTab(AppAddMembersTab).open({

View File

@ -13,6 +13,7 @@ import AvatarEdit from "../../avatarEdit";
import I18n from "../../../lib/langPack";
import ButtonCorner from "../../buttonCorner";
import getUserStatusString from "../../wrappers/getUserStatusString";
import appImManager from "../../../lib/appManagers/appImManager";
interface OpenStreetMapInterface {
place_id?: number;
@ -79,9 +80,10 @@ export default class AppNewGroupTab extends SliderSuperTab {
this.nextBtn.addEventListener('click', () => {
const title = this.groupNameInputField.value;
if(this.isGeoChat){
let promise: Promise<ChatId>;
if(this.isGeoChat) {
if(!this.userLocationAddress || !this.userLocationCoords) return;
this.managers.appChatsManager.createChannel({
promise = this.managers.appChatsManager.createChannel({
title,
about: '',
geo_point: {
@ -97,26 +99,35 @@ export default class AppNewGroupTab extends SliderSuperTab {
});
}
if(this.peerIds.length){
if(this.peerIds.length) {
this.managers.appChatsManager.inviteToChannel(chatId, this.peerIds);
}
appSidebarLeft.removeTabFromHistory(this);
appSidebarLeft.selectTab(0);
return chatId;
});
} else {
this.nextBtn.disabled = true;
this.managers.appChatsManager.createChat(title, this.peerIds.map((peerId) => peerId.toUserId())).then((chatId) => {
promise = this.managers.appChatsManager.createChat(title, this.peerIds.map((peerId) => peerId.toUserId())).then((chatId) => {
if(this.uploadAvatar) {
this.uploadAvatar().then((inputFile) => {
this.managers.appChatsManager.editPhoto(chatId, inputFile);
});
}
appSidebarLeft.removeTabFromHistory(this);
appSidebarLeft.selectTab(0);
return chatId;
});
}
if(!promise) {
return;
}
promise.then((chatId) => {
appSidebarLeft.removeTabFromHistory(this);
appSidebarLeft.selectTab(0);
appImManager.setInnerPeer({peerId: chatId.toPeerId(true)});
});
});
const chatsSection = new SettingSection({

View File

@ -257,13 +257,13 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
loadPromises.push(loadThumbPromise);
}
const perf = performance.now();
// const perf = performance.now();
await loadThumbPromise;
const elapsedTime = performance.now() - perf;
if(elapsedTime > 4) {
console.log('wrapping photo thumb time', elapsedTime, photo, size);
}
// const elapsedTime = performance.now() - perf;
// if(elapsedTime > 4) {
// console.log('wrapping photo thumb time', elapsedTime, photo, size);
// }
return {
loadPromises: {

View File

@ -19,7 +19,7 @@ const App = {
version: process.env.VERSION,
versionFull: process.env.VERSION_FULL,
build: +process.env.BUILD,
langPackVersion: '0.4.0',
langPackVersion: '0.4.1',
langPack: 'macos',
langPackCode: 'en',
domains: [MAIN_DOMAIN] as string[],

View File

@ -9,7 +9,7 @@ import type { IDBIndex } from '../../lib/idb';
const DATABASE_STATE: Database<'session' | 'stickerSets' | 'users' | 'chats' | 'messages' | 'dialogs'> = {
name: 'tweb',
version: 11,
version: 7,
stores: [{
name: 'session'
}, {
@ -20,18 +20,18 @@ const DATABASE_STATE: Database<'session' | 'stickerSets' | 'users' | 'chats' | '
name: 'chats'
}, {
name: 'dialogs',
indexes: [
...(new Array(20 + 2).fill(0)).map((_, idx) => {
const name = `index_${idx}`;
const index: IDBIndex = {
indexName: name,
keyPath: name,
objectParameters: {}
};
// indexes: [
// ...(new Array(20 + 2).fill(0)).map((_, idx) => {
// const name = `index_${idx}`;
// const index: IDBIndex = {
// indexName: name,
// keyPath: name,
// objectParameters: {}
// };
return index
})
]
// return index
// })
// ]
}, {
name: 'messages'
}]

View File

@ -1,3 +1,3 @@
const IS_SHARED_WORKER_SUPPORTED = typeof(SharedWorker) !== 'undefined' && false;
const IS_SHARED_WORKER_SUPPORTED = typeof(SharedWorker) !== 'undefined'/* && false */;
export default IS_SHARED_WORKER_SUPPORTED;

View File

@ -0,0 +1,15 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
export default function partition<T>(arr: T[], callback: (item: T, idx: number, arr: T[]) => boolean) {
const good: T[] = [], bad: T[] = [];
for(let i = 0, length = arr.length; i < length; ++i) {
const item = arr[i];
(callback(item, i, arr) ? good : bad).push(item);
}
return [good, bad];
}

View File

@ -12,16 +12,18 @@ const FOCUS_EVENT_NAME = IS_TOUCH_SUPPORTED ? 'touchstart' : 'mousemove';
export class IdleController extends EventListenerBase<{
change: (idle: boolean) => void
}> {
public idle = {
isIDLE: true,
deactivated: false,
focusPromise: Promise.resolve(),
focusResolve: () => {}
};
private _isIdle: boolean;
private focusPromise: Promise<void>;
private focusResolve: () => void;
constructor() {
super();
this._isIdle = true;
this.focusPromise = Promise.resolve();
this.focusResolve = () => {};
window.addEventListener('blur', () => {
this.isIdle = true;
@ -37,21 +39,29 @@ export class IdleController extends EventListenerBase<{
this.addEventListener('change', (idle) => {
if(idle) {
this.idle.focusPromise = new Promise((resolve) => {
this.idle.focusResolve = resolve;
this.focusPromise = new Promise((resolve) => {
this.focusResolve = resolve;
});
} else {
this.idle.focusResolve();
this.focusResolve();
}
});
}
public getFocusPromise() {
return this.focusPromise;
}
public get isIdle() {
return this._isIdle;
}
public set isIdle(value: boolean) {
if(this.idle.isIDLE === value) {
if(this._isIdle === value) {
return;
}
this.idle.isIDLE = value;
this._isIdle = value;
this.dispatchEvent('change', value);
}
}

View File

@ -7,15 +7,21 @@
import ctx from "../environment/ctx";
import SuperMessagePort from "../lib/mtproto/superMessagePort";
export default function listenMessagePort(messagePort: SuperMessagePort<any, any, any>, onConnect?: (source: MessageEventSource) => void) {
if(typeof SharedWorkerGlobalScope !== 'undefined') {
(self as any as SharedWorkerGlobalScope).addEventListener('connect', (e) => {
const source = e.source;
messagePort.attachPort(source);
onConnect && onConnect(source);
});
export default function listenMessagePort(
messagePort: SuperMessagePort<any, any, any>,
onConnect?: (source: MessageEventSource) => void,
onDisconnect?: (source: MessageEventSource) => void
) {
const attachPort = (s: any) => {
messagePort.attachPort(s);
onConnect && onConnect(s);
};
onDisconnect && messagePort.setOnPortDisconnect(onDisconnect);
if(typeof(SharedWorkerGlobalScope) !== 'undefined') {
(ctx as any as SharedWorkerGlobalScope).addEventListener('connect', (e) => attachPort(e.source));
} else {
messagePort.attachPort(ctx);
onConnect && onConnect(ctx);
attachPort(ctx);
}
}

View File

@ -134,6 +134,7 @@ export default class ScrollSaver {
const newRect = this.anchor.getBoundingClientRect();
const diff = newRect.bottom - rect.bottom;
this.setScrollTop(scrollTop + diff, useReflow);
// if(diff) debugger;
// console.warn('scroll restore', rect, diff, newRect);
}

View File

@ -9,10 +9,10 @@ import AppStorage from "../lib/storage";
import sessionStorage from "../lib/sessionStorage";
import noop from "./noop";
export default function toggleStorages(enabled: boolean) {
export default function toggleStorages(enabled: boolean, clearWrite: boolean) {
return Promise.all([
AppStorage.toggleStorage(enabled),
CacheStorageController.toggleStorage(enabled),
sessionStorage.toggleStorage(enabled)
AppStorage.toggleStorage(enabled, clearWrite),
CacheStorageController.toggleStorage(enabled, clearWrite),
sessionStorage.toggleStorage(enabled, clearWrite)
]).then(noop, noop);
}

View File

@ -100,14 +100,14 @@ document.addEventListener('DOMContentLoaded', async() => {
};
let tabId: number;
rootScope.addEventListener('im_tab_change', (id) => {
(window as any).onImTabChange = (id: number) => {
const wasTabId = tabId !== undefined;
tabId = id;
if(wasTabId || tabId === 1) {
toggleResizeMode();
}
});
};
overlayCounter.addEventListener('change', () => {
toggleResizeMode();

View File

@ -55,6 +55,8 @@ const lang = {
},
"Deactivated.Title": "Too many tabs...",
"Deactivated.Subtitle": "Telegram supports only one active tab with the app.\nClick anywhere to continue using this tab.",
"Deactivated.Version.Title": "WebK has updated...",
"Deactivated.Version.Subtitle": "Another tab is running a newer version of Telegram.\nClick anywhere to reload this tab.",
// "Drafts": {
// "one_value": "%d draft",
// "other_value": "%d drafts",

230
src/layer.d.ts vendored
View File

@ -498,8 +498,6 @@ export namespace User {
apply_min_photo?: true,
fake?: true,
bot_attach_menu?: true,
premium?: true,
attach_menu_enabled?: true,
}>,
id: string | number,
access_hash?: string | number,
@ -1121,10 +1119,8 @@ export namespace MessageAction {
export type messageActionPaymentSent = {
_: 'messageActionPaymentSent',
flags?: number,
currency: string,
total_amount: string | number,
invoice_slug?: string
total_amount: string | number
};
export type messageActionPhoneCall = {
@ -1994,7 +1990,7 @@ export namespace MessagesFilter {
/**
* @link https://core.telegram.org/type/Update
*/
export type Update = Update.updateNewMessage | Update.updateMessageID | Update.updateDeleteMessages | Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChatParticipants | Update.updateUserStatus | Update.updateUserName | Update.updateUserPhoto | Update.updateNewEncryptedMessage | Update.updateEncryptedChatTyping | Update.updateEncryption | Update.updateEncryptedMessagesRead | Update.updateChatParticipantAdd | Update.updateChatParticipantDelete | Update.updateDcOptions | Update.updateNotifySettings | Update.updateServiceNotification | Update.updatePrivacy | Update.updateUserPhone | Update.updateReadHistoryInbox | Update.updateReadHistoryOutbox | Update.updateWebPage | Update.updateReadMessagesContents | Update.updateChannelTooLong | Update.updateChannel | Update.updateNewChannelMessage | Update.updateReadChannelInbox | Update.updateDeleteChannelMessages | Update.updateChannelMessageViews | Update.updateChatParticipantAdmin | Update.updateNewStickerSet | Update.updateStickerSetsOrder | Update.updateStickerSets | Update.updateSavedGifs | Update.updateBotInlineQuery | Update.updateBotInlineSend | Update.updateEditChannelMessage | Update.updateBotCallbackQuery | Update.updateEditMessage | Update.updateInlineBotCallbackQuery | Update.updateReadChannelOutbox | Update.updateDraftMessage | Update.updateReadFeaturedStickers | Update.updateRecentStickers | Update.updateConfig | Update.updatePtsChanged | Update.updateChannelWebPage | Update.updateDialogPinned | Update.updatePinnedDialogs | Update.updateBotWebhookJSON | Update.updateBotWebhookJSONQuery | Update.updateBotShippingQuery | Update.updateBotPrecheckoutQuery | Update.updatePhoneCall | Update.updateLangPackTooLong | Update.updateLangPack | Update.updateFavedStickers | Update.updateChannelReadMessagesContents | Update.updateContactsReset | Update.updateChannelAvailableMessages | Update.updateDialogUnreadMark | Update.updateMessagePoll | Update.updateChatDefaultBannedRights | Update.updateFolderPeers | Update.updatePeerSettings | Update.updatePeerLocated | Update.updateNewScheduledMessage | Update.updateDeleteScheduledMessages | Update.updateTheme | Update.updateGeoLiveViewed | Update.updateLoginToken | Update.updateMessagePollVote | Update.updateDialogFilter | Update.updateDialogFilterOrder | Update.updateDialogFilters | Update.updatePhoneCallSignalingData | Update.updateChannelMessageForwards | Update.updateReadChannelDiscussionInbox | Update.updateReadChannelDiscussionOutbox | Update.updatePeerBlocked | Update.updateChannelUserTyping | Update.updatePinnedMessages | Update.updatePinnedChannelMessages | Update.updateChat | Update.updateGroupCallParticipants | Update.updateGroupCall | Update.updatePeerHistoryTTL | Update.updateChatParticipant | Update.updateChannelParticipant | Update.updateBotStopped | Update.updateGroupCallConnection | Update.updateBotCommands | Update.updatePendingJoinRequests | Update.updateBotChatInviteRequester | Update.updateMessageReactions | Update.updateAttachMenuBots | Update.updateWebViewResultSent | Update.updateBotMenuButton | Update.updateSavedRingtones | Update.updateTranscribeAudio | Update.updateNewDiscussionMessage | Update.updateDeleteDiscussionMessages | Update.updateChannelReload;
export type Update = Update.updateNewMessage | Update.updateMessageID | Update.updateDeleteMessages | Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChatParticipants | Update.updateUserStatus | Update.updateUserName | Update.updateUserPhoto | Update.updateNewEncryptedMessage | Update.updateEncryptedChatTyping | Update.updateEncryption | Update.updateEncryptedMessagesRead | Update.updateChatParticipantAdd | Update.updateChatParticipantDelete | Update.updateDcOptions | Update.updateNotifySettings | Update.updateServiceNotification | Update.updatePrivacy | Update.updateUserPhone | Update.updateReadHistoryInbox | Update.updateReadHistoryOutbox | Update.updateWebPage | Update.updateReadMessagesContents | Update.updateChannelTooLong | Update.updateChannel | Update.updateNewChannelMessage | Update.updateReadChannelInbox | Update.updateDeleteChannelMessages | Update.updateChannelMessageViews | Update.updateChatParticipantAdmin | Update.updateNewStickerSet | Update.updateStickerSetsOrder | Update.updateStickerSets | Update.updateSavedGifs | Update.updateBotInlineQuery | Update.updateBotInlineSend | Update.updateEditChannelMessage | Update.updateBotCallbackQuery | Update.updateEditMessage | Update.updateInlineBotCallbackQuery | Update.updateReadChannelOutbox | Update.updateDraftMessage | Update.updateReadFeaturedStickers | Update.updateRecentStickers | Update.updateConfig | Update.updatePtsChanged | Update.updateChannelWebPage | Update.updateDialogPinned | Update.updatePinnedDialogs | Update.updateBotWebhookJSON | Update.updateBotWebhookJSONQuery | Update.updateBotShippingQuery | Update.updateBotPrecheckoutQuery | Update.updatePhoneCall | Update.updateLangPackTooLong | Update.updateLangPack | Update.updateFavedStickers | Update.updateChannelReadMessagesContents | Update.updateContactsReset | Update.updateChannelAvailableMessages | Update.updateDialogUnreadMark | Update.updateMessagePoll | Update.updateChatDefaultBannedRights | Update.updateFolderPeers | Update.updatePeerSettings | Update.updatePeerLocated | Update.updateNewScheduledMessage | Update.updateDeleteScheduledMessages | Update.updateTheme | Update.updateGeoLiveViewed | Update.updateLoginToken | Update.updateMessagePollVote | Update.updateDialogFilter | Update.updateDialogFilterOrder | Update.updateDialogFilters | Update.updatePhoneCallSignalingData | Update.updateChannelMessageForwards | Update.updateReadChannelDiscussionInbox | Update.updateReadChannelDiscussionOutbox | Update.updatePeerBlocked | Update.updateChannelUserTyping | Update.updatePinnedMessages | Update.updatePinnedChannelMessages | Update.updateChat | Update.updateGroupCallParticipants | Update.updateGroupCall | Update.updatePeerHistoryTTL | Update.updateChatParticipant | Update.updateChannelParticipant | Update.updateBotStopped | Update.updateGroupCallConnection | Update.updateBotCommands | Update.updatePendingJoinRequests | Update.updateBotChatInviteRequester | Update.updateMessageReactions | Update.updateAttachMenuBots | Update.updateWebViewResultSent | Update.updateBotMenuButton | Update.updateSavedRingtones | Update.updateNewDiscussionMessage | Update.updateDeleteDiscussionMessages | Update.updateChannelReload;
export namespace Update {
export type updateNewMessage = {
@ -2702,16 +2698,6 @@ export namespace Update {
_: 'updateSavedRingtones'
};
export type updateTranscribeAudio = {
_: 'updateTranscribeAudio',
flags?: number,
pFlags?: Partial<{
final?: true,
}>,
transcription_id: string | number,
text: string
};
export type updateNewDiscussionMessage = {
_: 'updateNewDiscussionMessage',
message?: Message
@ -2950,7 +2936,6 @@ export namespace DcOption {
tcpo_only?: true,
cdn?: true,
static?: true,
this_port_only?: true,
}>,
id: number,
ip_address: string,
@ -2976,7 +2961,6 @@ export namespace Config {
revoke_pm_inbox?: true,
blocked_mode?: true,
pfs_enabled?: true,
force_try_ipv6?: true,
}>,
date: number,
expires: number,
@ -3157,7 +3141,7 @@ export namespace EncryptedFile {
_: 'encryptedFile',
id: string | number,
access_hash: string | number,
size: string | number,
size: number,
dc_id: number,
key_fingerprint: number
};
@ -3944,7 +3928,7 @@ export namespace ReceivedNotifyMessage {
/**
* @link https://core.telegram.org/type/ExportedChatInvite
*/
export type ExportedChatInvite = ExportedChatInvite.chatInviteExported | ExportedChatInvite.chatInvitePublicJoinRequests;
export type ExportedChatInvite = ExportedChatInvite.chatInviteExported;
export namespace ExportedChatInvite {
export type chatInviteExported = {
@ -3965,10 +3949,6 @@ export namespace ExportedChatInvite {
requested?: number,
title?: string
};
export type chatInvitePublicJoinRequests = {
_: 'chatInvitePublicJoinRequests'
};
}
/**
@ -4110,13 +4090,10 @@ export type BotInfo = BotInfo.botInfo;
export namespace BotInfo {
export type botInfo = {
_: 'botInfo',
flags?: number,
user_id?: string | number,
description?: string,
description_photo?: Photo,
description_document?: Document,
commands?: Array<BotCommand>,
menu_button?: BotMenuButton
user_id: string | number,
description: string,
commands: Array<BotCommand>,
menu_button: BotMenuButton
};
}
@ -4295,7 +4272,7 @@ export namespace ReplyMarkup {
/**
* @link https://core.telegram.org/type/MessageEntity
*/
export type MessageEntity = MessageEntity.messageEntityUnknown | MessageEntity.messageEntityMention | MessageEntity.messageEntityHashtag | MessageEntity.messageEntityBotCommand | MessageEntity.messageEntityUrl | MessageEntity.messageEntityEmail | MessageEntity.messageEntityBold | MessageEntity.messageEntityItalic | MessageEntity.messageEntityCode | MessageEntity.messageEntityPre | MessageEntity.messageEntityTextUrl | MessageEntity.messageEntityMentionName | MessageEntity.inputMessageEntityMentionName | MessageEntity.messageEntityPhone | MessageEntity.messageEntityCashtag | MessageEntity.messageEntityUnderline | MessageEntity.messageEntityStrike | MessageEntity.messageEntityBlockquote | MessageEntity.messageEntityBankCard | MessageEntity.messageEntitySpoiler | MessageEntity.messageEntityAnimatedEmoji | MessageEntity.messageEntityEmoji | MessageEntity.messageEntityHighlight | MessageEntity.messageEntityLinebreak | MessageEntity.messageEntityCaret;
export type MessageEntity = MessageEntity.messageEntityUnknown | MessageEntity.messageEntityMention | MessageEntity.messageEntityHashtag | MessageEntity.messageEntityBotCommand | MessageEntity.messageEntityUrl | MessageEntity.messageEntityEmail | MessageEntity.messageEntityBold | MessageEntity.messageEntityItalic | MessageEntity.messageEntityCode | MessageEntity.messageEntityPre | MessageEntity.messageEntityTextUrl | MessageEntity.messageEntityMentionName | MessageEntity.inputMessageEntityMentionName | MessageEntity.messageEntityPhone | MessageEntity.messageEntityCashtag | MessageEntity.messageEntityUnderline | MessageEntity.messageEntityStrike | MessageEntity.messageEntityBlockquote | MessageEntity.messageEntityBankCard | MessageEntity.messageEntitySpoiler | MessageEntity.messageEntityEmoji | MessageEntity.messageEntityHighlight | MessageEntity.messageEntityLinebreak | MessageEntity.messageEntityCaret;
export namespace MessageEntity {
export type messageEntityUnknown = {
@ -4423,12 +4400,6 @@ export namespace MessageEntity {
length: number
};
export type messageEntityAnimatedEmoji = {
_: 'messageEntityAnimatedEmoji',
offset: number,
length: number
};
export type messageEntityEmoji = {
_: 'messageEntityEmoji',
offset?: number,
@ -5991,9 +5962,6 @@ export namespace PaymentsPaymentForm {
}>,
form_id: string | number,
bot_id: string | number,
title: string,
description: string,
photo?: WebDocument,
invoice: Invoice,
provider_id: string | number,
url: string,
@ -6262,10 +6230,6 @@ export type PhoneConnection = PhoneConnection.phoneConnection | PhoneConnection.
export namespace PhoneConnection {
export type phoneConnection = {
_: 'phoneConnection',
flags?: number,
pFlags?: Partial<{
tcp?: true,
}>,
id: string | number,
ip: string,
ipv6: string,
@ -6913,7 +6877,7 @@ export type FileHash = FileHash.fileHash;
export namespace FileHash {
export type fileHash = {
_: 'fileHash',
offset: string | number,
offset: number,
limit: number,
hash: Uint8Array
};
@ -6986,7 +6950,7 @@ export namespace SecureFile {
_: 'secureFile',
id: string | number,
access_hash: string | number,
size: string | number,
size: number,
dc_id: number,
date: number,
file_hash: Uint8Array,
@ -8365,7 +8329,7 @@ export namespace PaymentsBankCardData {
/**
* @link https://core.telegram.org/type/DialogFilter
*/
export type DialogFilter = DialogFilter.dialogFilter | DialogFilter.dialogFilterDefault;
export type DialogFilter = DialogFilter.dialogFilter;
export namespace DialogFilter {
export type dialogFilter = {
@ -8391,10 +8355,6 @@ export namespace DialogFilter {
peerId?: PeerId,
folder_id?: number
};
export type dialogFilterDefault = {
_: 'dialogFilterDefault'
};
}
/**
@ -9235,9 +9195,6 @@ export namespace SponsoredMessage {
export type sponsoredMessage = {
_: 'sponsoredMessage',
flags?: number,
pFlags?: Partial<{
recommended?: true,
}>,
random_id: Uint8Array,
from_id?: Peer,
chat_invite?: ChatInvite,
@ -9446,7 +9403,6 @@ export namespace AvailableReaction {
flags?: number,
pFlags?: Partial<{
inactive?: true,
premium?: true,
}>,
reaction: string,
title: string,
@ -9592,7 +9548,6 @@ export namespace AttachMenuBot {
}>,
bot_id: string | number,
short_name: string,
peer_types: Array<AttachMenuPeerType>,
icons: Array<AttachMenuBotIcon>
};
}
@ -9746,80 +9701,6 @@ export namespace AccountSavedRingtone {
};
}
/**
* @link https://core.telegram.org/type/AttachMenuPeerType
*/
export type AttachMenuPeerType = AttachMenuPeerType.attachMenuPeerTypeSameBotPM | AttachMenuPeerType.attachMenuPeerTypeBotPM | AttachMenuPeerType.attachMenuPeerTypePM | AttachMenuPeerType.attachMenuPeerTypeChat | AttachMenuPeerType.attachMenuPeerTypeBroadcast;
export namespace AttachMenuPeerType {
export type attachMenuPeerTypeSameBotPM = {
_: 'attachMenuPeerTypeSameBotPM'
};
export type attachMenuPeerTypeBotPM = {
_: 'attachMenuPeerTypeBotPM'
};
export type attachMenuPeerTypePM = {
_: 'attachMenuPeerTypePM'
};
export type attachMenuPeerTypeChat = {
_: 'attachMenuPeerTypeChat'
};
export type attachMenuPeerTypeBroadcast = {
_: 'attachMenuPeerTypeBroadcast'
};
}
/**
* @link https://core.telegram.org/type/InputInvoice
*/
export type InputInvoice = InputInvoice.inputInvoiceMessage | InputInvoice.inputInvoiceSlug;
export namespace InputInvoice {
export type inputInvoiceMessage = {
_: 'inputInvoiceMessage',
peer: InputPeer,
msg_id: number
};
export type inputInvoiceSlug = {
_: 'inputInvoiceSlug',
slug: string
};
}
/**
* @link https://core.telegram.org/type/payments.ExportedInvoice
*/
export type PaymentsExportedInvoice = PaymentsExportedInvoice.paymentsExportedInvoice;
export namespace PaymentsExportedInvoice {
export type paymentsExportedInvoice = {
_: 'payments.exportedInvoice',
url: string
};
}
/**
* @link https://core.telegram.org/type/messages.TranscribedAudio
*/
export type MessagesTranscribedAudio = MessagesTranscribedAudio.messagesTranscribedAudio;
export namespace MessagesTranscribedAudio {
export type messagesTranscribedAudio = {
_: 'messages.transcribedAudio',
flags?: number,
pFlags?: Partial<{
pending?: true,
}>,
transcription_id: string | number,
text: string
};
}
export interface ConstructorDeclMap {
'error': Error.error,
'inputPeerEmpty': InputPeer.inputPeerEmpty,
@ -10803,19 +10684,6 @@ export interface ConstructorDeclMap {
'notificationSoundRingtone': NotificationSound.notificationSoundRingtone,
'account.savedRingtone': AccountSavedRingtone.accountSavedRingtone,
'account.savedRingtoneConverted': AccountSavedRingtone.accountSavedRingtoneConverted,
'attachMenuPeerTypeSameBotPM': AttachMenuPeerType.attachMenuPeerTypeSameBotPM,
'attachMenuPeerTypeBotPM': AttachMenuPeerType.attachMenuPeerTypeBotPM,
'attachMenuPeerTypePM': AttachMenuPeerType.attachMenuPeerTypePM,
'attachMenuPeerTypeChat': AttachMenuPeerType.attachMenuPeerTypeChat,
'attachMenuPeerTypeBroadcast': AttachMenuPeerType.attachMenuPeerTypeBroadcast,
'chatInvitePublicJoinRequests': ExportedChatInvite.chatInvitePublicJoinRequests,
'inputInvoiceMessage': InputInvoice.inputInvoiceMessage,
'inputInvoiceSlug': InputInvoice.inputInvoiceSlug,
'payments.exportedInvoice': PaymentsExportedInvoice.paymentsExportedInvoice,
'updateTranscribeAudio': Update.updateTranscribeAudio,
'messages.transcribedAudio': MessagesTranscribedAudio.messagesTranscribedAudio,
'dialogFilterDefault': DialogFilter.dialogFilterDefault,
'messageEntityAnimatedEmoji': MessageEntity.messageEntityAnimatedEmoji,
'messageEntityEmoji': MessageEntity.messageEntityEmoji,
'messageEntityHighlight': MessageEntity.messageEntityHighlight,
'messageEntityLinebreak': MessageEntity.messageEntityLinebreak,
@ -11201,7 +11069,7 @@ export type UploadGetFile = {
precise?: boolean,
cdn_supported?: boolean,
location: InputFileLocation,
offset: string | number,
offset: number,
limit: number
};
@ -11626,7 +11494,7 @@ export type MessagesReorderStickerSets = {
export type MessagesGetDocumentByHash = {
sha256: Uint8Array,
size: string | number,
size: number,
mime_type: string
};
@ -11925,7 +11793,8 @@ export type UploadGetWebFile = {
export type PaymentsGetPaymentForm = {
flags?: number,
invoice: InputInvoice,
peer: InputPeer,
msg_id: number,
theme_params?: DataJSON
};
@ -11937,14 +11806,16 @@ export type PaymentsGetPaymentReceipt = {
export type PaymentsValidateRequestedInfo = {
flags?: number,
save?: boolean,
invoice: InputInvoice,
peer: InputPeer,
msg_id: number,
info: PaymentRequestedInfo
};
export type PaymentsSendPaymentForm = {
flags?: number,
form_id: string | number,
invoice: InputInvoice,
peer: InputPeer,
msg_id: number,
requested_info_id?: string,
shipping_option_id?: string,
credentials: InputPaymentCredentials,
@ -12066,7 +11937,7 @@ export type PhoneSaveCallDebug = {
export type UploadGetCdnFile = {
file_token: Uint8Array,
offset: string | number,
offset: number,
limit: number
};
@ -12119,7 +11990,7 @@ export type ChannelsGetAdminLog = {
export type UploadGetCdnFileHashes = {
file_token: Uint8Array,
offset: string | number
offset: number
};
export type MessagesSendScreenshotNotification = {
@ -12225,7 +12096,7 @@ export type MessagesSearchStickerSets = {
export type UploadGetFileHashes = {
location: InputFileLocation,
offset: string | number
offset: number
};
export type HelpGetTermsOfServiceUpdate = {
@ -12312,7 +12183,7 @@ export type AccountInitTakeoutSession = {
message_megagroups?: boolean,
message_channels?: boolean,
files?: boolean,
file_max_size?: string | number
file_max_size?: number
};
export type AccountFinishTakeoutSession = {
@ -13261,8 +13132,7 @@ export type MessagesRequestWebView = {
url?: string,
start_param?: string,
theme_params?: DataJSON,
reply_to_msg_id?: number,
send_as?: InputPeer
reply_to_msg_id?: number
};
export type MessagesProlongWebView = {
@ -13271,8 +13141,7 @@ export type MessagesProlongWebView = {
peer: InputPeer,
bot: InputUser,
query_id: string | number,
reply_to_msg_id?: number,
send_as?: InputPeer
reply_to_msg_id?: number
};
export type MessagesRequestSimpleWebView = {
@ -13326,45 +13195,6 @@ export type BotsSetBotGroupDefaultAdminRights = {
admin_rights: ChatAdminRights
};
export type PhoneSaveCallLog = {
peer: InputPhoneCall,
file: InputFile
};
export type ChannelsToggleJoinToSend = {
channel: InputChannel,
enabled: boolean
};
export type ChannelsToggleJoinRequest = {
channel: InputChannel,
enabled: boolean
};
export type PaymentsExportInvoice = {
invoice_media: InputMedia
};
export type MessagesTranscribeAudio = {
peer: InputPeer,
msg_id: number
};
export type MessagesRateTranscribedAudio = {
peer: InputPeer,
msg_id: number,
transcription_id: string | number,
good: boolean
};
export type PaymentsAssignAppStoreTransaction = {
transaction_id: string
};
export type PaymentsAssignPlayMarketTransaction = {
purchase_token: string
};
export interface MethodDeclMap {
'invokeAfterMsg': {req: InvokeAfterMsg, res: any},
'invokeAfterMsgs': {req: InvokeAfterMsgs, res: any},
@ -13800,13 +13630,5 @@ export interface MethodDeclMap {
'account.uploadRingtone': {req: AccountUploadRingtone, res: Document},
'bots.setBotBroadcastDefaultAdminRights': {req: BotsSetBotBroadcastDefaultAdminRights, res: boolean},
'bots.setBotGroupDefaultAdminRights': {req: BotsSetBotGroupDefaultAdminRights, res: boolean},
'phone.saveCallLog': {req: PhoneSaveCallLog, res: boolean},
'channels.toggleJoinToSend': {req: ChannelsToggleJoinToSend, res: Updates},
'channels.toggleJoinRequest': {req: ChannelsToggleJoinRequest, res: Updates},
'payments.exportInvoice': {req: PaymentsExportInvoice, res: PaymentsExportedInvoice},
'messages.transcribeAudio': {req: MessagesTranscribeAudio, res: MessagesTranscribedAudio},
'messages.rateTranscribedAudio': {req: MessagesRateTranscribedAudio, res: boolean},
'payments.assignAppStoreTransaction': {req: PaymentsAssignAppStoreTransaction, res: Updates},
'payments.assignPlayMarketTransaction': {req: PaymentsAssignPlayMarketTransaction, res: Updates},
}

View File

@ -377,8 +377,6 @@ export class AppChatsManager extends AppManager {
this.apiUpdatesManager.processUpdateMessage(updates);
const channelId = (updates as any).chats[0].id;
this.rootScope.dispatchEvent('history_focus', {peerId: channelId.toPeerId(true)});
return channelId;
});
}
@ -401,8 +399,6 @@ export class AppChatsManager extends AppManager {
this.apiUpdatesManager.processUpdateMessage(updates);
const chatId = (updates as any as Updates.updates).chats[0].id;
this.rootScope.dispatchEvent('history_focus', {peerId: chatId.toPeerId(true)});
return chatId;
});
}

View File

@ -1618,7 +1618,7 @@ export class AppDialogsManager {
}
lastMessage = dialog.topMessage;
if(!lastMessage) {
if(!lastMessage || lastMessage.mid !== dialog.top_message) {
const promise = this.managers.appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message);
lastMessage = await middleware(promise);
}

View File

@ -6,7 +6,7 @@
import type { ApiFileManager, DownloadMediaOptions, DownloadOptions } from "../mtproto/apiFileManager";
import deferredPromise, { CancellablePromise } from "../../helpers/cancellablePromise";
import { Document, InputFile } from "../../layer";
import { Document, InputFile, Photo, PhotoSize } from "../../layer";
import { getFileNameByLocation } from "../../helpers/fileName";
import getFileNameForUpload from "../../helpers/getFileNameForUpload";
import { AppManagers } from "./managers";
@ -201,11 +201,18 @@ export class AppDownloadManager {
// }
public downloadToDisc(options: DownloadMediaOptions) {
const media = options.media;
const isDocument = media._ === 'document';
if(!isDocument && !options.thumb) {
options.thumb = (media as Photo.photo).sizes.slice().pop() as PhotoSize.photoSize;
}
const promise = this.downloadMedia(options);
promise.then((blob) => {
const url = URL.createObjectURL(blob);
const media = options.media;
const downloadOptions = media._ === 'document' ? getDocumentDownloadOptions(media) : getPhotoDownloadOptions(media as any, {} as any);
const downloadOptions = isDocument ?
getDocumentDownloadOptions(media) :
getPhotoDownloadOptions(media as any, options.thumb);
const fileName = (options.media as Document.document).file_name || getFileNameByLocation(downloadOptions.location);
createDownloadAnchor(url, fileName, () => {
URL.revokeObjectURL(url);

View File

@ -35,7 +35,7 @@ import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd';
import replaceContent from '../../helpers/dom/replaceContent';
import whichChild from '../../helpers/dom/whichChild';
import PopupElement from '../../components/popups';
import singleInstance from '../mtproto/singleInstance';
import singleInstance, { InstanceDeactivateReason, SingleInstance } from '../mtproto/singleInstance';
import PopupStickers from '../../components/popups/stickers';
import PopupJoinChatInvite from '../../components/popups/joinChatInvite';
import { toast, toastNew } from '../../components/toast';
@ -88,6 +88,7 @@ import callsController from '../calls/callsController';
import getFilesFromEvent from '../../helpers/files/getFilesFromEvent';
import apiManagerProxy from '../mtproto/mtprotoworker';
import wrapPeerTitle from '../../components/wrappers/peerTitle';
import appRuntimeManager from './appRuntimeManager';
export const CHAT_ANIMATION_GROUP = 'chat';
@ -244,20 +245,6 @@ export class AppImManager extends EventListenerBase<{
});
});
rootScope.addEventListener('history_focus', (e) => {
let {peerId, threadId, mid, startParam} = e;
if(threadId) threadId = generateMessageId(threadId);
if(mid) mid = generateMessageId(mid); // because mid can come from notification, i.e. server message id
this.setInnerPeer({
peerId,
lastMsgId: mid,
type: threadId ? 'discussion' : undefined,
threadId,
startParam
});
});
this.addEventListener('peer_changing', (chat) => {
this.saveChatPosition(chat);
});
@ -305,7 +292,8 @@ export class AppImManager extends EventListenerBase<{
}
});
singleInstance.addEventListener('deactivated', () => {
const onInstanceDeactivated = (reason: InstanceDeactivateReason) => {
const isUpdated = reason === 'version';
const popup = new PopupElement('popup-instance-deactivated', undefined, {overlayClosable: true});
const c = document.createElement('div');
c.classList.add('instance-deactivated-container');
@ -313,17 +301,19 @@ export class AppImManager extends EventListenerBase<{
const header = document.createElement('div');
header.classList.add('header');
header.append(i18n('Deactivated.Title'));
header.append(i18n(isUpdated ? 'Deactivated.Version.Title' : 'Deactivated.Title'));
const subtitle = document.createElement('div');
subtitle.classList.add('subtitle');
subtitle.append(i18n('Deactivated.Subtitle'));
subtitle.append(i18n(isUpdated ? 'Deactivated.Version.Subtitle' : 'Deactivated.Subtitle'));
c.append(header, subtitle);
document.body.classList.add('deactivated');
popup.addEventListener('close', () => {
const onClose = isUpdated ? () => {
appRuntimeManager.reload();
} : () => {
document.body.classList.add('deactivated-backwards');
singleInstance.activateInstance();
@ -331,10 +321,16 @@ export class AppImManager extends EventListenerBase<{
setTimeout(() => {
document.body.classList.remove('deactivated', 'deactivated-backwards');
}, 333);
});
};
popup.addEventListener('close', onClose);
popup.show();
});
};
singleInstance.addEventListener('deactivated', onInstanceDeactivated);
if(singleInstance.deactivatedReason) {
onInstanceDeactivated(singleInstance.deactivatedReason);
}
// remove scroll listener when setting chat to tray
this.addEventListener('chat_changing', ({to}) => {
@ -348,8 +344,8 @@ export class AppImManager extends EventListenerBase<{
});
});
rootScope.addEventListener('notification_build', (options) => {
if(this.chat.peerId === options.message.peerId && !idleController.idle.isIDLE) {
apiManagerProxy.addEventListener('notificationBuild', (options) => {
if(this.chat.peerId === options.message.peerId && !idleController.isIdle) {
return;
}
@ -366,6 +362,8 @@ export class AppImManager extends EventListenerBase<{
}
appNavigationController.overrideHash(str);
apiManagerProxy.updateTabState('chatPeerIds', this.chats.map((chat) => chat.peerId).filter(Boolean));
});
// stateStorage.get('chatPositions').then((c) => {
@ -1502,7 +1500,8 @@ export class AppImManager extends EventListenerBase<{
}
}
rootScope.dispatchEvent('im_tab_change', id);
const onImTabChange = (window as any).onImTabChange;
onImTabChange && onImTabChange(id);
//this._selectTab(id, mediaSizes.isMobile);
//document.body.classList.toggle(RIGHT_COLUMN_ACTIVE_CLASSNAME, id === 2);
@ -1664,6 +1663,10 @@ export class AppImManager extends EventListenerBase<{
return;
}
if(options.threadId) {
options.type = 'discussion';
}
const type = options.type ??= 'chat';
// * prevent opening already opened peer

View File

@ -97,7 +97,6 @@ export class AppInlineBotsManager extends AppManager {
public switchToPM(fromPeerId: PeerId, botId: BotId, startParam: string) {
this.setHash[botId] = {peerId: fromPeerId, time: Date.now()};
this.rootScope.dispatchEvent('history_focus', {peerId: botId.toPeerId()});
return this.appMessagesManager.startBot(botId, undefined, startParam);
}
@ -224,7 +223,6 @@ export class AppInlineBotsManager extends AppManager {
}
public switchInlineQuery(peerId: PeerId, threadId: number, botId: BotId, query: string) {
this.rootScope.dispatchEvent('history_focus', {peerId, threadId});
this.appDraftsManager.setDraft(peerId, threadId, '@' + this.appUsersManager.getUser(botId).username + ' ' + query);
}

View File

@ -44,8 +44,7 @@ export class AppManagersManager {
this.cryptoPortAttached = true;
const port = event.ports[0];
cryptoMessagePort.attachListenPort(port);
cryptoMessagePort.attachSendPort(port);
cryptoMessagePort.attachPort(port);
this.cryptoPortPromise.resolve();
});
}

View File

@ -56,6 +56,8 @@ import getDocumentInputFileName from "./utils/docs/getDocumentInputFileName";
import getFileNameForUpload from "../../helpers/getFileNameForUpload";
import type { Progress } from "./appDownloadManager";
import noop from "../../helpers/noop";
import appTabsManager from "./appTabsManager";
import MTProtoMessagePort from "../mtproto/mtprotoMessagePort";
//console.trace('include');
// TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках
@ -209,7 +211,7 @@ export class AppMessagesManager extends AppManager {
private middleware: ReturnType<typeof getMiddleware>;
private unreadMentions: {[peerId: PeerId]: SlicedArray<number>} = {};
private goToNextMentionPromises: {[peerId: PeerId]: Promise<any>} = {};
private goToNextMentionPromises: {[peerId: PeerId]: Promise<number>} = {};
private batchUpdates: {
[k in keyof BatchUpdates]?: {
@ -3543,9 +3545,10 @@ export class AppMessagesManager extends AppManager {
private handleNewMessage(peerId: PeerId, mid: number) {
(this.newMessagesToHandle[peerId] ??= new Set()).add(mid);
if(!this.newMessagesHandleTimeout) {
this.newMessagesHandleTimeout = ctx.setTimeout(this.handleNewMessages, 0);
}
// if(!this.newMessagesHandleTimeout) {
// this.newMessagesHandleTimeout = ctx.setTimeout(this.handleNewMessages, 0);
// }
this.handleNewMessages();
}
private handleNewMessages = () => {
@ -3822,7 +3825,7 @@ export class AppMessagesManager extends AppManager {
const mid = last && last[last.length - 1];
if(mid) {
slicedArray.delete(mid);
this.rootScope.dispatchEvent('history_focus', {peerId, mid});
return mid;
} else {
this.fixUnreadMentionsCountIfNeeded(peerId, slicedArray);
}
@ -5104,10 +5107,22 @@ export class AppMessagesManager extends AppManager {
return;
}
this.rootScope.dispatchEvent('notification_build', {
const tabs = appTabsManager.getTabs();
let tab = tabs.find((tab) => {
const {chatPeerIds} = tab.state;
return chatPeerIds[chatPeerIds.length - 1] === peerId;
});
if(!tab) {
tabs.sort((a, b) => a.state.idleStartTime - b.state.idleStartTime);
tab = !tabs[0].state.idleStartTime ? tabs[0] : tabs[tabs.length - 1];
}
const port = MTProtoMessagePort.getInstance<false>();
port.invokeVoid('notificationBuild', {
message,
...options
});
}, tab.source);
}
public getScheduledMessagesStorage(peerId: PeerId) {

View File

@ -43,7 +43,7 @@ export class AppStateManager {
}
public setKeyValueToStorage<T extends keyof State>(key: T, value: State[T] = this.state[key], onlyLocal?: boolean) {
MTProtoMessagePort.getInstance<false>().invoke('mirror', {name: 'state', key, value});
MTProtoMessagePort.getInstance<false>().invokeVoid('mirror', {name: 'state', key, value});
this.storage.set({
[key]: value

View File

@ -0,0 +1,52 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type { TabState } from "../mtproto/mtprotoworker";
import { MOUNT_CLASS_TO } from "../../config/debug";
import MTProtoMessagePort from "../mtproto/mtprotoMessagePort";
type Tab = {
source: MessageEventSource,
state: TabState
};
export class AppTabsManager {
private tabs: Map<Tab['source'], Tab>;
constructor() {
this.tabs = new Map();
}
public start() {
const port = MTProtoMessagePort.getInstance<false>();
port.addEventListener('tabState', (state, source) => {
const tab = this.tabs.get(source);
tab.state = state;
});
}
public getTabs() {
return [...this.tabs.values()].filter((tab) => !!tab.state);
}
public addTab(source: MessageEventSource) {
const tab: Tab = {
source,
state: undefined
};
this.tabs.set(source, tab);
}
public deleteTab(source: MessageEventSource) {
this.tabs.delete(source);
}
}
const appTabsManager = new AppTabsManager();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appTabsManager = appTabsManager);
export default appTabsManager;

View File

@ -11,52 +11,52 @@ import { AckedResult } from "../mtproto/superMessagePort";
import noop from "../../helpers/noop";
import dT from "../../helpers/dT";
let stats: {
[manager: string]: {
[method: string]: {
times: number[],
byArgs: {
[args: string]: number[]
}
}
}
} = {};
// let stats: {
// [manager: string]: {
// [method: string]: {
// times: number[],
// byArgs: {
// [args: string]: number[]
// }
// }
// }
// } = {};
let sentCount = 0;
let sentMethods: {[key: string]: number} = {};
let sentMethods2: {[key: string]: number} = {};
function collectStats(manager: string, method: string, args: any[], promise: Promise<any>) {
++sentCount;
// let sentCount = 0;
// let sentMethods: {[key: string]: number} = {};
// let sentMethods2: {[key: string]: number} = {};
// function collectStats(manager: string, method: string, args: any[], promise: Promise<any>) {
// ++sentCount;
const key = [manager, method].join('-');
if(!sentMethods[key]) sentMethods[key] = 0;
++sentMethods[key];
// const key = [manager, method].join('-');
// if(!sentMethods[key]) sentMethods[key] = 0;
// ++sentMethods[key];
const key2 = [('00000' + sentCount).slice(-5), key].join('-');
// const key2 = [('00000' + sentCount).slice(-5), key].join('-');
let byManager = stats[manager] ??= {};
let byMethod = byManager[method] ??= {times: [], byArgs: {}};
// let byManager = stats[manager] ??= {};
// let byMethod = byManager[method] ??= {times: [], byArgs: {}};
const perf = performance.now();
promise.catch(noop).finally(() => {
const time = performance.now() - perf;
byMethod.times.push(time);
// const perf = performance.now();
// promise.catch(noop).finally(() => {
// const time = performance.now() - perf;
// byMethod.times.push(time);
sentMethods2[key2] = time;
// sentMethods2[key2] = time;
try {
const argsString = JSON.stringify(args);
byMethod.byArgs[argsString].push(time);
} catch(err) {}
});
}
// try {
// const argsString = JSON.stringify(args);
// byMethod.byArgs[argsString].push(time);
// } catch(err) {}
// });
// }
setInterval(() => {
console.log(dT(), '[PROXY] stats', stats, sentCount, sentMethods, sentMethods2);
sentCount = 0;
sentMethods = {};
sentMethods2 = {};
}, 2000);
// setInterval(() => {
// // console.log(dT(), '[PROXY] stats', stats, sentCount, sentMethods, sentMethods2);
// sentCount = 0;
// sentMethods = {};
// sentMethods2 = {};
// }, 2000);
function createProxy(/* source: T, */name: string, ack?: boolean) {
const proxy = new Proxy({}, {
@ -75,7 +75,7 @@ function createProxy(/* source: T, */name: string, ack?: boolean) {
args
}, ack as any);
collectStats(name, p as string, args, promise);
// collectStats(name, p as string, args, promise);
return promise;
@ -114,7 +114,6 @@ export default function getProxiedManagers() {
proxied = createProxyProxy({}, false);
proxied.acknowledged = createProxyProxy({}, true);
proxied.acknowledged.apiFileManager.cancelDownload('asd');
return proxied;
}

View File

@ -22,8 +22,10 @@ import webPushApiManager, { PushSubscriptionNotify } from "../mtproto/webPushApi
import fixEmoji from "../richTextProcessor/fixEmoji";
import wrapPlainText from "../richTextProcessor/wrapPlainText";
import rootScope from "../rootScope";
import appImManager from "./appImManager";
import appRuntimeManager from "./appRuntimeManager";
import { AppManagers } from "./managers";
import generateMessageId from "./utils/messageId/generateMessageId";
import getPeerId from "./utils/peers/getPeerId";
type MyNotification = Notification & {
@ -124,7 +126,7 @@ export class UiNotificationsManager {
this.cancel(str);
});
rootScope.addEventListener('push_init', (tokenData) => {
webPushApiManager.addEventListener('push_init', (tokenData) => {
this.pushInited = true;
if(!this.settings.nodesktop && !this.settings.nopush) {
if(tokenData) {
@ -136,10 +138,10 @@ export class UiNotificationsManager {
this.unregisterDevice(tokenData);
}
});
rootScope.addEventListener('push_subscribe', (tokenData) => {
webPushApiManager.addEventListener('push_subscribe', (tokenData) => {
this.registerDevice(tokenData);
});
rootScope.addEventListener('push_unsubscribe', (tokenData) => {
webPushApiManager.addEventListener('push_unsubscribe', (tokenData) => {
this.unregisterDevice(tokenData);
});
@ -148,7 +150,7 @@ export class UiNotificationsManager {
this.topMessagesDeferred.resolve();
}, {once: true});
rootScope.addEventListener('push_notification_click', (notificationData) => {
webPushApiManager.addEventListener('push_notification_click', (notificationData) => {
if(notificationData.action === 'push_settings') {
/* this.topMessagesDeferred.then(() => {
$modal.open({
@ -192,9 +194,9 @@ export class UiNotificationsManager {
return;
}
rootScope.dispatchEvent('history_focus', {
appImManager.setInnerPeer({
peerId,
mid: +notificationData.custom.msg_id
lastMsgId: generateMessageId(+notificationData.custom.msg_id)
});
});
}
@ -253,7 +255,7 @@ export class UiNotificationsManager {
notification.title = wrapPlainText(notification.title);
notification.onclick = () => {
rootScope.dispatchEvent('history_focus', {peerId, mid: message.mid});
appImManager.setInnerPeer({peerId, lastMsgId: message.mid});
};
notification.message = notificationMessage;
@ -275,7 +277,7 @@ export class UiNotificationsManager {
}
}
private toggleToggler(enable = idleController.idle.isIDLE) {
private toggleToggler(enable = idleController.isIdle) {
if(IS_MOBILE) return;
const resetTitle = () => {

View File

@ -44,7 +44,11 @@ async function loadStateInner() {
const recordPromise = recordPromiseBound(log);
const promises = ALL_KEYS.map((key) => recordPromise(stateStorage.get(key), 'state ' + key))
.concat(recordPromise(sessionStorage.get('user_auth'), 'auth'), recordPromise(sessionStorage.get('state_id'), 'auth'))
.concat(
recordPromise(sessionStorage.get('user_auth'), 'auth'),
recordPromise(sessionStorage.get('state_id'), 'auth'),
recordPromise(sessionStorage.get('k_build'), 'auth')
)
.concat(recordPromise(stateStorage.get('user_auth'), 'old auth')); // support old webk format
const arr = await Promise.all(promises);
@ -119,6 +123,7 @@ async function loadStateInner() {
// * Read auth
let auth = arr.shift() as UserAuth | number;
const stateId = arr.shift() as number;
const sessionBuild = arr.shift() as number;
const shiftedWebKAuth = arr.shift() as UserAuth | number;
if(!auth && shiftedWebKAuth) { // support old webk auth
auth = shiftedWebKAuth;
@ -335,6 +340,10 @@ async function loadStateInner() {
pushToState('build', BUILD);
}
if(sessionBuild !== BUILD && (!sessionBuild || sessionBuild < BUILD)) {
sessionStorage.set({k_build: BUILD});
}
// ! probably there is better place for it
rootScope.settings = state.settings;

View File

@ -131,9 +131,13 @@ export default class CacheStorageController {
return Promise.resolve(fakeWriter);
}
public static toggleStorage(enabled: boolean) {
public static toggleStorage(enabled: boolean, clearWrite: boolean) {
return Promise.all(this.STORAGES.map((storage) => {
storage.useStorage = enabled;
if(!clearWrite) {
return;
}
if(!enabled) {
return storage.deleteAll();

View File

@ -5,7 +5,6 @@
*/
import CTR from "./utils/aesCTR";
import bytesToHex from "../../helpers/bytes/bytesToHex";
import subtle from "./subtle";
const aesCTRs: Map<number, K> = new Map();

View File

@ -113,7 +113,7 @@ class LocalStorage<Storage extends Record<string, any>> {
} */
public clear() {
const keys: string[] = ['dc', 'server_time_offset', 'xt_instance', 'user_auth', 'state_id'];
const keys: string[] = ['dc', 'server_time_offset', 'xt_instance', 'user_auth', 'state_id', 'k_build'];
for(let i = 1; i <= 5; ++i) {
keys.push(`dc${i}_server_salt`);
keys.push(`dc${i}_auth_key`);
@ -124,9 +124,13 @@ class LocalStorage<Storage extends Record<string, any>> {
}
}
public toggleStorage(enabled: boolean) {
public toggleStorage(enabled: boolean, clearWrite: boolean) {
this.useStorage = enabled;
if(!clearWrite) {
return;
}
if(!enabled) {
this.clear();
} else {
@ -191,7 +195,7 @@ export default class LocalStorageController<Storage extends Record<string, any>>
return this.proxy<void>('clear'/* , preserveKeys */);
}
public toggleStorage(enabled: boolean) {
return this.proxy<void>('toggleStorage', enabled);
public toggleStorage(enabled: boolean, clearWrite: boolean) {
return this.proxy<void>('toggleStorage', enabled, clearWrite);
}
}

View File

@ -596,19 +596,24 @@ export class ApiFileManager extends AppManager {
}
public downloadMedia(options: DownloadMediaOptions): DownloadPromise {
const {media, thumb} = options;
const isPhoto = media?._ === 'photo';
let {media, thumb} = options;
const isPhoto = media._ === 'photo';
if(media._ === 'photoEmpty' || (isPhoto && !thumb)) {
return Promise.reject('preloadPhoto photoEmpty!');
}
// get original instance with correct file_reference instead of using copies
const isDocument = media._ === 'document';
if(isDocument) media = this.appDocsManager.getDoc(media.id);
else if(isPhoto) media = this.appPhotosManager.getPhoto(media.id);
const {fileName, downloadOptions} = getDownloadMediaDetails(options);
let promise = this.getDownload(fileName);
if(!promise) {
promise = this.download(downloadOptions);
if(media._ === 'document') {
if(isDocument) {
this.rootScope.dispatchEvent('document_downloading', media.id);
promise.catch(noop).finally(() => {
this.rootScope.dispatchEvent('document_downloaded', media.id);

View File

@ -297,7 +297,7 @@ export class ApiManager extends ApiManagerMethods {
const clear = async() => {
this.baseDcId = undefined;
//this.telegramMeNotify(false);
await toggleStorages(false);
await toggleStorages(false, true);
IDB.closeDatabases();
this.rootScope.dispatchEvent('logging_out');
};

View File

@ -9,7 +9,6 @@ import '../polyfill';
import '../../helpers/peerIdPolyfill';
import cryptoWorker from "../crypto/cryptoMessagePort";
import CacheStorageController from '../cacheStorage';
import { setEnvironment } from '../../environment/utils';
import appStateManager from '../appManagers/appStateManager';
import transportController from './transports/controller';
@ -19,8 +18,8 @@ import appManagersManager from '../appManagers/appManagersManager';
import listenMessagePort from '../../helpers/listenMessagePort';
import { logger } from '../logger';
import { State } from '../../config/state';
import AppStorage from '../storage';
import toggleStorages from '../../helpers/toggleStorages';
import appTabsManager from '../appManagers/appTabsManager';
let _isServiceWorkerOnline = true;
export function isServiceWorkerOnline() {
@ -55,7 +54,9 @@ port.addMultipleEventsListeners({
RESET_STORAGES_PROMISE.resolve(resetStorages);
},
toggleStorages: (enabled) => toggleStorages(enabled),
toggleStorages: ({enabled, clearWrite}) => {
return toggleStorages(enabled, clearWrite);
},
event: (payload, source) => {
log('will redirect event', payload, source);
@ -68,7 +69,7 @@ port.addMultipleEventsListeners({
createObjectURL: (blob) => {
return URL.createObjectURL(blob);
}
},
// socketProxy: (task) => {
// const socketTask = task.payload;
@ -84,24 +85,16 @@ port.addMultipleEventsListeners({
// socketsProxied.delete(id);
// }
// },
// refreshReference: (task: RefreshReferenceTaskResponse) => {
// const hex = bytesToHex(task.originalPayload);
// const r = apiFileManager.refreshReferencePromises[hex];
// const deferred = r?.deferred;
// if(deferred) {
// if(task.error) {
// deferred.reject(task.error);
// } else {
// deferred.resolve(task.payload);
// }
// }
// },
});
log('MTProto start');
appManagersManager.start();
appManagersManager.getManagers();
appTabsManager.start();
listenMessagePort(port);
listenMessagePort(port, (source) => {
appTabsManager.addTab(source);
}, (source) => {
appTabsManager.deleteTab(source);
});

View File

@ -10,7 +10,7 @@ import type loadState from "../appManagers/utils/state/loadState";
import type { StoragesResults } from "../appManagers/utils/storages/loadStorages";
import type { LocalStorageProxyTask } from "../localStorage";
import type { Awaited } from "../../types";
import type { Mirrors, MirrorTaskPayload } from "./mtprotoworker";
import type { Mirrors, MirrorTaskPayload, NotificationBuildTaskPayload, TabState } from "./mtprotoworker";
import type toggleStorages from "../../helpers/toggleStorages";
import SuperMessagePort from "./superMessagePort";
@ -25,20 +25,22 @@ export default class MTProtoMessagePort<Master extends boolean = true> extends S
crypto: (payload: {method: string, args: any[]}) => Promise<any>,
state: (payload: {userId: UserId} & Awaited<ReturnType<typeof loadState>> & {storagesResults?: StoragesResults}) => void,
manager: (payload: MTProtoManagerTaskPayload) => any,
toggleStorages: typeof toggleStorages,
toggleStorages: (payload: {enabled: boolean, clearWrite: boolean}) => ReturnType<typeof toggleStorages>,
serviceWorkerOnline: (online: boolean) => void,
cryptoPort: (payload: void, source: MessageEventSource, event: MessageEvent) => void,
createObjectURL: (blob: Blob) => string
createObjectURL: (blob: Blob) => string,
tabState: (payload: TabState, source: MessageEventSource) => void,
} & MTProtoBroadcastEvent, {
convertWebp: (payload: {fileName: string, bytes: Uint8Array}) => Promise<Uint8Array>,
convertOpus: (payload: {fileName: string, bytes: Uint8Array}) => Promise<Uint8Array>,
localStorageProxy: (payload: LocalStorageProxyTask['payload']) => Promise<any>,
mirror: (payload: MirrorTaskPayload) => void
mirror: (payload: MirrorTaskPayload) => void,
notificationBuild: (payload: NotificationBuildTaskPayload) => void
} & MTProtoBroadcastEvent, Master> {
private static INSTANCE: MTProtoMessagePort;
constructor() {
super(false);
super();
MTProtoMessagePort.INSTANCE = this;

View File

@ -6,16 +6,17 @@
import type { RequestFilePartTask, RequestFilePartTaskResponse, ServiceWorkerTask } from '../serviceWorker/index.service';
import type { Awaited, WorkerTaskVoidTemplate } from '../../types';
import type { CacheStorageDbName } from '../cacheStorage';
import type { State } from '../../config/state';
import type { Message, MessagePeerReaction, PeerNotifySettings } from '../../layer';
import { CryptoMethods } from '../crypto/crypto_methods';
import rootScope from '../rootScope';
import webpWorkerController from '../webp/webpWorkerController';
import { MOUNT_CLASS_TO } from '../../config/debug';
import sessionStorage from '../sessionStorage';
import webPushApiManager from './webPushApiManager';
import AppStorage from '../storage';
import appRuntimeManager from '../appManagers/appRuntimeManager';
import telegramMeWebManager from './telegramMeWebManager';
import CacheStorageController, { CacheStorageDbName } from '../cacheStorage';
import pause from '../../helpers/schedulers/pause';
import isObject from '../../helpers/object/isObject';
import ENVIRONMENT from '../../environment';
@ -25,12 +26,12 @@ import MTProtoMessagePort from './mtprotoMessagePort';
import cryptoMessagePort from '../crypto/cryptoMessagePort';
import SuperMessagePort from './superMessagePort';
import IS_SHARED_WORKER_SUPPORTED from '../../environment/sharedWorkerSupport';
import type { State } from '../../config/state';
import toggleStorages from '../../helpers/toggleStorages';
import idleController from '../../helpers/idleController';
export interface ToggleStorageTask extends WorkerTaskVoidTemplate {
type: 'toggleStorages',
payload: boolean
payload: {enabled: boolean, clearWrite: boolean}
};
export type Mirrors = {
@ -43,6 +44,18 @@ export type MirrorTaskPayload<T extends keyof Mirrors = keyof Mirrors, K extends
value: any
};
export type NotificationBuildTaskPayload = {
message: Message.message | Message.messageService,
fwdCount?: number,
peerReaction?: MessagePeerReaction,
peerTypeNotifySettings?: PeerNotifySettings
};
export type TabState = {
chatPeerIds: PeerId[],
idleStartTime: number,
};
class ApiManagerProxy extends MTProtoMessagePort {
private worker: /* Window */Worker;
private isSWRegistered: boolean;
@ -53,12 +66,18 @@ class ApiManagerProxy extends MTProtoMessagePort {
public newVersion: string;
public oldVersion: string;
private tabState: TabState;
constructor() {
super();
this.isSWRegistered = true;
this.taskListenersSW = {};
this.mirrors = {} as any;
this.tabState = {
chatPeerIds: [],
idleStartTime: 0
};
this.log('constructor');
@ -158,7 +177,7 @@ class ApiManagerProxy extends MTProtoMessagePort {
rootScope.addEventListener('logging_out', () => {
const toClear: CacheStorageDbName[] = ['cachedFiles', 'cachedStreamChunks'];
Promise.all([
toggleStorages(false),
toggleStorages(false, true),
sessionStorage.clear(),
Promise.race([
telegramMeWebManager.setAuthorized(false),
@ -171,6 +190,11 @@ class ApiManagerProxy extends MTProtoMessagePort {
});
});
idleController.addEventListener('change', (idle) => {
this.updateTabStateIdle(idle);
});
this.updateTabStateIdle(idleController.isIdle);
this.log('Passing environment:', ENVIRONMENT);
this.invoke('environment', ENVIRONMENT);
// this.sendState();
@ -357,10 +381,10 @@ class ApiManagerProxy extends MTProtoMessagePort {
}
/// #endif
public async toggleStorages(enabled: boolean) {
await toggleStorages(enabled);
this.invoke('toggleStorages', enabled);
const task: ToggleStorageTask = {type: 'toggleStorages', payload: enabled};
public async toggleStorages(enabled: boolean, clearWrite: boolean) {
await toggleStorages(enabled, clearWrite);
this.invoke('toggleStorages', {enabled, clearWrite});
const task: ToggleStorageTask = {type: 'toggleStorages', payload: {enabled, clearWrite}};
this.postSWMessage(task);
}
@ -373,6 +397,15 @@ class ApiManagerProxy extends MTProtoMessagePort {
return this.getMirror('state');
}
public updateTabState<T extends keyof TabState>(key: T, value: TabState[T]) {
this.tabState[key] = value;
this.invokeVoid('tabState', this.tabState);
}
public updateTabStateIdle(idle: boolean) {
this.updateTabState('idleStartTime', idle ? Date.now() : 0);
}
private onMirrorTask = (payload: MirrorTaskPayload) => {
const {name, key, value} = payload;
if(!payload.hasOwnProperty('key')) {

File diff suppressed because one or more lines are too long

View File

@ -9,14 +9,16 @@
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
import App from "../../config/app";
import { MOUNT_CLASS_TO } from "../../config/debug";
import tabId from "../../config/tabId";
import IS_SHARED_WORKER_SUPPORTED from "../../environment/sharedWorkerSupport";
import EventListenerBase from "../../helpers/eventListenerBase";
import idleController from "../../helpers/idleController";
import { nextRandomUint } from "../../helpers/random";
import { logger } from "../logger";
import rootScope from "../rootScope";
import sessionStorage from "../sessionStorage";
import apiManagerProxy from "./mtprotoworker";
export type AppInstance = {
id: number,
@ -24,29 +26,40 @@ export type AppInstance = {
time: number
};
export type InstanceDeactivateReason = 'version' | 'tabs';
const CHECK_INSTANCE_INTERVAL = 5000;
const DEACTIVATE_TIMEOUT = 30000;
const MULTIPLE_TABS_THRESHOLD = 20000;
const IS_MULTIPLE_INSTANCES_SUPPORTED = IS_SHARED_WORKER_SUPPORTED;
const IS_MULTIPLE_TABS_SUPPORTED = IS_SHARED_WORKER_SUPPORTED;
export class SingleInstance extends EventListenerBase<{
activated: () => void,
deactivated: () => void
deactivated: (reason: InstanceDeactivateReason) => void
}> {
private instanceID: number;
private instanceId: number;
private started: boolean;
private masterInstance: boolean;
private deactivateTimeout: number;
private deactivated: boolean;
private initial: boolean;
private deactivated: InstanceDeactivateReason;
private log = logger('INSTANCE');
public start() {
if(!this.started && !IS_MULTIPLE_INSTANCES_SUPPORTED/* && !Config.Navigator.mobile && !Config.Modes.packed */) {
this.started = true;
constructor() {
super(false);
this.reset();
//IdleManager.start();
this.log = logger('INSTANCE');
this.instanceId = tabId;
}
public get deactivatedReason() {
return this.deactivated;
}
public start() {
this.reset();
if(!this.started/* && !Config.Navigator.mobile && !Config.Modes.packed */) {
this.started = true;
idleController.addEventListener('change', this.checkInstance);
setInterval(this.checkInstance, CHECK_INSTANCE_INTERVAL);
@ -59,94 +72,93 @@ export class SingleInstance extends EventListenerBase<{
}
private reset() {
if(IS_MULTIPLE_INSTANCES_SUPPORTED) return;
this.instanceID = nextRandomUint(32);
this.masterInstance = false;
if(this.deactivateTimeout) clearTimeout(this.deactivateTimeout);
this.deactivateTimeout = 0;
this.deactivated = false;
this.initial = false;
this.clearDeactivateTimeout();
this.deactivated = undefined;
}
private clearInstance = () => {
if(this.masterInstance && !this.deactivated && !IS_MULTIPLE_INSTANCES_SUPPORTED) {
if(this.masterInstance && !this.deactivated) {
this.log.warn('clear master instance');
sessionStorage.delete('xt_instance');
}
};
public activateInstance() {
if(this.deactivated && !IS_MULTIPLE_INSTANCES_SUPPORTED) {
if(this.deactivated) {
this.reset();
this.checkInstance(false);
this.dispatchEvent('activated');
}
}
private deactivateInstance = () => {
if(this.masterInstance || this.deactivated || IS_MULTIPLE_INSTANCES_SUPPORTED) {
private deactivateInstance(reason: InstanceDeactivateReason) {
if(this.masterInstance || this.deactivated) {
return;
}
this.log('deactivate');
this.deactivateTimeout = 0;
this.deactivated = true;
this.clearInstance();
//$modalStack.dismissAll();
this.log.warn('deactivate', reason);
this.clearDeactivateTimeout();
this.deactivated = reason;
//document.title = _('inactive_tab_title_raw')
this.dispatchEvent('deactivated', reason);
}
idleController.idle.deactivated = true;
this.dispatchEvent('deactivated');
};
private clearDeactivateTimeout() {
if(this.deactivateTimeout) {
clearTimeout(this.deactivateTimeout);
this.deactivateTimeout = 0;
}
}
private checkInstance = (idle = idleController.idle?.isIDLE) => {
if(this.deactivated || IS_MULTIPLE_INSTANCES_SUPPORTED) {
private checkInstance = async(idle = idleController.isIdle) => {
if(this.deactivated) {
return;
}
const time = Date.now();
const newInstance: AppInstance = {
id: this.instanceID,
id: this.instanceId,
idle,
time
};
sessionStorage.get('xt_instance', false).then((curInstance: AppInstance) => {
// this.log('check instance', newInstance, curInstance)
if(!idle ||
!curInstance ||
curInstance.id === this.instanceID ||
curInstance.time < (time - MULTIPLE_TABS_THRESHOLD)) {
sessionStorage.set({xt_instance: newInstance});
const [curInstance, build = App.build] = await Promise.all([
sessionStorage.get('xt_instance', false),
sessionStorage.get('k_build', false)
]);
if(!this.masterInstance) {
rootScope.managers.networkerFactory.startAll();
if(!this.initial) {
this.initial = true;
} else {
this.log.warn('now master instance', newInstance);
}
if(build > App.build) {
this.masterInstance = false;
rootScope.managers.networkerFactory.stopAll();
this.deactivateInstance('version');
apiManagerProxy.toggleStorages(false, false);
return;
} else if(IS_MULTIPLE_TABS_SUPPORTED) {
sessionStorage.set({xt_instance: newInstance});
return;
}
// this.log('check instance', newInstance, curInstance)
if(!idle ||
!curInstance ||
curInstance.id === this.instanceId ||
curInstance.time < (time - MULTIPLE_TABS_THRESHOLD)) {
sessionStorage.set({xt_instance: newInstance});
this.masterInstance = true;
}
if(this.deactivateTimeout) {
clearTimeout(this.deactivateTimeout);
this.deactivateTimeout = 0;
}
} else {
if(this.masterInstance) {
rootScope.managers.networkerFactory.stopAll();
this.log.warn('now idle instance', newInstance);
if(!this.deactivateTimeout) {
this.deactivateTimeout = window.setTimeout(this.deactivateInstance, DEACTIVATE_TIMEOUT);
}
this.masterInstance = false;
}
if(!this.masterInstance) {
this.masterInstance = true;
rootScope.managers.networkerFactory.startAll();
this.log.warn('now master instance', newInstance);
}
});
this.clearDeactivateTimeout();
} else if(this.masterInstance) {
this.masterInstance = false;
rootScope.managers.networkerFactory.stopAll();
this.log.warn('now idle instance', newInstance);
this.deactivateTimeout ||= window.setTimeout(() => this.deactivateInstance('tabs'), DEACTIVATE_TIMEOUT);
}
};
}

View File

@ -9,7 +9,6 @@ import ctx from "../../environment/ctx";
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
import { IS_SERVICE_WORKER, IS_WORKER, notifyAll } from "../../helpers/context";
import EventListenerBase from "../../helpers/eventListenerBase";
import pause from "../../helpers/schedulers/pause";
import { Awaited, WorkerTaskTemplate, WorkerTaskVoidTemplate } from "../../types";
import { logger } from "../logger";
@ -54,9 +53,13 @@ interface BatchTask extends SuperMessagePortTask {
payload: Task[]
}
type Task = InvokeTask | ResultTask | AckTask | PingTask | PongTask | BatchTask;
interface CloseTask extends SuperMessagePortTask {
type: 'close'
}
type Task = InvokeTask | ResultTask | AckTask | PingTask | PongTask | BatchTask | CloseTask;
type TaskMap = {
[type in Task as type['type']]?: (task: Extract<Task, type>) => void | Promise<any>
[type in Task as type['type']]?: (task: Extract<Task, type>, source: MessageEventSource, event: MessageEvent<any>) => void | Promise<any>
};
export type AckedResult<T> = {
@ -77,8 +80,8 @@ type SendPort = WindowProxy | MessagePort | ServiceWorker | Worker;
type ListenerCallback = (payload: any, source: MessageEventSource, event: MessageEvent<any>) => any;
type Listeners = Record<string, ListenerCallback>;
const PING_INTERVAL = DEBUG || true ? 0x7FFFFFFF : 1000;
const PING_TIMEOUT = DEBUG || true ? 0x7FFFFFFF : 5000;
// const PING_INTERVAL = DEBUG && false ? 0x7FFFFFFF : 5000;
// const PING_TIMEOUT = DEBUG && false ? 0x7FFFFFFF : 10000;
export default class SuperMessagePort<
Workers extends Listeners,
@ -105,6 +108,23 @@ export default class SuperMessagePort<
protected debug: boolean;
protected releasingPending: boolean;
protected processTaskMap: TaskMap;
protected onPortDisconnect: (source: MessageEventSource) => void;
constructor() {
super(false);
this.processTaskMap = {
result: this.processResultTask,
ack: this.processAckTask,
invoke: this.processInvokeTask,
ping: this.processPingTask,
pong: this.processPongTask,
close: this.processCloseTask
};
}
public _constructor() {
super._constructor(false);
@ -116,6 +136,17 @@ export default class SuperMessagePort<
this.pending = new Map();
this.log = logger('MP');
this.debug = DEBUG;
if(typeof(window) !== 'undefined') {
window.addEventListener('beforeunload', () => {
const task = this.createTask('close', undefined);
this.postMessage(undefined, task);
});
}
}
public setOnPortDisconnect(callback: (source: MessageEventSource) => void) {
this.onPortDisconnect = callback;
}
public attachPort(port: MessageEventSource) {
@ -129,51 +160,64 @@ export default class SuperMessagePort<
}
public attachSendPort(port: SendPort) {
this.log.warn('attaching port');
if((port as MessagePort).start) {
(port as MessagePort).start();
}
this.sendPorts.push(port);
this.sendPing(port);
// this.sendPing(port);
}
protected sendPing(port: SendPort, loop = IS_WORKER) {
let timeout: number;
const promise = new Promise<void>((resolve, reject) => {
this.pingResolves.set(port, resolve);
this.pushTask(this.createTask('ping', undefined), port);
// ! Can't rely on ping because timers can be suspended
// protected sendPing(port: SendPort, loop = IS_WORKER) {
// let timeout: number;
// const promise = new Promise<void>((resolve, reject) => {
// this.pingResolves.set(port, resolve);
// this.pushTask(this.createTask('ping', undefined), port);
timeout = ctx.setTimeout(() => {
reject();
}, PING_TIMEOUT);
});
// timeout = ctx.setTimeout(() => {
// reject();
// }, PING_TIMEOUT);
// });
promise.then(() => {
clearTimeout(timeout);
this.pingResolves.delete(port);
// promise.then(() => {
// // this.log('got pong');
if(loop) {
this.sendPingWithTimeout(port);
}
}, () => {
this.pingResolves.delete(port);
// clearTimeout(timeout);
// this.pingResolves.delete(port);
indexOfAndSplice(this.listenPorts, port);
indexOfAndSplice(this.sendPorts, port);
if((port as MessagePort).close) {
(port as MessagePort).close();
}
});
}
// if(loop) {
// this.sendPingWithTimeout(port);
// }
// }, () => {
// this.pingResolves.delete(port);
// this.detachPort(port);
// });
// }
protected sendPingWithTimeout(port: SendPort, timeout = PING_INTERVAL) {
ctx.setTimeout(() => {
if(!this.sendPorts.includes(port)) {
return;
}
// protected sendPingWithTimeout(port: SendPort, timeout = PING_INTERVAL) {
// ctx.setTimeout(() => {
// if(!this.sendPorts.includes(port)) {
// return;
// }
this.sendPing(port);
}, timeout);
// this.sendPing(port);
// }, timeout);
// }
protected detachPort(port: SendPort) {
this.log.warn('disconnecting port');
port.removeEventListener('message', this.onMessage as any);
indexOfAndSplice(this.listenPorts, port);
indexOfAndSplice(this.sendPorts, port);
if((port as MessagePort).close) {
(port as MessagePort).close();
}
this.onPortDisconnect && this.onPortDisconnect(port as any);
}
protected postMessage(port: SendPort | SendPort[], task: Task) {
@ -188,27 +232,20 @@ export default class SuperMessagePort<
// this.log('got message', task);
const source: MessageEventSource = event.source || event.currentTarget as any; // can have no source
if(task.type === 'batch') {
/* if(task.type === 'batch') {
const newEvent: MessageEvent = {data: event.data, source: event.source, currentTarget: event.currentTarget} as any;
task.payload.forEach((task) => {
// @ts-ignore
newEvent.data = task;
this.onMessage(newEvent);
});
} else if(task.type === 'result') {
this.processResultTask(task);
} else if(task.type === 'ack') {
this.processAckTask(task);
} else if(task.type === 'invoke') {
this.processInvokeTask(task, source, event);
} else if(task.type === 'ping') {
this.processPingTask(task, source, event);
} else if(task.type === 'pong') {
this.processPongTask(task, source, event);
}
} */
// @ts-ignore
this.processTaskMap[task.type](task, source, event);
};
protected async releasePending() {
protected /* async */ releasePending() {
//return;
if(!this.listenPorts.length || this.releasingPending) {
@ -217,28 +254,28 @@ export default class SuperMessagePort<
this.releasingPending = true;
// const perf = performance.now();
await pause(0);
// await pause(0);
this.debug && this.log.debug('releasing tasks, length:', this.pending.size/* , performance.now() - perf */);
this.pending.forEach((portTasks, port) => {
let batchTask: BatchTask;
const tasks: Task[] = [];
portTasks.forEach((task) => {
if(task.transfer) {
batchTask = undefined;
tasks.push(task);
} else {
if(!batchTask) {
batchTask = this.createTask('batch', []);
tasks.push(batchTask);
}
// let batchTask: BatchTask;
// const tasks: Task[] = [];
// portTasks.forEach((task) => {
// if(task.transfer) {
// batchTask = undefined;
// tasks.push(task);
// } else {
// if(!batchTask) {
// batchTask = this.createTask('batch', []);
// tasks.push(batchTask);
// }
batchTask.payload.push(task);
}
});
// batchTask.payload.push(task);
// }
// });
// const tasks = portTasks;
const tasks = portTasks;
tasks.forEach((task) => {
// if(task.type === 'batch') {
@ -263,7 +300,7 @@ export default class SuperMessagePort<
this.releasingPending = false;
}
protected processResultTask(task: ResultTask) {
protected processResultTask = (task: ResultTask) => {
const {taskId, result, error} = task.payload;
const deferred = this.awaiting[taskId];
if(!deferred) {
@ -271,11 +308,11 @@ export default class SuperMessagePort<
}
this.debug && this.log.debug('done', deferred.taskType, result, error);
error ? deferred.reject(error) : deferred.resolve(result);
'error' in task.payload ? deferred.reject(error) : deferred.resolve(result);
delete this.awaiting[taskId];
}
};
protected processAckTask(task: AckTask) {
protected processAckTask = (task: AckTask) => {
const payload = task.payload;
const deferred = this.awaiting[payload.taskId];
if(!deferred) {
@ -316,21 +353,25 @@ export default class SuperMessagePort<
};
previousResolve(ret);
}
};
protected processPingTask(task: PingTask, source: MessageEventSource, event: MessageEvent) {
protected processPingTask = (task: PingTask, source: MessageEventSource, event: MessageEvent) => {
this.pushTask(this.createTask('pong', undefined), event.source);
}
};
protected processPongTask(task: PongTask, source: MessageEventSource, event: MessageEvent) {
protected processPongTask = (task: PongTask, source: MessageEventSource, event: MessageEvent) => {
const pingResolve = this.pingResolves.get(source);
if(pingResolve) {
this.pingResolves.delete(source);
pingResolve();
}
}
};
protected async processInvokeTask(task: InvokeTask, source: MessageEventSource, event: MessageEvent) {
protected processCloseTask = (task: CloseTask, source: MessageEventSource, event: MessageEvent) => {
this.detachPort(source);
};
protected processInvokeTask = async(task: InvokeTask, source: MessageEventSource, event: MessageEvent) => {
const id = task.id;
const innerTask = task.payload;
@ -397,7 +438,7 @@ export default class SuperMessagePort<
}
this.pushTask(resultTask, source);
}
};
protected createTask<T extends Task['type'], K extends Task = Parameters<TaskMap[T]>[0]>(type: T, payload: K['payload'], transfer?: Transferable[]): K {
return {

View File

@ -17,6 +17,8 @@ export default class Obfuscation {
private dec: aesjs.ModeOfOperation.ModeOfOperationCTR; */
private id: number;
private idPromise: Promise<Obfuscation['id']>;
private process: (data: Uint8Array, operation: 'encrypt' | 'decrypt') => ReturnType<Obfuscation['_process']>;
// private cryptoEncKey: CryptoKey;
// private cryptoDecKey: CryptoKey;
@ -28,7 +30,7 @@ export default class Obfuscation {
// private decIvCounter: Counter;
public async init(codec: Codec) {
if(this.id !== undefined) {
if(this.idPromise !== undefined) {
this.release();
}
@ -69,12 +71,21 @@ export default class Obfuscation {
// console.log('encKey', encKey.hex, encIv.hex);
// console.log('decKey', decKey.hex, decIv.hex);
this.id = await cryptoMessagePort.invokeCrypto('aes-ctr-prepare', {
const idPromise = this.idPromise = cryptoMessagePort.invokeCrypto('aes-ctr-prepare', {
encKey,
encIv,
decKey,
decIv
});
this.process = async(data, operation) => {
await idPromise;
return this._process(data, operation);
};
this.id = await idPromise;
this.process = this._process;
// this.decIvCounter = new Counter(this.decIv);
/* const key = this.cryptoEncKey = await subtle.importKey(
@ -145,12 +156,12 @@ export default class Obfuscation {
return res;
} */
private process(data: Uint8Array, operation: 'encrypt' | 'decrypt') {
private _process = (data: Uint8Array, operation: 'encrypt' | 'decrypt') => {
return cryptoMessagePort.invoke('invoke', {
method: 'aes-ctr-process',
args: [{id: this.id, data, operation}],
}, undefined, undefined, [data.buffer]) as Promise<Uint8Array>;
}
};
public encode(payload: Uint8Array) {
/* return subtle.encrypt({
@ -168,13 +179,17 @@ export default class Obfuscation {
return this.process(payload, 'decrypt');
}
public release() {
if(this.id === undefined) {
public async release() {
const idPromise = this.idPromise;
if(idPromise === undefined) {
return;
}
cryptoMessagePort.invokeCrypto('aes-ctr-destroy', this.id);
this.id = undefined;
this.idPromise = undefined;
const id = await idPromise;
cryptoMessagePort.invokeCrypto('aes-ctr-destroy', id);
}
public destroy() {

View File

@ -12,14 +12,15 @@
import type { ServiceWorkerNotificationsClearTask, ServiceWorkerPingTask, ServiceWorkerPushClickTask } from "../serviceWorker/index.service";
import { MOUNT_CLASS_TO } from "../../config/debug";
import { logger } from "../logger";
import rootScope from "../rootScope";
import apiManager from "./mtprotoworker";
import apiManagerProxy from "./mtprotoworker";
import I18n, { LangPackKey } from "../langPack";
import { IS_MOBILE } from "../../environment/userAgent";
import appRuntimeManager from "../appManagers/appRuntimeManager";
import copy from "../../helpers/object/copy";
import type { NotificationSettings } from "../appManagers/uiNotificationsManager";
import idleController from "../../helpers/idleController";
import singleInstance from "./singleInstance";
import EventListenerBase from "../../helpers/eventListenerBase";
import type { PushNotificationObject } from "../serviceWorker/push";
export type PushSubscriptionNotifyType = 'init' | 'subscribe' | 'unsubscribe';
export type PushSubscriptionNotifyEvent = `push_${PushSubscriptionNotifyType}`;
@ -29,7 +30,12 @@ export type PushSubscriptionNotify = {
tokenValue: string
};
export class WebPushApiManager {
export class WebPushApiManager extends EventListenerBase<{
push_notification_click: (n: PushNotificationObject) => void,
push_init: (n: PushSubscriptionNotify) => void,
push_subscribe: (n: PushSubscriptionNotify) => void,
push_unsubscribe: (n: PushSubscriptionNotify) => void
}> {
public isAvailable = true;
private isPushEnabled = false;
private localNotificationsAvailable = true;
@ -41,6 +47,8 @@ export class WebPushApiManager {
private log = logger('PM');
constructor() {
super(false);
if(!('PushManager' in window) ||
!('Notification' in window) ||
!('serviceWorker' in navigator)) {
@ -156,7 +164,7 @@ export class WebPushApiManager {
}
public isAliveNotify = () => {
if(!this.isAvailable || idleController.idle?.deactivated) {
if(!this.isAvailable || singleInstance.deactivatedReason) {
return;
}
@ -182,7 +190,7 @@ export class WebPushApiManager {
}
};
apiManager.postSWMessage(task);
apiManagerProxy.postSWMessage(task);
this.isAliveTO = setTimeout(this.isAliveNotify, 10000);
}
@ -199,7 +207,7 @@ export class WebPushApiManager {
}
const task: ServiceWorkerNotificationsClearTask = {type: 'notifications_clear'};
apiManager.postSWMessage(task);
apiManagerProxy.postSWMessage(task);
}
public setUpServiceWorkerChannel() {
@ -207,13 +215,13 @@ export class WebPushApiManager {
return;
}
apiManager.addServiceWorkerTaskListener('push_click', (task: ServiceWorkerPushClickTask) => {
if(idleController.idle?.deactivated) {
apiManagerProxy.addServiceWorkerTaskListener('push_click', (task: ServiceWorkerPushClickTask) => {
if(singleInstance.deactivatedReason) {
appRuntimeManager.reload();
return;
}
rootScope.dispatchEvent('push_notification_click', task.payload);
this.dispatchEvent('push_notification_click', task.payload);
});
navigator.serviceWorker.ready.then(this.isAliveNotify);
@ -235,13 +243,13 @@ export class WebPushApiManager {
}
this.log.warn('Push', event, subscriptionObj);
rootScope.dispatchEvent(('push_' + event) as PushSubscriptionNotifyEvent, {
this.dispatchEvent(('push_' + event) as PushSubscriptionNotifyEvent, {
tokenType: 10,
tokenValue: JSON.stringify(subscriptionObj)
});
} else {
this.log.warn('Push', event, false);
rootScope.dispatchEvent(('push_' + event) as PushSubscriptionNotifyEvent, false as any);
this.dispatchEvent(('push_' + event) as PushSubscriptionNotifyEvent, false as any);
}
}
}

View File

@ -10,8 +10,6 @@ import type { MyDialogFilter } from "./storages/filters";
import type { Folder } from "./storages/dialogs";
import type { UserTyping } from "./appManagers/appProfileManager";
import type { MyDraftMessage } from "./appManagers/appDraftsManager";
import type { PushSubscriptionNotify } from "./mtproto/webPushApiManager";
import type { PushNotificationObject } from "./serviceWorker/push";
import type { ConnectionStatusChange } from "./mtproto/connectionStatus";
import type { GroupCallId } from "./appManagers/appGroupCallsManager";
import type { AppManagers } from "./appManagers/managers";
@ -67,7 +65,6 @@ export type BroadcastEvents = {
'history_delete': {peerId: PeerId, msgs: Set<number>},
'history_forbidden': PeerId,
'history_reload': PeerId,
'history_focus': {peerId: PeerId, threadId?: number, mid?: number, startParam?: string},
//'history_request': void,
'message_edit': {storageKey: MessagesStorageKey, peerId: PeerId, mid: number, message: MyMessage},
@ -104,8 +101,6 @@ export type BroadcastEvents = {
'settings_updated': {key: string, value: any, settings: State['settings']},
'draft_updated': {peerId: PeerId, threadId: number, draft: MyDraftMessage | undefined, force?: boolean},
'im_tab_change': number,
'background_change': void,
'privacy_update': Update.updatePrivacy,
@ -115,21 +110,12 @@ export type BroadcastEvents = {
'notification_reset': string,
'notification_cancel': string,
'notification_build': {
message: Message.message | Message.messageService,
fwdCount?: number,
peerReaction?: MessagePeerReaction,
peerTypeNotifySettings?: PeerNotifySettings
},
'language_change': string,
'theme_change': void,
'push_notification_click': PushNotificationObject,
'push_init': PushSubscriptionNotify,
'push_subscribe': PushSubscriptionNotify,
'push_unsubscribe': PushSubscriptionNotify,
'media_play': void,
'emoji_recent': string,
@ -153,9 +139,11 @@ export type BroadcastEvents = {
'logging_out': void
};
export class RootScope extends EventListenerBase<{
export type BroadcastEventsListeners = {
[name in keyof BroadcastEvents]: (e: BroadcastEvents[name]) => void
}> {
};
export class RootScope extends EventListenerBase<BroadcastEventsListeners> {
public myId: PeerId = NULL_PEER_ID;
private connectionStatus: {[name: string]: ConnectionStatusChange} = {};
public settings: State['settings'];

View File

@ -94,7 +94,8 @@ const taskListeners: {
}
},
toggleStorages: (task: ToggleStorageTask) => {
CacheStorageController.toggleStorage(task.payload);
const {enabled, clearWrite} = task.payload;
CacheStorageController.toggleStorage(enabled, clearWrite);
}
};
ctx.addEventListener('message', (e) => {

View File

@ -30,7 +30,8 @@ const sessionStorage = new LocalStorageController<{
tgme_sync: {
canRedirect: boolean,
ts: number
}
},
k_build: number
}>(/* ['kz_version'] */);
MOUNT_CLASS_TO.appStorage = sessionStorage;
export default sessionStorage;

View File

@ -292,11 +292,11 @@ export default class AppStorage<
return this.storage.clear().catch(noop);
}
public static toggleStorage(enabled: boolean) {
public static toggleStorage(enabled: boolean, clearWrite: boolean) {
return Promise.all(this.STORAGES.map((storage) => {
storage.useStorage = enabled;
if(!IS_WORKER) {
if(!IS_WORKER || !clearWrite) {
return;
}

View File

@ -442,11 +442,11 @@ export default class FiltersStorage extends AppManager {
public saveDialogFilter(filter: DialogFilter, update = true) {
// defineNotNumerableProperties(filter, ['includePeerIds', 'excludePeerIds', 'pinnedPeerIds']);
if(filter._ === 'dialogFilterDefault') {
return;
// filter = this.getLocalFilter(0);
// delete filter.orderIndex;
}
// if(filter._ === 'dialogFilterDefault') {
// return;
// // filter = this.getLocalFilter(0);
// // delete filter.orderIndex;
// }
assumeType<MyDialogFilter>(filter);
convertment.forEach(([from, to]) => {

View File

@ -341,7 +341,7 @@ let onFirstMount = () => {
const keepSigned = signedCheckboxField.checked;
rootScope.managers.appStateManager.pushToState('keepSigned', keepSigned);
apiManagerProxy.toggleStorages(keepSigned);
apiManagerProxy.toggleStorages(keepSigned, true);
});
apiManagerProxy.getState().then((state) => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -946,6 +946,12 @@ $bubble-beside-button-width: 38px;
left: 50%;
transform: translate(-50%, -50%);
@include animation-level(2) {
.bubble-content-wrapper {
transition: var(--bubble-transition-in);
}
}
&.has-description {
.service-msg {
flex-direction: column;