From 0eb59521ab8d69b2b4813011e0778e43fd1bbca8 Mon Sep 17 00:00:00 2001 From: Mickael KERJEAN Date: Fri, 6 Apr 2018 13:18:38 +1000 Subject: [PATCH] feature (prompt): proper prompt system --- client/components/prompt.js | 29 ++++-- client/helpers/prompt.js | 23 ++--- client/pages/connectpage/credentials.js | 122 +++++++++++------------ client/pages/filespage/thing-existing.js | 3 +- client/router.js | 2 +- 5 files changed, 93 insertions(+), 86 deletions(-) diff --git a/client/components/prompt.js b/client/components/prompt.js index a0c81b18..0439b3ed 100644 --- a/client/components/prompt.js +++ b/client/components/prompt.js @@ -8,48 +8,61 @@ export class ModalPrompt extends React.Component { constructor(props){ super(props); this.state = { - appear: false + appear: false, + value: '' }; + this.onCancel = this.onCancel.bind(this); + this.onSubmit = this.onSubmit.bind(this); + this.onEscapeKeyPress = this.onEscapeKeyPress.bind(this); } componentDidMount(){ prompt.subscribe((text, okCallback, cancelCallback, type) => { - console.log("REQUEST FOR PROMPT"); this.setState({ appear: true, + value: '', error: null, type: type || 'text', text: text || '', fns: {ok: okCallback, cancel: cancelCallback} }); }); + window.addEventListener('keydown', this.onEscapeKeyPress); + } + + componentDidUmount(){ + window.removeEventListener('keydown', this.onEscapeKeyPress); } onCancel(){ this.setState({appear: false}); - this.state.fns.cancelCallback(); + this.state.fns.cancel(); } onSubmit(e){ e && e.preventDefault && e.preventDefault(); - this.state.fns.okCallback(this.state.value) + this.state.fns.ok(this.state.value) .then(() => this.setState({appear: false})) .catch((message) => this.setState({error: message})); } + onEscapeKeyPress(e){ + if(e.keyCode === 27 && this.state.fns){ this.onCancel(); } + } + render() { return ( - +

{this.state.text}

-
+ this.setState({value: e.target.value})} />
{this.state.error} 
- - + +
diff --git a/client/helpers/prompt.js b/client/helpers/prompt.js index 9c6d456f..13abba15 100644 --- a/client/helpers/prompt.js +++ b/client/helpers/prompt.js @@ -1,20 +1,15 @@ -import { Observable } from 'rxjs/Observable'; - const Prompt = function (){ - let obs = null; + let fn = null; + return { - emit: function(text, okCallback, cancelCallbck, type){ - console.log(obs); - obs.emit(text, okCallback, cancelcallBack, type); + now: function(text, okCallback, cancelCallback, type){ + if(!fn){ return window.setTimeout(() => this.now(text, okCallback, cancelCallback, type), 50); } + fn(text, okCallback, cancelCallback, type); }, - subscribe: function(){ - console.log("> SUBSCRIBE") - return new Observable((_obs) => { - console.log(_obs); - obs = _obs; - }); + subscribe: function(_fn){ + fn = _fn; } - } -} + }; +}; export const prompt = new Prompt(); diff --git a/client/pages/connectpage/credentials.js b/client/pages/connectpage/credentials.js index 9a348c7f..1b90f765 100644 --- a/client/pages/connectpage/credentials.js +++ b/client/pages/connectpage/credentials.js @@ -3,20 +3,32 @@ import PropTypes from 'prop-types'; import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; import { ModalPrompt } from '../../components/'; -import { encrypt, decrypt, memory } from '../../helpers/'; +import { encrypt, decrypt, memory, prompt } from '../../helpers/'; export class Credentials extends React.Component { constructor(props){ super(props); - const key = memory.get('credentials_key') || ''; + const key = memory.get('credentials_key') || ''; // we use a clojure for the "key" because we + // want to persist it in memory, not just in the + // state which is kill whenever react decide this.state = { - modal_appear: key ? false : this.props.remember_me, key: key || '', message: '', error: null }; - // we use a clojure for the "key" because we want to persist it in memory - // not just in the state which is kill whenever react decide + + if(this.props.remember_me === true){ + if(key === ""){ + let raw = window.localStorage.hasOwnProperty('credentials'); + if(raw){ + this.promptForExistingPassword(); + }else{ + this.promptForNewPassword(); + } + }else{ + this.hidrate_credentials(key); + } + } } componentWillReceiveProps(new_props){ @@ -27,25 +39,58 @@ export class Credentials extends React.Component { } if(new_props.remember_me === true && this.props.remember_me === false){ - this.setState({modal_appear: true}); - this.init(); + this.promptForNewPassword(); }else if(new_props.remember_me === false && this.props.remember_me === true){ memory.set('credentials_key', ''); - this.setState({modal_appear: false, key: ''}); + this.setState({key: ''}); } } - componentDidMount(){ - this.init(); - if(this.state.key) this.onSubmit(this.state.key); + promptForExistingPassword(){ + prompt.now( + "Your Master Password", + (key) => { + if(!key.trim()) return Promise.reject("Password can\'t be empty"); + this.setState({key: key}); + memory.set('credentials_key', key); + return this.hidrate_credentials(key); + }, + () => { + memory.set('credentials_key', ''); + this.setState({key: ''}); + }, + 'password' + ); + } + promptForNewPassword(){ + prompt.now( + "Pick a Master Password", + (key) => { + if(!key.trim()) return Promise.reject("Password can\'t be empty"); + memory.set('credentials_key', key); + this.setState({key: key}, () => { + this.saveCreds(this.props.credentials); + }); + return Promise.resolve(); + }, + () => {}, + 'password' + ); } - init(){ - let raw = window.localStorage.hasOwnProperty('credentials'); + hidrate_credentials(key){ + let raw = window.localStorage.getItem('credentials'); if(raw){ - this.setState({message: "Your Master Password:"}); + try{ + let credentials = decrypt(raw, key); + this.props.onCredentialsFound(credentials); + return Promise.resolve(); + }catch(e){ + return Promise.reject('Incorrect password'); + } }else{ - this.setState({message: "Pick a Master Password:"}); + this.saveCreds(this.props.credentials); + return Promise.resolve(); } } @@ -56,52 +101,7 @@ export class Credentials extends React.Component { } } - onCancel(should_clear){ - memory.set('credentials_key', ''); - this.setState({modal_appear: false, key: ''}); - } - - onSubmit(key){ - this.setState({key: key}); - memory.set('credentials_key', key); - /* - * 2 differents use cases: - * - a user is creating a new master password - * - a user want to unlock existing credentials - */ - if(key !== ''){ - let raw = window.localStorage.getItem('credentials'); - if(raw){ - try{ - let credentials = decrypt(raw, key); - this.setState({modal_appear: false}); - this.props.onCredentialsFound(credentials); - }catch(e){ - this.setState({error: 'Incorrect password'}); - } - }else{ - this.saveCreds(this.props.credentials); - this.setState({modal_appear: false}); - } - }else{ - this.setState({error: 'Password can\'t be empty'}); - window.setTimeout(() => this.setState({error: null}), 1500); - } - } - render() { - return ( - - ); + return null; } } - -Credentials.propTypes = { -}; diff --git a/client/pages/filespage/thing-existing.js b/client/pages/filespage/thing-existing.js index ede9ac8e..11dfcf68 100644 --- a/client/pages/filespage/thing-existing.js +++ b/client/pages/filespage/thing-existing.js @@ -100,8 +100,7 @@ export class ExistingThing extends React.Component { } onDeleteRequest(filename){ - console.log(prompt); - prompt.emit( + prompt.now( "Confirm by tapping \""+this._confirm_delete_text()+"\"", (answer) => { // click on ok if(answer === this._confirm_delete_text()){ diff --git a/client/router.js b/client/router.js index 69176922..4ac258d0 100644 --- a/client/router.js +++ b/client/router.js @@ -2,7 +2,7 @@ import React from 'react'; import { BrowserRouter, Route, IndexRoute, Switch } from 'react-router-dom'; import { NotFoundPage, ConnectPage, HomePage, LogoutPage, FilesPage, ViewerPage } from './pages/'; import { Bundle, URL_HOME, URL_FILES, URL_VIEWER, URL_LOGIN, URL_LOGOUT } from './helpers/'; -import { ModalPrompt, Audio, Video } from './components/'; +import { ModalPrompt, Notification, Audio, Video } from './components/'; export default class AppRouter extends React.Component { render() {