Temporary commit
This commit is contained in:
parent
a2f6e45e9c
commit
67e76a3f03
@ -55,12 +55,13 @@ class InputField {
|
||||
public container: HTMLElement;
|
||||
public input: HTMLElement;
|
||||
public inputFake: HTMLElement;
|
||||
public label: HTMLLabelElement;
|
||||
|
||||
//public onLengthChange: (length: number, isOverflow: boolean) => void;
|
||||
private wasInputFakeClientHeight: number;
|
||||
private showScrollDebounced: () => void;
|
||||
protected wasInputFakeClientHeight: number;
|
||||
protected showScrollDebounced: () => void;
|
||||
|
||||
constructor(private options: {
|
||||
constructor(protected options: {
|
||||
placeholder?: string,
|
||||
label?: string,
|
||||
name?: string,
|
||||
@ -131,6 +132,10 @@ class InputField {
|
||||
input.addEventListener('input', () => checkAndSetRTL(input));
|
||||
}
|
||||
|
||||
if(label) {
|
||||
this.label = this.container.lastElementChild as HTMLLabelElement;
|
||||
}
|
||||
|
||||
let processInput: () => void;
|
||||
if(maxLength) {
|
||||
const labelEl = this.container.lastElementChild as HTMLLabelElement;
|
||||
|
5
src/components/monkeys/index.ts
Normal file
5
src/components/monkeys/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import InputField from "../inputField";
|
||||
|
||||
export default interface Monkey {
|
||||
attachToInputField: (inputField: InputField) => void;
|
||||
}
|
59
src/components/monkeys/password.ts
Normal file
59
src/components/monkeys/password.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import lottieLoader, { RLottiePlayer } from "../../lib/lottieLoader";
|
||||
import PasswordInputField from "../passwordInputField";
|
||||
|
||||
export default class PasswordMonkey {
|
||||
public container: HTMLElement;
|
||||
public animation: RLottiePlayer;
|
||||
public needFrame = 0;
|
||||
protected loadPromise: Promise<any>;
|
||||
|
||||
constructor(protected passwordInputField: PasswordInputField, protected size: number) {
|
||||
this.container = document.createElement('div');
|
||||
this.container.classList.add('media-sticker-wrapper');
|
||||
}
|
||||
|
||||
public load() {
|
||||
if(this.loadPromise) return this.loadPromise;
|
||||
return this.loadPromise = lottieLoader.loadAnimationFromURL({
|
||||
container: this.container,
|
||||
loop: false,
|
||||
autoplay: false,
|
||||
width: this.size,
|
||||
height: this.size,
|
||||
noCache: true
|
||||
//}, 'assets/img/TwoFactorSetupMonkeyClose.tgs').then(_animation => {
|
||||
}, 'assets/img/TwoFactorSetupMonkeyPeek.tgs').then(_animation => {
|
||||
//return;
|
||||
this.animation = _animation;
|
||||
this.animation.addListener('enterFrame', currentFrame => {
|
||||
//console.log('enterFrame', currentFrame, this.needFrame);
|
||||
|
||||
if((this.animation.direction === 1 && currentFrame >= this.needFrame) ||
|
||||
(this.animation.direction === -1 && currentFrame <= this.needFrame)) {
|
||||
this.animation.setSpeed(1);
|
||||
this.animation.pause();
|
||||
}
|
||||
});
|
||||
|
||||
this.passwordInputField.onVisibilityClickAdditional = () => {
|
||||
if(this.passwordInputField.passwordVisible) {
|
||||
this.animation.setDirection(1);
|
||||
this.animation.curFrame = 0;
|
||||
this.needFrame = 16;
|
||||
this.animation.play();
|
||||
} else {
|
||||
this.animation.setDirection(-1);
|
||||
this.animation.curFrame = 16;
|
||||
this.needFrame = 0;
|
||||
this.animation.play();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public remove() {
|
||||
if(this.animation) {
|
||||
this.animation.remove();
|
||||
}
|
||||
}
|
||||
}
|
41
src/components/passwordInputField.ts
Normal file
41
src/components/passwordInputField.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { cancelEvent } from "../helpers/dom";
|
||||
import InputField from "./inputField";
|
||||
|
||||
export default class PasswordInputField extends InputField {
|
||||
public passwordVisible = false;
|
||||
public toggleVisible: HTMLElement;
|
||||
public onVisibilityClickAdditional: () => void;
|
||||
|
||||
constructor(options: {
|
||||
label?: string,
|
||||
name?: string
|
||||
} = {}) {
|
||||
super({
|
||||
plainText: true,
|
||||
...options
|
||||
});
|
||||
|
||||
const input = this.input as HTMLInputElement;
|
||||
input.type = 'password';
|
||||
input.setAttribute('required', '');
|
||||
input.autocomplete = 'off';
|
||||
|
||||
const toggleVisible = this.toggleVisible = document.createElement('span');
|
||||
toggleVisible.classList.add('toggle-visible', 'tgico');
|
||||
|
||||
this.container.classList.add('input-field-password');
|
||||
this.container.append(toggleVisible);
|
||||
|
||||
toggleVisible.addEventListener('click', this.onVisibilityClick);
|
||||
toggleVisible.addEventListener('touchend', this.onVisibilityClick);
|
||||
}
|
||||
|
||||
public onVisibilityClick = (e: Event) => {
|
||||
cancelEvent(e);
|
||||
this.passwordVisible = !this.passwordVisible;
|
||||
|
||||
this.toggleVisible.classList.toggle('eye-hidden', this.passwordVisible);
|
||||
(this.input as HTMLInputElement).type = this.passwordVisible ? 'text' : 'password';
|
||||
this.onVisibilityClickAdditional && this.onVisibilityClickAdditional();
|
||||
};
|
||||
}
|
18
src/components/popups/confirmAction.ts
Normal file
18
src/components/popups/confirmAction.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import PopupElement, { addCancelButton, PopupButton, PopupOptions } from ".";
|
||||
|
||||
export default class PopupConfirmAction extends PopupElement {
|
||||
constructor(className: string, buttons: PopupButton[], options: PopupOptions & Partial<{title: string, text: string}> = {}) {
|
||||
super('popup-peer popup-confirm-action ' + className, addCancelButton(buttons), {
|
||||
overlayClosable: true,
|
||||
...options
|
||||
});
|
||||
|
||||
this.title.innerHTML = options.title || 'Warning';
|
||||
|
||||
const p = document.createElement('p');
|
||||
p.classList.add('popup-description');
|
||||
p.innerHTML = options.text;
|
||||
|
||||
this.container.insertBefore(p, this.header.nextElementSibling);
|
||||
}
|
||||
}
|
@ -1,11 +1,23 @@
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import { blurActiveElement, cancelEvent, findUpClassName } from "../../helpers/dom";
|
||||
import { blurActiveElement, findUpClassName } from "../../helpers/dom";
|
||||
import { ripple } from "../ripple";
|
||||
import animationIntersector from "../animationIntersector";
|
||||
import appNavigationController, { NavigationItem } from "../appNavigationController";
|
||||
import { isMobileSafari, isSafari } from "../../helpers/userAgent";
|
||||
|
||||
export type PopupOptions = Partial<{closable: true, overlayClosable: true, withConfirm: string, body: true}>;
|
||||
export type PopupButton = {
|
||||
text: string,
|
||||
callback?: () => void,
|
||||
isDanger?: true,
|
||||
isCancel?: true
|
||||
};
|
||||
|
||||
export type PopupOptions = Partial<{
|
||||
closable: true,
|
||||
overlayClosable: true,
|
||||
withConfirm: string,
|
||||
body: true
|
||||
}>;
|
||||
|
||||
export default class PopupElement {
|
||||
protected element = document.createElement('div');
|
||||
protected container = document.createElement('div');
|
||||
@ -140,9 +152,14 @@ export default class PopupElement {
|
||||
};
|
||||
}
|
||||
|
||||
export type PopupButton = {
|
||||
text: string,
|
||||
callback?: () => void,
|
||||
isDanger?: true,
|
||||
isCancel?: true
|
||||
export const addCancelButton = (buttons: PopupButton[]) => {
|
||||
const button = buttons.find(b => b.isCancel);
|
||||
if(!button) {
|
||||
buttons.push({
|
||||
text: 'CANCEL',
|
||||
isCancel: true
|
||||
});
|
||||
}
|
||||
|
||||
return buttons;
|
||||
};
|
||||
|
@ -20,7 +20,7 @@ export default class Row {
|
||||
radioField: Row['radioField'],
|
||||
checkboxField: Row['checkboxField'],
|
||||
title: string,
|
||||
clickable: boolean,
|
||||
clickable: boolean | ((e: Event) => void),
|
||||
navigationTab: SliderSuperTab
|
||||
}> = {}) {
|
||||
this.container = document.createElement('div');
|
||||
@ -63,14 +63,17 @@ export default class Row {
|
||||
}
|
||||
|
||||
if(options.navigationTab) {
|
||||
this.container.addEventListener('click', () => {
|
||||
if(this.freezed) return;
|
||||
options.navigationTab.open();
|
||||
});
|
||||
options.clickable = true;
|
||||
options.clickable = () => options.navigationTab.open();
|
||||
}
|
||||
|
||||
if(options.clickable) {
|
||||
if(typeof(options.clickable) === 'function') {
|
||||
this.container.addEventListener('click', (e) => {
|
||||
if(this.freezed) return;
|
||||
(options.clickable as any)(e);
|
||||
});
|
||||
}
|
||||
|
||||
this.container.classList.add('row-clickable', 'hover-effect');
|
||||
ripple(this.container);
|
||||
}
|
||||
|
135
src/components/sidebarLeft/tabs/2fa/enterPassword.ts
Normal file
135
src/components/sidebarLeft/tabs/2fa/enterPassword.ts
Normal file
@ -0,0 +1,135 @@
|
||||
import AppTwoStepVerificationTab from ".";
|
||||
import { SettingSection } from "../..";
|
||||
import { attachClickEvent, cancelEvent } from "../../../../helpers/dom";
|
||||
import { AccountPassword } from "../../../../layer";
|
||||
import passwordManager from "../../../../lib/mtproto/passwordManager";
|
||||
import Button from "../../../button";
|
||||
import { putPreloader } from "../../../misc";
|
||||
import PasswordMonkey from "../../../monkeys/password";
|
||||
import PasswordInputField from "../../../passwordInputField";
|
||||
import { ripple } from "../../../ripple";
|
||||
import SidebarSlider, { SliderSuperTab } from "../../../slider";
|
||||
import AppTwoStepVerificationReEnterPasswordTab from "./reEnterPassword";
|
||||
|
||||
export default class AppTwoStepVerificationEnterPasswordTab extends SliderSuperTab {
|
||||
public state: AccountPassword;
|
||||
|
||||
constructor(slider: SidebarSlider) {
|
||||
super(slider, true);
|
||||
}
|
||||
|
||||
protected init() {
|
||||
this.container.classList.add('two-step-verification-enter-password');
|
||||
this.title.innerHTML = this.state.pFlags.has_password ? 'Enter Your Password' : 'Enter a Password';
|
||||
|
||||
const section = new SettingSection({
|
||||
noDelimiter: true
|
||||
});
|
||||
|
||||
const inputWrapper = document.createElement('div');
|
||||
inputWrapper.classList.add('input-wrapper');
|
||||
|
||||
const passwordInputField = new PasswordInputField({
|
||||
name: 'first-password',
|
||||
label: this.state.pFlags.has_password ? this.state.hint ?? 'Password' : 'Enter a Password'
|
||||
});
|
||||
|
||||
const monkey = new PasswordMonkey(passwordInputField, 157);
|
||||
monkey.load();
|
||||
|
||||
const btnContinue = Button('btn-primary', {text: 'CONTINUE'});
|
||||
|
||||
inputWrapper.append(passwordInputField.container, btnContinue);
|
||||
section.content.append(monkey.container, inputWrapper);
|
||||
|
||||
this.scrollable.container.append(section.container);
|
||||
|
||||
passwordInputField.input.addEventListener('keypress', (e) => {
|
||||
if(passwordInputField.input.classList.contains('error')) {
|
||||
passwordInputField.input.classList.remove('error');
|
||||
btnContinue.innerText = 'CONTINUE';
|
||||
ripple(btnContinue);
|
||||
}
|
||||
|
||||
if(e.key === 'Enter') {
|
||||
return btnContinue.click();
|
||||
}
|
||||
});
|
||||
|
||||
const verifyInput = () => {
|
||||
if(!passwordInputField.value.length) {
|
||||
passwordInputField.input.classList.add('error');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if(this.state.pFlags.has_password) {
|
||||
let getStateInterval: number;
|
||||
|
||||
let getState = () => {
|
||||
// * just to check session relevance
|
||||
if(!getStateInterval) {
|
||||
getStateInterval = window.setInterval(getState, 10e3);
|
||||
}
|
||||
|
||||
return passwordManager.getState().then(_state => {
|
||||
this.state = _state;
|
||||
|
||||
passwordInputField.label.innerText = this.state.hint ?? 'Password';
|
||||
});
|
||||
};
|
||||
|
||||
const submit = (e?: Event) => {
|
||||
if(!verifyInput()) {
|
||||
cancelEvent(e);
|
||||
return;
|
||||
}
|
||||
|
||||
btnContinue.setAttribute('disabled', 'true');
|
||||
btnContinue.textContent = 'PLEASE WAIT...';
|
||||
putPreloader(btnContinue);
|
||||
|
||||
const plainPassword = passwordInputField.value;
|
||||
passwordManager.check(passwordInputField.value, this.state).then(auth => {
|
||||
console.log(auth);
|
||||
|
||||
if(auth._ === 'auth.authorization') {
|
||||
clearInterval(getStateInterval);
|
||||
if(monkey) monkey.remove();
|
||||
const tab = new AppTwoStepVerificationTab(this.slider);
|
||||
tab.state = this.state;
|
||||
tab.plainPassword = plainPassword;
|
||||
tab.open();
|
||||
}
|
||||
}, (err) => {
|
||||
btnContinue.removeAttribute('disabled');
|
||||
passwordInputField.input.classList.add('error');
|
||||
|
||||
switch(err.type) {
|
||||
default:
|
||||
//btnContinue.innerText = err.type;
|
||||
btnContinue.innerText = 'INVALID PASSWORD';
|
||||
break;
|
||||
}
|
||||
|
||||
getState();
|
||||
});
|
||||
};
|
||||
|
||||
attachClickEvent(btnContinue, submit);
|
||||
|
||||
getState();
|
||||
} else {
|
||||
attachClickEvent(btnContinue, (e) => {
|
||||
cancelEvent(e);
|
||||
if(!verifyInput()) return;
|
||||
|
||||
const tab = new AppTwoStepVerificationReEnterPasswordTab(this.slider);
|
||||
tab.state = this.state;
|
||||
tab.open();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,20 @@
|
||||
import { generateSection, SettingSection } from "../..";
|
||||
import { SettingSection } from "../..";
|
||||
import { attachClickEvent } from "../../../../helpers/dom";
|
||||
import { AccountPassword } from "../../../../layer";
|
||||
import appStickersManager from "../../../../lib/appManagers/appStickersManager";
|
||||
import passwordManager from "../../../../lib/mtproto/passwordManager";
|
||||
import Button from "../../../button";
|
||||
import PopupConfirmAction from "../../../popups/confirmAction";
|
||||
import SidebarSlider, { SliderSuperTab } from "../../../slider";
|
||||
import { wrapSticker } from "../../../wrappers";
|
||||
import AppTwoStepVerificationEnterPasswordTab from "./enterPassword";
|
||||
|
||||
export default class AppTwoStepVerificationTab extends SliderSuperTab {
|
||||
public passwordState: AccountPassword;
|
||||
public state: AccountPassword;
|
||||
public plainPassword: string;
|
||||
|
||||
constructor(slider: SidebarSlider) {
|
||||
super(slider);
|
||||
super(slider, true);
|
||||
}
|
||||
|
||||
protected init() {
|
||||
@ -40,19 +45,40 @@ export default class AppTwoStepVerificationTab extends SliderSuperTab {
|
||||
section.content.append(stickerContainer);
|
||||
|
||||
const c = section.generateContentElement();
|
||||
if(this.passwordState.pFlags.has_password) {
|
||||
if(this.state.pFlags.has_password) {
|
||||
section.caption.innerHTML = 'You have enabled Two-Step verification.<br/>You\'ll need the password you set up here to log in to your Telegram account';
|
||||
|
||||
const btnChangePassword = Button('btn-primary btn-transparent', {icon: 'edit', text: 'Change Password'});
|
||||
const btnRemovePassword = Button('btn-primary btn-transparent', {icon: 'passwordoff', text: 'Turn Password Off'});
|
||||
const btnDisablePassword = Button('btn-primary btn-transparent', {icon: 'passwordoff', text: 'Turn Password Off'});
|
||||
const btnSetRecoveryEmail = Button('btn-primary btn-transparent', {icon: 'email', text: 'Set Recovery Email'});
|
||||
|
||||
c.append(btnChangePassword, btnRemovePassword, btnSetRecoveryEmail);
|
||||
attachClickEvent(btnDisablePassword, () => {
|
||||
const popup = new PopupConfirmAction('popup-disable-password', [{
|
||||
text: 'DISABLE',
|
||||
callback: () => {
|
||||
passwordManager.updateSettings({currentPassword: this.plainPassword});
|
||||
},
|
||||
isDanger: true,
|
||||
}], {
|
||||
title: 'Warning',
|
||||
text: 'Are you sure you want to disable your password?'
|
||||
});
|
||||
|
||||
popup.show();
|
||||
});
|
||||
|
||||
c.append(btnChangePassword, btnDisablePassword, btnSetRecoveryEmail);
|
||||
} else {
|
||||
section.caption.innerHTML = 'You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS.';
|
||||
|
||||
const btnSetPassword = Button('btn-primary', {text: 'SET PASSWORD'});
|
||||
c.append(btnSetPassword);
|
||||
|
||||
attachClickEvent(btnSetPassword, (e) => {
|
||||
const tab = new AppTwoStepVerificationEnterPasswordTab(this.slider);
|
||||
tab.state = this.state;
|
||||
tab.open();
|
||||
});
|
||||
}
|
||||
|
||||
this.scrollable.container.append(section.container);
|
||||
|
74
src/components/sidebarLeft/tabs/2fa/reEnterPassword.ts
Normal file
74
src/components/sidebarLeft/tabs/2fa/reEnterPassword.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import AppTwoStepVerificationTab from ".";
|
||||
import { SettingSection } from "../..";
|
||||
import { attachClickEvent, cancelEvent } from "../../../../helpers/dom";
|
||||
import { AccountPassword } from "../../../../layer";
|
||||
import passwordManager from "../../../../lib/mtproto/passwordManager";
|
||||
import Button from "../../../button";
|
||||
import { putPreloader } from "../../../misc";
|
||||
import PasswordMonkey from "../../../monkeys/password";
|
||||
import PasswordInputField from "../../../passwordInputField";
|
||||
import { ripple } from "../../../ripple";
|
||||
import SidebarSlider, { SliderSuperTab } from "../../../slider";
|
||||
|
||||
export default class AppTwoStepVerificationReEnterPasswordTab extends SliderSuperTab {
|
||||
public state: AccountPassword;
|
||||
|
||||
constructor(slider: SidebarSlider) {
|
||||
super(slider, true);
|
||||
}
|
||||
|
||||
protected init() {
|
||||
this.container.classList.add('two-step-verification-enter-password');
|
||||
this.title.innerHTML = 'Re-Enter your Password';
|
||||
|
||||
const section = new SettingSection({
|
||||
noDelimiter: true
|
||||
});
|
||||
|
||||
const inputWrapper = document.createElement('div');
|
||||
inputWrapper.classList.add('input-wrapper');
|
||||
|
||||
const passwordInputField = new PasswordInputField({
|
||||
name: 're-enter-password',
|
||||
label: 'Re-Enter your Password'
|
||||
});
|
||||
|
||||
const monkey = new PasswordMonkey(passwordInputField, 157);
|
||||
monkey.load();
|
||||
|
||||
const btnContinue = Button('btn-primary', {text: 'CONTINUE'});
|
||||
|
||||
inputWrapper.append(passwordInputField.container, btnContinue);
|
||||
section.content.append(monkey.container, inputWrapper);
|
||||
|
||||
this.scrollable.container.append(section.container);
|
||||
|
||||
passwordInputField.input.addEventListener('keypress', (e) => {
|
||||
if(passwordInputField.input.classList.contains('error')) {
|
||||
passwordInputField.input.classList.remove('error');
|
||||
btnContinue.innerText = 'CONTINUE';
|
||||
ripple(btnContinue);
|
||||
}
|
||||
|
||||
if(e.key === 'Enter') {
|
||||
return btnContinue.click();
|
||||
}
|
||||
});
|
||||
|
||||
const verifyInput = () => {
|
||||
if(!passwordInputField.value.length) {
|
||||
passwordInputField.input.classList.add('error');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
attachClickEvent(btnContinue, (e) => {
|
||||
cancelEvent(e);
|
||||
if(!verifyInput()) return;
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
import SidebarSlider, { SliderSuperTab } from "../../slider";
|
||||
import { generateSection, SettingSection } from "..";
|
||||
import Row from "../../row";
|
||||
import { InputPrivacyKey, PrivacyRule } from "../../../layer";
|
||||
import { AccountPassword, InputPrivacyKey, PrivacyRule } from "../../../layer";
|
||||
import appPrivacyManager from "../../../lib/appManagers/appPrivacyManager";
|
||||
import AppPrivacyPhoneNumberTab from "./privacy/phoneNumber";
|
||||
import AppTwoStepVerificationTab from "./2fa";
|
||||
import passwordManager from "../../../lib/mtproto/passwordManager";
|
||||
import AppTwoStepVerificationEnterPasswordTab from "./2fa/enterPassword";
|
||||
|
||||
export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
|
||||
constructor(slider: SidebarSlider) {
|
||||
@ -28,14 +29,25 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
|
||||
clickable: true
|
||||
});
|
||||
|
||||
const tab = new AppTwoStepVerificationTab(this.slider);
|
||||
|
||||
const twoFactorRow = new Row({
|
||||
let passwordState: AccountPassword;
|
||||
const twoFactorRowOptions = {
|
||||
icon: 'lock',
|
||||
title: 'Two-Step Verification',
|
||||
subtitle: 'Loading...',
|
||||
navigationTab: tab
|
||||
});
|
||||
clickable: (e: Event) => {
|
||||
let tab: AppTwoStepVerificationTab | AppTwoStepVerificationEnterPasswordTab;
|
||||
if(passwordState.pFlags.has_password) {
|
||||
tab = new AppTwoStepVerificationEnterPasswordTab(this.slider);
|
||||
} else {
|
||||
tab = new AppTwoStepVerificationTab(this.slider);
|
||||
}
|
||||
|
||||
tab.state = passwordState;
|
||||
tab.open();
|
||||
}
|
||||
};
|
||||
|
||||
const twoFactorRow = new Row(twoFactorRowOptions);
|
||||
twoFactorRow.freezed = true;
|
||||
|
||||
const activeSessionRow = new Row({
|
||||
@ -49,10 +61,11 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
|
||||
this.scrollable.append(section.container);
|
||||
|
||||
passwordManager.getState().then(state => {
|
||||
passwordState = state;
|
||||
twoFactorRow.subtitle.innerText = state.pFlags.has_password ? 'On' : 'Off';
|
||||
twoFactorRow.freezed = false;
|
||||
tab.passwordState = state;
|
||||
//console.log('password state', state);
|
||||
|
||||
console.log('password state', state);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2465,7 +2465,7 @@ export class AppMessagesManager {
|
||||
if(document.type === 'video') {
|
||||
messageText = '<i>Video' + (message.message ? ', ' : '') + '</i>';
|
||||
} else if(document.type === 'voice') {
|
||||
messageText = '<i>Voice message</i>';
|
||||
messageText = '<i>Voice message' + (message.message ? ', ' : '') + '</i>';
|
||||
} else if(document.type === 'gif') {
|
||||
messageText = '<i>GIF' + (message.message ? ', ' : '') + '</i>';
|
||||
} else if(document.type === 'round') {
|
||||
|
@ -1,5 +1,7 @@
|
||||
import type { AccountPassword, AccountPasswordInputSettings, AccountUpdatePasswordSettings, InputCheckPasswordSRP, PasswordKdfAlgo } from '../../layer';
|
||||
import type CryptoWorkerMethods from '../crypto/crypto_methods';
|
||||
import { MOUNT_CLASS_TO } from '../../config/debug';
|
||||
import { AccountPassword } from '../../layer';
|
||||
import appUsersManager from '../appManagers/appUsersManager';
|
||||
import apiManager from './mtprotoworker';
|
||||
//import { computeCheck } from "../crypto/srp";
|
||||
|
||||
@ -10,54 +12,71 @@ export class PasswordManager {
|
||||
});
|
||||
}
|
||||
|
||||
/* public updateSettings(state: any, settings: any) {
|
||||
var currentHashPromise;
|
||||
var newHashPromise;
|
||||
var params: any = {
|
||||
new_settings: {
|
||||
_: 'account.passwordInputSettings',
|
||||
hint: settings.hint || ''
|
||||
public updateSettings(settings: {
|
||||
hint?: string,
|
||||
email?: string,
|
||||
newPassword?: string,
|
||||
currentPassword?: string
|
||||
} = {}) {
|
||||
//state = Object.assign({}, state);
|
||||
//state.new_algo = Object.assign({}, state.new_algo);
|
||||
|
||||
this.getState().then(state => {
|
||||
let currentHashPromise: ReturnType<CryptoWorkerMethods['computeSRP']>;
|
||||
let newHashPromise: Promise<Uint8Array>;
|
||||
const params: AccountUpdatePasswordSettings = {
|
||||
password: null,
|
||||
new_settings: {
|
||||
_: 'account.passwordInputSettings',
|
||||
hint: settings.hint,
|
||||
email: settings.email
|
||||
}
|
||||
};
|
||||
|
||||
if(settings.currentPassword) {
|
||||
currentHashPromise = apiManager.computeSRP(settings.currentPassword, state);
|
||||
} else {
|
||||
currentHashPromise = Promise.resolve({
|
||||
_: 'inputCheckPasswordEmpty'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if(typeof settings.cur_password === 'string' &&
|
||||
settings.cur_password.length > 0) {
|
||||
currentHashPromise = this.makePasswordHash(state.current_salt, settings.cur_password);
|
||||
} else {
|
||||
currentHashPromise = Promise.resolve([]);
|
||||
}
|
||||
|
||||
if (typeof settings.new_password === 'string' &&
|
||||
settings.new_password.length > 0) {
|
||||
var saltRandom = new Array(8);
|
||||
var newSalt = bufferConcat(state.new_salt, saltRandom);
|
||||
secureRandom.nextBytes(saltRandom);
|
||||
newHashPromise = this.makePasswordHash(newSalt, settings.new_password);
|
||||
params.new_settings.new_salt = newSalt;
|
||||
} else {
|
||||
if(typeof settings.new_password === 'string') {
|
||||
params.new_settings.new_salt = [];
|
||||
|
||||
// * https://core.telegram.org/api/srp#setting-a-new-2fa-password, but still there is a mistake, TDesktop passes 'new_algo' everytime
|
||||
const newAlgo = state.new_algo as PasswordKdfAlgo.passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow;
|
||||
const salt1 = new Uint8Array(newAlgo.salt1.length + 32);
|
||||
salt1.randomize();
|
||||
salt1.set(newAlgo.salt1, 0);
|
||||
newAlgo.salt1 = salt1;
|
||||
|
||||
if(settings.newPassword) {
|
||||
newHashPromise = Promise.resolve(new Uint8Array());
|
||||
} else {
|
||||
newHashPromise = Promise.resolve(new Uint8Array());
|
||||
}
|
||||
newHashPromise = Promise.resolve([]);
|
||||
}
|
||||
|
||||
if(typeof settings.email === 'string') {
|
||||
params.new_settings.email = settings.email || '';
|
||||
}
|
||||
|
||||
return Promise.all([currentHashPromise, newHashPromise]).then((hashes) => {
|
||||
params.current_password_hash = hashes[0];
|
||||
params.new_settings.new_password_hash = hashes[1];
|
||||
|
||||
return apiManager.invokeApi('account.updatePasswordSettings', params);
|
||||
|
||||
return Promise.all([currentHashPromise, newHashPromise]).then((hashes) => {
|
||||
params.password = hashes[0];
|
||||
params.new_settings.new_algo = newAlgo;
|
||||
params.new_settings.new_password_hash = hashes[1];
|
||||
|
||||
return apiManager.invokeApi('account.updatePasswordSettings', params);
|
||||
});
|
||||
});
|
||||
} */
|
||||
}
|
||||
|
||||
public check(password: string, state: AccountPassword, options: any = {}) {
|
||||
return apiManager.computeSRP(password, state).then((inputCheckPassword) => {
|
||||
//console.log('SRP', inputCheckPassword);
|
||||
return apiManager.invokeApi('auth.checkPassword', {
|
||||
password: inputCheckPassword
|
||||
}, options);
|
||||
}, options).then(auth => {
|
||||
if(auth._ === 'auth.authorization') {
|
||||
appUsersManager.saveApiUser(auth.user);
|
||||
apiManager.setUserAuth(auth.user.id);
|
||||
}
|
||||
|
||||
return auth;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,48 +1,28 @@
|
||||
//import CryptoWorker from '../lib/crypto/cryptoworker';
|
||||
//import apiManager from '../lib/mtproto/apiManager';
|
||||
import { putPreloader } from '../components/misc';
|
||||
import mediaSizes from '../helpers/mediaSizes';
|
||||
import { isAppleMobile, isSafari } from '../helpers/userAgent';
|
||||
import { AccountPassword } from '../layer';
|
||||
import appStateManager from '../lib/appManagers/appStateManager';
|
||||
import LottieLoader, { RLottiePlayer } from '../lib/lottieLoader';
|
||||
//import passwordManager from '../lib/mtproto/passwordManager';
|
||||
import apiManager from '../lib/mtproto/mtprotoworker';
|
||||
import passwordManager from '../lib/mtproto/passwordManager';
|
||||
import { cancelEvent } from '../helpers/dom';
|
||||
import Page from './page';
|
||||
import pageIm from './pageIm';
|
||||
import InputField from '../components/inputField';
|
||||
import Button from '../components/button';
|
||||
import PasswordInputField from '../components/passwordInputField';
|
||||
import PasswordMonkey from '../components/monkeys/password';
|
||||
import { ripple } from '../components/ripple';
|
||||
|
||||
const TEST = false;
|
||||
let passwordInput: HTMLInputElement;
|
||||
|
||||
let onFirstMount = (): Promise<any> => {
|
||||
let needFrame = 0;
|
||||
let animation: RLottiePlayer;
|
||||
|
||||
let passwordVisible = false;
|
||||
|
||||
const btnNext = Button('btn-primary', {text: 'NEXT'});
|
||||
|
||||
const passwordInputField = new InputField({
|
||||
const passwordInputField = new PasswordInputField({
|
||||
label: 'Password',
|
||||
name: 'password',
|
||||
plainText: true
|
||||
name: 'password'
|
||||
});
|
||||
|
||||
passwordInput = passwordInputField.input as HTMLInputElement;
|
||||
passwordInput.type = 'password';
|
||||
passwordInput.setAttribute('required', '');
|
||||
passwordInput.autocomplete = 'off';
|
||||
|
||||
const passwordInputLabel = passwordInput.nextElementSibling as HTMLLabelElement;
|
||||
|
||||
const toggleVisible = document.createElement('span');
|
||||
toggleVisible.classList.add('toggle-visible', 'tgico');
|
||||
|
||||
passwordInputField.container.append(toggleVisible);
|
||||
|
||||
page.pageEl.querySelector('.input-wrapper').append(passwordInputField.container, btnNext);
|
||||
|
||||
@ -57,13 +37,13 @@ let onFirstMount = (): Promise<any> => {
|
||||
return !TEST && passwordManager.getState().then(_state => {
|
||||
state = _state;
|
||||
|
||||
passwordInputLabel.innerText = state.hint ?? 'Password';
|
||||
passwordInputField.label.innerText = state.hint ?? 'Password';
|
||||
});
|
||||
};
|
||||
|
||||
let handleError = (err: any) => {
|
||||
btnNext.removeAttribute('disabled');
|
||||
passwordInput.classList.add('error');
|
||||
passwordInputField.input.classList.add('error');
|
||||
|
||||
switch(err.type) {
|
||||
default:
|
||||
@ -75,29 +55,6 @@ let onFirstMount = (): Promise<any> => {
|
||||
getState();
|
||||
};
|
||||
|
||||
const onVisibilityClick = function(this: typeof toggleVisible, e: Event) {
|
||||
cancelEvent(e);
|
||||
passwordVisible = !passwordVisible;
|
||||
|
||||
this.classList.toggle('eye-hidden', passwordVisible);
|
||||
if(passwordVisible) {
|
||||
passwordInput.setAttribute('type', 'text');
|
||||
animation.setDirection(1);
|
||||
animation.curFrame = 0;
|
||||
needFrame = 16;
|
||||
animation.play();
|
||||
} else {
|
||||
passwordInput.setAttribute('type', 'password');
|
||||
animation.setDirection(-1);
|
||||
animation.curFrame = 16;
|
||||
needFrame = 0;
|
||||
animation.play();
|
||||
}
|
||||
};
|
||||
|
||||
toggleVisible.addEventListener('click', onVisibilityClick);
|
||||
toggleVisible.addEventListener('touchend', onVisibilityClick);
|
||||
|
||||
let state: AccountPassword;
|
||||
|
||||
btnNext.addEventListener('click', function(this, e) {
|
||||
@ -117,15 +74,14 @@ let onFirstMount = (): Promise<any> => {
|
||||
|
||||
switch(response._) {
|
||||
case 'auth.authorization':
|
||||
apiManager.setUserAuth(response.user.id);
|
||||
|
||||
clearInterval(getStateInterval);
|
||||
pageIm.mount();
|
||||
if(animation) animation.remove();
|
||||
if(monkey) monkey.remove();
|
||||
break;
|
||||
default:
|
||||
btnNext.removeAttribute('disabled');
|
||||
btnNext.innerText = response._;
|
||||
ripple(btnNext);
|
||||
break;
|
||||
}
|
||||
}).catch(handleError);
|
||||
@ -134,42 +90,18 @@ let onFirstMount = (): Promise<any> => {
|
||||
passwordInput.addEventListener('keypress', function(this, e) {
|
||||
this.classList.remove('error');
|
||||
btnNext.innerText = 'NEXT';
|
||||
ripple(btnNext);
|
||||
|
||||
if(e.key === 'Enter') {
|
||||
return btnNext.click();
|
||||
}
|
||||
});
|
||||
|
||||
/* passwordInput.addEventListener('input', function(this, e) {
|
||||
|
||||
}); */
|
||||
const size = mediaSizes.isMobile ? 100 : 166;
|
||||
const monkey = new PasswordMonkey(passwordInputField, size);
|
||||
page.pageEl.querySelector('.auth-image').append(monkey.container);
|
||||
return Promise.all([
|
||||
LottieLoader.loadAnimationFromURL({
|
||||
container: page.pageEl.querySelector('.auth-image'),
|
||||
loop: false,
|
||||
autoplay: false,
|
||||
width: size,
|
||||
height: size,
|
||||
noCache: true
|
||||
//}, 'assets/img/TwoFactorSetupMonkeyClose.tgs').then(_animation => {
|
||||
}, 'assets/img/TwoFactorSetupMonkeyPeek.tgs').then(_animation => {
|
||||
//return;
|
||||
animation = _animation;
|
||||
animation.addListener('enterFrame', currentFrame => {
|
||||
//console.log('enterFrame', e, needFrame);
|
||||
|
||||
if((animation.direction === 1 && currentFrame >= needFrame) ||
|
||||
(animation.direction === -1 && currentFrame <= needFrame)) {
|
||||
animation.setSpeed(1);
|
||||
animation.pause();
|
||||
}
|
||||
});
|
||||
|
||||
needFrame = 49;
|
||||
//animation.play();
|
||||
}),
|
||||
|
||||
monkey.load(),
|
||||
getState()
|
||||
]);
|
||||
};
|
||||
|
@ -51,6 +51,7 @@
|
||||
transform-origin: left center;
|
||||
pointer-events: none;
|
||||
margin-top: calc((var(--height) - 1.5rem) / 2); // * Center of first line
|
||||
user-select: none;
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none;
|
||||
@ -301,4 +302,54 @@ input:focus, button:focus {
|
||||
margin-right: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-field-password {
|
||||
.input-field-input {
|
||||
padding-right: 2.5rem;
|
||||
max-height: 54px;
|
||||
|
||||
&[type="password"] {
|
||||
font-size: 2.25rem;
|
||||
padding-left: calc(.875rem - var(--border-width));
|
||||
|
||||
@media (-webkit-min-device-pixel-ratio: 2) {
|
||||
font-size: 1.75rem;
|
||||
letter-spacing: .125rem;
|
||||
}
|
||||
|
||||
html.is-ios & {
|
||||
// ! меньше 1rem будет зумить поле
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-visible {
|
||||
position: absolute;
|
||||
right: .375rem;
|
||||
z-index: 2;
|
||||
font-size: 1.5rem;
|
||||
color: $placeholder-color;
|
||||
cursor: pointer;
|
||||
transition: color .2s;
|
||||
padding: .5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
&:before {
|
||||
content: $tgico-eye1;
|
||||
}
|
||||
|
||||
&.eye-hidden:before {
|
||||
content: $tgico-eye2;
|
||||
}
|
||||
|
||||
@include hover() {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -843,6 +843,10 @@
|
||||
@include respond-to(not-handhelds) {
|
||||
margin: 0 .625rem;
|
||||
}
|
||||
|
||||
> .btn-primary {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-name {
|
||||
@ -857,7 +861,7 @@
|
||||
padding: 0 .875rem;
|
||||
}
|
||||
|
||||
.btn-primary, .checkbox-field, .radio-field {
|
||||
.checkbox-field, .radio-field {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@ -905,6 +909,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.two-step-verification-enter-password {
|
||||
.media-sticker-wrapper {
|
||||
width: 157px;
|
||||
height: 157px;
|
||||
}
|
||||
}
|
||||
|
||||
.range-setting-selector {
|
||||
padding: 1rem .875rem;
|
||||
|
||||
|
@ -91,7 +91,8 @@
|
||||
left: 50%;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
|
||||
&-path {
|
||||
// ! do not change it to &-path
|
||||
.preloader-path {
|
||||
stroke: $button-primary-background;
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +1,3 @@
|
||||
.page-password {
|
||||
.input-field-input {
|
||||
padding-right: 2.5rem;
|
||||
max-height: 54px;
|
||||
|
||||
&[type="password"] {
|
||||
font-size: 2.25rem;
|
||||
padding-left: 10px;
|
||||
|
||||
@media (-webkit-min-device-pixel-ratio: 2) {
|
||||
font-size: 1.75rem;
|
||||
letter-spacing: .125rem;
|
||||
}
|
||||
|
||||
html.is-ios & {
|
||||
// ! меньше 1rem будет зумить поле
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* .page-password {
|
||||
|
||||
.toggle-visible {
|
||||
position: absolute;
|
||||
right: .25rem;
|
||||
z-index: 2;
|
||||
font-size: 1.25rem;
|
||||
color: $placeholder-color;
|
||||
cursor: pointer;
|
||||
transition: color .2s;
|
||||
padding: .5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
&:before {
|
||||
content: $tgico-eye1;
|
||||
}
|
||||
|
||||
&.eye-hidden:before {
|
||||
content: $tgico-eye2;
|
||||
}
|
||||
|
||||
@include hover() {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
@ -4,25 +4,28 @@
|
||||
#{$parent} {
|
||||
&-header {
|
||||
display: flex;
|
||||
margin-bottom: 0.4375rem;
|
||||
margin-bottom: .4375rem;
|
||||
align-items: center;
|
||||
padding: 0.125rem 0.25rem;
|
||||
padding: .125rem .25rem;
|
||||
}
|
||||
|
||||
&-container {
|
||||
padding: 1rem 1.5rem 0.75rem 1rem;
|
||||
padding: 1rem 1.5rem .75rem 1rem;
|
||||
}
|
||||
|
||||
&-title {
|
||||
padding-left: 0.75rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.125rem;
|
||||
margin-bottom: .125rem;
|
||||
text-transform: capitalize;
|
||||
|
||||
&:not(:first-child) {
|
||||
padding-left: .75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-description {
|
||||
padding: 0 0.25rem;
|
||||
padding: 0 .25rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.625rem;
|
||||
min-width: 15rem;
|
||||
@ -33,13 +36,13 @@
|
||||
}
|
||||
|
||||
&-buttons {
|
||||
margin-right: -0.75rem;
|
||||
margin-right: -.75rem;
|
||||
|
||||
.btn {
|
||||
font-weight: 500;
|
||||
|
||||
& + .btn {
|
||||
margin-top: 0.625rem;
|
||||
margin-top: .625rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user