From b8f98e76c2921c39937c4f933ce5cb6a49862ea1 Mon Sep 17 00:00:00 2001 From: morethanwords Date: Tue, 2 Mar 2021 21:23:01 +0400 Subject: [PATCH] Privacy updates --- src/components/privacySection.ts | 4 +- src/components/scrollable.ts | 7 +- .../sidebarLeft/tabs/activeSessions.ts | 21 +++-- .../sidebarLeft/tabs/blockedUsers.ts | 27 ++++++ .../sidebarLeft/tabs/privacyAndSecurity.ts | 90 +++++++++++++------ src/lib/appManagers/appPrivacyManager.ts | 53 +++++++++-- src/lib/appManagers/appUsersManager.ts | 2 +- src/lib/rootScope.ts | 6 +- 8 files changed, 168 insertions(+), 42 deletions(-) diff --git a/src/components/privacySection.ts b/src/components/privacySection.ts index 6f87838d..4de64262 100644 --- a/src/components/privacySection.ts +++ b/src/components/privacySection.ts @@ -122,9 +122,9 @@ export default class PrivacySection { }); } - setTimeout(() => { + /* setTimeout(() => { this.setRadio(PrivacyType.Contacts); - }, 0); + }, 0); */ const promise = appPrivacyManager.getPrivacy(options.inputKey).then(rules => { const details = appPrivacyManager.getPrivacyRulesDetails(rules); diff --git a/src/components/scrollable.ts b/src/components/scrollable.ts index eb2efbb5..e3212037 100644 --- a/src/components/scrollable.ts +++ b/src/components/scrollable.ts @@ -177,7 +177,12 @@ export default class Scrollable extends ScrollableBase { }; public checkForTriggers = () => { - if((!this.onScrolledTop && !this.onScrolledBottom) || this.isHeavyAnimationInProgress) return; + if((!this.onScrolledTop && !this.onScrolledBottom)) return; + + if(this.isHeavyAnimationInProgress) { + this.onScroll(); + return; + } const scrollHeight = this.container.scrollHeight; if(!scrollHeight) { // незачем вызывать триггеры если блок пустой или не виден diff --git a/src/components/sidebarLeft/tabs/activeSessions.ts b/src/components/sidebarLeft/tabs/activeSessions.ts index 654e5871..808af56b 100644 --- a/src/components/sidebarLeft/tabs/activeSessions.ts +++ b/src/components/sidebarLeft/tabs/activeSessions.ts @@ -10,8 +10,10 @@ import ButtonMenu from "../../buttonMenu"; import PopupConfirmAction from "../../popups/confirmAction"; import apiManager from "../../../lib/mtproto/mtprotoworker"; import { toast } from "../../toast"; +import AppPrivacyAndSecurityTab from "./privacyAndSecurity"; export default class AppActiveSessionsTab extends SliderSuperTab { + public privacyTab: AppPrivacyAndSecurityTab; public authorizations: Authorization.authorization[]; private menuElement: HTMLElement; @@ -57,11 +59,15 @@ export default class AppActiveSessionsTab extends SliderSuperTab { text: 'TERMINATE', isDanger: true, callback: () => { - toggleDisability([btnTerminate], true); + const b = [btnTerminate]; + toggleDisability(b, true); apiManager.invokeApi('auth.resetAuthorizations').then(value => { //toggleDisability([btnTerminate], false); btnTerminate.remove(); otherSection.container.remove(); + this.privacyTab.updateActiveSessions(); + }, onError).finally(() => { + toggleDisability(b, false); }); } }], { @@ -90,6 +96,12 @@ export default class AppActiveSessionsTab extends SliderSuperTab { this.scrollable.append(otherSection.container); + const onError = (err: any) => { + if(err.type === 'FRESH_RESET_AUTHORISATION_FORBIDDEN') { + toast('For security reasons, you can\'t terminate older sessions from a device that you\'ve just connected. Please use an earlier connection or wait for a few hours.'); + } + }; + let target: HTMLElement; const onTerminateClick = () => { const hash = target.dataset.hash; @@ -102,12 +114,9 @@ export default class AppActiveSessionsTab extends SliderSuperTab { .then(value => { if(value) { target.remove(); + this.privacyTab.updateActiveSessions(); } - }, (err) => { - if(err.type === 'FRESH_RESET_AUTHORISATION_FORBIDDEN') { - toast('For security reasons, you can\'t terminate older sessions from a device that you\'ve just connected. Please use an earlier connection or wait for a few hours.'); - } - }); + }, onError); } }], { title: 'Terminate Session', diff --git a/src/components/sidebarLeft/tabs/blockedUsers.ts b/src/components/sidebarLeft/tabs/blockedUsers.ts index cc953e40..c9d58cd0 100644 --- a/src/components/sidebarLeft/tabs/blockedUsers.ts +++ b/src/components/sidebarLeft/tabs/blockedUsers.ts @@ -103,6 +103,33 @@ export default class AppBlockedUsersTab extends SliderSuperTab { } } }); + + const LOAD_COUNT = 50; + let loading = false; + this.scrollable.onScrolledBottom = () => { + if(loading) { + return; + } + + loading = true; + appUsersManager.getBlocked(list.childElementCount, LOAD_COUNT).then(res => { + for(const peerId of res.peerIds) { + add(peerId, true); + } + + if(res.peerIds.length < LOAD_COUNT) { + this.scrollable.onScrolledBottom = null; + } + + this.scrollable.checkForTriggers(); + }).finally(() => { + loading = false; + }); + }; + } + + onOpenAfterTimeout() { + this.scrollable.onScroll(); } onCloseAfterTimeout() { diff --git a/src/components/sidebarLeft/tabs/privacyAndSecurity.ts b/src/components/sidebarLeft/tabs/privacyAndSecurity.ts index 6c704891..75a13239 100644 --- a/src/components/sidebarLeft/tabs/privacyAndSecurity.ts +++ b/src/components/sidebarLeft/tabs/privacyAndSecurity.ts @@ -1,7 +1,7 @@ import { SliderSuperTab } from "../../slider"; import { generateSection, SettingSection } from ".."; import Row from "../../row"; -import { AccountPassword, Authorization, InputPrivacyKey, PrivacyRule } from "../../../layer"; +import { AccountPassword, Authorization, InputPrivacyKey } from "../../../layer"; import appPrivacyManager, { PrivacyType } from "../../../lib/appManagers/appPrivacyManager"; import AppPrivacyPhoneNumberTab from "./privacy/phoneNumber"; import AppTwoStepVerificationTab from "./2fa"; @@ -17,14 +17,20 @@ import AppActiveSessionsTab from "./activeSessions"; import apiManager from "../../../lib/mtproto/mtprotoworker"; import AppBlockedUsersTab from "./blockedUsers"; import appUsersManager from "../../../lib/appManagers/appUsersManager"; +import rootScope from "../../../lib/rootScope"; export default class AppPrivacyAndSecurityTab extends SliderSuperTab { + private activeSessionsRow: Row; + private authorizations: Authorization.authorization[]; + protected init() { this.container.classList.add('privacy-container'); this.title.innerText = 'Privacy and Security'; const section = generateSection.bind(null, this.scrollable); + const SUBTITLE = 'Loading...'; + { const section = new SettingSection({noDelimiter: true}); @@ -32,7 +38,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { const blockedUsersRow = new Row({ icon: 'deleteuser', title: 'Blocked Users', - subtitle: 'Loading...', + subtitle: SUBTITLE, clickable: () => { const tab = new AppBlockedUsersTab(this.slider); tab.peerIds = blockedPeerIds; @@ -45,7 +51,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { const twoFactorRowOptions = { icon: 'lock', title: 'Two-Step Verification', - subtitle: 'Loading...', + subtitle: SUBTITLE, clickable: (e: Event) => { let tab: AppTwoStepVerificationTab | AppTwoStepVerificationEnterPasswordTab | AppTwoStepVerificationEmailConfirmationTab; if(passwordState.pFlags.has_password) { @@ -68,24 +74,39 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { const twoFactorRow = new Row(twoFactorRowOptions); twoFactorRow.freezed = true; - const activeSessionRow = new Row({ + const activeSessionsRow = this.activeSessionsRow = new Row({ icon: 'activesessions', title: 'Active Sessions', - subtitle: 'Loading...', + subtitle: SUBTITLE, clickable: () => { const tab = new AppActiveSessionsTab(this.slider); - tab.authorizations = authorizations; + tab.privacyTab = this; + tab.authorizations = this.authorizations; tab.open(); } }); - activeSessionRow.freezed = true; + activeSessionsRow.freezed = true; - section.content.append(blockedUsersRow.container, twoFactorRow.container, activeSessionRow.container); + section.content.append(blockedUsersRow.container, twoFactorRow.container, activeSessionsRow.container); this.scrollable.append(section.container); + let blockedCount: number; + const setBlockedCount = (count: number) => { + blockedCount = count; + blockedUsersRow.subtitle.innerText = count + ' ' + (count !== 1 ? 'users' : 'user'); + }; + + this.listenerSetter.add(rootScope, 'peer_block', (update) => { + const {blocked, peerId} = update; + if(!blocked) blockedPeerIds.findAndSplice(p => p === peerId); + else blockedPeerIds.unshift(peerId); + blockedCount += blocked ? 1 : -1; + setBlockedCount(blockedCount); + }); + appUsersManager.getBlocked().then(res => { blockedUsersRow.freezed = false; - blockedUsersRow.subtitle.innerText = res.count + ' ' + (res.count !== 1 ? 'users' : 'user'); + setBlockedCount(res.count); blockedPeerIds = res.peerIds; }); @@ -97,13 +118,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { //console.log('password state', state); }); - let authorizations: Authorization.authorization[]; - apiManager.invokeApi('account.getAuthorizations').then(auths => { - activeSessionRow.freezed = false; - authorizations = auths.authorizations; - activeSessionRow.subtitle.innerText = authorizations.length + ' ' + (authorizations.length !== 1 ? 'devices' : 'device'); - console.log('auths', auths); - }); + this.updateActiveSessions(); } { @@ -117,7 +132,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { const numberVisibilityRow = rowsByKeys['inputPrivacyKeyPhoneNumber'] = new Row({ title: 'Who can see my phone number?', - subtitle: 'My Contacts', + subtitle: SUBTITLE, clickable: () => { new AppPrivacyPhoneNumberTab(this.slider).open() } @@ -125,7 +140,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { const lastSeenTimeRow = rowsByKeys['inputPrivacyKeyStatusTimestamp'] = new Row({ title: 'Who can see your Last Seen time?', - subtitle: 'Everybody', + subtitle: SUBTITLE, clickable: () => { new AppPrivacyLastSeenTab(this.slider).open() } @@ -133,7 +148,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { const photoVisibilityRow = rowsByKeys['inputPrivacyKeyProfilePhoto'] = new Row({ title: 'Who can see my profile photo?', - subtitle: 'Everybody', + subtitle: SUBTITLE, clickable: () => { new AppPrivacyProfilePhotoTab(this.slider).open(); } @@ -141,7 +156,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { const callRow = rowsByKeys['inputPrivacyKeyPhoneCall'] = new Row({ title: 'Who can call me?', - subtitle: 'Everybody', + subtitle: SUBTITLE, clickable: () => { new AppPrivacyCallsTab(this.slider).open(); } @@ -149,7 +164,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { const linkAccountRow = rowsByKeys['inputPrivacyKeyForwards'] = new Row({ title: 'Who can add a link to my account when forwarding my messages?', - subtitle: 'Everybody', + subtitle: SUBTITLE, clickable: () => { new AppPrivacyForwardMessagesTab(this.slider).open(); } @@ -157,15 +172,19 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { const groupChatsAddRow = rowsByKeys['inputPrivacyKeyChatInvite'] = new Row({ title: 'Who can add me to group chats?', - subtitle: 'Everybody', + subtitle: SUBTITLE, clickable: () => { new AppPrivacyAddToGroupsTab(this.slider).open(); } }); - for(const key in rowsByKeys) { - const row = rowsByKeys[key as keyof typeof rowsByKeys]; - appPrivacyManager.getPrivacy(key as keyof typeof rowsByKeys).then(rules => { + const updatePrivacyRow = (key: InputPrivacyKey['_']) => { + const row = rowsByKeys[key]; + if(!row) { + return; + } + + appPrivacyManager.getPrivacy(key).then(rules => { const details = appPrivacyManager.getPrivacyRulesDetails(rules); const type = details.type === PrivacyType.Everybody ? 'Everybody' : (details.type === PrivacyType.Contacts ? 'My Contacts' : 'Nobody'); const disallowLength = details.disallowPeers.users.length + details.disallowPeers.chats.length; @@ -173,9 +192,30 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab { const str = type + (disallowLength || allowLength ? ` (${[-disallowLength, allowLength ? '+' + allowLength : 0].filter(Boolean).join(', ')})` : ''); row.subtitle.innerHTML = str; }); + }; + + for(const key in rowsByKeys) { + updatePrivacyRow(key as keyof typeof rowsByKeys); } + rootScope.on('privacy_update', (update) => { + let key: string = update.key._; + key = key[0].toUpperCase() + key.slice(1); + key = 'input' + key; + + updatePrivacyRow(key as any); + }); + container.append(numberVisibilityRow.container, lastSeenTimeRow.container, photoVisibilityRow.container, callRow.container, linkAccountRow.container, groupChatsAddRow.container); } } + + public updateActiveSessions() { + apiManager.invokeApi('account.getAuthorizations').then(auths => { + this.activeSessionsRow.freezed = false; + this.authorizations = auths.authorizations; + this.activeSessionsRow.subtitle.innerText = this.authorizations.length + ' ' + (this.authorizations.length !== 1 ? 'devices' : 'device'); + //console.log('auths', auths); + }); + } } diff --git a/src/lib/appManagers/appPrivacyManager.ts b/src/lib/appManagers/appPrivacyManager.ts index 4082f821..7fa50af4 100644 --- a/src/lib/appManagers/appPrivacyManager.ts +++ b/src/lib/appManagers/appPrivacyManager.ts @@ -1,8 +1,10 @@ import { MOUNT_CLASS_TO } from "../../config/debug"; -import { InputPrivacyKey, InputPrivacyRule, PrivacyRule } from "../../layer"; +import { InputPrivacyKey, InputPrivacyRule, PrivacyRule, Update, PrivacyKey } from "../../layer"; import apiManager from "../mtproto/mtprotoworker"; import appChatsManager from "./appChatsManager"; import appUsersManager from "./appUsersManager"; +import apiUpdatesManager from "./apiUpdatesManager"; +import rootScope from "../rootScope"; export enum PrivacyType { Everybody = 2, @@ -11,8 +13,27 @@ export enum PrivacyType { } export class AppPrivacyManager { + private privacy: Partial<{ + [key in PrivacyKey['_']]: PrivacyRule[] | Promise + }> = {}; + constructor() { + rootScope.on('apiUpdate', (e) => { + const update = e as Update; + + switch(update._) { + case 'updatePrivacy': + const key = update.key._; + this.privacy[key] = update.rules; + rootScope.broadcast('privacy_update', update); + break; + } + }); + } + public convertInputKeyToKey(inputKey: string) { + let str = inputKey.replace('input', ''); + return (str[0].toLowerCase() + str.slice(1)) as string; } public setPrivacy(inputKey: InputPrivacyKey['_'], rules: InputPrivacyRule[]) { @@ -22,17 +43,39 @@ export class AppPrivacyManager { }, rules }).then(privacyRules => { - /* appUsersManager.saveApiUsers(privacyRules.users); + appUsersManager.saveApiUsers(privacyRules.users); appChatsManager.saveApiChats(privacyRules.chats); - console.log('privacy rules', inputKey, privacyRules, privacyRules.rules); */ + apiUpdatesManager.processUpdateMessage({ + _: 'updateShort', + update: { + _: 'updatePrivacy', + key: { + _: this.convertInputKeyToKey(inputKey) + }, + rules: rules.map(inputRule => { + const rule: PrivacyRule = {} as any; + Object.assign(rule, inputRule); + rule._ = this.convertInputKeyToKey(rule._) as any; + return rule; + }) + } as Update.updatePrivacy + }); + + //console.log('privacy rules', inputKey, privacyRules, privacyRules.rules); return privacyRules.rules; }); } public getPrivacy(inputKey: InputPrivacyKey['_']) { - return apiManager.invokeApi('account.getPrivacy', { + const privacyKey: PrivacyKey['_'] = this.convertInputKeyToKey(inputKey) as any; + const rules = this.privacy[privacyKey]; + if(rules) { + return Promise.resolve(rules); + } + + return this.privacy[privacyKey] = apiManager.invokeApi('account.getPrivacy', { key: { _: inputKey } @@ -42,7 +85,7 @@ export class AppPrivacyManager { //console.log('privacy rules', inputKey, privacyRules, privacyRules.rules); - return privacyRules.rules; + return this.privacy[privacyKey] = privacyRules.rules; }); } diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index 2c90a20e..285e97a2 100644 --- a/src/lib/appManagers/appUsersManager.ts +++ b/src/lib/appManagers/appUsersManager.ts @@ -28,7 +28,7 @@ export class AppUsersManager { private contactsFillPromise: Promise>; public contactsList: Set = new Set(); private updatedContactsList = false; - + private getTopPeersPromise: Promise; constructor() { diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index a0b46dc5..f9b30f5a 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -92,6 +92,8 @@ type BroadcastEvents = { 'overlay_toggle': boolean, 'background_change': void, + + 'privacy_update': Update.updatePrivacy }; class RootScope extends EventListenerBase { @@ -126,11 +128,11 @@ class RootScope extends EventListenerBase { } public broadcast = (name: T, detail?: BroadcastEvents[T]) => { - //if(DEBUG) { + /* //if(DEBUG) { if(name !== 'user_update') { console.debug('Broadcasting ' + name + ' event, with args:', detail); } - //} + //} */ this.setListenerResult(name, detail); };