Notifications almost finished

Maybe fix updates
Removed jsbn dependency
This commit is contained in:
Eduard Kuzmenko 2021-03-09 02:15:44 +04:00
parent 328e3d2582
commit 4d7638af5a
66 changed files with 1768 additions and 425 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -8,7 +8,7 @@ import rootScope from "../lib/rootScope";
import { cancelEvent, findUpAttribute, findUpClassName } from "../helpers/dom"; import { cancelEvent, findUpAttribute, findUpClassName } from "../helpers/dom";
import Scrollable from "./scrollable"; import Scrollable from "./scrollable";
import { FocusDirection } from "../helpers/fastSmoothScroll"; import { FocusDirection } from "../helpers/fastSmoothScroll";
import CheckboxField from "./checkbox"; import CheckboxField from "./checkboxField";
type PeerType = 'contacts' | 'dialogs'; type PeerType = 'contacts' | 'dialogs';
@ -346,7 +346,7 @@ export default class AppSelectPeers {
if(this.multiSelect) { if(this.multiSelect) {
const selected = this.selected.has(peerId); const selected = this.selected.has(peerId);
const checkboxField = CheckboxField(); const checkboxField = new CheckboxField();
if(selected) { if(selected) {
dom.listEl.classList.add('active'); dom.listEl.classList.add('active');

View File

@ -6,7 +6,7 @@ import { isTouchSupported } from "../../helpers/touchSupport";
import { blurActiveElement, cancelEvent, cancelSelection, findUpClassName, getSelectedText } from "../../helpers/dom"; import { blurActiveElement, cancelEvent, cancelSelection, findUpClassName, getSelectedText } from "../../helpers/dom";
import Button from "../button"; import Button from "../button";
import ButtonIcon from "../buttonIcon"; import ButtonIcon from "../buttonIcon";
import CheckboxField from "../checkbox"; import CheckboxField from "../checkboxField";
import PopupDeleteMessages from "../popups/deleteMessages"; import PopupDeleteMessages from "../popups/deleteMessages";
import PopupForward from "../popups/forward"; import PopupForward from "../popups/forward";
import { toast } from "../toast"; import { toast } from "../toast";
@ -161,7 +161,7 @@ export default class ChatSelection {
if(show) { if(show) {
if(hasCheckbox) return; if(hasCheckbox) return;
const checkboxField = CheckboxField({ const checkboxField = new CheckboxField({
name: bubble.dataset.mid, name: bubble.dataset.mid,
round: true round: true
}); });

View File

@ -1,78 +0,0 @@
import appStateManager from "../lib/appManagers/appStateManager";
import { getDeepProperty } from "../helpers/object";
const CheckboxField = (options: {
text?: string,
name?: string,
round?: boolean,
stateKey?: string,
disabled?: boolean
} = {}) => {
const label = document.createElement('label');
label.classList.add('checkbox-field');
if(options.round) {
label.classList.add('checkbox-field-round');
}
if(options.disabled) {
label.classList.add('checkbox-disabled');
}
const input = document.createElement('input');
input.type = 'checkbox';
if(options.name) {
input.id = 'input-' + name;
}
if(options.stateKey) {
appStateManager.getState().then(state => {
input.checked = getDeepProperty(state, options.stateKey);
});
input.addEventListener('change', () => {
appStateManager.setByKey(options.stateKey, input.checked);
});
}
let span: HTMLSpanElement;
if(options.text) {
span = document.createElement('span');
span.classList.add('checkbox-caption');
if(options.text) {
span.innerText = options.text;
}
} else {
label.classList.add('checkbox-without-caption');
}
const box = document.createElement('div');
box.classList.add('checkbox-box');
const checkSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
checkSvg.classList.add('checkbox-box-check');
checkSvg.setAttributeNS(null, 'viewBox', '0 0 24 24');
const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
use.setAttributeNS(null, 'href', '#check');
use.setAttributeNS(null, 'x', '-1');
checkSvg.append(use);
const bg = document.createElement('div');
bg.classList.add('checkbox-box-background');
const border = document.createElement('div');
border.classList.add('checkbox-box-border');
box.append(border, bg, checkSvg);
label.append(input, box);
if(span) {
label.append(span);
}
return {label, input, span};
};
export default CheckboxField;

View File

@ -0,0 +1,100 @@
import appStateManager from "../lib/appManagers/appStateManager";
import { getDeepProperty } from "../helpers/object";
export default class CheckboxField {
public input: HTMLInputElement;
public label: HTMLLabelElement;
public span: HTMLSpanElement;
constructor(options: {
text?: string,
name?: string,
round?: boolean,
stateKey?: string,
disabled?: boolean,
checked?: boolean,
} = {}) {
const label = this.label = document.createElement('label');
label.classList.add('checkbox-field');
if(options.round) {
label.classList.add('checkbox-field-round');
}
if(options.disabled) {
label.classList.add('checkbox-disabled');
}
const input = this.input = document.createElement('input');
input.type = 'checkbox';
if(options.name) {
input.id = 'input-' + name;
}
if(options.checked) {
input.checked = true;
}
if(options.stateKey) {
appStateManager.getState().then(state => {
this.value = getDeepProperty(state, options.stateKey);
input.addEventListener('change', () => {
appStateManager.setByKey(options.stateKey, input.checked);
});
});
}
let span: HTMLSpanElement;
if(options.text) {
span = this.span = document.createElement('span');
span.classList.add('checkbox-caption');
if(options.text) {
span.innerText = options.text;
}
} else {
label.classList.add('checkbox-without-caption');
}
const box = document.createElement('div');
box.classList.add('checkbox-box');
const checkSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
checkSvg.classList.add('checkbox-box-check');
checkSvg.setAttributeNS(null, 'viewBox', '0 0 24 24');
const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
use.setAttributeNS(null, 'href', '#check');
use.setAttributeNS(null, 'x', '-1');
checkSvg.append(use);
const bg = document.createElement('div');
bg.classList.add('checkbox-box-background');
const border = document.createElement('div');
border.classList.add('checkbox-box-border');
box.append(border, bg, checkSvg);
label.append(input, box);
if(span) {
label.append(span);
}
}
get value() {
return this.input.checked;
}
set value(value: boolean) {
this.setValueSilently(value);
const event = new Event('change', {bubbles: true, cancelable: true});
this.input.dispatchEvent(event);
}
public setValueSilently(value: boolean) {
this.input.checked = value;
}
}

View File

@ -206,4 +206,4 @@ class InputField {
} }
} }
export default InputField; export default InputField;

View File

@ -23,7 +23,7 @@ const map: Map<HTMLElement, {
}> = new Map(); }> = new Map();
const testQueue: Set<HTMLElement> = new Set(); const testQueue: Set<HTMLElement> = new Set();
const fontFamily = 'Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif'; export const fontFamily = 'Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif';
const fontSize = '16px'; const fontSize = '16px';
let timeoutId: number; let timeoutId: number;

View File

@ -2,7 +2,7 @@ import type { Poll } from "../../lib/appManagers/appPollsManager";
import type Chat from "../chat/chat"; import type Chat from "../chat/chat";
import PopupElement from "."; import PopupElement from ".";
import { cancelEvent, findUpTag, getRichValue, isInputEmpty, whichChild } from "../../helpers/dom"; import { cancelEvent, findUpTag, getRichValue, isInputEmpty, whichChild } from "../../helpers/dom";
import CheckboxField from "../checkbox"; import CheckboxField from "../checkboxField";
import InputField from "../inputField"; import InputField from "../inputField";
import RadioField from "../radioField"; import RadioField from "../radioField";
import Scrollable from "../scrollable"; import Scrollable from "../scrollable";
@ -20,7 +20,7 @@ export default class PopupCreatePoll extends PopupElement {
private scrollable: Scrollable; private scrollable: Scrollable;
private tempId = 0; private tempId = 0;
private anonymousCheckboxField: ReturnType<typeof CheckboxField>; private anonymousCheckboxField: CheckboxField;
private multipleCheckboxField: PopupCreatePoll['anonymousCheckboxField']; private multipleCheckboxField: PopupCreatePoll['anonymousCheckboxField'];
private quizCheckboxField: PopupCreatePoll['anonymousCheckboxField']; private quizCheckboxField: PopupCreatePoll['anonymousCheckboxField'];
@ -77,7 +77,7 @@ export default class PopupCreatePoll extends PopupElement {
settingsCaption.innerText = 'Settings'; settingsCaption.innerText = 'Settings';
if(!this.chat.appPeersManager.isBroadcast(this.chat.peerId)) { if(!this.chat.appPeersManager.isBroadcast(this.chat.peerId)) {
this.anonymousCheckboxField = CheckboxField({ this.anonymousCheckboxField = new CheckboxField({
text: 'Anonymous Voting', text: 'Anonymous Voting',
name: 'anonymous' name: 'anonymous'
}); });
@ -85,11 +85,11 @@ export default class PopupCreatePoll extends PopupElement {
dd.append(this.anonymousCheckboxField.label); dd.append(this.anonymousCheckboxField.label);
} }
this.multipleCheckboxField = CheckboxField({ this.multipleCheckboxField = new CheckboxField({
text: 'Multiple Answers', text: 'Multiple Answers',
name: 'multiple' name: 'multiple'
}); });
this.quizCheckboxField = CheckboxField({ this.quizCheckboxField = new CheckboxField({
text: 'Quiz Mode', text: 'Quiz Mode',
name: 'quiz' name: 'quiz'
}); });
@ -303,7 +303,7 @@ export default class PopupCreatePoll extends PopupElement {
}); });
questionField.input.addEventListener('input', this.onInput); questionField.input.addEventListener('input', this.onInput);
const radioField = RadioField('', 'question'); const radioField = new RadioField('', 'question');
radioField.main.append(questionField.container); radioField.main.append(questionField.container);
questionField.input.addEventListener('click', cancelEvent); questionField.input.addEventListener('click', cancelEvent);
radioField.label.classList.add('hidden-widget'); radioField.label.classList.add('hidden-widget');

View File

@ -6,7 +6,7 @@ import PopupElement from ".";
import Scrollable from "../scrollable"; import Scrollable from "../scrollable";
import { toast } from "../toast"; import { toast } from "../toast";
import { prepareAlbum, wrapDocument } from "../wrappers"; import { prepareAlbum, wrapDocument } from "../wrappers";
import CheckboxField from "../checkbox"; import CheckboxField from "../checkboxField";
import SendContextMenu from "../chat/sendContextMenu"; import SendContextMenu from "../chat/sendContextMenu";
import { createPosterForVideo, createPosterFromVideo, onVideoLoad } from "../../helpers/files"; import { createPosterForVideo, createPosterFromVideo, onVideoLoad } from "../../helpers/files";
import { MyDocument } from "../../lib/appManagers/appDocsManager"; import { MyDocument } from "../../lib/appManagers/appDocsManager";
@ -28,7 +28,7 @@ const MAX_LENGTH_CAPTION = 1024;
export default class PopupNewMedia extends PopupElement { export default class PopupNewMedia extends PopupElement {
private input: HTMLElement; private input: HTMLElement;
private mediaContainer: HTMLElement; private mediaContainer: HTMLElement;
private groupCheckboxField: { label: HTMLLabelElement; input: HTMLInputElement; span: HTMLSpanElement; }; private groupCheckboxField: CheckboxField;
private wasInputValue = ''; private wasInputValue = '';
private willAttach: Partial<{ private willAttach: Partial<{
@ -89,7 +89,7 @@ export default class PopupNewMedia extends PopupElement {
this.container.append(scrollable.container); this.container.append(scrollable.container);
if(files.length > 1) { if(files.length > 1) {
this.groupCheckboxField = CheckboxField({ this.groupCheckboxField = new CheckboxField({
text: 'Group items', text: 'Group items',
name: 'group-items' name: 'group-items'
}); });

View File

@ -62,7 +62,7 @@ export default class PrivacySection {
const random = randomLong(); const random = randomLong();
r.forEach(({type, text}) => { r.forEach(({type, text}) => {
this.radioRows.set(type, new Row({radioField: RadioField(text, random, '' + type)})); this.radioRows.set(type, new Row({radioField: new RadioField(text, random, '' + type)}));
}); });
const form = RadioFormFromRows([...this.radioRows.values()], this.onRadioChange); const form = RadioFormFromRows([...this.radioRows.values()], this.onRadioChange);

View File

@ -1,48 +1,50 @@
import appStateManager from "../lib/appManagers/appStateManager"; import appStateManager from "../lib/appManagers/appStateManager";
import { getDeepProperty } from "../helpers/object"; import { getDeepProperty } from "../helpers/object";
const RadioField = (text: string, name: string, value?: string, stateKey?: string) => { export default class RadioField {
const label = document.createElement('label'); public input: HTMLInputElement;
label.classList.add('radio-field'); public label: HTMLLabelElement;
public main: HTMLElement;
const input = document.createElement('input'); constructor(text: string, name: string, value?: string, stateKey?: string) {
input.type = 'radio'; const label = this.label = document.createElement('label');
/* input.id = */input.name = 'input-radio-' + name; label.classList.add('radio-field');
if(value) {
input.value = value;
if(stateKey) {
appStateManager.getState().then(state => {
input.checked = getDeepProperty(state, stateKey) === value;
});
input.addEventListener('change', () => { const input = this.input = document.createElement('input');
appStateManager.setByKey(stateKey, value); input.type = 'radio';
}); /* input.id = */input.name = 'input-radio-' + name;
if(value) {
input.value = value;
if(stateKey) {
appStateManager.getState().then(state => {
input.checked = getDeepProperty(state, stateKey) === value;
});
input.addEventListener('change', () => {
appStateManager.setByKey(stateKey, value);
});
}
} }
}
const main = this.main = document.createElement('div');
const main = document.createElement('div'); main.classList.add('radio-field-main');
main.classList.add('radio-field-main');
if(text) {
if(text) { main.innerHTML = text;
main.innerHTML = text; /* const caption = document.createElement('div');
/* const caption = document.createElement('div'); caption.classList.add('radio-field-main-caption');
caption.classList.add('radio-field-main-caption'); caption.innerHTML = text;
caption.innerHTML = text;
if(subtitle) {
if(subtitle) { label.classList.add('radio-field-with-subtitle');
label.classList.add('radio-field-with-subtitle'); caption.insertAdjacentHTML('beforeend', `<div class="radio-field-main-subtitle">${subtitle}</div>`);
caption.insertAdjacentHTML('beforeend', `<div class="radio-field-main-subtitle">${subtitle}</div>`); }
main.append(caption); */
} }
main.append(caption); */ label.append(input, main);
} }
label.append(input, main);
return {label, input, main};
}; };
export default RadioField;

View File

@ -1,4 +1,4 @@
import CheckboxField from "./checkbox"; import CheckboxField from "./checkboxField";
import RadioField from "./radioField"; import RadioField from "./radioField";
import { ripple } from "./ripple"; import { ripple } from "./ripple";
import { SliderSuperTab } from "./slider"; import { SliderSuperTab } from "./slider";
@ -9,8 +9,8 @@ export default class Row {
public title: HTMLDivElement; public title: HTMLDivElement;
public subtitle: HTMLElement; public subtitle: HTMLElement;
public checkboxField: ReturnType<typeof CheckboxField>; public checkboxField: CheckboxField;
public radioField: ReturnType<typeof RadioField>; public radioField: RadioField;
public freezed = false; public freezed = false;
@ -44,6 +44,10 @@ export default class Row {
if(options.checkboxField) { if(options.checkboxField) {
this.checkboxField = options.checkboxField; this.checkboxField = options.checkboxField;
this.container.append(this.checkboxField.label); this.container.append(this.checkboxField.label);
this.checkboxField.input.addEventListener('change', () => {
this.subtitle.innerHTML = this.checkboxField.input.checked ? 'Enabled' : 'Disabled';
});
} }
} else { } else {
if(options.title) { if(options.title) {

View File

@ -12,7 +12,7 @@ import appStateManager from "../../../lib/appManagers/appStateManager";
import apiManager from "../../../lib/mtproto/mtprotoworker"; import apiManager from "../../../lib/mtproto/mtprotoworker";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";
import Button from "../../button"; import Button from "../../button";
import CheckboxField from "../../checkbox"; import CheckboxField from "../../checkboxField";
import ProgressivePreloader from "../../preloader"; import ProgressivePreloader from "../../preloader";
import { SliderSuperTab } from "../../slider"; import { SliderSuperTab } from "../../slider";
import { wrapPhoto } from "../../wrappers"; import { wrapPhoto } from "../../wrappers";
@ -28,7 +28,7 @@ export default class AppBackgroundTab extends SliderSuperTab {
const uploadButton = Button('btn-primary btn-transparent', {icon: 'cameraadd', text: 'Upload Wallpaper', disabled: true}); const uploadButton = Button('btn-primary btn-transparent', {icon: 'cameraadd', text: 'Upload Wallpaper', disabled: true});
const colorButton = Button('btn-primary btn-transparent', {icon: 'colorize', text: 'Set a Color', disabled: true}); const colorButton = Button('btn-primary btn-transparent', {icon: 'colorize', text: 'Set a Color', disabled: true});
const blurCheckboxField = CheckboxField({ const blurCheckboxField = new CheckboxField({
text: 'Blur Wallpaper Image', text: 'Blur Wallpaper Image',
name: 'blur', name: 'blur',
stateKey: 'settings.background.blur' stateKey: 'settings.background.blur'

View File

@ -2,7 +2,7 @@ import { SliderSuperTab } from "../../slider"
import { generateSection } from ".."; import { generateSection } from "..";
import RangeSelector from "../../rangeSelector"; import RangeSelector from "../../rangeSelector";
import Button from "../../button"; import Button from "../../button";
import CheckboxField from "../../checkbox"; import CheckboxField from "../../checkboxField";
import RadioField from "../../radioField"; import RadioField from "../../radioField";
import appStateManager from "../../../lib/appManagers/appStateManager"; import appStateManager from "../../../lib/appManagers/appStateManager";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";
@ -73,7 +73,7 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
new AppBackgroundTab(this.slider).open(); new AppBackgroundTab(this.slider).open();
}); });
const animationsCheckboxField = CheckboxField({ const animationsCheckboxField = new CheckboxField({
text: 'Enable Animations', text: 'Enable Animations',
name: 'animations', name: 'animations',
stateKey: 'settings.animationsEnabled' stateKey: 'settings.animationsEnabled'
@ -88,12 +88,12 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
const form = document.createElement('form'); const form = document.createElement('form');
const enterRow = new Row({ const enterRow = new Row({
radioField: RadioField('Send by Enter', 'send-shortcut', 'enter', 'settings.sendShortcut'), radioField: new RadioField('Send by Enter', 'send-shortcut', 'enter', 'settings.sendShortcut'),
subtitle: 'New line by Shift + Enter', subtitle: 'New line by Shift + Enter',
}); });
const ctrlEnterRow = new Row({ const ctrlEnterRow = new Row({
radioField: RadioField(`Send by ${isApple ? '⌘' : 'Ctrl'} + Enter`, 'send-shortcut', 'ctrlEnter', 'settings.sendShortcut'), radioField: new RadioField(`Send by ${isApple ? '⌘' : 'Ctrl'} + Enter`, 'send-shortcut', 'ctrlEnter', 'settings.sendShortcut'),
subtitle: 'New line by Enter', subtitle: 'New line by Enter',
}); });
@ -105,22 +105,22 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
const container = section('Auto-Download Media'); const container = section('Auto-Download Media');
container.classList.add('sidebar-left-section-disabled'); container.classList.add('sidebar-left-section-disabled');
const contactsCheckboxField = CheckboxField({ const contactsCheckboxField = new CheckboxField({
text: 'Contacts', text: 'Contacts',
name: 'contacts', name: 'contacts',
stateKey: 'settings.autoDownload.contacts' stateKey: 'settings.autoDownload.contacts'
}); });
const privateCheckboxField = CheckboxField({ const privateCheckboxField = new CheckboxField({
text: 'Private Chats', text: 'Private Chats',
name: 'private', name: 'private',
stateKey: 'settings.autoDownload.private' stateKey: 'settings.autoDownload.private'
}); });
const groupsCheckboxField = CheckboxField({ const groupsCheckboxField = new CheckboxField({
text: 'Group Chats', text: 'Group Chats',
name: 'groups', name: 'groups',
stateKey: 'settings.autoDownload.groups' stateKey: 'settings.autoDownload.groups'
}); });
const channelsCheckboxField = CheckboxField({ const channelsCheckboxField = new CheckboxField({
text: 'Channels', text: 'Channels',
name: 'channels', name: 'channels',
stateKey: 'settings.autoDownload.channels' stateKey: 'settings.autoDownload.channels'
@ -133,12 +133,12 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
const container = section('Auto-Play Media'); const container = section('Auto-Play Media');
container.classList.add('sidebar-left-section-disabled'); container.classList.add('sidebar-left-section-disabled');
const gifsCheckboxField = CheckboxField({ const gifsCheckboxField = new CheckboxField({
text: 'GIFs', text: 'GIFs',
name: 'gifs', name: 'gifs',
stateKey: 'settings.autoPlay.gifs' stateKey: 'settings.autoPlay.gifs'
}); });
const videosCheckboxField = CheckboxField({ const videosCheckboxField = new CheckboxField({
text: 'Videos', text: 'Videos',
name: 'videos', name: 'videos',
stateKey: 'settings.autoPlay.videos' stateKey: 'settings.autoPlay.videos'
@ -150,12 +150,12 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
{ {
const container = section('Stickers'); const container = section('Stickers');
const suggestCheckboxField = CheckboxField({ const suggestCheckboxField = new CheckboxField({
text: 'Suggest Stickers by Emoji', text: 'Suggest Stickers by Emoji',
name: 'suggest', name: 'suggest',
stateKey: 'settings.stickers.suggest' stateKey: 'settings.stickers.suggest'
}); });
const loopCheckboxField = CheckboxField({ const loopCheckboxField = new CheckboxField({
text: 'Loop Animated Stickers', text: 'Loop Animated Stickers',
name: 'loop', name: 'loop',
stateKey: 'settings.stickers.loop' stateKey: 'settings.stickers.loop'

View File

@ -7,7 +7,7 @@ import { MyDialogFilter as DialogFilter } from "../../../lib/storages/filters";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";
import { copy } from "../../../helpers/object"; import { copy } from "../../../helpers/object";
import ButtonIcon from "../../buttonIcon"; import ButtonIcon from "../../buttonIcon";
import CheckboxField from "../../checkbox"; import CheckboxField from "../../checkboxField";
import Button from "../../button"; import Button from "../../button";
import AppEditFolderTab from "./editFolder"; import AppEditFolderTab from "./editFolder";
@ -94,7 +94,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
} }
checkbox(selected?: boolean) { checkbox(selected?: boolean) {
const checkboxField = CheckboxField({ const checkboxField = new CheckboxField({
round: true round: true
}); });
if(selected) { if(selected) {

View File

@ -0,0 +1,128 @@
import { SettingSection } from "..";
import Row from "../../row";
import CheckboxField from "../../checkboxField";
import { InputNotifyPeer, PeerNotifySettings, Update } from "../../../layer";
import appNotificationsManager from "../../../lib/appManagers/appNotificationsManager";
import { SliderSuperTabEventable } from "../../sliderTab";
import { copy } from "../../../helpers/object";
import rootScope from "../../../lib/rootScope";
import { convertKeyToInputKey } from "../../../helpers/string";
type InputNotifyKey = Exclude<InputNotifyPeer['_'], 'inputNotifyPeer'>;
export default class AppNotificationsTab extends SliderSuperTabEventable {
protected init() {
this.container.classList.add('notifications-container');
this.title.innerText = 'Notifications';
const NotifySection = (options: {
name: string,
typeText: string,
inputKey: InputNotifyKey,
}) => {
const section = new SettingSection({
name: options.name
});
const enabledRow = new Row({
checkboxField: new CheckboxField({text: options.typeText, checked: true}),
subtitle: 'Loading...',
});
const previewEnabledRow = new Row({
checkboxField: new CheckboxField({text: 'Message preview', checked: true}),
subtitle: 'Loading...',
});
section.content.append(enabledRow.container, previewEnabledRow.container);
this.scrollable.append(section.container);
const inputNotifyPeer = {_: options.inputKey};
appNotificationsManager.getNotifySettings(inputNotifyPeer).then((notifySettings) => {
const applySettings = () => {
const muted = appNotificationsManager.isMuted(notifySettings);
enabledRow.checkboxField.value = !muted;
previewEnabledRow.checkboxField.value = notifySettings.show_previews;
return muted;
};
applySettings();
this.eventListener.addListener('destroy', () => {
const mute = !enabledRow.checkboxField.value;
const showPreviews = previewEnabledRow.checkboxField.value;
if(mute === appNotificationsManager.isMuted(notifySettings) && showPreviews === notifySettings.show_previews) {
return;
}
const inputSettings: any = copy(notifySettings);
inputSettings._ = 'inputPeerNotifySettings';
inputSettings.mute_until = mute ? 2147483647 : 0;
inputSettings.show_previews = showPreviews;
appNotificationsManager.updateNotifySettings(inputNotifyPeer, inputSettings);
}, true);
this.listenerSetter.add(rootScope, 'notify_settings', (update: Update.updateNotifySettings) => {
const inputKey = convertKeyToInputKey(update.peer._) as any;
if(options.inputKey === inputKey) {
notifySettings = update.notify_settings;
applySettings();
}
});
});
};
NotifySection({
name: 'Private Chats',
typeText: 'Notifications for private chats',
inputKey: 'inputNotifyUsers'
});
NotifySection({
name: 'Groups',
typeText: 'Notifications for groups',
inputKey: 'inputNotifyChats'
});
NotifySection({
name: 'Channels',
typeText: 'Notifications for channels',
inputKey: 'inputNotifyBroadcasts'
});
{
const section = new SettingSection({
name: 'Other'
});
const contactsSignUpRow = new Row({
checkboxField: new CheckboxField({text: 'Contacts joined Telegram', checked: true}),
subtitle: 'Loading...',
});
const soundRow = new Row({
checkboxField: new CheckboxField({text: 'Notification sound', checked: true, stateKey: 'settings.notifications.sound'}),
subtitle: 'Enabled',
});
section.content.append(contactsSignUpRow.container, soundRow.container);
this.scrollable.append(section.container);
appNotificationsManager.getContactSignUpNotification().then(enabled => {
contactsSignUpRow.checkboxField.value = enabled;
this.eventListener.addListener('destroy', () => {
const _enabled = contactsSignUpRow.checkboxField.value;
if(enabled !== _enabled) {
appNotificationsManager.setContactSignUpNotification(!_enabled);
}
}, true);
});
}
}
}

View File

@ -18,6 +18,7 @@ import apiManager from "../../../lib/mtproto/mtprotoworker";
import AppBlockedUsersTab from "./blockedUsers"; import AppBlockedUsersTab from "./blockedUsers";
import appUsersManager from "../../../lib/appManagers/appUsersManager"; import appUsersManager from "../../../lib/appManagers/appUsersManager";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";
import { convertKeyToInputKey } from "../../../helpers/string";
export default class AppPrivacyAndSecurityTab extends SliderSuperTab { export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
private activeSessionsRow: Row; private activeSessionsRow: Row;
@ -204,11 +205,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
} }
rootScope.on('privacy_update', (update) => { rootScope.on('privacy_update', (update) => {
let key: string = update.key._; updatePrivacyRow(convertKeyToInputKey(update.key._) as any);
key = key[0].toUpperCase() + key.slice(1);
key = 'input' + key;
updatePrivacyRow(key as any);
}); });
container.append(numberVisibilityRow.container, lastSeenTimeRow.container, photoVisibilityRow.container, callRow.container, linkAccountRow.container, groupChatsAddRow.container); container.append(numberVisibilityRow.container, lastSeenTimeRow.container, photoVisibilityRow.container, callRow.container, linkAccountRow.container, groupChatsAddRow.container);

View File

@ -8,6 +8,7 @@ import AppPrivacyAndSecurityTab from "./privacyAndSecurity";
import AppGeneralSettingsTab from "./generalSettings"; import AppGeneralSettingsTab from "./generalSettings";
import AppEditProfileTab from "./editProfile"; import AppEditProfileTab from "./editProfile";
import AppChatFoldersTab from "./chatFolders"; import AppChatFoldersTab from "./chatFolders";
import AppNotificationsTab from "./notifications";
//import AppMediaViewer from "../../appMediaViewerNew"; //import AppMediaViewer from "../../appMediaViewerNew";
export default class AppSettingsTab extends SliderSuperTab { export default class AppSettingsTab extends SliderSuperTab {
@ -97,7 +98,7 @@ export default class AppSettingsTab extends SliderSuperTab {
buttonsDiv.append(this.buttons.edit = Button(className, {icon: 'edit', text: 'Edit Profile'})); buttonsDiv.append(this.buttons.edit = Button(className, {icon: 'edit', text: 'Edit Profile'}));
buttonsDiv.append(this.buttons.folders = Button(className, {icon: 'folder', text: 'Chat Folders'})); buttonsDiv.append(this.buttons.folders = Button(className, {icon: 'folder', text: 'Chat Folders'}));
buttonsDiv.append(this.buttons.general = Button(className, {icon: 'settings', text: 'General Settings'})); buttonsDiv.append(this.buttons.general = Button(className, {icon: 'settings', text: 'General Settings'}));
buttonsDiv.append(this.buttons.notifications = Button(className, {icon: 'unmute', text: 'Notifications', disabled: true})); buttonsDiv.append(this.buttons.notifications = Button(className, {icon: 'unmute', text: 'Notifications'}));
buttonsDiv.append(this.buttons.privacy = Button(className, {icon: 'lock', text: 'Privacy and Security'})); buttonsDiv.append(this.buttons.privacy = Button(className, {icon: 'lock', text: 'Privacy and Security'}));
buttonsDiv.append(this.buttons.language = Button(className, {icon: 'language', text: 'Language', disabled: true})); buttonsDiv.append(this.buttons.language = Button(className, {icon: 'language', text: 'Language', disabled: true}));
@ -122,6 +123,10 @@ export default class AppSettingsTab extends SliderSuperTab {
new AppGeneralSettingsTab(this.slider as any).open(); new AppGeneralSettingsTab(this.slider as any).open();
}); });
this.buttons.notifications.addEventListener('click', () => {
new AppNotificationsTab(this.slider).open();
});
this.buttons.privacy.addEventListener('click', () => { this.buttons.privacy.addEventListener('click', () => {
new AppPrivacyAndSecurityTab(this.slider).open(); new AppPrivacyAndSecurityTab(this.slider).open();
}); });

View File

@ -10,7 +10,7 @@ import AppSearchSuper, { SearchSuperType } from "../../appSearchSuper.";
import AvatarElement from "../../avatar"; import AvatarElement from "../../avatar";
import Scrollable from "../../scrollable"; import Scrollable from "../../scrollable";
import { SliderTab } from "../../slider"; import { SliderTab } from "../../slider";
import CheckboxField from "../../checkbox"; import CheckboxField from "../../checkboxField";
import { attachClickEvent, cancelEvent } from "../../../helpers/dom"; import { attachClickEvent, cancelEvent } from "../../../helpers/dom";
import appSidebarRight from ".."; import appSidebarRight from "..";
import { TransitionSlider } from "../../transition"; import { TransitionSlider } from "../../transition";
@ -81,7 +81,7 @@ export default class AppSharedMediaTab implements SliderTab {
notificationsStatus: this.profileContentEl.querySelector('.profile-row-notifications > p') notificationsStatus: this.profileContentEl.querySelector('.profile-row-notifications > p')
}; };
const checkboxField = CheckboxField({ const checkboxField = new CheckboxField({
text: 'Notifications', text: 'Notifications',
name: 'notifications' name: 'notifications'
}); });

View File

@ -87,3 +87,14 @@ export const checkRTL = (s: string) => {
}; };
//(window as any).checkRTL = checkRTL; //(window as any).checkRTL = checkRTL;
export function convertInputKeyToKey(inputKey: string) {
const str = inputKey.replace('input', '');
return (str[0].toLowerCase() + str.slice(1)) as string;
}
export function convertKeyToInputKey(key: string) {
key = key[0].toUpperCase() + key.slice(1);
key = 'input' + key;
return key;
}

View File

@ -21,3 +21,5 @@ export const isAppleMobile = (/iPad|iPhone|iPod/.test(navigator.platform) ||
export const isSafari = !!('safari' in ctx) || !!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))))/* || true */; export const isSafari = !!('safari' in ctx) || !!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))))/* || true */;
export const isMobileSafari = isSafari && isAppleMobile; export const isMobileSafari = isSafari && isAppleMobile;
export const isMobile = /* screen.width && screen.width < 480 || */navigator.userAgent.search(/iOS|iPhone OS|Android|BlackBerry|BB10|Series ?[64]0|J2ME|MIDP|opera mini|opera mobi|mobi.+Gecko|Windows Phone/i) != -1;

View File

@ -1,5 +1,5 @@
//import apiManager from '../mtproto/apiManager'; //import apiManager from '../mtproto/apiManager';
import { MOUNT_CLASS_TO } from '../../config/debug'; import DEBUG, { MOUNT_CLASS_TO } from '../../config/debug';
import { logger, LogLevels } from '../logger'; import { logger, LogLevels } from '../logger';
import apiManager from '../mtproto/mtprotoworker'; import apiManager from '../mtproto/mtprotoworker';
import rootScope from '../rootScope'; import rootScope from '../rootScope';
@ -10,14 +10,14 @@ import appStateManager from './appStateManager';
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
type UpdatesState = { type UpdatesState = {
pendingPtsUpdates: any[], pendingPtsUpdates: {pts: number, pts_count: number}[],
pendingSeqUpdates?: any, pendingSeqUpdates?: {[seq: number]: {seq: number, date: number, updates: any[]}},
syncPending: { syncPending: {
seqAwaiting?: number, seqAwaiting?: number,
ptsAwaiting?: true, ptsAwaiting?: true,
timeout: number timeout: number
}, },
syncLoading: boolean, syncLoading: Promise<void>,
seq?: number, seq?: number,
pts?: number, pts?: number,
@ -32,13 +32,14 @@ export class ApiUpdatesManager {
pendingPtsUpdates: [], pendingPtsUpdates: [],
pendingSeqUpdates: {}, pendingSeqUpdates: {},
syncPending: null, syncPending: null,
syncLoading: true syncLoading: null
}; };
public channelStates: {[channelId: number]: UpdatesState} = {}; public channelStates: {[channelId: number]: UpdatesState} = {};
private attached = false; private attached = false;
private log = logger('UPDATES', LogLevels.error | LogLevels.log | LogLevels.warn | LogLevels.debug); private log = logger('UPDATES', LogLevels.error | LogLevels.log | LogLevels.warn | LogLevels.debug);
private debug = DEBUG;
constructor() { constructor() {
// * false for test purposes // * false for test purposes
@ -53,31 +54,33 @@ export class ApiUpdatesManager {
} }
public popPendingSeqUpdate() { public popPendingSeqUpdate() {
const nextSeq = this.updatesState.seq + 1; const state = this.updatesState;
const pendingUpdatesData = this.updatesState.pendingSeqUpdates[nextSeq]; const nextSeq = state.seq + 1;
const pendingUpdatesData = state.pendingSeqUpdates[nextSeq];
if(!pendingUpdatesData) { if(!pendingUpdatesData) {
return false; return false;
} }
const updates = pendingUpdatesData.updates; const updates = pendingUpdatesData.updates;
for(let i = 0, length = updates.length; i < length; i++) { for(let i = 0, length = updates.length; i < length; ++i) {
this.saveUpdate(updates[i]); this.saveUpdate(updates[i]);
} }
this.updatesState.seq = pendingUpdatesData.seq;
if(pendingUpdatesData.date && this.updatesState.date < pendingUpdatesData.date) { state.seq = pendingUpdatesData.seq;
this.updatesState.date = pendingUpdatesData.date; if(pendingUpdatesData.date && state.date < pendingUpdatesData.date) {
state.date = pendingUpdatesData.date;
} }
delete this.updatesState.pendingSeqUpdates[nextSeq]; delete state.pendingSeqUpdates[nextSeq];
if(!this.popPendingSeqUpdate() && if(!this.popPendingSeqUpdate() &&
this.updatesState.syncPending && state.syncPending &&
this.updatesState.syncPending.seqAwaiting && state.syncPending.seqAwaiting &&
this.updatesState.seq >= this.updatesState.syncPending.seqAwaiting) { state.seq >= state.syncPending.seqAwaiting) {
if(!this.updatesState.syncPending.ptsAwaiting) { if(!state.syncPending.ptsAwaiting) {
clearTimeout(this.updatesState.syncPending.timeout); clearTimeout(state.syncPending.timeout);
this.updatesState.syncPending = null; state.syncPending = null;
} else { } else {
delete this.updatesState.syncPending.seqAwaiting; delete state.syncPending.seqAwaiting;
} }
} }
@ -89,7 +92,8 @@ export class ApiUpdatesManager {
if(!curState.pendingPtsUpdates.length) { if(!curState.pendingPtsUpdates.length) {
return false; return false;
} }
curState.pendingPtsUpdates.sort((a: any, b: any) => {
curState.pendingPtsUpdates.sort((a, b) => {
return a.pts - b.pts; return a.pts - b.pts;
}); });
// this.log('pop update', channelId, curState.pendingPtsUpdates) // this.log('pop update', channelId, curState.pendingPtsUpdates)
@ -97,7 +101,7 @@ export class ApiUpdatesManager {
let curPts = curState.pts; let curPts = curState.pts;
let goodPts = 0; let goodPts = 0;
let goodIndex = 0; let goodIndex = 0;
for(let i = 0, length = curState.pendingPtsUpdates.length; i < length; i++) { for(let i = 0, length = curState.pendingPtsUpdates.length; i < length; ++i) {
const update = curState.pendingPtsUpdates[i]; const update = curState.pendingPtsUpdates[i];
curPts += update.pts_count; curPts += update.pts_count;
if(curPts >= update.pts) { if(curPts >= update.pts) {
@ -110,10 +114,10 @@ export class ApiUpdatesManager {
return false; return false;
} }
this.log('pop pending pts updates', goodPts, curState.pendingPtsUpdates.slice(0, goodIndex + 1)); this.debug && this.log('pop pending pts updates', goodPts, curState.pendingPtsUpdates.slice(0, goodIndex + 1));
curState.pts = goodPts; curState.pts = goodPts;
for(let i = 0; i <= goodIndex; i++) { for(let i = 0; i <= goodIndex; ++i) {
const update = curState.pendingPtsUpdates[i]; const update = curState.pendingPtsUpdates[i];
this.saveUpdate(update); this.saveUpdate(update);
} }
@ -137,18 +141,18 @@ export class ApiUpdatesManager {
} }
} }
public processUpdateMessage = (updateMessage: any, options: Partial<{ public processUpdateMessage = (updateMessage: any/* , options: Partial<{
ignoreSyncLoading: boolean ignoreSyncLoading: boolean
}> = {}) => { }> = {} */) => {
// return forceGetDifference() // return forceGetDifference()
const processOpts = { const processOpts = {
date: updateMessage.date, date: updateMessage.date,
seq: updateMessage.seq, seq: updateMessage.seq,
seqStart: updateMessage.seq_start, seqStart: updateMessage.seq_start,
ignoreSyncLoading: options.ignoreSyncLoading //ignoreSyncLoading: options.ignoreSyncLoading
}; };
this.log('processUpdateMessage', updateMessage); this.debug && this.log('processUpdateMessage', updateMessage);
switch(updateMessage._) { switch(updateMessage._) {
case 'updatesTooLong': case 'updatesTooLong':
@ -162,7 +166,7 @@ export class ApiUpdatesManager {
case 'updateShortMessage': case 'updateShortMessage':
case 'updateShortChatMessage': { case 'updateShortChatMessage': {
this.log('updateShortMessage | updateShortChatMessage', {...updateMessage}); this.debug && this.log('updateShortMessage | updateShortChatMessage', {...updateMessage});
const isOut = updateMessage.pFlags.out; const isOut = updateMessage.pFlags.out;
const fromId = updateMessage.from_id || (isOut ? rootScope.myId : updateMessage.user_id); const fromId = updateMessage.from_id || (isOut ? rootScope.myId : updateMessage.user_id);
const toId = updateMessage.chat_id const toId = updateMessage.chat_id
@ -204,37 +208,34 @@ export class ApiUpdatesManager {
} }
}; };
public getDifference(first = false) { public getDifference(first = false): Promise<void> {
// this.trace('Get full diff') // this.trace('Get full diff')
const updatesState = this.updatesState; const updatesState = this.updatesState;
if(!updatesState.syncLoading) { let wasSyncing = updatesState.syncLoading;
updatesState.syncLoading = true; if(!wasSyncing) {
updatesState.pendingSeqUpdates = {}; updatesState.pendingSeqUpdates = {};
updatesState.pendingPtsUpdates = []; updatesState.pendingPtsUpdates = [];
rootScope.broadcast('state_synchronizing');
} }
if(updatesState.syncPending) { if(updatesState.syncPending) {
clearTimeout(updatesState.syncPending.timeout); clearTimeout(updatesState.syncPending.timeout);
updatesState.syncPending = null; updatesState.syncPending = null;
} }
return apiManager.invokeApi('updates.getDifference', { const promise = apiManager.invokeApi('updates.getDifference', {
pts: updatesState.pts, pts: updatesState.pts,
date: updatesState.date, date: updatesState.date,
qts: -1 qts: -1
}, { }, {
timeout: 0x7fffffff timeout: 0x7fffffff
}).then((differenceResult) => { }).then((differenceResult) => {
this.log('Get diff result', differenceResult); this.debug && this.log('Get diff result', differenceResult);
if(differenceResult._ === 'updates.differenceEmpty') { if(differenceResult._ === 'updates.differenceEmpty') {
this.log('apply empty diff', differenceResult.seq); this.debug && this.log('apply empty diff', differenceResult.seq);
updatesState.date = differenceResult.date; updatesState.date = differenceResult.date;
updatesState.seq = differenceResult.seq; updatesState.seq = differenceResult.seq;
updatesState.syncLoading = false; return;
rootScope.broadcast('state_synchronized');
return false;
} }
// ! SORRY I'M SORRY I'M SORRY // ! SORRY I'M SORRY I'M SORRY
@ -284,23 +285,24 @@ export class ApiUpdatesManager {
// this.log('apply diff', updatesState.seq, updatesState.pts) // this.log('apply diff', updatesState.seq, updatesState.pts)
if(differenceResult._ === 'updates.differenceSlice') { if(differenceResult._ === 'updates.differenceSlice') {
this.getDifference(); return this.getDifference();
} else { } else {
// this.log('finished get diff') this.debug && this.log('finished get diff');
rootScope.broadcast('state_synchronized');
updatesState.syncLoading = false;
} }
}, () => {
updatesState.syncLoading = false;
}); });
if(!wasSyncing) {
this.justAName(updatesState, promise);
}
return promise;
} }
public getChannelDifference(channelId: number) { public getChannelDifference(channelId: number): Promise<void> {
const channelState = this.getChannelState(channelId); const channelState = this.getChannelState(channelId);
if(!channelState.syncLoading) { const wasSyncing = channelState.syncLoading;
channelState.syncLoading = true; if(!wasSyncing) {
channelState.pendingPtsUpdates = []; channelState.pendingPtsUpdates = [];
rootScope.broadcast('state_synchronizing', channelId);
} }
if(channelState.syncPending) { if(channelState.syncPending) {
@ -309,40 +311,37 @@ export class ApiUpdatesManager {
} }
//this.log.trace('Get channel diff', appChatsManager.getChat(channelId), channelState.pts); //this.log.trace('Get channel diff', appChatsManager.getChat(channelId), channelState.pts);
apiManager.invokeApi('updates.getChannelDifference', { const promise = apiManager.invokeApi('updates.getChannelDifference', {
channel: appChatsManager.getChannelInput(channelId), channel: appChatsManager.getChannelInput(channelId),
filter: {_: 'channelMessagesFilterEmpty'}, filter: {_: 'channelMessagesFilterEmpty'},
pts: channelState.pts, pts: channelState.pts,
limit: 30 limit: 30
}, {timeout: 0x7fffffff}).then((differenceResult) => { }, {timeout: 0x7fffffff}).then((differenceResult) => {
this.log('Get channel diff result', differenceResult) this.debug && this.log('Get channel diff result', differenceResult)
channelState.pts = 'pts' in differenceResult ? differenceResult.pts : undefined; channelState.pts = 'pts' in differenceResult ? differenceResult.pts : undefined;
if(differenceResult._ === 'updates.channelDifferenceEmpty') { if(differenceResult._ === 'updates.channelDifferenceEmpty') {
this.log('apply channel empty diff', differenceResult); this.debug && this.log('apply channel empty diff', differenceResult);
channelState.syncLoading = false; return;
rootScope.broadcast('state_synchronized', channelId);
return false;
} }
if(differenceResult._ === 'updates.channelDifferenceTooLong') { if(differenceResult._ === 'updates.channelDifferenceTooLong') {
this.log('channel diff too long', differenceResult); this.debug && this.log('channel diff too long', differenceResult);
channelState.syncLoading = false;
delete this.channelStates[channelId]; delete this.channelStates[channelId];
this.saveUpdate({_: 'updateChannelReload', channel_id: channelId}); this.saveUpdate({_: 'updateChannelReload', channel_id: channelId});
return false; return;
} }
appUsersManager.saveApiUsers(differenceResult.users); appUsersManager.saveApiUsers(differenceResult.users);
appChatsManager.saveApiChats(differenceResult.chats); appChatsManager.saveApiChats(differenceResult.chats);
// Should be first because of updateMessageID // Should be first because of updateMessageID
this.log('applying', differenceResult.other_updates.length, 'channel other updates'); this.debug && this.log('applying', differenceResult.other_updates.length, 'channel other updates');
differenceResult.other_updates.forEach((update) => { differenceResult.other_updates.forEach((update) => {
this.saveUpdate(update); this.saveUpdate(update);
}); });
this.log('applying', differenceResult.new_messages.length, 'channel new messages'); this.debug && this.log('applying', differenceResult.new_messages.length, 'channel new messages');
differenceResult.new_messages.forEach((apiMessage) => { differenceResult.new_messages.forEach((apiMessage) => {
this.saveUpdate({ this.saveUpdate({
_: 'updateNewChannelMessage', _: 'updateNewChannelMessage',
@ -352,18 +351,32 @@ export class ApiUpdatesManager {
}); });
}); });
this.log('apply channel diff', channelState.pts); this.debug && this.log('apply channel diff', channelState.pts);
if(differenceResult._ === 'updates.channelDifference' && if(differenceResult._ === 'updates.channelDifference' &&
!differenceResult.pFlags['final']) { !differenceResult.pFlags['final']) {
this.getChannelDifference(channelId); return this.getChannelDifference(channelId);
} else { } else {
this.log('finished channel get diff'); this.debug && this.log('finished channel get diff');
rootScope.broadcast('state_synchronized', channelId);
channelState.syncLoading = false;
} }
});
if(!wasSyncing) {
this.justAName(channelState, promise, channelId);
}
return promise;
}
private justAName(state: UpdatesState, promise: UpdatesState['syncLoading'], channelId?: number) {
state.syncLoading = promise;
rootScope.broadcast('state_synchronizing', channelId);
promise.then(() => {
state.syncLoading = null;
rootScope.broadcast('state_synchronized', channelId);
}, () => { }, () => {
channelState.syncLoading = false; state.syncLoading = null;
}); });
} }
@ -377,7 +390,7 @@ export class ApiUpdatesManager {
pts, pts,
pendingPtsUpdates: [], pendingPtsUpdates: [],
syncPending: null, syncPending: null,
syncLoading: false syncLoading: null
}; };
return true; return true;
@ -397,8 +410,8 @@ export class ApiUpdatesManager {
public processUpdate(update: any, options: Partial<{ public processUpdate(update: any, options: Partial<{
date: number, date: number,
seq: number, seq: number,
seqStart: number, seqStart: number/* ,
ignoreSyncLoading: boolean ignoreSyncLoading: boolean */
}> = {}) { }> = {}) {
let channelId = 0; let channelId = 0;
switch(update._) { switch(update._) {
@ -421,7 +434,7 @@ export class ApiUpdatesManager {
// this.log.log('process', channelId, curState.pts, update) // this.log.log('process', channelId, curState.pts, update)
if(curState.syncLoading && !options.ignoreSyncLoading) { if(curState.syncLoading/* && !options.ignoreSyncLoading */) {
return false; return false;
} }
@ -466,18 +479,24 @@ export class ApiUpdatesManager {
if(update.pts) { if(update.pts) {
const newPts = curState.pts + (update.pts_count || 0); const newPts = curState.pts + (update.pts_count || 0);
if(newPts < update.pts) { if(newPts < update.pts) {
this.log.warn('Pts hole', curState, update, channelId && appChatsManager.getChat(channelId)); this.debug && this.log.warn('Pts hole', curState, update, channelId && appChatsManager.getChat(channelId));
curState.pendingPtsUpdates.push(update); curState.pendingPtsUpdates.push(update);
if(!curState.syncPending) { if(!curState.syncPending && !curState.syncLoading) {
curState.syncPending = { curState.syncPending = {
timeout: window.setTimeout(() => { timeout: window.setTimeout(() => {
curState.syncPending = null;
if(curState.syncLoading) {
return;
}
if(channelId) { if(channelId) {
this.getChannelDifference(channelId); this.getChannelDifference(channelId);
} else { } else {
this.getDifference(); this.getDifference();
} }
}, SYNC_DELAY) }, SYNC_DELAY)
} };
} }
curState.syncPending.ptsAwaiting = true; curState.syncPending.ptsAwaiting = true;
@ -503,7 +522,7 @@ export class ApiUpdatesManager {
if(seqStart !== curState.seq + 1) { if(seqStart !== curState.seq + 1) {
if(seqStart > curState.seq) { if(seqStart > curState.seq) {
this.log.warn('Seq hole', curState, curState.syncPending && curState.syncPending.seqAwaiting); this.debug && this.log.warn('Seq hole', curState, curState.syncPending && curState.syncPending.seqAwaiting);
if(curState.pendingSeqUpdates[seqStart] === undefined) { if(curState.pendingSeqUpdates[seqStart] === undefined) {
curState.pendingSeqUpdates[seqStart] = {seq, date: options.date, updates: []}; curState.pendingSeqUpdates[seqStart] = {seq, date: options.date, updates: []};
@ -513,9 +532,15 @@ export class ApiUpdatesManager {
if(!curState.syncPending) { if(!curState.syncPending) {
curState.syncPending = { curState.syncPending = {
timeout: window.setTimeout(() => { timeout: window.setTimeout(() => {
curState.syncPending = null;
if(curState.syncLoading) {
return;
}
this.getDifference(); this.getDifference();
}, SYNC_DELAY) }, SYNC_DELAY)
} };
} }
if(!curState.syncPending.seqAwaiting || if(!curState.syncPending.seqAwaiting ||
@ -563,20 +588,23 @@ export class ApiUpdatesManager {
//rootScope.broadcast('state_synchronizing'); //rootScope.broadcast('state_synchronizing');
if(!state || !state.pts || !state.date || !state.seq) { if(!state || !state.pts || !state.date || !state.seq) {
apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult) => { this.updatesState.syncLoading = new Promise((resolve) => {
this.updatesState.seq = stateResult.seq; apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult) => {
this.updatesState.pts = stateResult.pts; this.updatesState.seq = stateResult.seq;
this.updatesState.date = stateResult.date; this.updatesState.pts = stateResult.pts;
//setTimeout(() => { this.updatesState.date = stateResult.date;
this.updatesState.syncLoading = false; //setTimeout(() => {
//rootScope.broadcast('state_synchronized'); this.updatesState.syncLoading = null;
//}, 1000); resolve();
//rootScope.broadcast('state_synchronized');
// ! for testing //}, 1000);
// updatesState.seq = 1
// updatesState.pts = stateResult.pts - 5000 // ! for testing
// updatesState.date = 1 // updatesState.seq = 1
// getDifference() // updatesState.pts = stateResult.pts - 5000
// updatesState.date = 1
// getDifference()
});
}); });
} else { } else {
// ! for testing // ! for testing

View File

@ -326,13 +326,13 @@ export class AppChatsManager {
return this.cachedPhotoLocations[id]; return this.cachedPhotoLocations[id];
} }
/* public getChatString(id: number) { public getChatString(id: number) {
const chat = this.getChat(id); const chat = this.getChat(id);
if(this.isChannel(id)) { if(this.isChannel(id)) {
return (this.isMegagroup(id) ? 's' : 'c') + id + '_' + chat.access_hash; return (this.isMegagroup(id) ? 's' : 'c') + id + '_' + chat.access_hash;
} }
return 'g' + id; return 'g' + id;
} */ }
public getChatMembersString(id: number) { public getChatMembersString(id: number) {
const chat = this.getChat(id); const chat = this.getChat(id);

View File

@ -5,7 +5,6 @@ import { attachContextMenuListener, putPreloader } from "../../components/misc";
import { ripple } from "../../components/ripple"; import { ripple } from "../../components/ripple";
//import Scrollable from "../../components/scrollable"; //import Scrollable from "../../components/scrollable";
import Scrollable, { ScrollableX, SliceSides } from "../../components/scrollable"; import Scrollable, { ScrollableX, SliceSides } from "../../components/scrollable";
import appSidebarLeft from "../../components/sidebarLeft";
import { formatDateAccordingToToday } from "../../helpers/date"; import { formatDateAccordingToToday } from "../../helpers/date";
import { escapeRegExp } from "../../helpers/string"; import { escapeRegExp } from "../../helpers/string";
import { isSafari } from "../../helpers/userAgent"; import { isSafari } from "../../helpers/userAgent";
@ -165,8 +164,8 @@ class ConnectionStatusComponent {
DEBUG && this.log('setState: isShown:', this.connecting || this.updating); DEBUG && this.log('setState: isShown:', this.connecting || this.updating);
}; };
//this.setStateTimeout = window.setTimeout(cb, timeout); this.setStateTimeout = window.setTimeout(cb, timeout);
cb(); //cb();
/* if(timeout) this.setStateTimeout = window.setTimeout(cb, timeout); /* if(timeout) this.setStateTimeout = window.setTimeout(cb, timeout);
else cb(); */ else cb(); */
}); });
@ -499,9 +498,19 @@ export class AppDialogsManager {
return this.loadDialogs(); return this.loadDialogs();
}).then(() => { }).then(() => {
appMessagesManager.getConversationsAll('', 0).finally(() => { const allDialogsLoaded = appMessagesManager.dialogsStorage.allDialogsLoaded;
appMessagesManager.getConversationsAll('', 1).then(() => { const wasLoaded = allDialogsLoaded[0] || allDialogsLoaded[1];
const a: Promise<any> = allDialogsLoaded[0] ? Promise.resolve() : appMessagesManager.getConversationsAll('', 0);
const b: Promise<any> = allDialogsLoaded[1] ? Promise.resolve() : appMessagesManager.getConversationsAll('', 1);
a.finally(() => {
b.then(() => {
this.accumulateArchivedUnread(); this.accumulateArchivedUnread();
if(wasLoaded) {
(apiUpdatesManager.updatesState.syncLoading || Promise.resolve()).then(() => {
appMessagesManager.refreshConversations();
});
}
}); });
}); });
}); });

View File

@ -69,7 +69,11 @@ export class AppDraftsManager {
public getAllDrafts() { public getAllDrafts() {
return this.getAllDraftPromise || (this.getAllDraftPromise = new Promise((resolve) => { return this.getAllDraftPromise || (this.getAllDraftPromise = new Promise((resolve) => {
apiManager.invokeApi('messages.getAllDrafts').then((updates) => { apiManager.invokeApi('messages.getAllDrafts').then((updates) => {
apiUpdatesManager.processUpdateMessage(updates, {ignoreSyncLoading: true}); const p = apiUpdatesManager.updatesState.syncLoading || Promise.resolve();
p.then(() => {
apiUpdatesManager.processUpdateMessage(updates);
});
resolve(); resolve();
}); });
})); }));

View File

@ -86,14 +86,17 @@ export class AppImManager {
this.offline = rootScope.idle.isIDLE = true; this.offline = rootScope.idle.isIDLE = true;
this.updateStatus(); this.updateStatus();
clearInterval(this.updateStatusInterval); clearInterval(this.updateStatusInterval);
rootScope.broadcast('idle', true);
window.addEventListener('focus', () => { window.addEventListener('focus', () => {
this.offline = rootScope.idle.isIDLE = false; this.offline = rootScope.idle.isIDLE = false;
this.updateStatus(); this.updateStatus();
this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3); this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3);
// в обратном порядке // в обратном порядке
animationIntersector.checkAnimations(false); animationIntersector.checkAnimations(false);
rootScope.broadcast('idle', false);
}, {once: true}); }, {once: true});
}); });
@ -101,6 +104,8 @@ export class AppImManager {
window.addEventListener(FOCUS_EVENT_NAME, () => { window.addEventListener(FOCUS_EVENT_NAME, () => {
this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3); this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3);
this.updateStatus(); this.updateStatus();
rootScope.broadcast('idle', false);
}, {once: true, passive: true}); }, {once: true, passive: true});
this.chatsContainer = document.createElement('div'); this.chatsContainer = document.createElement('div');

View File

@ -6,7 +6,7 @@ import { createPosterForVideo } from "../../helpers/files";
import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object"; import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random"; import { randomLong } from "../../helpers/random";
import { splitStringByLength, limitSymbols } from "../../helpers/string"; import { splitStringByLength, limitSymbols } from "../../helpers/string";
import { ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputNotifyPeer, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PhotoSize, SendMessageAction, Update } from "../../layer"; import { ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update } from "../../layer";
import { InvokeApiOptions } from "../../types"; import { InvokeApiOptions } from "../../types";
import { langPack } from "../langPack"; import { langPack } from "../langPack";
import { logger, LogLevels } from "../logger"; import { logger, LogLevels } from "../logger";
@ -37,6 +37,7 @@ import { getFileNameByLocation } from "../../helpers/fileName";
import appProfileManager from "./appProfileManager"; import appProfileManager from "./appProfileManager";
import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug"; import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug";
import SlicedArray, { Slice, SliceEnd } from "../../helpers/slicedArray"; import SlicedArray, { Slice, SliceEnd } from "../../helpers/slicedArray";
import appNotificationsManager, { NotifyOptions } from "./appNotificationsManager";
//console.trace('include'); //console.trace('include');
// TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет // TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет
@ -155,7 +156,14 @@ export class AppMessagesManager {
public newMessagesToHandle: {[peerId: string]: number[]} = {}; public newMessagesToHandle: {[peerId: string]: number[]} = {};
public newDialogsHandlePromise = 0; public newDialogsHandlePromise = 0;
public newDialogsToHandle: {[peerId: string]: {reload: true} | Dialog} = {}; public newDialogsToHandle: {[peerId: string]: {reload: true} | Dialog} = {};
public newUpdatesAfterReloadToHandle: any = {}; public newUpdatesAfterReloadToHandle: {[peerId: string]: any[]} = {};
private notificationsHandlePromise = 0;
private notificationsToHandle: {[peerId: string]: {
fwdCount: number,
fromId: number,
topMessage?: MyMessage
}} = {};
private reloadConversationsPromise: Promise<void>; private reloadConversationsPromise: Promise<void>;
private reloadConversationsPeers: number[] = []; private reloadConversationsPeers: number[] = [];
@ -342,6 +350,8 @@ export class AppMessagesManager {
}); });
} }
}); });
appNotificationsManager.start();
} }
public getInputEntities(entities: MessageEntity[]) { public getInputEntities(entities: MessageEntity[]) {
@ -1543,6 +1553,32 @@ export class AppMessagesManager {
return false; return false;
} }
public async refreshConversations() {
const limit = 100, outDialogs: Dialog[] = [];
for(let folderId = 0; folderId < 2; ++folderId) {
let offsetDate = 0;
for(;;) {
const {dialogs} = await appMessagesManager.getTopMessages(limit, folderId, offsetDate);
if(dialogs.length) {
outDialogs.push(...dialogs as Dialog[]);
const dialog = dialogs[dialogs.length - 1];
offsetDate = this.getMessageByPeer(dialog.peerId, dialog.top_message).date;
} else {
break;
}
}
}
let obj: {[peerId: string]: Dialog} = {};
outDialogs.forEach(dialog => {
obj[dialog.peerId] = dialog;
});
rootScope.broadcast('dialogs_multiupdate', obj);
return outDialogs;
}
public async getConversationsAll(query = '', folderId = 0) { public async getConversationsAll(query = '', folderId = 0) {
const limit = 100, outDialogs: Dialog[] = []; const limit = 100, outDialogs: Dialog[] = [];
for(; folderId < 2; ++folderId) { for(; folderId < 2; ++folderId) {
@ -1609,7 +1645,7 @@ export class AppMessagesManager {
}); });
} }
return this.getTopMessages(limit, realFolderId).then(totalCount => { return this.getTopMessages(limit, realFolderId).then(messagesDialogs => {
//const curDialogStorage = this.dialogsStorage[folderId]; //const curDialogStorage = this.dialogsStorage[folderId];
offset = 0; offset = 0;
@ -1625,7 +1661,7 @@ export class AppMessagesManager {
return { return {
dialogs: curDialogStorage.slice(offset, offset + limit), dialogs: curDialogStorage.slice(offset, offset + limit),
count: totalCount, count: messagesDialogs._ === 'messages.dialogs' ? messagesDialogs.dialogs.length : messagesDialogs.count,
isEnd: this.dialogsStorage.allDialogsLoaded[realFolderId] && (offset + limit) >= curDialogStorage.length isEnd: this.dialogsStorage.allDialogsLoaded[realFolderId] && (offset + limit) >= curDialogStorage.length
}; };
}); });
@ -1645,16 +1681,19 @@ export class AppMessagesManager {
} }
} }
public getTopMessages(limit: number, folderId: number): Promise<number> { public getTopMessages(limit: number, folderId: number, offsetDate?: number) {
const dialogs = this.dialogsStorage.getFolder(folderId); const dialogs = this.dialogsStorage.getFolder(folderId);
let offsetId = 0; let offsetId = 0;
let offsetDate = 0;
let offsetPeerId = 0; let offsetPeerId = 0;
let offsetIndex = 0; let offsetIndex = 0;
if(this.dialogsStorage.dialogsOffsetDate[folderId]) { if(offsetDate === undefined) {
offsetDate = this.dialogsStorage.dialogsOffsetDate[folderId] + serverTimeManager.serverTimeOffset; offsetDate = this.dialogsStorage.getOffsetDate(folderId);
offsetIndex = this.dialogsStorage.dialogsOffsetDate[folderId] * 0x10000; }
if(offsetDate) {
offsetIndex = offsetDate * 0x10000;
offsetDate += serverTimeManager.serverTimeOffset;
} }
// ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА: // ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА:
@ -1747,7 +1786,7 @@ export class AppMessagesManager {
rootScope.broadcast('dialogs_multiupdate', {}); rootScope.broadcast('dialogs_multiupdate', {});
} }
return count; return dialogsResult;
}); });
} }
@ -2766,6 +2805,7 @@ export class AppMessagesManager {
(dialogsResult.dialogs as Dialog[]).forEach((dialog) => { (dialogsResult.dialogs as Dialog[]).forEach((dialog) => {
const peerId = appPeersManager.getPeerId(dialog.peer); const peerId = appPeersManager.getPeerId(dialog.peer);
let topMessage = dialog.top_message; let topMessage = dialog.top_message;
const topPendingMessage = this.pendingTopMsgs[peerId]; const topPendingMessage = this.pendingTopMsgs[peerId];
if(topPendingMessage) { if(topPendingMessage) {
if(!topMessage if(!topMessage
@ -2781,34 +2821,17 @@ export class AppMessagesManager {
} */ } */
if(topMessage || (dialog.draft && dialog.draft._ === 'draftMessage')) { if(topMessage || (dialog.draft && dialog.draft._ === 'draftMessage')) {
//const wasDialogBefore = this.getDialogByPeerID(peerId)[0];
// here need to just replace, not FULL replace dialog! WARNING
/* if(wasDialogBefore?.pFlags?.pinned && !dialog?.pFlags?.pinned) {
this.log.error('here need to just replace, not FULL replace dialog! WARNING', wasDialogBefore, dialog);
if(!dialog.pFlags) dialog.pFlags = {};
dialog.pFlags.pinned = true;
} */
this.saveConversation(dialog); this.saveConversation(dialog);
updatedDialogs[peerId] = dialog;
/* if(wasDialogBefore) {
rootScope.$broadcast('dialog_top', dialog);
} else { */
//if(wasDialogBefore?.top_message !== topMessage) {
updatedDialogs[peerId] = dialog;
//}
//}
} else { } else {
const dropped = this.dialogsStorage.dropDialog(peerId); const dropped = this.dialogsStorage.dropDialog(peerId);
if(dropped.length) { if(dropped.length) {
rootScope.broadcast('dialog_drop', {peerId: peerId, dialog: dropped[0]}); rootScope.broadcast('dialog_drop', {peerId, dialog: dropped[0]});
} }
} }
if(this.newUpdatesAfterReloadToHandle[peerId] !== undefined) { if(this.newUpdatesAfterReloadToHandle[peerId] !== undefined) {
for(const i in this.newUpdatesAfterReloadToHandle[peerId]) { for(const update of this.newUpdatesAfterReloadToHandle[peerId]) {
const update = this.newUpdatesAfterReloadToHandle[peerId][i];
this.handleUpdate(update); this.handleUpdate(update);
} }
@ -2925,6 +2948,8 @@ export class AppMessagesManager {
historyStorage.readMaxId = dialog.read_inbox_max_id; historyStorage.readMaxId = dialog.read_inbox_max_id;
historyStorage.readOutboxMaxId = dialog.read_outbox_max_id; historyStorage.readOutboxMaxId = dialog.read_outbox_max_id;
appNotificationsManager.savePeerSettings(peerId, dialog.notify_settings)
if(channelId && dialog.pts) { if(channelId && dialog.pts) {
apiUpdatesManager.addChannelState(channelId, dialog.pts); apiUpdatesManager.addChannelState(channelId, dialog.pts);
} }
@ -3534,6 +3559,19 @@ export class AppMessagesManager {
}); });
} }
if(!threadId && historyStorage && historyStorage.history.length) {
const slice = historyStorage.history.slice;
for(const mid of slice) {
const message = this.getMessageByPeer(peerId, mid);
if(message && !message.pFlags.out) {
message.pFlags.unread = false;
appNotificationsManager.cancel('msg' + mid);
}
}
}
appNotificationsManager.soundReset(appPeersManager.getPeerString(peerId));
if(historyStorage.readPromise) { if(historyStorage.readPromise) {
return historyStorage.readPromise; return historyStorage.readPromise;
} }
@ -3602,6 +3640,40 @@ export class AppMessagesManager {
return this.historiesStorage[peerId] ?? (this.historiesStorage[peerId] = {count: null, history: new SlicedArray()}); return this.historiesStorage[peerId] ?? (this.historiesStorage[peerId] = {count: null, history: new SlicedArray()});
} }
private handleNotifications = () => {
window.clearTimeout(this.notificationsHandlePromise);
this.notificationsHandlePromise = 0;
//var timeout = $rootScope.idle.isIDLE && StatusManager.isOtherDeviceActive() ? 30000 : 1000;
//const timeout = 1000;
for(const _peerId in this.notificationsToHandle) {
const peerId = +_peerId;
const notifyPeerToHandle = this.notificationsToHandle[peerId];
Promise.all([
appNotificationsManager.getPeerMuted(peerId),
appNotificationsManager.getNotifySettings(appPeersManager.getInputNotifyPeerById(peerId, true))
]).then(([muted, peerTypeNotifySettings]) => {
const topMessage = notifyPeerToHandle.topMessage;
if(muted || !topMessage.pFlags.unread) {
return;
}
//setTimeout(() => {
if(topMessage.pFlags.unread) {
this.notifyAboutMessage(topMessage, {
fwdCount: notifyPeerToHandle.fwdCount,
peerTypeNotifySettings
});
}
//}, timeout);
});
}
this.notificationsToHandle = {};
};
public handleUpdate(update: Update) { public handleUpdate(update: Update) {
/* if(DEBUG) { /* if(DEBUG) {
this.log.debug('handleUpdate', update._, update); this.log.debug('handleUpdate', update._, update);
@ -3703,14 +3775,40 @@ export class AppMessagesManager {
} }
const dialog = foundDialog[0]; const dialog = foundDialog[0];
const inboxUnread = !message.pFlags.out && message.pFlags.unread;
if(dialog) { if(dialog) {
const inboxUnread = !message.pFlags.out && message.pFlags.unread;
this.setDialogTopMessage(message, dialog); this.setDialogTopMessage(message, dialog);
if(inboxUnread) { if(inboxUnread) {
dialog.unread_count++; dialog.unread_count++;
} }
} }
if(inboxUnread/* && ($rootScope.selectedPeerID != peerID || $rootScope.idle.isIDLE) */) {
const notifyPeer = message.peerId;
let notifyPeerToHandle = this.notificationsToHandle[notifyPeer];
if(notifyPeerToHandle === undefined) {
notifyPeerToHandle = this.notificationsToHandle[notifyPeer] = {
fwdCount: 0,
fromId: 0
};
}
if(notifyPeerToHandle.fromId !== message.fromId) {
notifyPeerToHandle.fromId = message.fromId;
notifyPeerToHandle.fwdCount = 0;
}
if((message as Message.message).fwd_from) {
notifyPeerToHandle.fwdCount++;
}
notifyPeerToHandle.topMessage = message;
if(!this.notificationsHandlePromise) {
this.notificationsHandlePromise = window.setTimeout(this.handleNotifications, 0);
}
}
break; break;
} }
@ -3986,8 +4084,9 @@ export class AppMessagesManager {
if(!message.pFlags.out && !threadId && stillUnreadCount === undefined) { if(!message.pFlags.out && !threadId && stillUnreadCount === undefined) {
newUnreadCount = --foundDialog.unread_count; newUnreadCount = --foundDialog.unread_count;
//NotificationsManager.cancel('msg' + messageId); // warning
} }
appNotificationsManager.cancel('msg' + messageId);
} }
} }
@ -4194,7 +4293,7 @@ export class AppMessagesManager {
this.pendingTopMsgs[peerId] = messageId; this.pendingTopMsgs[peerId] = messageId;
this.handleUpdate({ this.handleUpdate({
_: 'updateNewMessage', _: 'updateNewMessage',
message: message message
} as any); } as any);
} }
@ -4261,13 +4360,14 @@ export class AppMessagesManager {
case 'updateNotifySettings': { case 'updateNotifySettings': {
const {peer, notify_settings} = update; const {peer, notify_settings} = update;
if(peer._ === 'notifyPeer') {
const peerId = appPeersManager.getPeerId((peer as NotifyPeer.notifyPeer).peer);
const peerId = appPeersManager.getPeerId((peer as NotifyPeer.notifyPeer).peer); const dialog = this.getDialogByPeerId(peerId)[0];
if(dialog) {
const dialog = this.getDialogByPeerId(peerId)[0]; dialog.notify_settings = notify_settings;
if(dialog) { rootScope.broadcast('dialog_notify_settings', peerId);
dialog.notify_settings = notify_settings; }
rootScope.broadcast('dialog_notify_settings', peerId);
} }
/////this.log('updateNotifySettings', peerId, notify_settings); /////this.log('updateNotifySettings', peerId, notify_settings);
@ -4379,17 +4479,11 @@ export class AppMessagesManager {
} }
public mutePeer(peerId: number) { public mutePeer(peerId: number) {
let inputPeer = appPeersManager.getInputPeerById(peerId); const settings: InputPeerNotifySettings = {
let inputNotifyPeer: InputNotifyPeer.inputNotifyPeer = {
_: 'inputNotifyPeer',
peer: inputPeer
};
let settings: InputPeerNotifySettings = {
_: 'inputPeerNotifySettings' _: 'inputPeerNotifySettings'
}; };
let dialog = appMessagesManager.getDialogByPeerId(peerId)[0]; const dialog = appMessagesManager.getDialogByPeerId(peerId)[0];
let muted = true; let muted = true;
if(dialog && dialog.notify_settings) { if(dialog && dialog.notify_settings) {
muted = dialog.notify_settings.mute_until > (Date.now() / 1000 | 0); muted = dialog.notify_settings.mute_until > (Date.now() / 1000 | 0);
@ -4398,25 +4492,11 @@ export class AppMessagesManager {
if(!muted) { if(!muted) {
settings.mute_until = 2147483647; settings.mute_until = 2147483647;
} }
apiManager.invokeApi('account.updateNotifySettings', { return appNotificationsManager.updateNotifySettings({
peer: inputNotifyPeer, _: 'inputNotifyPeer',
settings: settings peer: appPeersManager.getInputPeerById(peerId)
}).then(bool => { }, settings);
if(bool) {
this.handleUpdate({
_: 'updateNotifySettings',
peer: {
_: 'notifyPeer',
peer: appPeersManager.getOutputPeer(peerId)
},
notify_settings: { // ! WOW, IT WORKS !
...settings,
_: 'peerNotifySettings',
}
});
}
});
} }
public canWriteToPeer(peerId: number) { public canWriteToPeer(peerId: number) {
@ -4530,6 +4610,226 @@ export class AppMessagesManager {
}); });
} }
private notifyAboutMessage(message: MyMessage, options: Partial<{
fwdCount: number,
peerTypeNotifySettings: PeerNotifySettings
}> = {}) {
const peerId = this.getMessagePeer(message);
let peerString: string;
const notification: NotifyOptions = {};
var notificationMessage: string,
notificationPhoto: any;
const _ = (str: string) => str;
const localSettings = appNotificationsManager.getLocalSettings();
if(options.peerTypeNotifySettings.show_previews) {
if(message._ === 'message') {
if(message.fwd_from && options.fwdCount) {
notificationMessage = 'Forwarded ' + options.fwdCount + ' messages';//fwdMessagesPluralize(options.fwd_count);
} else if(message.message) {
if(localSettings.nopreview) {
notificationMessage = _('conversation_message_sent');
} else {
notificationMessage = RichTextProcessor.wrapPlainText(message.message);
}
} else if(message.media) {
var captionEmoji: string;
switch (message.media._) {
case 'messageMediaPhoto':
notificationMessage = _('conversation_media_photo_raw');
captionEmoji = '🖼';
break;
case 'messageMediaDocument':
if(message.media.document._ === 'documentEmpty') break;
switch(message.media.document.type) {
case 'gif':
notificationMessage = _('conversation_media_gif_raw');
captionEmoji = '🎬';
break;
case 'sticker':
notificationMessage = _('conversation_media_sticker');
var stickerEmoji = message.media.document.stickerEmojiRaw;
if(stickerEmoji !== undefined) {
notificationMessage = RichTextProcessor.wrapPlainText(stickerEmoji) + ' ' + notificationMessage;
}
break;
case 'video':
notificationMessage = _('conversation_media_video_raw');
captionEmoji = '📹';
break;
case 'round':
notificationMessage = _('conversation_media_round_raw');
captionEmoji = '📹';
break;
case 'voice':
case 'audio':
notificationMessage = _('conversation_media_audio_raw');
break;
default:
if(message.media.document.file_name) {
notificationMessage = RichTextProcessor.wrapPlainText('📎 ' + message.media.document.file_name);
} else {
notificationMessage = _('conversation_media_document_raw');
captionEmoji = '📎';
}
break;
}
break;
case 'messageMediaGeo':
case 'messageMediaVenue':
notificationMessage = _('conversation_media_location_raw');
captionEmoji = '📍';
break;
case 'messageMediaContact':
notificationMessage = _('conversation_media_contact_raw');
break;
case 'messageMediaGame':
notificationMessage = RichTextProcessor.wrapPlainText('🎮 ' + message.media.game.title);
break;
case 'messageMediaUnsupported':
notificationMessage = _('conversation_media_unsupported_raw');
break;
default:
notificationMessage = _('conversation_media_attachment_raw');
break;
}
if(captionEmoji !== undefined && (message.media as any).caption) {
notificationMessage = RichTextProcessor.wrapPlainText(captionEmoji + ' ' + (message.media as any).caption);
}
}
} else {
switch(message.action._) {
case 'messageActionChatCreate':
notificationMessage = _('conversation_group_created_raw')
break
case 'messageActionChatEditTitle':
notificationMessage = _('conversation_group_renamed_raw')
break
case 'messageActionChatEditPhoto':
notificationMessage = _('conversation_group_photo_updated_raw')
break
case 'messageActionChatDeletePhoto':
notificationMessage = _('conversation_group_photo_removed_raw')
break
case 'messageActionChatAddUser':
// @ts-ignore
case 'messageActionChatAddUsers':
notificationMessage = _('conversation_invited_user_message_raw')
break
/* case 'messageActionChatReturn':
notificationMessage = _('conversation_returned_to_group_raw')
break
case 'messageActionChatJoined':
notificationMessage = _('conversation_joined_group_raw')
break */
case 'messageActionChatDeleteUser':
notificationMessage = _('conversation_kicked_user_message_raw')
break
/* case 'messageActionChatLeave':
notificationMessage = _('conversation_left_group_raw')
break */
case 'messageActionChatJoinedByLink':
notificationMessage = _('conversation_joined_by_link_raw')
break
case 'messageActionChannelCreate':
notificationMessage = _('conversation_created_channel_raw')
break
/* case 'messageActionChannelEditTitle':
notificationMessage = _('conversation_changed_channel_name_raw')
break
case 'messageActionChannelEditPhoto':
notificationMessage = _('conversation_changed_channel_photo_raw')
break
case 'messageActionChannelDeletePhoto':
notificationMessage = _('conversation_removed_channel_photo_raw')
break */
case 'messageActionPinMessage':
notificationMessage = _('conversation_pinned_message_raw')
break
/* case 'messageActionGameScore':
notificationMessage = gameScorePluralize(message.action.score)
break */
case 'messageActionPhoneCall':
switch((message.action as any).type) {
case 'out_missed':
notificationMessage = _('message_service_phonecall_canceled_raw')
break
case 'in_missed':
notificationMessage = _('message_service_phonecall_missed_raw')
break
case 'out_ok':
notificationMessage = _('message_service_phonecall_outgoing_raw')
break
case 'in_ok':
notificationMessage = _('message_service_phonecall_incoming_raw')
break
}
break
}
}
} else {
notificationMessage = 'New notification';
}
if(peerId > 0) {
const fromUser = appUsersManager.getUser(message.fromId);
const fromPhoto = appUsersManager.getUserPhoto(message.fromId);
notification.title = (fromUser.first_name || '') +
(fromUser.first_name && fromUser.last_name ? ' ' : '') +
(fromUser.last_name || '');
if(!notification.title) {
notification.title = fromUser.phone || _('conversation_unknown_user_raw');
}
notificationPhoto = fromPhoto;
peerString = appUsersManager.getUserString(peerId);
} else {
notification.title = appChatsManager.getChat(-peerId).title || _('conversation_unknown_chat_raw');
if(message.fromId) {
var fromUser = appUsersManager.getUser(message.fromId);
notification.title = (fromUser.first_name || fromUser.last_name || _('conversation_unknown_user_raw')) +
' @ ' +
notification.title;
}
notificationPhoto = appChatsManager.getChatPhoto(-peerId);
peerString = appChatsManager.getChatString(-peerId);
}
notification.title = RichTextProcessor.wrapPlainText(notification.title);
notification.onclick = () => {
/* rootScope.broadcast('history_focus', {
peerString: peerString,
messageID: message.flags & 16 ? message.mid : 0
}); */
};
notification.message = notificationMessage;
notification.key = 'msg' + message.mid;
notification.tag = peerString;
notification.silent = true;//message.pFlags.silent || false;
/* if(notificationPhoto.location && !notificationPhoto.location.empty) {
apiManager.downloadSmallFile(notificationPhoto.location, notificationPhoto.size).then(function (blob) {
if (message.pFlags.unread) {
notification.image = blob
NotificationsManager.notify(notification)
}
})
} else { */
appNotificationsManager.notify(notification);
//}
}
public getScheduledMessagesStorage(peerId: number) { public getScheduledMessagesStorage(peerId: number) {
return this.scheduledMessagesStorage[peerId] ?? (this.scheduledMessagesStorage[peerId] = this.createMessageStorage()); return this.scheduledMessagesStorage[peerId] ?? (this.scheduledMessagesStorage[peerId] = this.createMessageStorage());
} }
@ -4915,6 +5215,7 @@ export class AppMessagesManager {
if(!message.pFlags.out && !message.pFlags.is_outgoing && message.pFlags.unread) { if(!message.pFlags.out && !message.pFlags.is_outgoing && message.pFlags.unread) {
history.unread++; history.unread++;
appNotificationsManager.cancel('msg' + mid);
} }
history.count++; history.count++;
history.msgs[mid] = true; history.msgs[mid] = true;

View File

@ -0,0 +1,675 @@
import { fontFamily } from "../../components/middleEllipsis";
import { MOUNT_CLASS_TO } from "../../config/debug";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
import { tsNow } from "../../helpers/date";
import { copy, deepEqual } from "../../helpers/object";
import { convertInputKeyToKey } from "../../helpers/string";
import { isMobile } from "../../helpers/userAgent";
import { InputNotifyPeer, InputPeerNotifySettings, NotifyPeer, PeerNotifySettings, Update } from "../../layer";
import Config from "../config";
import apiManager from "../mtproto/mtprotoworker";
import rootScope from "../rootScope";
import sessionStorage from "../sessionStorage";
import apiUpdatesManager from "./apiUpdatesManager";
import appChatsManager from "./appChatsManager";
import appPeersManager from "./appPeersManager";
import appStateManager from "./appStateManager";
import appUsersManager from "./appUsersManager";
type MyNotification = Notification & {
hidden?: boolean,
show?: () => void,
};
export type NotifyOptions = Partial<{
tag: string;
image: string;
key: string;
title: string;
message: string;
silent: boolean;
onclick: () => void;
}>;
export class AppNotificationsManager {
private notificationsUiSupport: boolean;
private notificationsShown: {[key: string]: MyNotification} = {};
private notificationIndex = 0;
private notificationsCount = 0;
private soundsPlayed: {[tag: string]: number} = {};
private vibrateSupport = !!navigator.vibrate;
private nextSoundAt: number;
private prevSoundVolume: number;
private peerSettings = {
notifyPeer: {} as {[peerId: number]: Promise<PeerNotifySettings>},
notifyUsers: null as Promise<PeerNotifySettings>,
notifyChats: null as Promise<PeerNotifySettings>,
notifyBroadcasts: null as Promise<PeerNotifySettings>
};
private exceptions: {[peerId: string]: PeerNotifySettings} = {};
private notifyContactsSignUp: Promise<boolean>;
private faviconEl: HTMLLinkElement = document.head.querySelector('link[rel="icon"]');
private langNotificationsPluralize = 'notifications';//_.pluralize('page_title_pluralize_notifications');
private titleBackup = document.title;
private titleChanged = false;
private titleInterval: number;
private prevFavicon: string;
private stopped = false;
private settings: Partial<{
nodesktop: boolean,
volume: number,
novibrate: boolean,
nopreview: boolean,
nopush: boolean,
nosound: boolean,
}> = {};
private registeredDevice: any;
private pushInited = false;
private topMessagesDeferred: CancellablePromise<void>;
private notifySoundEl: HTMLElement;
constructor() {
// @ts-ignore
navigator.vibrate = navigator.vibrate || navigator.mozVibrate || navigator.webkitVibrate;
this.notificationsUiSupport = ('Notification' in window) || ('mozNotification' in navigator);
this.topMessagesDeferred = deferredPromise<void>();
this.notifySoundEl = document.createElement('div');
this.notifySoundEl.id = 'notify-sound';
document.body.append(this.notifySoundEl);
/* rootScope.on('idle.deactivated', (newVal) => {
if(newVal) {
stop();
}
});*/
rootScope.on('idle', (newVal) => {
if(this.stopped) {
return;
}
if(!newVal) {
this.clear();
}
this.toggleToggler();
});
rootScope.on('apiUpdate', (update) => {
// console.log('on apiUpdate', update)
switch(update._) {
case 'updateNotifySettings': {
this.savePeerSettings(update.peer._ === 'notifyPeer' ? appPeersManager.getPeerId(update.peer.peer) : update.peer._, update.notify_settings);
rootScope.broadcast('notify_settings', update);
break;
}
}
});
/* rootScope.on('push_init', (tokenData) => {
this.pushInited = true
if(!this.settings.nodesktop && !this.settings.nopush) {
if(tokenData) {
this.registerDevice(tokenData);
} else {
WebPushApiManager.subscribe();
}
} else {
this.unregisterDevice(tokenData);
}
});
rootScope.on('push_subscribe', (tokenData) => {
this.registerDevice(tokenData);
});
rootScope.on('push_unsubscribe', (tokenData) => {
this.unregisterDevice(tokenData);
}); */
rootScope.addListener('dialogs_multiupdate', () => {
//unregisterTopMsgs()
this.topMessagesDeferred.resolve();
}, true);
/* rootScope.on('push_notification_click', (notificationData) => {
if(notificationData.action === 'push_settings') {
this.topMessagesDeferred.then(() => {
$modal.open({
templateUrl: templateUrl('settings_modal'),
controller: 'SettingsModalController',
windowClass: 'settings_modal_window mobile_modal',
backdrop: 'single'
})
});
return;
}
if(notificationData.action === 'mute1d') {
apiManager.invokeApi('account.updateDeviceLocked', {
period: 86400
}).then(() => {
// var toastData = toaster.pop({
// type: 'info',
// body: _('push_action_mute1d_success'),
// bodyOutputType: 'trustedHtml',
// clickHandler: () => {
// toaster.clear(toastData)
// },
// showCloseButton: false
// })
});
return;
}
const peerId = notificationData.custom && notificationData.custom.peerId;
console.log('click', notificationData, peerId);
if(peerId) {
this.topMessagesDeferred.then(() => {
if(notificationData.custom.channel_id &&
!appChatsManager.hasChat(notificationData.custom.channel_id)) {
return;
}
if(peerId > 0 && !appUsersManager.hasUser(peerId)) {
return;
}
// rootScope.broadcast('history_focus', {
// peerString: appPeersManager.getPeerString(peerId)
// });
});
}
}); */
}
private toggleToggler(enable = rootScope.idle.isIDLE) {
if(isMobile) return;
const resetTitle = () => {
this.titleChanged = false;
document.title = this.titleBackup;
this.setFavicon();
};
window.clearInterval(this.titleInterval);
this.titleInterval = 0;
if(!enable) {
resetTitle();
} else {
this.titleInterval = window.setInterval(() => {
if(!this.notificationsCount) {
this.toggleToggler(false);
} else if(this.titleChanged) {
resetTitle();
} else {
this.titleChanged = true;
document.title = this.notificationsCount + ' ' + this.langNotificationsPluralize;
//this.setFavicon('assets/img/favicon_unread.ico');
// fetch('assets/img/favicon.ico')
// .then(res => res.blob())
// .then(blob => {
// const img = document.createElement('img');
// img.src = URL.createObjectURL(blob);
const canvas = document.createElement('canvas');
canvas.width = 32 * window.devicePixelRatio;
canvas.height = canvas.width;
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, canvas.width / 2, 0, 2 * Math.PI, false);
ctx.fillStyle = '#5b8af1';
ctx.fill();
let fontSize = 24;
let str = '' + this.notificationsCount;
if(this.notificationsCount < 10) {
fontSize = 22;
} else if(this.notificationsCount < 100) {
fontSize = 20;
} else {
str = '99+';
fontSize = 18;
}
ctx.font = `700 ${fontSize}px ${fontFamily}`;
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillStyle = 'white';
ctx.fillText('' + this.notificationsCount, canvas.width / 2, canvas.height * .5625);
/* const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height); */
this.setFavicon(canvas.toDataURL());
// });
}
}, 1000);
}
}
public updateLocalSettings() {
Promise.all(['notify_nodesktop', 'notify_volume', 'notify_novibrate', 'notify_nopreview', 'notify_nopush'].map(k => sessionStorage.get(k as any)))
.then((updSettings) => {
this.settings.nodesktop = updSettings[0];
this.settings.volume = updSettings[1] === undefined ? 0.5 : updSettings[1];
this.settings.novibrate = updSettings[2];
this.settings.nopreview = updSettings[3];
this.settings.nopush = updSettings[4];
/* if(this.pushInited) {
const needPush = !this.settings.nopush && !this.settings.nodesktop && WebPushApiManager.isAvailable || false;
const hasPush = this.registeredDevice !== false;
if(needPush !== hasPush) {
if(needPush) {
WebPushApiManager.subscribe();
} else {
WebPushApiManager.unsubscribe();
}
}
}
WebPushApiManager.setSettings(this.settings); */
});
appStateManager.getState().then(state => {
this.settings.nosound = !state.settings.notifications.sound;
});
}
public getLocalSettings() {
return this.settings;
}
public getNotifySettings(peer: InputNotifyPeer): Promise<PeerNotifySettings> {
let key: any = convertInputKeyToKey(peer._);
let obj: any = this.peerSettings[key as NotifyPeer['_']];
if(peer._ === 'inputNotifyPeer') {
key = appPeersManager.getPeerId(peer.peer);
obj = obj[key];
}
if(obj) {
return obj;
}
return (obj || this.peerSettings)[key] = apiManager.invokeApi('account.getNotifySettings', {peer})/* .then(settings => {
return settings;
}) */;
}
public updateNotifySettings(peer: InputNotifyPeer, settings: InputPeerNotifySettings) {
//this.savePeerSettings(peerId, settings);
/* const inputSettings: InputPeerNotifySettings = copy(settings) as any;
inputSettings._ = 'inputPeerNotifySettings'; */
return apiManager.invokeApi('account.updateNotifySettings', {
peer,
settings
}).then(value => {
if(value) {
apiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updateNotifySettings',
peer: {
...peer,
_: convertInputKeyToKey(peer._)
},
notify_settings: { // ! WOW, IT WORKS !
...settings,
_: 'peerNotifySettings',
}
} as Update.updateNotifySettings
});
}
});
}
public getNotifyExceptions() {
apiManager.invokeApi('account.getNotifyExceptions', {compare_sound: true})
.then((updates) => {
apiUpdatesManager.processUpdateMessage(updates);
});
}
public getContactSignUpNotification() {
if(this.notifyContactsSignUp) return this.notifyContactsSignUp;
return this.notifyContactsSignUp = apiManager.invokeApi('account.getContactSignUpNotification');
}
public setContactSignUpNotification(silent: boolean) {
apiManager.invokeApi('account.setContactSignUpNotification', {silent})
.then(value => {
this.notifyContactsSignUp = Promise.resolve(!silent);
});
}
private setFavicon(href: string = 'assets/img/favicon.ico') {
if(this.prevFavicon === href) {
return;
}
const link = this.faviconEl.cloneNode() as HTMLLinkElement;
link.href = href;
this.faviconEl.parentNode.replaceChild(link, this.faviconEl);
this.faviconEl = link;
this.prevFavicon = href;
}
public savePeerSettings(key: number | NotifyPeer['_'], settings: PeerNotifySettings) {
const p = Promise.resolve(settings);
let obj: any;
if(typeof(key) === 'number') {
obj = this.peerSettings['notifyPeer'];
}
(obj || this.peerSettings)[key] = p;
//rootScope.broadcast('notify_settings', {peerId: peerId});
}
public isMuted(peerNotifySettings: PeerNotifySettings) {
return peerNotifySettings._ === 'peerNotifySettings' &&
(peerNotifySettings.mute_until * 1000) > tsNow();
}
public getPeerMuted(peerId: number) {
return this.getNotifySettings({_: 'inputNotifyPeer', peer: appPeersManager.getInputPeerById(peerId)})
.then((peerNotifySettings) => this.isMuted(peerNotifySettings));
}
public start() {
this.updateLocalSettings();
//rootScope.on('settings_changed', this.updateNotifySettings);
//WebPushApiManager.start();
if(!this.notificationsUiSupport) {
return false;
}
if('Notification' in window && Notification.permission !== 'granted' && Notification.permission !== 'denied') {
window.addEventListener('click', this.requestPermission);
}
try {
if('onbeforeunload' in window) {
window.addEventListener('beforeunload', this.clear);
}
} catch (e) {}
}
private stop() {
this.clear();
window.clearInterval(this.titleInterval);
this.titleInterval = 0;
this.setFavicon();
this.stopped = true;
}
private requestPermission = () => {
Notification.requestPermission();
window.removeEventListener('click', this.requestPermission);
};
public notify(data: NotifyOptions) {
console.log('notify', data, rootScope.idle.isIDLE, this.notificationsUiSupport, this.stopped);
if(this.stopped) {
return;
}
// FFOS Notification blob src bug workaround
/* if(Config.Navigator.ffos && !Config.Navigator.ffos2p) {
data.image = 'https://telegram.org/img/t_logo.png'
}
else if (data.image && !angular.isString(data.image)) {
if (Config.Navigator.ffos2p) {
FileManager.getDataUrl(data.image, 'image/jpeg').then(function (url) {
data.image = url
notify(data)
})
return false
} else {
data.image = FileManager.getUrl(data.image, 'image/jpeg')
}
}
else if (!data.image) */ {
data.image = 'assets/img/logo.svg';
}
// console.log('notify image', data.image)
this.notificationsCount++;
if(!this.titleInterval) {
this.toggleToggler();
}
const now = tsNow();
if(this.settings.volume > 0 && !this.settings.nosound/* &&
(
!data.tag ||
!this.soundsPlayed[data.tag] ||
now > this.soundsPlayed[data.tag] + 60000
) */
) {
this.testSound(this.settings.volume);
this.soundsPlayed[data.tag] = now;
}
if(!this.notificationsUiSupport ||
'Notification' in window && Notification.permission !== 'granted') {
return false;
}
if(this.settings.nodesktop) {
if(this.vibrateSupport && !this.settings.novibrate) {
navigator.vibrate([200, 100, 200]);
return;
}
return;
}
const idx = ++this.notificationIndex;
const key = data.key || 'k' + idx;
let notification: MyNotification;
if('Notification' in window) {
try {
if(data.tag) {
for(let i in this.notificationsShown) {
const notification = this.notificationsShown[i];
if(notification &&
notification.tag === data.tag) {
notification.hidden = true
}
}
}
notification = new Notification(data.title, {
icon: data.image || '',
body: data.message || '',
tag: data.tag || '',
silent: data.silent || false
});
} catch(e) {
this.notificationsUiSupport = false;
//WebPushApiManager.setLocalNotificationsDisabled();
return
}
} /* else if('mozNotification' in navigator) {
notification = navigator.mozNotification.createNotification(data.title, data.message || '', data.image || '')
} else if(notificationsMsSiteMode) {
window.external.msSiteModeClearIconOverlay()
window.external.msSiteModeSetIconOverlay('img/icons/icon16.png', data.title)
window.external.msSiteModeActivate()
notification = {
index: idx
}
} */ else {
return;
}
notification.onclick = () => {
notification.close();
//AppRuntimeManager.focus();
this.clear();
if(data.onclick) {
data.onclick();
}
};
notification.onclose = () => {
if(!notification.hidden) {
delete this.notificationsShown[key];
this.clear();
}
};
if(notification.show) {
notification.show();
}
this.notificationsShown[key] = notification;
if(!isMobile) {
setTimeout(() => {
this.hide(key);
}, 8000);
}
}
public testSound(volume: number) {
const now = tsNow();
if(this.nextSoundAt && now < this.nextSoundAt && this.prevSoundVolume === volume) {
return;
}
this.nextSoundAt = now + 1000;
this.prevSoundVolume = volume;
const filename = 'assets/audio/notification.mp3';
const audio = document.createElement('audio');
audio.autoplay = true;
audio.setAttribute('mozaudiochannel', 'notification');
audio.volume = volume;
audio.innerHTML = `
<source src="${filename}" type="audio/mpeg" />
<embed hidden="true" autostart="true" loop="false" volume="${volume * 100}" src="${filename}" />
`;
this.notifySoundEl.append(audio);
audio.addEventListener('ended', () => {
audio.remove();
}, {once: true});
}
public cancel(key: string) {
const notification = this.notificationsShown[key];
if(notification) {
if(this.notificationsCount > 0) {
this.notificationsCount--;
}
try {
if(notification.close) {
notification.hidden = true;
notification.close();
}/* else if(notificationsMsSiteMode &&
notification.index === notificationIndex) {
window.external.msSiteModeClearIconOverlay()
} */
} catch (e) {}
delete this.notificationsShown[key];
}
}
private hide(key: string) {
const notification = this.notificationsShown[key];
if(notification) {
try {
if(notification.close) {
notification.hidden = true;
notification.close();
}
} catch (e) {}
}
}
public soundReset(tag: string) {
delete this.soundsPlayed[tag];
}
public clear() {
/* if(notificationsMsSiteMode) {
window.external.msSiteModeClearIconOverlay()
} else { */
for(let i in this.notificationsShown) {
const notification = this.notificationsShown[i];
try {
if(notification.close) {
notification.close();
}
} catch (e) {}
}
/* } */
this.notificationsShown = {};
this.notificationsCount = 0;
//WebPushApiManager.hidePushNotifications();
}
private registerDevice(tokenData: any) {
if(this.registeredDevice &&
deepEqual(this.registeredDevice, tokenData)) {
return false;
}
apiManager.invokeApi('account.registerDevice', {
token_type: tokenData.tokenType,
token: tokenData.tokenValue,
other_uids: [],
app_sandbox: false,
secret: new Uint8Array()
}).then(() => {
this.registeredDevice = tokenData;
}, (error) => {
error.handled = true;
})
}
private unregisterDevice(tokenData: any) {
if(!this.registeredDevice) {
return false;
}
apiManager.invokeApi('account.unregisterDevice', {
token_type: tokenData.tokenType,
token: tokenData.tokenValue,
other_uids: []
}).then(() => {
this.registeredDevice = false
}, (error) => {
error.handled = true
})
}
public getVibrateSupport() {
return this.vibrateSupport
}
}
const appNotificationsManager = new AppNotificationsManager();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appNotificationsManager = appNotificationsManager);
export default appNotificationsManager;

View File

@ -1,6 +1,6 @@
import { MOUNT_CLASS_TO } from "../../config/debug"; import { MOUNT_CLASS_TO } from "../../config/debug";
import { isObject } from "../../helpers/object"; import { isObject } from "../../helpers/object";
import { DialogPeer, InputDialogPeer, InputPeer, Peer, Update } from "../../layer"; import { DialogPeer, InputDialogPeer, InputNotifyPeer, InputPeer, Peer, Update } from "../../layer";
import { RichTextProcessor } from "../richtextprocessor"; import { RichTextProcessor } from "../richtextprocessor";
import rootScope from "../rootScope"; import rootScope from "../rootScope";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
@ -105,12 +105,12 @@ export class AppPeersManager {
return {_: 'peerChat', chat_id: chatId}; return {_: 'peerChat', chat_id: chatId};
} }
/* public getPeerString(peerId: number) { public getPeerString(peerId: number) {
if(peerId > 0) { if(peerId > 0) {
return appUsersManager.getUserString(peerId); return appUsersManager.getUserString(peerId);
} }
return appChatsManager.getChatString(-peerId); return appChatsManager.getChatString(-peerId);
} */ }
public getPeerUsername(peerId: number): string { public getPeerUsername(peerId: number): string {
if(peerId > 0) { if(peerId > 0) {
@ -163,6 +163,13 @@ export class AppPeersManager {
return (peerId > 0) && appUsersManager.isBot(peerId); return (peerId > 0) && appUsersManager.isBot(peerId);
} }
/**
* Will check notification exceptions by type too
*/
public isPeerMuted(peerId: number) {
}
/* public getInputPeer(peerString: string): InputPeer { /* public getInputPeer(peerString: string): InputPeer {
var firstChar = peerString.charAt(0); var firstChar = peerString.charAt(0);
var peerParams = peerString.substr(1).split('_'); var peerParams = peerString.substr(1).split('_');
@ -195,6 +202,25 @@ export class AppPeersManager {
} }
} */ } */
public getInputNotifyPeerById(peerId: number, ignorePeerId = false): InputNotifyPeer {
if(ignorePeerId) {
if(peerId > 0) {
return {_: 'inputNotifyUsers'};
} else {
if(appPeersManager.isBroadcast(peerId)) {
return {_: 'inputNotifyBroadcasts'};
} else {
return {_: 'inputNotifyChats'};
}
}
} else {
return {
_: 'inputNotifyPeer',
peer: this.getInputPeerById(peerId)
};
}
}
public getInputPeerById(peerId: number): InputPeer { public getInputPeerById(peerId: number): InputPeer {
if(!peerId) { if(!peerId) {
return {_: 'inputPeerEmpty'}; return {_: 'inputPeerEmpty'};

View File

@ -5,6 +5,7 @@ import appChatsManager from "./appChatsManager";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
import apiUpdatesManager from "./apiUpdatesManager"; import apiUpdatesManager from "./apiUpdatesManager";
import rootScope from "../rootScope"; import rootScope from "../rootScope";
import { convertInputKeyToKey } from "../../helpers/string";
export enum PrivacyType { export enum PrivacyType {
Everybody = 2, Everybody = 2,
@ -31,11 +32,6 @@ export class AppPrivacyManager {
}); });
} }
public convertInputKeyToKey(inputKey: string) {
let str = inputKey.replace('input', '');
return (str[0].toLowerCase() + str.slice(1)) as string;
}
public setPrivacy(inputKey: InputPrivacyKey['_'], rules: InputPrivacyRule[]) { public setPrivacy(inputKey: InputPrivacyKey['_'], rules: InputPrivacyRule[]) {
return apiManager.invokeApi('account.setPrivacy', { return apiManager.invokeApi('account.setPrivacy', {
key: { key: {
@ -51,12 +47,12 @@ export class AppPrivacyManager {
update: { update: {
_: 'updatePrivacy', _: 'updatePrivacy',
key: { key: {
_: this.convertInputKeyToKey(inputKey) _: convertInputKeyToKey(inputKey)
}, },
rules: rules.map(inputRule => { rules: rules.map(inputRule => {
const rule: PrivacyRule = {} as any; const rule: PrivacyRule = {} as any;
Object.assign(rule, inputRule); Object.assign(rule, inputRule);
rule._ = this.convertInputKeyToKey(rule._) as any; rule._ = convertInputKeyToKey(rule._) as any;
return rule; return rule;
}) })
} as Update.updatePrivacy } as Update.updatePrivacy
@ -69,7 +65,7 @@ export class AppPrivacyManager {
} }
public getPrivacy(inputKey: InputPrivacyKey['_']) { public getPrivacy(inputKey: InputPrivacyKey['_']) {
const privacyKey: PrivacyKey['_'] = this.convertInputKeyToKey(inputKey) as any; const privacyKey: PrivacyKey['_'] = convertInputKeyToKey(inputKey) as any;
const rules = this.privacy[privacyKey]; const rules = this.privacy[privacyKey];
if(rules) { if(rules) {
return Promise.resolve(rules); return Promise.resolve(rules);

View File

@ -63,6 +63,9 @@ export type State = Partial<{
highlightningColor?: string, highlightningColor?: string,
color?: string, color?: string,
slug?: string, slug?: string,
},
notifications: {
sound: boolean
} }
}, },
drafts: AppDraftsManager['drafts'] drafts: AppDraftsManager['drafts']
@ -109,6 +112,9 @@ export const STATE_INIT: State = {
type: 'image', type: 'image',
blur: false, blur: false,
slug: 'ByxGo2lrMFAIAAAAmkJxZabh8eM', // * new blurred camomile slug: 'ByxGo2lrMFAIAAAAmkJxZabh8eM', // * new blurred camomile
},
notifications: {
sound: true
} }
}, },
drafts: {} drafts: {}

View File

@ -469,10 +469,10 @@ export class AppUsersManager {
return this.cachedPhotoLocations[id]; return this.cachedPhotoLocations[id];
} }
/* public getUserString(id: number) { public getUserString(id: number) {
const user = this.getUser(id); const user = this.getUser(id);
return 'u' + id + (user.access_hash ? '_' + user.access_hash : ''); return 'u' + id + (user.access_hash ? '_' + user.access_hash : '');
} */ }
public getUserInput(id: number): InputUser { public getUserInput(id: number): InputUser {
const user = this.getUser(id); const user = this.getUser(id);

View File

@ -9,10 +9,7 @@ import {str2bigInt, bpe, equalsInt, greater,
copy_, eGCD_, add_, rightShift_, sub_, copyInt_, isZero, copy_, eGCD_, add_, rightShift_, sub_, copyInt_, isZero,
divide_, one, bigInt2str, powMod, bigInt2bytes} from '../../vendor/leemon';//from 'leemon'; divide_, one, bigInt2str, powMod, bigInt2bytes} from '../../vendor/leemon';//from 'leemon';
// @ts-ignore import { addPadding } from '../mtproto/bin_utils';
import {BigInteger} from 'jsbn';
import { addPadding, bytesFromBigInt } from '../mtproto/bin_utils';
import { bytesToWordss, bytesFromWordss, bytesToHex, bytesFromHex } from '../../helpers/bytes'; import { bytesToWordss, bytesFromWordss, bytesToHex, bytesFromHex } from '../../helpers/bytes';
import { nextRandomInt } from '../../helpers/random'; import { nextRandomInt } from '../../helpers/random';
@ -148,21 +145,20 @@ export async function hash_pbkdf2(/* hasher: 'string', */buffer: any, salt: any
return bits; return bits;
} }
export function pqPrimeFactorization(pqBytes: any) { export function pqPrimeFactorization(pqBytes: number[]) {
var what = new BigInteger(pqBytes); let result: ReturnType<typeof pqPrimeLeemon>;
var result: any = false;
//console.log(dT(), 'PQ start', pqBytes, what.toString(16), what.bitLength()) //console.log('PQ start', pqBytes, bytesToHex(pqBytes));
try { try {
//console.time('PQ leemon'); //console.time('PQ leemon');
result = pqPrimeLeemon(str2bigInt(what.toString(16), 16, Math.ceil(64 / bpe) + 1)); result = pqPrimeLeemon(str2bigInt(bytesToHex(pqBytes), 16, Math.ceil(64 / bpe) + 1));
//console.timeEnd('PQ leemon'); //console.timeEnd('PQ leemon');
} catch (e) { } catch (e) {
console.error('Pq leemon Exception', e); console.error('Pq leemon Exception', e);
} }
//console.log(dT(), 'PQ finish'); //console.log('PQ finish', result);
return result; return result;
} }
@ -253,11 +249,11 @@ export function bytesModPow(x: any, y: any, m: any) {
var resBigInt = powMod(xBigInt, yBigInt, mBigInt); var resBigInt = powMod(xBigInt, yBigInt, mBigInt);
return bytesFromHex(bigInt2str(resBigInt, 16)); return bytesFromHex(bigInt2str(resBigInt, 16));
} catch (e) { } catch(e) {
console.error('mod pow error', e); console.error('mod pow error', e);
} }
return bytesFromBigInt(new BigInteger(x).modPow(new BigInteger(y), new BigInteger(m)), 256); //return bytesFromBigInt(new BigInteger(x).modPow(new BigInteger(y), new BigInteger(m)), 256);
} }
export function gzipUncompress(bytes: ArrayBuffer, toString: true): string; export function gzipUncompress(bytes: ArrayBuffer, toString: true): string;

View File

@ -3,15 +3,12 @@ import dcConfigurator from "./dcConfigurator";
import rsaKeysManager from "./rsaKeysManager"; import rsaKeysManager from "./rsaKeysManager";
import timeManager from "./timeManager"; import timeManager from "./timeManager";
// @ts-ignore
import { BigInteger } from "jsbn";
import CryptoWorker from "../crypto/cryptoworker"; import CryptoWorker from "../crypto/cryptoworker";
import { logger, LogLevels } from "../logger"; import { logger, LogLevels } from "../logger";
import { bytesCmp, bytesToHex, bytesFromHex, bytesXor } from "../../helpers/bytes"; import { bytesCmp, bytesToHex, bytesFromHex, bytesXor } from "../../helpers/bytes";
import DEBUG from "../../config/debug"; import DEBUG from "../../config/debug";
//import { bigInt2str, greater, int2bigInt, one, powMod, str2bigInt, sub } from "../../vendor/leemon"; import { cmp, int2bigInt, one, pow, str2bigInt, sub } from "../../vendor/leemon";
/* let fNewNonce: any = bytesFromHex('8761970c24cb2329b5b2459752c502f3057cb7e8dbab200e526e8767fdc73b3c').reverse(); /* let fNewNonce: any = bytesFromHex('8761970c24cb2329b5b2459752c502f3057cb7e8dbab200e526e8767fdc73b3c').reverse();
let fNonce: any = bytesFromHex('b597720d11faa5914ef485c529cde414').reverse(); let fNonce: any = bytesFromHex('b597720d11faa5914ef485c529cde414').reverse();
@ -401,21 +398,21 @@ export class Authorizer {
this.log('dhPrime cmp OK'); this.log('dhPrime cmp OK');
} }
var gABigInt = new BigInteger(bytesToHex(gA), 16); //var gABigInt = new BigInteger(bytesToHex(gA), 16);
//const _gABigInt = str2bigInt(bytesToHex(gA), 16); const _gABigInt = str2bigInt(bytesToHex(gA), 16);
var dhPrimeBigInt = new BigInteger(dhPrimeHex, 16); //var dhPrimeBigInt = new BigInteger(dhPrimeHex, 16);
//const _dhPrimeBigInt = str2bigInt(dhPrimeHex, 16); const _dhPrimeBigInt = str2bigInt(dhPrimeHex, 16);
//this.log('gABigInt.compareTo(BigInteger.ONE) <= 0', gABigInt.compareTo(BigInteger.ONE), BigInteger.ONE.compareTo(BigInteger.ONE), greater(_gABigInt, one)); //this.log('gABigInt.compareTo(BigInteger.ONE) <= 0', gABigInt.compareTo(BigInteger.ONE), BigInteger.ONE.compareTo(BigInteger.ONE), greater(_gABigInt, one));
if(gABigInt.compareTo(BigInteger.ONE) <= 0) { //if(gABigInt.compareTo(BigInteger.ONE) <= 0) {
//if(!greater(_gABigInt, one)) { if(cmp(_gABigInt, one) <= 0) {
throw new Error('[MT] DH params are not verified: gA <= 1'); throw new Error('[MT] DH params are not verified: gA <= 1');
} }
/* this.log('gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)) >= 0', gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)), /* this.log('gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)) >= 0', gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)),
greater(gABigInt, sub(_dhPrimeBigInt, one))); */ greater(gABigInt, sub(_dhPrimeBigInt, one))); */
if(gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)) >= 0) { //if(gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)) >= 0) {
//if(greater(gABigInt, sub(_dhPrimeBigInt, one))) { if(cmp(_gABigInt, sub(_dhPrimeBigInt, one)) >= 0) {
throw new Error('[MT] DH params are not verified: gA >= dhPrime - 1'); throw new Error('[MT] DH params are not verified: gA >= dhPrime - 1');
} }
@ -424,19 +421,25 @@ export class Authorizer {
} }
var two = new BigInteger(/* null */''); //var two = new BigInteger(/* null */'');
two.fromInt(2); //two.fromInt(2);
//const _two = int2bigInt(2, 10, 0); const _two = int2bigInt(2, 32, 0);
//this.log('_two:', bigInt2str(_two, 16), two.toString(16)); //this.log('_two:', bigInt2str(_two, 16), two.toString(16));
var twoPow = two.pow(2048 - 64); let perf = performance.now();
//const _twoPow = powMod(_two, int2bigInt(2048 - 64, 10, 0), null); //var twoPow = two.pow(2048 - 64);
//console.log('jsbn pow', performance.now() - perf);
perf = performance.now();
const _twoPow = pow(_two, 2048 - 64);
//console.log('leemon pow', performance.now() - perf);
//this.log('twoPow:', twoPow.toString(16), bigInt2str(_twoPow, 16)); //this.log('twoPow:', twoPow.toString(16), bigInt2str(_twoPow, 16));
// this.log('gABigInt.compareTo(twoPow) < 0'); // this.log('gABigInt.compareTo(twoPow) < 0');
if(gABigInt.compareTo(twoPow) < 0) { //if(gABigInt.compareTo(twoPow) < 0) {
if(cmp(_gABigInt, _twoPow) < 0) {
throw new Error('[MT] DH params are not verified: gA < 2^{2048-64}'); throw new Error('[MT] DH params are not verified: gA < 2^{2048-64}');
} }
if(gABigInt.compareTo(dhPrimeBigInt.subtract(twoPow)) >= 0) { //if(gABigInt.compareTo(dhPrimeBigInt.subtract(twoPow)) >= 0) {
if(cmp(_gABigInt, sub(_dhPrimeBigInt, _twoPow)) >= 0) {
throw new Error('[MT] DH params are not verified: gA > dhPrime - 2^{2048-64}'); throw new Error('[MT] DH params are not verified: gA > dhPrime - 2^{2048-64}');
} }

View File

@ -1,8 +1,6 @@
//import {str2bigInt, divInt_, int2bigInt, bigInt2str, bigInt2bytes} from '../vendor/leemon'; import { bufferConcats } from '../../helpers/bytes';
import { add_, bigInt2str, cmp, leftShift_, str2bigInt } from '../../vendor/leemon';
// @ts-ignore import { nextRandomInt } from '../../helpers/random';
import {BigInteger, SecureRandom} from 'jsbn';
import { bufferConcat, bufferConcats } from '../../helpers/bytes';
/// #if !MTPROTO_WORKER /// #if !MTPROTO_WORKER
// @ts-ignore // @ts-ignore
@ -22,13 +20,13 @@ export function isObject(object: any) {
return typeof(object) === 'object' && object !== null; return typeof(object) === 'object' && object !== null;
} }
export function bigint(num: number) { /* export function bigint(num: number) {
return new BigInteger(num.toString(16), 16); return new BigInteger(num.toString(16), 16);
} } */
export function bigStringInt(strNum: string) { /* export function bigStringInt(strNum: string) {
return new BigInteger(strNum, 10); return new BigInteger(strNum, 10);
} } */
/* export function base64ToBlob(base64str: string, mimeType: string) { /* export function base64ToBlob(base64str: string, mimeType: string) {
var sliceSize = 1024; var sliceSize = 1024;
@ -62,7 +60,7 @@ export function dataUrlToBlob(url: string) {
return blob; return blob;
} */ } */
export function bytesFromBigInt(bigInt: BigInteger, len?: number) { /* export function bytesFromBigInt(bigInt: BigInteger, len?: number) {
var bytes = bigInt.toByteArray(); var bytes = bigInt.toByteArray();
if(len && bytes.length < len) { if(len && bytes.length < len) {
@ -82,10 +80,36 @@ export function bytesFromBigInt(bigInt: BigInteger, len?: number) {
} }
return bytes; return bytes;
} */
export function longFromInts(high: number, low: number): string {
//let perf = performance.now();
//let str = bigint(high).shiftLeft(32).add(bigint(low)).toString(10);
//console.log('longFromInts jsbn', performance.now() - perf);
//perf = performance.now();
const bigInt = str2bigInt(high.toString(16), 16, 32);//int2bigInt(high, 64, 64);
//console.log('longFromInts construct high', bigint(high).toString(10), bigInt2str(bigInt, 10));
leftShift_(bigInt, 32);
//console.log('longFromInts shiftLeft', bigint(high).shiftLeft(32).toString(10), bigInt2str(bigInt, 10));
add_(bigInt, str2bigInt(low.toString(16), 16, 32));
const _str = bigInt2str(bigInt, 10);
//console.log('longFromInts leemon', performance.now() - perf);
//console.log('longFromInts', high, low, str, _str, str === _str);
return _str;
} }
export function longFromInts(high: number, low: number) { export function sortLongsArray(arr: string[]) {
return bigint(high).shiftLeft(32).add(bigint(low)).toString(10); return arr.map(long => {
return str2bigInt(long, 10);
}).sort((a, b) => {
return cmp(a, b);
}).map(bigInt => {
return bigInt2str(bigInt, 10);
});
} }
export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean, full = false, prepend = false) { export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean, full = false, prepend = false) {
@ -99,7 +123,9 @@ export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean,
padding[i] = 0; padding[i] = 0;
} }
} else { } else {
(new SecureRandom()).nextBytes(padding); for(let i = 0; i < padding.length; ++i) {
padding[i] = nextRandomInt(255);
}
} }
if(bytes instanceof ArrayBuffer) { if(bytes instanceof ArrayBuffer) {
@ -112,4 +138,4 @@ export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean,
} }
return bytes; return bytes;
} }

View File

@ -55,7 +55,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
private isSWRegistered = true; private isSWRegistered = true;
private debug = DEBUG; private debug = DEBUG && false;
private sockets: Map<number, Socket> = new Map(); private sockets: Map<number, Socket> = new Map();
@ -140,7 +140,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
} }
private onWorkerMessage = (e: MessageEvent) => { private onWorkerMessage = (e: MessageEvent) => {
this.log('got message from worker:', e.data); //this.log('got message from worker:', e.data);
const task = e.data; const task = e.data;

View File

@ -1,5 +1,4 @@
import {isObject} from './bin_utils'; import {isObject, sortLongsArray} from './bin_utils';
import {bigStringInt} from './bin_utils';
import {TLDeserialization, TLSerialization} from './tl_utils'; import {TLDeserialization, TLSerialization} from './tl_utils';
import CryptoWorker from '../crypto/cryptoworker'; import CryptoWorker from '../crypto/cryptoworker';
import sessionStorage from '../sessionStorage'; import sessionStorage from '../sessionStorage';
@ -21,6 +20,7 @@ import HTTP from './transports/http';
/// #endif /// #endif
import type TcpObfuscated from './transports/tcpObfuscated'; import type TcpObfuscated from './transports/tcpObfuscated';
import { bigInt2str, cmp, rightShift_, str2bigInt } from '../../vendor/leemon';
//console.error('networker included!', new Error().stack); //console.error('networker included!', new Error().stack);
@ -793,11 +793,16 @@ export default class MTPNetworker {
const currentTime = Date.now(); const currentTime = Date.now();
let messagesByteLen = 0; let messagesByteLen = 0;
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD
let hasApiCall = false; let hasApiCall = false;
let hasHttpWait = false; let hasHttpWait = false;
/// #endif
let lengthOverflow = false; let lengthOverflow = false;
for(const messageId in this.pendingMessages) { const keys = sortLongsArray(Object.keys(this.pendingMessages));
for(const messageId of keys) {
const value = this.pendingMessages[messageId]; const value = this.pendingMessages[messageId];
if(!value || value <= currentTime) { if(!value || value <= currentTime) {
@ -808,25 +813,26 @@ export default class MTPNetworker {
} */ } */
const messageByteLength = message.body.length + 32; const messageByteLength = message.body.length + 32;
if(!message.notContentRelated && lengthOverflow) {
continue; // maybe break here
}
if(!message.notContentRelated && if((messagesByteLen + messageByteLength) > 655360) { // 640 Kb
messagesByteLen &&
messagesByteLen + messageByteLength > 655360) { // 640 Kb
this.log.warn('lengthOverflow', message, messages); this.log.warn('lengthOverflow', message, messages);
lengthOverflow = true; lengthOverflow = true;
continue; // maybe break here
if(outMessage) { // if it is a first message
break;
}
} }
messages.push(message); messages.push(message);
messagesByteLen += messageByteLength; messagesByteLen += messageByteLength;
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD
if(message.isAPI) { if(message.isAPI) {
hasApiCall = true; hasApiCall = true;
} else if(message.longPoll) { } else if(message.longPoll) {
hasHttpWait = true; hasHttpWait = true;
} }
/// #endif
outMessage = message; outMessage = message;
} else { } else {
@ -1426,7 +1432,10 @@ export default class MTPNetworker {
this.log.error('Bad msg notification', message); this.log.error('Bad msg notification', message);
if(message.error_code === 16 || message.error_code === 17) { if(message.error_code === 16 || message.error_code === 17) {
const changedOffset = timeManager.applyServerTime(bigStringInt(messageId).shiftRight(32).toString(10)); //const changedOffset = timeManager.applyServerTime(bigStringInt(messageId).shiftRight(32).toString(10));
const bigInt = str2bigInt(messageId, 10);
rightShift_(bigInt, 32);
const changedOffset = timeManager.applyServerTime(+bigInt2str(bigInt, 10));
if(message.error_code === 17 || changedOffset) { if(message.error_code === 17 || changedOffset) {
this.log('Update session'); this.log('Update session');
this.updateSession(); this.updateSession();

View File

@ -1,7 +1,7 @@
import { TLSerialization } from "./tl_utils"; import { TLSerialization } from "./tl_utils";
import { bigStringInt } from "./bin_utils";
import CryptoWorker from '../crypto/cryptoworker'; import CryptoWorker from '../crypto/cryptoworker';
import { bytesFromArrayBuffer, bytesFromHex, bytesToHex } from "../../helpers/bytes"; import { bytesFromArrayBuffer, bytesFromHex, bytesToHex } from "../../helpers/bytes";
import { bigInt2str, str2bigInt } from "../../vendor/leemon";
export class RSAKeysManager { export class RSAKeysManager {
@ -106,7 +106,7 @@ export class RSAKeysManager {
let fingerprintBytes = bytesFromArrayBuffer(hash).slice(-8); let fingerprintBytes = bytesFromArrayBuffer(hash).slice(-8);
fingerprintBytes.reverse(); fingerprintBytes.reverse();
this.publicKeysParsed[bytesToHex(fingerprintBytes)] = { this.publicKeysParsed[bytesToHex(fingerprintBytes).toLowerCase()] = {
modulus: keyParsed.modulus, modulus: keyParsed.modulus,
exponent: keyParsed.exponent exponent: keyParsed.exponent
}; };
@ -125,7 +125,8 @@ export class RSAKeysManager {
var fingerprintHex, foundKey, i; var fingerprintHex, foundKey, i;
for(i = 0; i < fingerprints.length; i++) { for(i = 0; i < fingerprints.length; i++) {
fingerprintHex = bigStringInt(fingerprints[i]).toString(16); //fingerprintHex = bigStringInt(fingerprints[i]).toString(16);
fingerprintHex = bigInt2str(str2bigInt(fingerprints[i], 10), 16).toLowerCase();
if(fingerprintHex.length < 16) { if(fingerprintHex.length < 16) {
fingerprintHex = new Array(16 - fingerprintHex.length).fill('0').join('') + fingerprintHex; fingerprintHex = new Array(16 - fingerprintHex.length).fill('0').join('') + fingerprintHex;

View File

@ -1,11 +1,12 @@
import { bytesToHex } from '../../helpers/bytes'; import { bytesToHex } from '../../helpers/bytes';
import { bigint, bigStringInt, isObject } from './bin_utils'; import { isObject, longFromInts } from './bin_utils';
import { MOUNT_CLASS_TO } from '../../config/debug'; import { MOUNT_CLASS_TO } from '../../config/debug';
import { str2bigInt, dup, divide_, bigInt2str } from '../../vendor/leemon';
import Schema, { MTProtoConstructor } from './schema';
/// #if MTPROTO_WORKER /// #if MTPROTO_WORKER
// @ts-ignore // @ts-ignore
import { gzipUncompress } from '../crypto/crypto_utils'; import { gzipUncompress } from '../crypto/crypto_utils';
import Schema, { MTProtoConstructor } from './schema';
/// #endif /// #endif
const boolFalse = +Schema.API.constructors.find(c => c.predicate === 'boolFalse').id >>> 0; const boolFalse = +Schema.API.constructors.find(c => c.predicate === 'boolFalse').id >>> 0;
@ -138,10 +139,29 @@ class TLSerialization {
if(typeof sLong !== 'string') { if(typeof sLong !== 'string') {
sLong = sLong ? sLong.toString() : '0'; sLong = sLong ? sLong.toString() : '0';
} }
const divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000));
const R = 0x100000000;
//const divRem = bigStringInt(sLong).divideAndRemainder(bigint(R));
const a = str2bigInt(sLong, 10, 64);
const q = dup(a);
const r = dup(a);
divide_(a, str2bigInt((R).toString(16), 16, 64), q, r);
//divInt_(a, R);
const high = +bigInt2str(q, 10);
let low = +bigInt2str(r, 10);
if(high < low) {
low -= R;
}
//console.log('storeLong', sLong, divRem[0].intValue(), divRem[1].intValue(), high, low);
this.writeInt(divRem[1].intValue(), (field || '') + ':long[low]'); //this.writeInt(divRem[1].intValue(), (field || '') + ':long[low]');
this.writeInt(divRem[0].intValue(), (field || '') + ':long[high]'); //this.writeInt(divRem[0].intValue(), (field || '') + ':long[high]');
this.writeInt(low, (field || '') + ':long[low]');
this.writeInt(high, (field || '') + ':long[high]');
} }
public storeDouble(f: any, field?: string) { public storeDouble(f: any, field?: string) {
@ -480,7 +500,8 @@ class TLDeserialization {
const iLow = this.readInt((field || '') + ':long[low]'); const iLow = this.readInt((field || '') + ':long[low]');
const iHigh = this.readInt((field || '') + ':long[high]'); const iHigh = this.readInt((field || '') + ':long[high]');
const longDec = bigint(iHigh).shiftLeft(32).add(bigint(iLow)).toString(); //const longDec = bigint(iHigh).shiftLeft(32).add(bigint(iLow)).toString();
const longDec = longFromInts(iHigh, iLow);
return longDec; return longDec;
} }

View File

@ -1,5 +1,3 @@
// @ts-ignore
//import {SecureRandom} from 'jsbn';
import { bytesToHex, bytesFromHex, bufferConcats } from '../helpers/bytes'; import { bytesToHex, bytesFromHex, bufferConcats } from '../helpers/bytes';
import { nextRandomInt } from '../helpers/random'; import { nextRandomInt } from '../helpers/random';

View File

@ -89,18 +89,22 @@ type BroadcastEvents = {
'im_mount': void, 'im_mount': void,
'im_tab_change': number, 'im_tab_change': number,
'idle': boolean,
'overlay_toggle': boolean, 'overlay_toggle': boolean,
'background_change': void, 'background_change': void,
'privacy_update': Update.updatePrivacy 'privacy_update': Update.updatePrivacy,
'notify_settings': Update.updateNotifySettings
}; };
class RootScope extends EventListenerBase<any> { class RootScope extends EventListenerBase<any> {
private _overlayIsActive: boolean = false; private _overlayIsActive: boolean = false;
public myId = 0; public myId = 0;
public idle = { public idle = {
isIDLE: false isIDLE: true
}; };
public connectionStatus: {[name: string]: ConnectionStatusChange} = {}; public connectionStatus: {[name: string]: ConnectionStatusChange} = {};
public settings: State['settings']; public settings: State['settings'];

View File

@ -9,16 +9,27 @@ export default class DialogsStorage {
public dialogs: {[peerId: string]: Dialog} = {}; public dialogs: {[peerId: string]: Dialog} = {};
public byFolders: {[folderId: number]: Dialog[]} = {}; public byFolders: {[folderId: number]: Dialog[]} = {};
public allDialogsLoaded: {[folder_id: number]: boolean} = {}; public allDialogsLoaded: {[folder_id: number]: boolean};
public dialogsOffsetDate: {[folder_id: number]: number} = {}; private dialogsOffsetDate: {[folder_id: number]: number};
public pinnedOrders: {[folder_id: number]: number[]} = { public pinnedOrders: {[folder_id: number]: number[]};
0: [], private dialogsNum: number;
1: []
};
public dialogsNum = 0;
constructor(private appMessagesManager: AppMessagesManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private serverTimeManager: ServerTimeManager) { constructor(private appMessagesManager: AppMessagesManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private serverTimeManager: ServerTimeManager) {
this.reset();
}
public reset() {
this.allDialogsLoaded = {};
this.dialogsOffsetDate = {};
this.pinnedOrders = {
0: [],
1: []
};
this.dialogsNum = 0;
}
public getOffsetDate(folderId: number) {
return this.dialogsOffsetDate[folderId] || 0;
} }
public getFolder(id: number) { public getFolder(id: number) {
@ -181,4 +192,4 @@ export default class DialogsStorage {
return foundDialog; return foundDialog;
} }
} }

View File

@ -259,4 +259,4 @@ export default class FiltersStorage {
filter.orderIndex = this.orderIndex++; filter.orderIndex = this.orderIndex++;
} }
} }
} }

View File

@ -9,7 +9,7 @@ import Page from "./page";
import pageAuthCode from "./pageAuthCode"; import pageAuthCode from "./pageAuthCode";
import pageSignQR from './pageSignQR'; import pageSignQR from './pageSignQR';
import InputField from "../components/inputField"; import InputField from "../components/inputField";
import CheckboxField from "../components/checkbox"; import CheckboxField from "../components/checkboxField";
import Button from "../components/button"; import Button from "../components/button";
import { isAppleMobile } from "../helpers/userAgent"; import { isAppleMobile } from "../helpers/userAgent";
import fastSmoothScroll from "../helpers/fastSmoothScroll"; import fastSmoothScroll from "../helpers/fastSmoothScroll";
@ -299,7 +299,7 @@ let onFirstMount = () => {
this.removeAttribute('readonly'); // fix autocomplete this.removeAttribute('readonly'); // fix autocomplete
});*/ });*/
const signedCheckboxField = CheckboxField({ const signedCheckboxField = new CheckboxField({
text: 'Keep me signed in', text: 'Keep me signed in',
name: 'keepSession' name: 'keepSession'
}); });

View File

@ -1040,6 +1040,12 @@
} }
} }
.notifications-container {
.sidebar-left-section {
padding-bottom: 0;
}
}
.range-setting-selector { .range-setting-selector {
padding: 1rem .875rem; padding: 1rem .875rem;

View File

@ -1140,10 +1140,17 @@ middle-ellipsis-element {
overflow: hidden; overflow: hidden;
} }
.radio-field { .radio-field-main, .checkbox-field {
&-main { padding-left: 3.375rem;
padding-left: 3.375rem; margin-left: -3.375rem;
margin-left: -3.375rem; }
.checkbox-field {
margin-right: 0;
height: auto;
.checkbox-caption {
padding-left: 0;
} }
} }

40
src/vendor/leemon.ts vendored
View File

@ -494,6 +494,20 @@ export function powMod(x: number[], y: number[], n: number[]): number[] {
return trim(ans, 1) return trim(ans, 1)
} }
/**
* Simple pow with no optimizations (in 40x times slower than jsbn's pow)
* @param x bigInt
* @param e
*/
export function pow(x: number[], e: number) {
let ans = dup(x);
e -= 1;
for(let i = 0; i < e; ++i) {
ans = mult(ans, x);
}
return trim(ans, 1);
}
/** /**
* return (x-y) for bigInts x and y * return (x-y) for bigInts x and y
* *
@ -1482,6 +1496,11 @@ export function bigInt2str(x: number[], base: number): string {
return s return s
} }
/**
* Convert a bigInt into bytes
* @param x bigInt
* @param littleEndian byte order by default
*/
export function bigInt2bytes(x: number[], littleEndian = true) { export function bigInt2bytes(x: number[], littleEndian = true) {
if(s6.length !== x.length) s6 = dup(x); if(s6.length !== x.length) s6 = dup(x);
else copy_(s6, x); else copy_(s6, x);
@ -1504,6 +1523,27 @@ export function bigInt2bytes(x: number[], littleEndian = true) {
return out; return out;
} }
/**
* Compare two bigInts and return -1 if x is less, 0 if equals, 1 if greater
* @param x bigInt
* @param y bigInt
*/
export function cmp(x: number[], y: number[]) {
return greater(x, y) ? 1 : (equals(x, y) ? 0 : -1);
}
/* Object.assign(self, {
cmp,
str2bigInt,
int2bigInt,
bigInt2str,
one,
divide_,
divInt_,
dup,
negative
}); */
/** /**
* Returns a duplicate of bigInt x * Returns a duplicate of bigInt x
* *

View File

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 276 KiB

View File

Before

Width:  |  Height:  |  Size: 260 KiB

After

Width:  |  Height:  |  Size: 260 KiB

View File

Before

Width:  |  Height:  |  Size: 259 KiB

After

Width:  |  Height:  |  Size: 259 KiB

View File

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 247 KiB

View File

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 225 KiB

View File

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 225 KiB

View File

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 221 KiB

View File

Before

Width:  |  Height:  |  Size: 260 KiB

After

Width:  |  Height:  |  Size: 260 KiB

View File

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 272 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 212 KiB

View File

Before

Width:  |  Height:  |  Size: 373 KiB

After

Width:  |  Height:  |  Size: 373 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB