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 Scrollable from "./scrollable";
import { FocusDirection } from "../helpers/fastSmoothScroll";
import CheckboxField from "./checkbox";
import CheckboxField from "./checkboxField";
type PeerType = 'contacts' | 'dialogs';
@ -346,7 +346,7 @@ export default class AppSelectPeers {
if(this.multiSelect) {
const selected = this.selected.has(peerId);
const checkboxField = CheckboxField();
const checkboxField = new CheckboxField();
if(selected) {
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 Button from "../button";
import ButtonIcon from "../buttonIcon";
import CheckboxField from "../checkbox";
import CheckboxField from "../checkboxField";
import PopupDeleteMessages from "../popups/deleteMessages";
import PopupForward from "../popups/forward";
import { toast } from "../toast";
@ -161,7 +161,7 @@ export default class ChatSelection {
if(show) {
if(hasCheckbox) return;
const checkboxField = CheckboxField({
const checkboxField = new CheckboxField({
name: bubble.dataset.mid,
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();
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';
let timeoutId: number;

View File

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

View File

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

View File

@ -62,7 +62,7 @@ export default class PrivacySection {
const random = randomLong();
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);

View File

@ -1,48 +1,50 @@
import appStateManager from "../lib/appManagers/appStateManager";
import { getDeepProperty } from "../helpers/object";
const RadioField = (text: string, name: string, value?: string, stateKey?: string) => {
const label = document.createElement('label');
label.classList.add('radio-field');
export default class RadioField {
public input: HTMLInputElement;
public label: HTMLLabelElement;
public main: HTMLElement;
const input = document.createElement('input');
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;
});
constructor(text: string, name: string, value?: string, stateKey?: string) {
const label = this.label = document.createElement('label');
label.classList.add('radio-field');
input.addEventListener('change', () => {
appStateManager.setByKey(stateKey, value);
});
const input = this.input = document.createElement('input');
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 = document.createElement('div');
main.classList.add('radio-field-main');
if(text) {
main.innerHTML = text;
/* const caption = document.createElement('div');
caption.classList.add('radio-field-main-caption');
caption.innerHTML = text;
if(subtitle) {
label.classList.add('radio-field-with-subtitle');
caption.insertAdjacentHTML('beforeend', `<div class="radio-field-main-subtitle">${subtitle}</div>`);
const main = this.main = document.createElement('div');
main.classList.add('radio-field-main');
if(text) {
main.innerHTML = text;
/* const caption = document.createElement('div');
caption.classList.add('radio-field-main-caption');
caption.innerHTML = text;
if(subtitle) {
label.classList.add('radio-field-with-subtitle');
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 { ripple } from "./ripple";
import { SliderSuperTab } from "./slider";
@ -9,8 +9,8 @@ export default class Row {
public title: HTMLDivElement;
public subtitle: HTMLElement;
public checkboxField: ReturnType<typeof CheckboxField>;
public radioField: ReturnType<typeof RadioField>;
public checkboxField: CheckboxField;
public radioField: RadioField;
public freezed = false;
@ -44,6 +44,10 @@ export default class Row {
if(options.checkboxField) {
this.checkboxField = options.checkboxField;
this.container.append(this.checkboxField.label);
this.checkboxField.input.addEventListener('change', () => {
this.subtitle.innerHTML = this.checkboxField.input.checked ? 'Enabled' : 'Disabled';
});
}
} else {
if(options.title) {

View File

@ -12,7 +12,7 @@ import appStateManager from "../../../lib/appManagers/appStateManager";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import rootScope from "../../../lib/rootScope";
import Button from "../../button";
import CheckboxField from "../../checkbox";
import CheckboxField from "../../checkboxField";
import ProgressivePreloader from "../../preloader";
import { SliderSuperTab } from "../../slider";
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 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',
name: 'blur',
stateKey: 'settings.background.blur'

View File

@ -2,7 +2,7 @@ import { SliderSuperTab } from "../../slider"
import { generateSection } from "..";
import RangeSelector from "../../rangeSelector";
import Button from "../../button";
import CheckboxField from "../../checkbox";
import CheckboxField from "../../checkboxField";
import RadioField from "../../radioField";
import appStateManager from "../../../lib/appManagers/appStateManager";
import rootScope from "../../../lib/rootScope";
@ -73,7 +73,7 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
new AppBackgroundTab(this.slider).open();
});
const animationsCheckboxField = CheckboxField({
const animationsCheckboxField = new CheckboxField({
text: 'Enable Animations',
name: 'animations',
stateKey: 'settings.animationsEnabled'
@ -88,12 +88,12 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
const form = document.createElement('form');
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',
});
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',
});
@ -105,22 +105,22 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
const container = section('Auto-Download Media');
container.classList.add('sidebar-left-section-disabled');
const contactsCheckboxField = CheckboxField({
const contactsCheckboxField = new CheckboxField({
text: 'Contacts',
name: 'contacts',
stateKey: 'settings.autoDownload.contacts'
});
const privateCheckboxField = CheckboxField({
const privateCheckboxField = new CheckboxField({
text: 'Private Chats',
name: 'private',
stateKey: 'settings.autoDownload.private'
});
const groupsCheckboxField = CheckboxField({
const groupsCheckboxField = new CheckboxField({
text: 'Group Chats',
name: 'groups',
stateKey: 'settings.autoDownload.groups'
});
const channelsCheckboxField = CheckboxField({
const channelsCheckboxField = new CheckboxField({
text: 'Channels',
name: 'channels',
stateKey: 'settings.autoDownload.channels'
@ -133,12 +133,12 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
const container = section('Auto-Play Media');
container.classList.add('sidebar-left-section-disabled');
const gifsCheckboxField = CheckboxField({
const gifsCheckboxField = new CheckboxField({
text: 'GIFs',
name: 'gifs',
stateKey: 'settings.autoPlay.gifs'
});
const videosCheckboxField = CheckboxField({
const videosCheckboxField = new CheckboxField({
text: 'Videos',
name: 'videos',
stateKey: 'settings.autoPlay.videos'
@ -150,12 +150,12 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
{
const container = section('Stickers');
const suggestCheckboxField = CheckboxField({
const suggestCheckboxField = new CheckboxField({
text: 'Suggest Stickers by Emoji',
name: 'suggest',
stateKey: 'settings.stickers.suggest'
});
const loopCheckboxField = CheckboxField({
const loopCheckboxField = new CheckboxField({
text: 'Loop Animated Stickers',
name: '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 { copy } from "../../../helpers/object";
import ButtonIcon from "../../buttonIcon";
import CheckboxField from "../../checkbox";
import CheckboxField from "../../checkboxField";
import Button from "../../button";
import AppEditFolderTab from "./editFolder";
@ -94,7 +94,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
}
checkbox(selected?: boolean) {
const checkboxField = CheckboxField({
const checkboxField = new CheckboxField({
round: true
});
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 appUsersManager from "../../../lib/appManagers/appUsersManager";
import rootScope from "../../../lib/rootScope";
import { convertKeyToInputKey } from "../../../helpers/string";
export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
private activeSessionsRow: Row;
@ -204,11 +205,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
}
rootScope.on('privacy_update', (update) => {
let key: string = update.key._;
key = key[0].toUpperCase() + key.slice(1);
key = 'input' + key;
updatePrivacyRow(key as any);
updatePrivacyRow(convertKeyToInputKey(update.key._) as any);
});
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 AppEditProfileTab from "./editProfile";
import AppChatFoldersTab from "./chatFolders";
import AppNotificationsTab from "./notifications";
//import AppMediaViewer from "../../appMediaViewerNew";
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.folders = Button(className, {icon: 'folder', text: 'Chat Folders'}));
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.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();
});
this.buttons.notifications.addEventListener('click', () => {
new AppNotificationsTab(this.slider).open();
});
this.buttons.privacy.addEventListener('click', () => {
new AppPrivacyAndSecurityTab(this.slider).open();
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import { createPosterForVideo } from "../../helpers/files";
import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random";
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 { langPack } from "../langPack";
import { logger, LogLevels } from "../logger";
@ -37,6 +37,7 @@ import { getFileNameByLocation } from "../../helpers/fileName";
import appProfileManager from "./appProfileManager";
import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug";
import SlicedArray, { Slice, SliceEnd } from "../../helpers/slicedArray";
import appNotificationsManager, { NotifyOptions } from "./appNotificationsManager";
//console.trace('include');
// TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет
@ -155,7 +156,14 @@ export class AppMessagesManager {
public newMessagesToHandle: {[peerId: string]: number[]} = {};
public newDialogsHandlePromise = 0;
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 reloadConversationsPeers: number[] = [];
@ -342,6 +350,8 @@ export class AppMessagesManager {
});
}
});
appNotificationsManager.start();
}
public getInputEntities(entities: MessageEntity[]) {
@ -1543,6 +1553,32 @@ export class AppMessagesManager {
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) {
const limit = 100, outDialogs: Dialog[] = [];
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];
offset = 0;
@ -1625,7 +1661,7 @@ export class AppMessagesManager {
return {
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
};
});
@ -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);
let offsetId = 0;
let offsetDate = 0;
let offsetPeerId = 0;
let offsetIndex = 0;
if(this.dialogsStorage.dialogsOffsetDate[folderId]) {
offsetDate = this.dialogsStorage.dialogsOffsetDate[folderId] + serverTimeManager.serverTimeOffset;
offsetIndex = this.dialogsStorage.dialogsOffsetDate[folderId] * 0x10000;
if(offsetDate === undefined) {
offsetDate = this.dialogsStorage.getOffsetDate(folderId);
}
if(offsetDate) {
offsetIndex = offsetDate * 0x10000;
offsetDate += serverTimeManager.serverTimeOffset;
}
// ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА:
@ -1747,7 +1786,7 @@ export class AppMessagesManager {
rootScope.broadcast('dialogs_multiupdate', {});
}
return count;
return dialogsResult;
});
}
@ -2766,6 +2805,7 @@ export class AppMessagesManager {
(dialogsResult.dialogs as Dialog[]).forEach((dialog) => {
const peerId = appPeersManager.getPeerId(dialog.peer);
let topMessage = dialog.top_message;
const topPendingMessage = this.pendingTopMsgs[peerId];
if(topPendingMessage) {
if(!topMessage
@ -2781,34 +2821,17 @@ export class AppMessagesManager {
} */
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);
/* if(wasDialogBefore) {
rootScope.$broadcast('dialog_top', dialog);
} else { */
//if(wasDialogBefore?.top_message !== topMessage) {
updatedDialogs[peerId] = dialog;
//}
//}
updatedDialogs[peerId] = dialog;
} else {
const dropped = this.dialogsStorage.dropDialog(peerId);
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) {
for(const i in this.newUpdatesAfterReloadToHandle[peerId]) {
const update = this.newUpdatesAfterReloadToHandle[peerId][i];
for(const update of this.newUpdatesAfterReloadToHandle[peerId]) {
this.handleUpdate(update);
}
@ -2925,6 +2948,8 @@ export class AppMessagesManager {
historyStorage.readMaxId = dialog.read_inbox_max_id;
historyStorage.readOutboxMaxId = dialog.read_outbox_max_id;
appNotificationsManager.savePeerSettings(peerId, dialog.notify_settings)
if(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) {
return historyStorage.readPromise;
}
@ -3602,6 +3640,40 @@ export class AppMessagesManager {
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) {
/* if(DEBUG) {
this.log.debug('handleUpdate', update._, update);
@ -3703,14 +3775,40 @@ export class AppMessagesManager {
}
const dialog = foundDialog[0];
const inboxUnread = !message.pFlags.out && message.pFlags.unread;
if(dialog) {
const inboxUnread = !message.pFlags.out && message.pFlags.unread;
this.setDialogTopMessage(message, dialog);
if(inboxUnread) {
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;
}
@ -3986,8 +4084,9 @@ export class AppMessagesManager {
if(!message.pFlags.out && !threadId && stillUnreadCount === undefined) {
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.handleUpdate({
_: 'updateNewMessage',
message: message
message
} as any);
}
@ -4261,13 +4360,14 @@ export class AppMessagesManager {
case 'updateNotifySettings': {
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) {
dialog.notify_settings = notify_settings;
rootScope.broadcast('dialog_notify_settings', peerId);
const dialog = this.getDialogByPeerId(peerId)[0];
if(dialog) {
dialog.notify_settings = notify_settings;
rootScope.broadcast('dialog_notify_settings', peerId);
}
}
/////this.log('updateNotifySettings', peerId, notify_settings);
@ -4379,17 +4479,11 @@ export class AppMessagesManager {
}
public mutePeer(peerId: number) {
let inputPeer = appPeersManager.getInputPeerById(peerId);
let inputNotifyPeer: InputNotifyPeer.inputNotifyPeer = {
_: 'inputNotifyPeer',
peer: inputPeer
};
let settings: InputPeerNotifySettings = {
const settings: InputPeerNotifySettings = {
_: 'inputPeerNotifySettings'
};
let dialog = appMessagesManager.getDialogByPeerId(peerId)[0];
const dialog = appMessagesManager.getDialogByPeerId(peerId)[0];
let muted = true;
if(dialog && dialog.notify_settings) {
muted = dialog.notify_settings.mute_until > (Date.now() / 1000 | 0);
@ -4398,25 +4492,11 @@ export class AppMessagesManager {
if(!muted) {
settings.mute_until = 2147483647;
}
apiManager.invokeApi('account.updateNotifySettings', {
peer: inputNotifyPeer,
settings: settings
}).then(bool => {
if(bool) {
this.handleUpdate({
_: 'updateNotifySettings',
peer: {
_: 'notifyPeer',
peer: appPeersManager.getOutputPeer(peerId)
},
notify_settings: { // ! WOW, IT WORKS !
...settings,
_: 'peerNotifySettings',
}
});
}
});
return appNotificationsManager.updateNotifySettings({
_: 'inputNotifyPeer',
peer: appPeersManager.getInputPeerById(peerId)
}, settings);
}
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) {
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) {
history.unread++;
appNotificationsManager.cancel('msg' + mid);
}
history.count++;
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 { 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 rootScope from "../rootScope";
import appChatsManager from "./appChatsManager";
@ -105,12 +105,12 @@ export class AppPeersManager {
return {_: 'peerChat', chat_id: chatId};
}
/* public getPeerString(peerId: number) {
public getPeerString(peerId: number) {
if(peerId > 0) {
return appUsersManager.getUserString(peerId);
}
return appChatsManager.getChatString(-peerId);
} */
}
public getPeerUsername(peerId: number): string {
if(peerId > 0) {
@ -163,6 +163,13 @@ export class AppPeersManager {
return (peerId > 0) && appUsersManager.isBot(peerId);
}
/**
* Will check notification exceptions by type too
*/
public isPeerMuted(peerId: number) {
}
/* public getInputPeer(peerString: string): InputPeer {
var firstChar = peerString.charAt(0);
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 {
if(!peerId) {
return {_: 'inputPeerEmpty'};

View File

@ -5,6 +5,7 @@ import appChatsManager from "./appChatsManager";
import appUsersManager from "./appUsersManager";
import apiUpdatesManager from "./apiUpdatesManager";
import rootScope from "../rootScope";
import { convertInputKeyToKey } from "../../helpers/string";
export enum PrivacyType {
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[]) {
return apiManager.invokeApi('account.setPrivacy', {
key: {
@ -51,12 +47,12 @@ export class AppPrivacyManager {
update: {
_: 'updatePrivacy',
key: {
_: this.convertInputKeyToKey(inputKey)
_: convertInputKeyToKey(inputKey)
},
rules: rules.map(inputRule => {
const rule: PrivacyRule = {} as any;
Object.assign(rule, inputRule);
rule._ = this.convertInputKeyToKey(rule._) as any;
rule._ = convertInputKeyToKey(rule._) as any;
return rule;
})
} as Update.updatePrivacy
@ -69,7 +65,7 @@ export class AppPrivacyManager {
}
public getPrivacy(inputKey: InputPrivacyKey['_']) {
const privacyKey: PrivacyKey['_'] = this.convertInputKeyToKey(inputKey) as any;
const privacyKey: PrivacyKey['_'] = convertInputKeyToKey(inputKey) as any;
const rules = this.privacy[privacyKey];
if(rules) {
return Promise.resolve(rules);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,12 @@
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 { str2bigInt, dup, divide_, bigInt2str } from '../../vendor/leemon';
import Schema, { MTProtoConstructor } from './schema';
/// #if MTPROTO_WORKER
// @ts-ignore
import { gzipUncompress } from '../crypto/crypto_utils';
import Schema, { MTProtoConstructor } from './schema';
/// #endif
const boolFalse = +Schema.API.constructors.find(c => c.predicate === 'boolFalse').id >>> 0;
@ -138,10 +139,29 @@ class TLSerialization {
if(typeof sLong !== 'string') {
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[0].intValue(), (field || '') + ':long[high]');
//this.writeInt(divRem[1].intValue(), (field || '') + ':long[low]');
//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) {
@ -480,7 +500,8 @@ class TLDeserialization {
const iLow = this.readInt((field || '') + ':long[low]');
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;
}

View File

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

View File

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

View File

@ -9,16 +9,27 @@ export default class DialogsStorage {
public dialogs: {[peerId: string]: Dialog} = {};
public byFolders: {[folderId: number]: Dialog[]} = {};
public allDialogsLoaded: {[folder_id: number]: boolean} = {};
public dialogsOffsetDate: {[folder_id: number]: number} = {};
public pinnedOrders: {[folder_id: number]: number[]} = {
0: [],
1: []
};
public dialogsNum = 0;
public allDialogsLoaded: {[folder_id: number]: boolean};
private dialogsOffsetDate: {[folder_id: number]: number};
public pinnedOrders: {[folder_id: number]: number[]};
private dialogsNum: number;
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) {
@ -181,4 +192,4 @@ export default class DialogsStorage {
return foundDialog;
}
}
}

View File

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

View File

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

View File

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

View File

@ -1140,10 +1140,17 @@ middle-ellipsis-element {
overflow: hidden;
}
.radio-field {
&-main {
padding-left: 3.375rem;
margin-left: -3.375rem;
.radio-field-main, .checkbox-field {
padding-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)
}
/**
* 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
*
@ -1482,6 +1496,11 @@ export function bigInt2str(x: number[], base: number): string {
return s
}
/**
* Convert a bigInt into bytes
* @param x bigInt
* @param littleEndian byte order by default
*/
export function bigInt2bytes(x: number[], littleEndian = true) {
if(s6.length !== x.length) s6 = dup(x);
else copy_(s6, x);
@ -1504,6 +1523,27 @@ export function bigInt2bytes(x: number[], littleEndian = true) {
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
*

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