From 236b03775e78faa547b3db63d2a764df7f0648b7 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Thu, 29 Mar 2018 01:26:18 +0200 Subject: [PATCH] [BIGUPDATE] [lib.field-validator] added generic field validator with error message generation + is_valid() checker [popup] refactored all --- webpack/lib/channel-controller.js | 8 +- webpack/lib/field-manager.js | 36 +++++++++ webpack/lib/field-validator.js | 116 +++++++++++++++++++++++++++++ webpack/lib/popup-controller.js | 117 ++++++++++++++++++++++-------- webpack/lib/room-controller.js | 2 +- webpack/page/noauth/login.js | 101 ++++---------------------- webpack/page/noauth/register.js | 111 ++++------------------------ webpack/scss/global.scss | 21 +++++- webpack/scss/layout.scss | 17 +---- webpack/scss/pop-up.scss | 9 +++ webpack/setup.js | 15 ++-- webpack/vue/auth/dialog.vue | 2 +- webpack/vue/auth/wrapper.vue | 48 ++++++------ webpack/vue/noauth/login.vue | 9 ++- webpack/vue/noauth/register.vue | 12 +-- 15 files changed, 348 insertions(+), 276 deletions(-) create mode 100644 webpack/lib/field-manager.js create mode 100644 webpack/lib/field-validator.js diff --git a/webpack/lib/channel-controller.js b/webpack/lib/channel-controller.js index 8c25fbe..7a994d9 100644 --- a/webpack/lib/channel-controller.js +++ b/webpack/lib/channel-controller.js @@ -41,10 +41,8 @@ export default class ChannelController{ /* (4) Update buffer */ this._buffer = {}; for( let c of this.list ) - if( c.id === this.current ){ - console.warn(c); + if( c.id === this.current ) this._buffer = c; - } /* (5) Load rooms */ gs.get.room.fetch(); @@ -203,11 +201,11 @@ export default class ChannelController{ create(name=null, link=null){ /* (1) Manage invalid @link */ - if( typeof link !== 'string' || /^[a-z0-9\._-]$/i.test(link) ) + if( typeof link !== 'string' || /^[a-z0-9-]$/i.test(link) ) return false; /* (2) Manage invalid @name */ - if( typeof name !== 'string' || /^[a-z0-9\._-]$/i.test(name) ) + if( typeof name !== 'string' || !/^[a-z0-9\/_-]{3,}$/i.test(name) ) return false; /* (3) Try to create room in API */ diff --git a/webpack/lib/field-manager.js b/webpack/lib/field-manager.js new file mode 100644 index 0000000..9bbfde6 --- /dev/null +++ b/webpack/lib/field-manager.js @@ -0,0 +1,36 @@ +/* (1) Init all +---------------------------------------------------------*/ +/* (1) Import field validator */ +const fv = require('./field-validator.js'); + +/* (2) Create class easy access */ +window.FieldValidator = fv.Class; + + + +/* (1) Create basic validators +---------------------------------------------------------*/ +/* (1) BYPASS */ +FieldValidator.pushFormat('bypass', () => true); + +/* (2) Basic name */ +FieldValidator.pushFormat('basic-name', (input) => { + return typeof input === 'string' && /^[a-z0-9 _-]{3,20}$/i.test(input); +}, '3 characters required: letters, numbers, spaces, dots, hyphens'); + +/* (3) URL name */ +FieldValidator.pushFormat('url-name', (input) => { + return typeof input === 'string' && /^[a-z0-9_-]{3,20}$/i.test(input); +}, '3 characters required: letters, numbers, dots, hyphens'); + +/* (4) Password */ +FieldValidator.pushFormat('password', (input) => { + return typeof input === 'string' && /^[^<>\/\\]{8,50}$/.test(input); +}, '8 characters required'); + +/* (5) Room type */ +FieldValidator.pushFormat('room.type', (input) => { + return typeof input === 'string' && ['text', 'voice'].indexOf(input) > -1; +}); + + diff --git a/webpack/lib/field-validator.js b/webpack/lib/field-validator.js new file mode 100644 index 0000000..15b4c0f --- /dev/null +++ b/webpack/lib/field-validator.js @@ -0,0 +1,116 @@ +export const validatorStack = {}; +export const inputStack = new WeakMap(); + +export class Class{ + + static get _validators(){ return validatorStack; } + + + /* (1) Push a new Format + * + * @_format Format name + * @_validator Format callback + * @_error Error message + * + * @return pushed Whether the validator has been pushed + * + ---------------------------------------------------------*/ + static pushFormat(_format=null, _validator=null, _error='error'){ + + /* (1) Error: invalid _format */ + if( typeof _format !== 'string' ) + return false; + + /* (2) Error: invalid _validator */ + if( !(_validator instanceof Function) ) + return false; + + /* (3) Store validator */ + Class._validators[_format] = { error: _error, validator: _validator }; + + return true; + + } + + /* (1) Pop an existing Format + * + * @_format Format name + * + * @return popped Whether the validator has been popped + * + ---------------------------------------------------------*/ + static popFormat(_format=null){ + + /* (1) Error: invalid _format */ + if( typeof _format !== 'string' ) + return false; + + /* (2) Error: _validator not found */ + if( Class._validators[_format] == null ) + return false; + + /* (3) Remove validator */ + delete Class._validators[_format]; + + return true; + + } + + + + /* (2) Builds an validat-able input + * + * @_format Existing validator name + * @_default [OPT] Mutable input default value (default to NULL) + * + ---------------------------------------------------------*/ + constructor(_format, _default=null){ + + /* (1) Store fields (default value) */ + this.mutable = _default; + + /* (2) Store _format in STATIC inputStack */ + inputStack.set(this, { format: _format }); + + } + + + /* (3) Checks if the _mutable has a current valid value + * + * @return valid Whether the _mutable is valid or not + * + ---------------------------------------------------------*/ + is_valid(){ + + // 1. Extract validator name + let format = inputStack.get(this).format; + + // 2. Dispatch validation + return validatorStack[format].validator( this.mutable ); + + } + + + /* (3) Get current error message + * + * @return NULL if is_valid() + * + ---------------------------------------------------------*/ + get error(){ + + // 1. NULL if valid + if( this.is_valid() ) + return ''; + + // 2. Extract validator name + let format = inputStack.get(this).format; + + // 3. Dispatch validation + return validatorStack[format].error; + + } + + + + +} \ No newline at end of file diff --git a/webpack/lib/popup-controller.js b/webpack/lib/popup-controller.js index b714586..941e891 100644 --- a/webpack/lib/popup-controller.js +++ b/webpack/lib/popup-controller.js @@ -22,58 +22,115 @@ export default class PopupController{ ---------------------------------------------------------*/ /* (1) Create a new Room */ this.register('room.create', { - data: { - type: 'text', - name: '' + + type: new FieldValidator('room.type', 'text'), + name: new FieldValidator('basic-name', ''), + reset(){ + this.type.mutable = 'text'; + this.name.mutable = ''; }, - reset(){ this.data.type = 'text'; this.data.name = ''; }, - submit(){ gs.get.room.create(this.data.type, this.data.name) && this.parent.hide(); } + submit(){ + + // validators + if( !this.name.is_valid() ) + return; + + if( gs.get.room.create(this.type.mutable, this.name.mutable) ) + return this.parent.hide(); + + } + }); + /* (2) Create a new Channel */ this.register('channel.create', { - data: { - name: '', - link: '' + name: new FieldValidator('basic-name', ''), + link: new FieldValidator('url-name', ''), + reset(){ + this.link.mutable = '', + this.name.mutable = ''; }, - reset(){ this.data.link = '', this.data.name = ''; }, - submit(){ gs.get.channel.create(this.data.name, this.data.link) && this.parent.hide(); } + submit(){ + + // validators + if( !this.name.is_valid() ) + return false; + + if( !this.link.is_valid() ) + return false; + + if( gs.get.channel.create(this.name.mutable, this.link.mutable) ) + return this.parent.hide(); + + } }); - /* (3) Change nickname */ - this.register('nickname.change', { - data: { - value: '' - }, - reset(){ this.data.value = ''; }, - submit(){ gs.get.content.change_username(this.data.value) && this.parent.hide(); } + + /* (3) Change username */ + this.register('username.change', { + username: new FieldValidator('basic-name', ''), + reset(){ this.username.mutable = ''; }, + submit(){ + + // validators + if( !this.username.is_valid() ) + return false; + + if( gs.get.content.change_username(this.username.mutable) ) + this.parent.hide(); + + } }); + /* (4) Invite to channel */ this.register('channel.invite', { - data: { - username: '' - }, - reset(){ this.data.username = ''; }, - submit(){ gs.get.channel.invite(this.data.username) && this.parent.hide(); } + username: new FieldValidator('basic-name', ''), + reset(){ this.username.mutable = ''; }, + submit(){ + + // validators + if( !this.username.is_valid() ) + return false; + + if( gs.get.channel.invite(this.username.mutable) ) + return this.parent.hide(); + + } }); + /* (5) Remove channel */ this.register('channel.remove', { - data: {}, reset(){ }, submit(){ gs.get.channel.remove() && this.parent.hide(); } }); + /* (6) Leave channel */ this.register('channel.leave', { - data: {}, reset(){ }, submit(){ gs.get.channel.remove() && this.parent.hide(); } }); + /* (6) Change password */ this.register('password.change', { - data: { - password: '', - confirm: '' - }, - reset(){ this.data.password = ''; this.data.confirm = ''; }, - submit(){ this.data.password === this.data.confirm && gs.get.content.change_password(this.data.password) && this.parent.hide(); } + password: new FieldValidator('password', ''), + confirm: new FieldValidator('password', ''), + matches: true, + reset(){ this.password.mutable = ''; this.confirm.mutable = ''; }, + submit(){ + + this.matches = this.password.mutable === this.confirm.mutable; + + // check passwords matches + if( !this.matches ) + return false; + + // field validator + if( !this.password.is_valid || !this.confirm.is_valid() ) + return false; + + if( gs.get.content.change_password(this.data.password.mutable) ) + return this.parent.hide(); + + } }); diff --git a/webpack/lib/room-controller.js b/webpack/lib/room-controller.js index 5a4e2e6..f1a7db0 100644 --- a/webpack/lib/room-controller.js +++ b/webpack/lib/room-controller.js @@ -223,7 +223,7 @@ export default class RoomController{ return false; /* (2) Manage invalid @name */ - if( typeof name !== 'string' ) + if( typeof name !== 'string' || !/^[a-z0-9\/_-]{3,}$/i.test(name) ) return false; /* (3) Try to create room in API */ diff --git a/webpack/page/noauth/login.js b/webpack/page/noauth/login.js index c3fc06c..da17834 100644 --- a/webpack/page/noauth/login.js +++ b/webpack/page/noauth/login.js @@ -3,23 +3,15 @@ /* (1) Default data structure */ gs.set('login', { // fields - username: { - model: '', - timeout: '', - error: '', - validate: (_username) => /^[a-z0-9_-]{3,20}$/i.test(_username) - }, + username: new FieldValidator('basic-name', ''), + password: new FieldValidator('password', ''), + + // login failed + failed: false, - password: { - model: '', - timeout: '', - error: '', - validate: (_password) => /^[^<>\/\\]{8,50}$/.test(_password) - }, // functions func: { - print_err(_field, _message, _duration){}, login(){}, forgot_pass(){}, press_enter(){} @@ -30,93 +22,28 @@ gs.set('login', { -/* (2) Manage error messages -* -* @_field Field to print errors to -* @_message Error message to print -* @_duration Durations (sec) for the message to be displayed -* ----------------------------------------------------------*/ -gs.get.login.func.print_err = function(_field, _message='error', _duration=null){ - - /* (1) Fail: invalid _field */ - if( typeof _field !== 'string' || this[_field] == null ) - return false; - - /* (2) Fail: invalid _message */ - if( typeof _message !== 'string' ) - return false; - - /* (3) Fail: invalid _message */ - if( isNaN(_duration) ) - return false; - - /* (4) Clear timeout if exists */ - !isNaN(this[_field].err_to) && clearTimeout(this[_field].err_to); - - /* (5) Display error */ - this[_field].error = _message; - - /* (6) No timeout if _duration if null */ - if( _duration === null ) - return true; - - /* (7) Setup Timeout */ - this[_field].err_to = setTimeout( function(){ - this.error = ''; - }.bind(this[_field]), _duration*1000); - - return true; - -}.bind(gs.get.login); - - - - - - - - -/* (3) Login attempt +/* (2) Login attempt * ---------------------------------------------------------*/ gs.get.login.func.login = function(){ /* (1) Cache fields' values */ - let username = this.username.model; - let password = this.password.model; + let username = this.username.mutable; + let password = this.password.mutable; /* (2) Manage errors */ - let errors = false; + if( !this.username.is_valid() ) + return false; - // username error - if( !this.username.validate(username) ){ - errors = true; - this.func.print_err('username', '3 characters are required: letters, numbers, dot'); - }else - this.func.print_err('username', '', 0); - - - // password error - if( !this.password.validate(password) ){ - errors = true; - this.func.print_err('password', '8 characters are required'); - }else - this.func.print_err('password', '', 0); - - // if errors -> fail - if( errors ) - return false; + if( !this.password.is_valid() ) + return false; /* (3) API bindings */ api.call('GET /user/token', {}, function(rs){ // manage error - if( rs.error !== 0 || rs.token == null ){ - this.func.print_err('username', 'Invalid combination'); - this.func.print_err('password', 'Invalid combination'); - return; - } + if( rs.error !== 0 || rs.token == null ) + return this.failed = true; // store TOKEN + user data auth.token = rs.token; diff --git a/webpack/page/noauth/register.js b/webpack/page/noauth/register.js index 988a808..4d28c6a 100644 --- a/webpack/page/noauth/register.js +++ b/webpack/page/noauth/register.js @@ -3,31 +3,12 @@ /* (1) Default data structure */ gs.set('register', { // fields - mail: { - model: '', - timeout: '', - error: '', - validate: (_mail) => true - // validate: (_mail) => /^[\w\.]+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$/i.test(_mail) - }, - - username: { - model: '', - timeout: '', - error: '', - validate: (_username) => /^[a-z0-9_-]{3,20}$/i.test(_username) - }, - - password: { - model: '', - timeout: '', - error: '', - validate: (_password) => /^[^<>\/\\]{8,50}$/.test(_password) - }, + mail: new FieldValidator('bypass', ''), + username: new FieldValidator('basic-name', ''), + password: new FieldValidator('password', ''), // functions func: { - print_err(_field, _message, _duration){}, register(){}, press_enter(){} } @@ -37,91 +18,31 @@ gs.set('register', { -/* (2) Manage error messages -* -* @_field Field to print errors to -* @_message Error message to print -* @_duration Durations (sec) for the message to be displayed -* ----------------------------------------------------------*/ -gs.get.register.func.print_err = function(_field, _message='error', _duration=null){ - - /* (1) Fail: invalid _field */ - if( typeof _field !== 'string' || this[_field] == null ) - return false; - - /* (2) Fail: invalid _message */ - if( typeof _message !== 'string' ) - return false; - - /* (3) Fail: invalid _message */ - if( isNaN(_duration) ) - return false; - - /* (4) Clear timeout if exists */ - !isNaN(this[_field].err_to) && clearTimeout(this[_field].err_to); - - /* (5) Display error */ - this[_field].error = _message; - - /* (6) No timeout if _duration if null */ - if( _duration === null ) - return true; - - /* (7) Setup Timeout */ - this[_field].err_to = setTimeout( function(){ - this.error = ''; - }.bind(this[_field]), _duration*1000); - - return true; - -}.bind(gs.get.register); - - - - - -/* (3) Login attempt +/* (2) Login attempt * ---------------------------------------------------------*/ gs.get.register.func.register = function(){ /* (1) Cache fields' values */ - let mail = this.mail.model; - let username = this.username.model; - let password = this.password.model; + let mail = this.mail.mutable; + let username = this.username.mutable; + let password = this.password.mutable; /* (2) Manage errors */ - let errors = false; + // mail error + if( !this.mail.is_valid() ) + return false; - // mail error - if( !this.mail.validate(mail) ){ - errors = true; - this.func.print_err('mail', 'This field is required'); - }else - this.func.print_err('mail', '', 0); + // username error + if( !this.username.is_valid() ) + return false; - // username error - if( !this.username.validate(username) ){ - errors = true; - this.func.print_err('username', '3 characters are required: letters, numbers, dot'); - }else - this.func.print_err('username', '', 0); - - - // password error - if( !this.password.validate(password) ){ - errors = true; - this.func.print_err('password', '8 characters are required'); - }else - this.func.print_err('password', '', 0); - - // if errors -> fail - if( errors ) - return false; + // password error + if( !this.password.is_valid() ) + return false; /* (3) API bindings */ api.call('POST /user', { username: username, password: password }, function(rs){ diff --git a/webpack/scss/global.scss b/webpack/scss/global.scss index 9ff75da..6686d54 100644 --- a/webpack/scss/global.scss +++ b/webpack/scss/global.scss @@ -14,6 +14,25 @@ font-size: .7em; text-transform: uppercase; letter-spacing: 1px; + + + + & > span{ + + color: #f04747; + text-transform: none; + + &:before{ content: '(' attr(data-err) ')'; } + + &:not([data-err]), + &[data-err='']{ + color: inherit; + + &:before{ content: ''; } + } + + } + } // input @@ -148,7 +167,7 @@ &:active{ background-color: darken($main, 10%);} &.invalid{ - $main: #e65835; + $main: #f04747; background-color: $main; &:hover{ background-color: darken($main, 5%);} &:active{ background-color: darken($main, 10%);} diff --git a/webpack/scss/layout.scss b/webpack/scss/layout.scss index ceba450..96f8a10 100644 --- a/webpack/scss/layout.scss +++ b/webpack/scss/layout.scss @@ -139,23 +139,8 @@ body > #WRAPPER.login{ margin-top: 1.2em; letter-spacing: .2em; - & > span{ - - color: inherit; - text-transform: none; - - &:before{ content: ''; } - &:after{ content: ''; } - - } - - &.err{ + &.err > span{ color: #f04747; - - & > span{ - &:before{ content: '('; } - &:after{ content: ')'; } - } } &.link{ diff --git a/webpack/scss/pop-up.scss b/webpack/scss/pop-up.scss index 6db3a61..db9337c 100644 --- a/webpack/scss/pop-up.scss +++ b/webpack/scss/pop-up.scss @@ -72,6 +72,11 @@ text-transform: uppercase; letter-spacing: 1px; + strong{ + color: $main; + text-transform: none; + } + } @@ -106,6 +111,10 @@ } } + strong{ + color: darken($main,5%); + } + } diff --git a/webpack/setup.js b/webpack/setup.js index 880c788..3be9e39 100644 --- a/webpack/setup.js +++ b/webpack/setup.js @@ -7,24 +7,25 @@ import routes from './routes' import Authentication from './lib/authentication.js' import XHRClientDriver from './lib/client/xhr.js' import WebSocketClientDriver from './lib/client/ws.js' -import APIClient from './lib/api-client.js' - - +import APIClient from './lib/api-client.js' /* (1) Custom lib accessors ---------------------------------------------------------*/ -/* (1) Global Store for Vue */ +/* (1) Field validation */ +require('./lib/field-manager.js'); + +/* (2) Global Store for Vue */ window.gs = new GlobalStore(); -/* (2) Authentication token management */ +/* (3) Authentication token management */ window.auth = new Authentication(); gs.set('auth', auth); -/* (3) XHR / WebSocket drivers */ +/* (4) XHR / WebSocket drivers */ window.xhrcd = XHRClientDriver; window.wscd = WebSocketClientDriver; -/* (4) ClientDriver instances */ +/* (5) ClientDriver instances */ window.api = new APIClient('api.douscord.xdrm.io'); window.ws = new WebSocketClientDriver('ws.douscord.xdrm.io'); diff --git a/webpack/vue/auth/dialog.vue b/webpack/vue/auth/dialog.vue index b3a9f3e..78cde13 100644 --- a/webpack/vue/auth/dialog.vue +++ b/webpack/vue/auth/dialog.vue @@ -104,7 +104,7 @@ Create channel Remove channel Create room - Change nickname + Change nickname Change password Leave channel Logout diff --git a/webpack/vue/auth/wrapper.vue b/webpack/vue/auth/wrapper.vue index 9154d8d..24b6bf0 100644 --- a/webpack/vue/auth/wrapper.vue +++ b/webpack/vue/auth/wrapper.vue @@ -18,15 +18,15 @@