From 7a9e5112eaaea58d55f181d3e5296e4ff839921c Mon Sep 17 00:00:00 2001 From: jloup Date: Wed, 14 Feb 2018 14:19:09 +0100 Subject: initial commit --- cmd/web/js/api.js | 176 ++++++++++++++++++++++++++++++++++++++++++++++++ cmd/web/js/app.js | 121 +++++++++++++++++++++++++++++++++ cmd/web/js/cookies.js | 65 ++++++++++++++++++ cmd/web/js/main.jsx | 101 +++++++++++++++++++++++++++ cmd/web/js/otp.jsx | 70 +++++++++++++++++++ cmd/web/js/poloniex.jsx | 49 ++++++++++++++ cmd/web/js/signin.jsx | 52 ++++++++++++++ cmd/web/js/signup.jsx | 56 +++++++++++++++ 8 files changed, 690 insertions(+) create mode 100644 cmd/web/js/api.js create mode 100644 cmd/web/js/app.js create mode 100644 cmd/web/js/cookies.js create mode 100644 cmd/web/js/main.jsx create mode 100644 cmd/web/js/otp.jsx create mode 100644 cmd/web/js/poloniex.jsx create mode 100644 cmd/web/js/signin.jsx create mode 100644 cmd/web/js/signup.jsx (limited to 'cmd/web/js') diff --git a/cmd/web/js/api.js b/cmd/web/js/api.js new file mode 100644 index 0000000..e2acd1d --- /dev/null +++ b/cmd/web/js/api.js @@ -0,0 +1,176 @@ +'use strict'; + +var App = require('./app.js'); + +var Api = {}; + +Api.API_HOST = process.env.API_HOST; +Api.API_PORT = process.env.API_PORT; + +if (process.env.API_HTTPS === 'true') { + Api.API_ROOT = 'https://'; +} else { + Api.API_ROOT = 'http://'; +} + +Api.API_ROOT += Api.API_HOST; +if (Api.API_PORT !== '80') { + Api.API_ROOT += ':' + Api.API_PORT; +} + +Api.API_ROOT += '/api'; + +var ApiEndpoints = { + 'SIGNUP': { + 'type': 'POST', + 'auth': false, + 'parameters': [ + {'name': 'email', 'mandatory': true, 'inquery': true}, + {'name': 'password', 'mandatory': true, 'inquery': true} + ], + 'buildUrl': function(params) { + return '/signup'; + } + }, + 'SIGNIN': { + 'type': 'POST', + 'auth': false, + 'parameters': [ + {'name': 'email', 'mandatory': true, 'inquery': true}, + {'name': 'password', 'mandatory': true, 'inquery': true} + ], + 'buildUrl': function(params) { + return '/signin'; + } + }, + 'MARKET': { + 'type': 'GET', + 'auth': true, + 'parameters': [ + {'name': 'name', 'mandatory': true, 'inquery': false}, + ], + 'buildUrl': function(params) { + return '/market/' + params.name; + } + }, + 'UPDATE_MARKET': { + 'type': 'POST', + 'auth': true, + 'parameters': [ + {'name': 'name', 'mandatory': true, 'inquery': false}, + {'name': 'key', 'mandatory': true, 'inquery': true}, + {'name': 'secret', 'mandatory': true, 'inquery': true}, + ], + 'buildUrl': function(params) { + return '/market/' + params.name + '/update'; + } + }, + 'OTP_ENROLL': { + 'type': 'GET', + 'auth': true, + 'parameters': [], + 'buildUrl': function(params) { + return '/otp/enroll'; + } + }, + 'OTP_VALIDATE': { + 'type': 'POST', + 'auth': true, + 'parameters': [ + {'name': 'pass', 'mandatory': true, 'inquery': true}, + ], + 'buildUrl': function(params) { + return '/otp/validate'; + } + }, +}; + +Api.BuildRequest = function(endpointId, params) { + var endpoint = ApiEndpoints[endpointId]; + var query = {}; + var url = ''; + var headers = {}; + var jwt = App.getUserToken(); + + if (endpoint === undefined) { + return {'err': 'cannot find endpoint ' + endpointId}; + } + + if (endpoint.auth === true && (jwt === undefined || jwt === null)) { + return {'err': 'this endpoint needs auth'}; + } + + if (jwt !== undefined && jwt !== null) { + headers.Authorization = 'Bearer ' + jwt; + } + + for (var i = 0; i < endpoint.parameters.length; i++) { + var parameter = endpoint.parameters[i]; + if (parameter.mandatory === true && params[parameter.name] === undefined) { + return {'err': 'parameter \'' + parameter.name + '\' is mandatory'}; + } + + if (parameter.inquery === true) { + query[parameter.name] = params[parameter.name]; + } + } + + url = endpoint.buildUrl(params); + + return {'err': null, 'url': Api.API_ROOT + url, 'headers': headers, 'params': query, 'type': endpoint.type}; +}; + +Api.Call = function(endpointId, params, fn) { + var request = Api.BuildRequest(endpointId, params); + + if (request.err !== null) { + console.error('Api BuildRequest error', request.err); + fn({'err': request.err}, -1, {}); + return null; + } + + return Api.DoRequest(request.type, request.url, request.params, request.headers, fn); +}; + +Api.DoRequest = function(type, url, params, headers, callback) { + return $.ajax({ + data: params, + headers: headers, + timeout: 30000, + dataType: 'json', + error: function(xhr, status, err) { + if (status === 'abort') { + return; + } + + var apiStatus = null; + var apiResponse = null; + var apiCode = null; + if (xhr.responseJSON) { + apiStatus = xhr.responseJSON.status; + apiResponse = xhr.responseJSON.response; + apiCode = xhr.responseJSON.code; + } + + if (xhr.status === 401 || xhr.status === 403) { + if (App.onUserNotAuthorized(xhr.status, apiCode) === false) { + return; + } + } + + callback({'url': url, 'err': err, 'status': status, 'code': apiCode, 'xhr': xhr}, apiStatus, apiResponse); + }, + success: function(data) { + var err = null; + if (data.status !== 'ok') { + err = {'url': url, 'status': status}; + } + callback(err, data.status, data.response); + }, + type: type, + url: url + }); +}; + +module.exports.Api = Api; + diff --git a/cmd/web/js/app.js b/cmd/web/js/app.js new file mode 100644 index 0000000..4946dcc --- /dev/null +++ b/cmd/web/js/app.js @@ -0,0 +1,121 @@ +'use strict'; + +var cookies = require('./cookies.js'); +var page = require('page'); + +var App = {}; +var cookieExpire = 60 * 30; + +App.errorCodeToMessage = function(code) { + switch (code) { + case 'invalid_email': + return 'The email is not valid'; + case 'invalid_password': + return 'The password is not valid'; + case 'email_exists': + return 'This email is already registered'; + case 'invalid_credentials': + return 'Invalid credentials'; + case 'invalid_otp': + return 'Invalid code !'; + case 'user_not_confirmed': + return 'Your account is being confirmed. Should be very soon !'; + } + + return code; +}; + +App.isUserSignedIn = function() { + return cookies.hasItem('jwt'); +}; + +App.getUserToken = function() { + return cookies.getItem('jwt'); +}; + +App.onUserSignIn = function(token) { + if (!token || token === '') { + page('/signin'); + return; + } + + cookies.setItem('jwt', token, cookieExpire); + page('/me'); +}; + +App.onUserValidateOtp = function(token) { + if (!token || token === '') { + page('/signin'); + return; + } + + cookies.setItem('jwt', token, cookieExpire); + page('/me'); +}; + +App.onUserSignUp = function(token) { + if (!token || token === '') { + page('/signin'); + return; + } + + cookies.setItem('jwt', token, cookieExpire); +}; + +App.getUserJWT = function() { + return cookies.getItem('jwt'); +}; + +App.page = function(path, needsAuth, fn) { + page(path, function(context) { + if (needsAuth && !App.isUserSignedIn()) { + page('/signin'); + return; + } + + fn(context); + }); +}; + +App.go = function(path) { + page(path); +}; + +App.start = function() { + page(); +}; + +App.onInternNavigation = function(href, e) { + e.preventDefault(); + page(href); +}; + +App.onUserNotAuthorized = function(httpCode, apiCode) { + switch (apiCode) { + case 'not_authorized': + cookies.removeItem('jwt'); + page('/signin'); + return false; + case 'otp_not_setup': + page('/otp/setup'); + return false; + case 'need_otp_validation': + page('/otp/validate'); + return false; + default: + return true; + } +}; + +App.mount = function(app) { + var root = React.createElement( + 'div', + {className: 'container'}, + app + ); + + ReactDOM.unmountComponentAtNode(document.getElementById('app')); + ReactDOM.render(root, document.getElementById('app')); +}; + +module.exports = App; diff --git a/cmd/web/js/cookies.js b/cmd/web/js/cookies.js new file mode 100644 index 0000000..9cc7ca9 --- /dev/null +++ b/cmd/web/js/cookies.js @@ -0,0 +1,65 @@ +'use strict'; + +/*\ +|*| +|*| :: cookies.js :: +|*| +|*| A complete cookies reader/writer framework with full unicode support. +|*| +|*| Revision #1 - September 4, 2014 +|*| +|*| https://developer.mozilla.org/en-US/docs/Web/API/document.cookie +|*| https://developer.mozilla.org/User:fusionchess +|*| +|*| This framework is released under the GNU Public License, version 3 or later. +|*| http://www.gnu.org/licenses/gpl-3.0-standalone.html +|*| +|*| Syntaxes: +|*| +|*| * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]]) +|*| * docCookies.getItem(name) +|*| * docCookies.removeItem(name[, path[, domain]]) +|*| * docCookies.hasItem(name) +|*| * docCookies.keys() +|*| +\*/ + +module.exports = { + getItem: function(sKey) { + if (!sKey) { return null; } + return decodeURIComponent(document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1')) || null; + }, + setItem: function(sKey, sValue, vEnd, sPath, sDomain, bSecure) { + if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; } + var sExpires = ''; + if (vEnd) { + switch (vEnd.constructor) { + case Number: + sExpires = vEnd === Infinity ? '; expires=Fri, 31 Dec 9999 23:59:59 GMT' : '; max-age=' + vEnd; + break; + case String: + sExpires = '; expires=' + vEnd; + break; + case Date: + sExpires = '; expires=' + vEnd.toUTCString(); + break; + } + } + document.cookie = encodeURIComponent(sKey) + '=' + encodeURIComponent(sValue) + sExpires + (sDomain ? '; domain=' + sDomain : '') + (sPath ? '; path=' + sPath : '') + (bSecure ? '; secure' : ''); + return true; + }, + removeItem: function(sKey, sPath, sDomain) { + if (!this.hasItem(sKey)) { return false; } + document.cookie = encodeURIComponent(sKey) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT' + (sDomain ? '; domain=' + sDomain : '') + (sPath ? '; path=' + sPath : ''); + return true; + }, + hasItem: function(sKey) { + if (!sKey) { return false; } + return (new RegExp('(?:^|;\\s*)' + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=')).test(document.cookie); + }, + keys: function() { + var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, '').split(/\s*(?:\=[^;]*)?;\s*/); + for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); } + return aKeys; + } +}; diff --git a/cmd/web/js/main.jsx b/cmd/web/js/main.jsx new file mode 100644 index 0000000..eb53057 --- /dev/null +++ b/cmd/web/js/main.jsx @@ -0,0 +1,101 @@ +var SignupForm = require('./signup.js').SignupForm; +var SigninForm = require('./signin.js').SigninForm; +var OtpEnrollForm = require('./otp.js').OtpEnrollForm; +var PoloniexForm = require('./poloniex.js').PoloniexForm; +var App = require('./app.js'); +var Api = require('./api.js').Api; +var cookies = require('./cookies.js'); + +var Logo = React.createClass({ + render: function() { + return (); + } +}); + +App.page('/signup', false, function(context) { + if (App.isUserSignedIn()) { + App.go('/me'); + return; + } + + App.mount( +
+ + +
+ ); +}); + +App.page('/signin', false, function(context) { + if (App.isUserSignedIn()) { + App.go('/me'); + return; + } + + App.mount( +
+ + +
+ ); +}); + +App.page('/signout', true, function(context) { + cookies.removeItem('jwt'); + + App.go('/'); +}); + +App.page('/me', true, function(context) { + Api.Call('MARKET', {'name': 'poloniex'}, function(err, status, data) { + if (err) { + console.error(err, data); + return; + } + + App.mount( +
+ +

Poloniex

+ +
+ ); + + }.bind(this)); +}); + +App.page('/otp/setup', true, function(context) { + Api.Call('OTP_ENROLL', {}, function(err, status, data) { + if (err) { + console.error(err, data); + return; + } + + App.mount( +
+ + +
+ ); + + }.bind(this)); +}); + +App.page('/otp/validate', true, function(context) { + App.mount( +
+ + +
+ ); +}); + +App.page('/', false, function(context) { + App.go('/me'); +}); + +$(document).ready(function() { + App.start(); +}); diff --git a/cmd/web/js/otp.jsx b/cmd/web/js/otp.jsx new file mode 100644 index 0000000..2717d9f --- /dev/null +++ b/cmd/web/js/otp.jsx @@ -0,0 +1,70 @@ +var Api = require('./api.js').Api; +var App = require('./app.js'); +var classNames = require('classnames'); + +var OtpQrCode = React.createClass({ + render: function() { + return ( +
+ +

{this.props.secret}

+
+ ); + } +}); + +module.exports.OtpEnrollForm = React.createClass({ + getInitialState: function() { + return {'hideMsg': true, 'msg': '', 'msgOk': false, 'pass': ''}; + }, + handleSubmit: function(e) { + Api.Call('OTP_VALIDATE', {'pass': this.state.pass}, function(err, status, data) { + if (err) { + console.error(err, data); + this.displayMessage(App.errorCodeToMessage(err.code), false); + return; + } + + this.displayMessage('OK', true); + this.props.onSuccess(data.token); + + }.bind(this)); + + e.preventDefault(); + }, + handlePassChange: function(event) { + this.setState({'pass': event.target.value}); + }, + hideMessage: function() { + this.setState({'hideMsg': true}); + }, + displayMessage: function(msg, ok) { + this.setState({'msg': msg, 'msgOk': ok, 'hideMsg': false}); + }, + render: function() { + var cName = classNames('form-message', {'hidden': this.state.hideMsg, 'message-ok': this.state.msgOk}); + var qrCode = null; + + if (this.props.img) { + qrCode = ( +
+ +
+ ); + } + return ( +
+
+ {qrCode} +
+
+ + +
{this.state.msg}
+
+
+
+
+ ); + } +}); diff --git a/cmd/web/js/poloniex.jsx b/cmd/web/js/poloniex.jsx new file mode 100644 index 0000000..877198d --- /dev/null +++ b/cmd/web/js/poloniex.jsx @@ -0,0 +1,49 @@ +var Api = require('./api.js').Api; +var App = require('./app.js'); +var classNames = require('classnames'); + +module.exports.PoloniexForm = React.createClass({ + getInitialState: function() { + return {'hideMsg': true, 'msg': '', 'msgOk': false, 'apiSecret': this.props.apiSecret, 'apiKey': this.props.apiKey}; + }, + handleSubmit: function(e) { + Api.Call('UPDATE_MARKET', {'key': this.state.apiKey, 'secret': this.state.apiSecret, 'name': 'poloniex'}, function(err, status, data) { + if (err) { + console.error(err, data); + this.displayMessage(App.errorCodeToMessage(err.code), false); + return; + } + + this.displayMessage('OK', true); + + }.bind(this)); + e.preventDefault(); + }, + handleApiKeyChange: function(event) { + this.setState({'apiKey': event.target.value}); + }, + handleApiSecretChange: function(event) { + this.setState({'apiSecret': event.target.value}); + }, + hideMessage: function() { + this.setState({'hideMsg': true}); + }, + displayMessage: function(msg, ok) { + this.setState({'msg': msg, 'msgOk': ok, 'hideMsg': false}); + }, + render: function() { + var cName = classNames('form-message', {'hidden': this.state.hideMsg, 'message-ok': this.state.msgOk}); + return ( +
+
+
+ + + +
{this.state.msg}
+
+
+
+ ); + } +}); diff --git a/cmd/web/js/signin.jsx b/cmd/web/js/signin.jsx new file mode 100644 index 0000000..443a461 --- /dev/null +++ b/cmd/web/js/signin.jsx @@ -0,0 +1,52 @@ +var Api = require('./api.js').Api; +var App = require('./app.js'); +var classNames = require('classnames'); + +module.exports.SigninForm = React.createClass({ + getInitialState: function() { + return {'hideMsg': true, 'msg': '', 'msgOk': false, 'password': '', 'email': ''}; + }, + handleSubmit: function(e) { + Api.Call('SIGNIN', {'password': this.state.password, 'email': this.state.email}, function(err, status, data) { + if (err) { + console.error(err, data); + this.displayMessage(App.errorCodeToMessage(err.code), false); + return; + } + + this.displayMessage('OK', true); + this.props.onSuccess(data.token); + + }.bind(this)); + e.preventDefault(); + }, + handlePasswordChange: function(event) { + this.setState({'password': event.target.value}); + }, + handleEmailChange: function(event) { + this.setState({'email': event.target.value}); + }, + hideMessage: function() { + this.setState({'hideMsg': true}); + }, + displayMessage: function(msg, ok) { + this.setState({'msg': msg, 'msgOk': ok, 'hideMsg': false}); + }, + render: function() { + var cName = classNames('form-message', {'hidden': this.state.hideMsg, 'message-ok': this.state.msgOk}); + return ( +
+
+
+ + + +
{this.state.msg}
+
+ Sign up +
+
+ ); + } +}); + diff --git a/cmd/web/js/signup.jsx b/cmd/web/js/signup.jsx new file mode 100644 index 0000000..149125a --- /dev/null +++ b/cmd/web/js/signup.jsx @@ -0,0 +1,56 @@ +var Api = require('./api.js').Api; +var App = require('./app.js'); +var classNames = require('classnames'); + +module.exports.SignupForm = React.createClass({ + getInitialState: function() { + return {'hideMsg': true, 'msg': '', 'msgOk': false, 'password': '', 'email': ''}; + }, + handleSubmit: function(e) { + Api.Call('SIGNUP', + { + 'password': this.state.password, + 'email': this.state.email + }, + function(err, status, data) { + if (err) { + console.error(err, data); + this.displayMessage(App.errorCodeToMessage(err.code), false); + return; + } + + this.displayMessage('Thank You. Your account is being confirmed. Check your mailbox soon', true); + this.props.onSuccess(data.token); + + }.bind(this)); + e.preventDefault(); + }, + handlePasswordChange: function(event) { + this.setState({'password': event.target.value}); + }, + handleEmailChange: function(event) { + this.setState({'email': event.target.value}); + }, + hideMessage: function() { + this.setState({'hideMsg': true}); + }, + displayMessage: function(msg, ok) { + this.setState({'msg': msg, 'msgOk': ok, 'hideMsg': false}); + }, + render: function() { + var cName = classNames('form-message', {'hidden': this.state.hideMsg, 'message-ok': this.state.msgOk}); + return ( +
+
+
+ + + +
{this.state.msg}
+ Sign In +
+
+
+ ); + } +}); -- cgit v1.2.3