aboutsummaryrefslogtreecommitdiff
path: root/cmd/web/js
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/web/js')
-rw-r--r--cmd/web/js/api.js176
-rw-r--r--cmd/web/js/app.js121
-rw-r--r--cmd/web/js/cookies.js65
-rw-r--r--cmd/web/js/main.jsx101
-rw-r--r--cmd/web/js/otp.jsx70
-rw-r--r--cmd/web/js/poloniex.jsx49
-rw-r--r--cmd/web/js/signin.jsx52
-rw-r--r--cmd/web/js/signup.jsx56
8 files changed, 690 insertions, 0 deletions
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 @@
1'use strict';
2
3var App = require('./app.js');
4
5var Api = {};
6
7Api.API_HOST = process.env.API_HOST;
8Api.API_PORT = process.env.API_PORT;
9
10if (process.env.API_HTTPS === 'true') {
11 Api.API_ROOT = 'https://';
12} else {
13 Api.API_ROOT = 'http://';
14}
15
16Api.API_ROOT += Api.API_HOST;
17if (Api.API_PORT !== '80') {
18 Api.API_ROOT += ':' + Api.API_PORT;
19}
20
21Api.API_ROOT += '/api';
22
23var ApiEndpoints = {
24 'SIGNUP': {
25 'type': 'POST',
26 'auth': false,
27 'parameters': [
28 {'name': 'email', 'mandatory': true, 'inquery': true},
29 {'name': 'password', 'mandatory': true, 'inquery': true}
30 ],
31 'buildUrl': function(params) {
32 return '/signup';
33 }
34 },
35 'SIGNIN': {
36 'type': 'POST',
37 'auth': false,
38 'parameters': [
39 {'name': 'email', 'mandatory': true, 'inquery': true},
40 {'name': 'password', 'mandatory': true, 'inquery': true}
41 ],
42 'buildUrl': function(params) {
43 return '/signin';
44 }
45 },
46 'MARKET': {
47 'type': 'GET',
48 'auth': true,
49 'parameters': [
50 {'name': 'name', 'mandatory': true, 'inquery': false},
51 ],
52 'buildUrl': function(params) {
53 return '/market/' + params.name;
54 }
55 },
56 'UPDATE_MARKET': {
57 'type': 'POST',
58 'auth': true,
59 'parameters': [
60 {'name': 'name', 'mandatory': true, 'inquery': false},
61 {'name': 'key', 'mandatory': true, 'inquery': true},
62 {'name': 'secret', 'mandatory': true, 'inquery': true},
63 ],
64 'buildUrl': function(params) {
65 return '/market/' + params.name + '/update';
66 }
67 },
68 'OTP_ENROLL': {
69 'type': 'GET',
70 'auth': true,
71 'parameters': [],
72 'buildUrl': function(params) {
73 return '/otp/enroll';
74 }
75 },
76 'OTP_VALIDATE': {
77 'type': 'POST',
78 'auth': true,
79 'parameters': [
80 {'name': 'pass', 'mandatory': true, 'inquery': true},
81 ],
82 'buildUrl': function(params) {
83 return '/otp/validate';
84 }
85 },
86};
87
88Api.BuildRequest = function(endpointId, params) {
89 var endpoint = ApiEndpoints[endpointId];
90 var query = {};
91 var url = '';
92 var headers = {};
93 var jwt = App.getUserToken();
94
95 if (endpoint === undefined) {
96 return {'err': 'cannot find endpoint ' + endpointId};
97 }
98
99 if (endpoint.auth === true && (jwt === undefined || jwt === null)) {
100 return {'err': 'this endpoint needs auth'};
101 }
102
103 if (jwt !== undefined && jwt !== null) {
104 headers.Authorization = 'Bearer ' + jwt;
105 }
106
107 for (var i = 0; i < endpoint.parameters.length; i++) {
108 var parameter = endpoint.parameters[i];
109 if (parameter.mandatory === true && params[parameter.name] === undefined) {
110 return {'err': 'parameter \'' + parameter.name + '\' is mandatory'};
111 }
112
113 if (parameter.inquery === true) {
114 query[parameter.name] = params[parameter.name];
115 }
116 }
117
118 url = endpoint.buildUrl(params);
119
120 return {'err': null, 'url': Api.API_ROOT + url, 'headers': headers, 'params': query, 'type': endpoint.type};
121};
122
123Api.Call = function(endpointId, params, fn) {
124 var request = Api.BuildRequest(endpointId, params);
125
126 if (request.err !== null) {
127 console.error('Api BuildRequest error', request.err);
128 fn({'err': request.err}, -1, {});
129 return null;
130 }
131
132 return Api.DoRequest(request.type, request.url, request.params, request.headers, fn);
133};
134
135Api.DoRequest = function(type, url, params, headers, callback) {
136 return $.ajax({
137 data: params,
138 headers: headers,
139 timeout: 30000,
140 dataType: 'json',
141 error: function(xhr, status, err) {
142 if (status === 'abort') {
143 return;
144 }
145
146 var apiStatus = null;
147 var apiResponse = null;
148 var apiCode = null;
149 if (xhr.responseJSON) {
150 apiStatus = xhr.responseJSON.status;
151 apiResponse = xhr.responseJSON.response;
152 apiCode = xhr.responseJSON.code;
153 }
154
155 if (xhr.status === 401 || xhr.status === 403) {
156 if (App.onUserNotAuthorized(xhr.status, apiCode) === false) {
157 return;
158 }
159 }
160
161 callback({'url': url, 'err': err, 'status': status, 'code': apiCode, 'xhr': xhr}, apiStatus, apiResponse);
162 },
163 success: function(data) {
164 var err = null;
165 if (data.status !== 'ok') {
166 err = {'url': url, 'status': status};
167 }
168 callback(err, data.status, data.response);
169 },
170 type: type,
171 url: url
172 });
173};
174
175module.exports.Api = Api;
176
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 @@
1'use strict';
2
3var cookies = require('./cookies.js');
4var page = require('page');
5
6var App = {};
7var cookieExpire = 60 * 30;
8
9App.errorCodeToMessage = function(code) {
10 switch (code) {
11 case 'invalid_email':
12 return 'The email is not valid';
13 case 'invalid_password':
14 return 'The password is not valid';
15 case 'email_exists':
16 return 'This email is already registered';
17 case 'invalid_credentials':
18 return 'Invalid credentials';
19 case 'invalid_otp':
20 return 'Invalid code !';
21 case 'user_not_confirmed':
22 return 'Your account is being confirmed. Should be very soon !';
23 }
24
25 return code;
26};
27
28App.isUserSignedIn = function() {
29 return cookies.hasItem('jwt');
30};
31
32App.getUserToken = function() {
33 return cookies.getItem('jwt');
34};
35
36App.onUserSignIn = function(token) {
37 if (!token || token === '') {
38 page('/signin');
39 return;
40 }
41
42 cookies.setItem('jwt', token, cookieExpire);
43 page('/me');
44};
45
46App.onUserValidateOtp = function(token) {
47 if (!token || token === '') {
48 page('/signin');
49 return;
50 }
51
52 cookies.setItem('jwt', token, cookieExpire);
53 page('/me');
54};
55
56App.onUserSignUp = function(token) {
57 if (!token || token === '') {
58 page('/signin');
59 return;
60 }
61
62 cookies.setItem('jwt', token, cookieExpire);
63};
64
65App.getUserJWT = function() {
66 return cookies.getItem('jwt');
67};
68
69App.page = function(path, needsAuth, fn) {
70 page(path, function(context) {
71 if (needsAuth && !App.isUserSignedIn()) {
72 page('/signin');
73 return;
74 }
75
76 fn(context);
77 });
78};
79
80App.go = function(path) {
81 page(path);
82};
83
84App.start = function() {
85 page();
86};
87
88App.onInternNavigation = function(href, e) {
89 e.preventDefault();
90 page(href);
91};
92
93App.onUserNotAuthorized = function(httpCode, apiCode) {
94 switch (apiCode) {
95 case 'not_authorized':
96 cookies.removeItem('jwt');
97 page('/signin');
98 return false;
99 case 'otp_not_setup':
100 page('/otp/setup');
101 return false;
102 case 'need_otp_validation':
103 page('/otp/validate');
104 return false;
105 default:
106 return true;
107 }
108};
109
110App.mount = function(app) {
111 var root = React.createElement(
112 'div',
113 {className: 'container'},
114 app
115 );
116
117 ReactDOM.unmountComponentAtNode(document.getElementById('app'));
118 ReactDOM.render(root, document.getElementById('app'));
119};
120
121module.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 @@
1'use strict';
2
3/*\
4|*|
5|*| :: cookies.js ::
6|*|
7|*| A complete cookies reader/writer framework with full unicode support.
8|*|
9|*| Revision #1 - September 4, 2014
10|*|
11|*| https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
12|*| https://developer.mozilla.org/User:fusionchess
13|*|
14|*| This framework is released under the GNU Public License, version 3 or later.
15|*| http://www.gnu.org/licenses/gpl-3.0-standalone.html
16|*|
17|*| Syntaxes:
18|*|
19|*| * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
20|*| * docCookies.getItem(name)
21|*| * docCookies.removeItem(name[, path[, domain]])
22|*| * docCookies.hasItem(name)
23|*| * docCookies.keys()
24|*|
25\*/
26
27module.exports = {
28 getItem: function(sKey) {
29 if (!sKey) { return null; }
30 return decodeURIComponent(document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1')) || null;
31 },
32 setItem: function(sKey, sValue, vEnd, sPath, sDomain, bSecure) {
33 if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
34 var sExpires = '';
35 if (vEnd) {
36 switch (vEnd.constructor) {
37 case Number:
38 sExpires = vEnd === Infinity ? '; expires=Fri, 31 Dec 9999 23:59:59 GMT' : '; max-age=' + vEnd;
39 break;
40 case String:
41 sExpires = '; expires=' + vEnd;
42 break;
43 case Date:
44 sExpires = '; expires=' + vEnd.toUTCString();
45 break;
46 }
47 }
48 document.cookie = encodeURIComponent(sKey) + '=' + encodeURIComponent(sValue) + sExpires + (sDomain ? '; domain=' + sDomain : '') + (sPath ? '; path=' + sPath : '') + (bSecure ? '; secure' : '');
49 return true;
50 },
51 removeItem: function(sKey, sPath, sDomain) {
52 if (!this.hasItem(sKey)) { return false; }
53 document.cookie = encodeURIComponent(sKey) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT' + (sDomain ? '; domain=' + sDomain : '') + (sPath ? '; path=' + sPath : '');
54 return true;
55 },
56 hasItem: function(sKey) {
57 if (!sKey) { return false; }
58 return (new RegExp('(?:^|;\\s*)' + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=')).test(document.cookie);
59 },
60 keys: function() {
61 var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, '').split(/\s*(?:\=[^;]*)?;\s*/);
62 for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
63 return aKeys;
64 }
65};
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 @@
1var SignupForm = require('./signup.js').SignupForm;
2var SigninForm = require('./signin.js').SigninForm;
3var OtpEnrollForm = require('./otp.js').OtpEnrollForm;
4var PoloniexForm = require('./poloniex.js').PoloniexForm;
5var App = require('./app.js');
6var Api = require('./api.js').Api;
7var cookies = require('./cookies.js');
8
9var Logo = React.createClass({
10 render: function() {
11 return (<div id='logo'>
12 <a href='/'>Cryptoportfolio</a>
13 </div>);
14 }
15});
16
17App.page('/signup', false, function(context) {
18 if (App.isUserSignedIn()) {
19 App.go('/me');
20 return;
21 }
22
23 App.mount(
24 <div>
25 <Logo />
26 <SignupForm onSuccess={App.onUserSignUp}/>
27 </div>
28 );
29});
30
31App.page('/signin', false, function(context) {
32 if (App.isUserSignedIn()) {
33 App.go('/me');
34 return;
35 }
36
37 App.mount(
38 <div>
39 <Logo />
40 <SigninForm onSuccess={App.onUserSignIn}/>
41 </div>
42 );
43});
44
45App.page('/signout', true, function(context) {
46 cookies.removeItem('jwt');
47
48 App.go('/');
49});
50
51App.page('/me', true, function(context) {
52 Api.Call('MARKET', {'name': 'poloniex'}, function(err, status, data) {
53 if (err) {
54 console.error(err, data);
55 return;
56 }
57
58 App.mount(
59 <div>
60 <Logo />
61 <p>Poloniex</p>
62 <PoloniexForm apiKey={data.key} apiSecret={data.secret}/>
63 </div>
64 );
65
66 }.bind(this));
67});
68
69App.page('/otp/setup', true, function(context) {
70 Api.Call('OTP_ENROLL', {}, function(err, status, data) {
71 if (err) {
72 console.error(err, data);
73 return;
74 }
75
76 App.mount(
77 <div>
78 <Logo />
79 <OtpEnrollForm onSuccess={App.onUserValidateOtp} img={'data:image/png;base64,' + data.base64img} secret={data.secret}/>
80 </div>
81 );
82
83 }.bind(this));
84});
85
86App.page('/otp/validate', true, function(context) {
87 App.mount(
88 <div>
89 <Logo />
90 <OtpEnrollForm onSuccess={App.onUserValidateOtp} />
91 </div>
92 );
93});
94
95App.page('/', false, function(context) {
96 App.go('/me');
97});
98
99$(document).ready(function() {
100 App.start();
101});
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 @@
1var Api = require('./api.js').Api;
2var App = require('./app.js');
3var classNames = require('classnames');
4
5var OtpQrCode = React.createClass({
6 render: function() {
7 return (
8 <div>
9 <img src={this.props.img} />
10 <p>{this.props.secret}</p>
11 </div>
12 );
13 }
14});
15
16module.exports.OtpEnrollForm = React.createClass({
17 getInitialState: function() {
18 return {'hideMsg': true, 'msg': '', 'msgOk': false, 'pass': ''};
19 },
20 handleSubmit: function(e) {
21 Api.Call('OTP_VALIDATE', {'pass': this.state.pass}, function(err, status, data) {
22 if (err) {
23 console.error(err, data);
24 this.displayMessage(App.errorCodeToMessage(err.code), false);
25 return;
26 }
27
28 this.displayMessage('OK', true);
29 this.props.onSuccess(data.token);
30
31 }.bind(this));
32
33 e.preventDefault();
34 },
35 handlePassChange: function(event) {
36 this.setState({'pass': event.target.value});
37 },
38 hideMessage: function() {
39 this.setState({'hideMsg': true});
40 },
41 displayMessage: function(msg, ok) {
42 this.setState({'msg': msg, 'msgOk': ok, 'hideMsg': false});
43 },
44 render: function() {
45 var cName = classNames('form-message', {'hidden': this.state.hideMsg, 'message-ok': this.state.msgOk});
46 var qrCode = null;
47
48 if (this.props.img) {
49 qrCode = (
50 <div className='row justify-content-center'>
51 <OtpQrCode img={this.props.img} secret={this.props.secret} />
52 </div>
53 );
54 }
55 return (
56 <div className='row otp-enroll justify-content-center'>
57 <div className='col-lg-offset-4 col-lg-4 col-md-offset-4 col-md-4 col-sm-offset-4 col-sm-4 col-xs-offset-1 col-xs-10'>
58 {qrCode}
59 <div className='row justify-content-center'>
60 <form role='form' onSubmit={this.handleSubmit}>
61 <input className='form-control' type='pass' placeholder='pass' onChange={this.handlePassChange} />
62 <input className='form-control submit' type='submit' value='Validate' />
63 <div className={cName} ref='message'>{this.state.msg}</div>
64 </form>
65 </div>
66 </div>
67 </div>
68 );
69 }
70});
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 @@
1var Api = require('./api.js').Api;
2var App = require('./app.js');
3var classNames = require('classnames');
4
5module.exports.PoloniexForm = React.createClass({
6 getInitialState: function() {
7 return {'hideMsg': true, 'msg': '', 'msgOk': false, 'apiSecret': this.props.apiSecret, 'apiKey': this.props.apiKey};
8 },
9 handleSubmit: function(e) {
10 Api.Call('UPDATE_MARKET', {'key': this.state.apiKey, 'secret': this.state.apiSecret, 'name': 'poloniex'}, function(err, status, data) {
11 if (err) {
12 console.error(err, data);
13 this.displayMessage(App.errorCodeToMessage(err.code), false);
14 return;
15 }
16
17 this.displayMessage('OK', true);
18
19 }.bind(this));
20 e.preventDefault();
21 },
22 handleApiKeyChange: function(event) {
23 this.setState({'apiKey': event.target.value});
24 },
25 handleApiSecretChange: function(event) {
26 this.setState({'apiSecret': event.target.value});
27 },
28 hideMessage: function() {
29 this.setState({'hideMsg': true});
30 },
31 displayMessage: function(msg, ok) {
32 this.setState({'msg': msg, 'msgOk': ok, 'hideMsg': false});
33 },
34 render: function() {
35 var cName = classNames('form-message', {'hidden': this.state.hideMsg, 'message-ok': this.state.msgOk});
36 return (
37 <div className='row justify-content-center api-credentials-form'>
38 <div className='col-lg-offset-4 col-lg-4 col-md-offset-4 col-md-4 col-sm-offset-4 col-sm-4 col-xs-offset-1 col-xs-10'>
39 <form role='form' onSubmit={this.handleSubmit}>
40 <input className='form-control' type='text' placeholder='apiKey' value={this.state.apiKey} onChange={this.handleApiKeyChange} />
41 <input className='form-control' type='text' placeholder='apiSecret' value={this.state.apiSecret} onChange={this.handleApiSecretChange} />
42 <input className='form-control submit' type='submit' value='Save' />
43 <div className={cName} ref='message'>{this.state.msg}</div>
44 </form>
45 </div>
46 </div>
47 );
48 }
49});
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 @@
1var Api = require('./api.js').Api;
2var App = require('./app.js');
3var classNames = require('classnames');
4
5module.exports.SigninForm = React.createClass({
6 getInitialState: function() {
7 return {'hideMsg': true, 'msg': '', 'msgOk': false, 'password': '', 'email': ''};
8 },
9 handleSubmit: function(e) {
10 Api.Call('SIGNIN', {'password': this.state.password, 'email': this.state.email}, function(err, status, data) {
11 if (err) {
12 console.error(err, data);
13 this.displayMessage(App.errorCodeToMessage(err.code), false);
14 return;
15 }
16
17 this.displayMessage('OK', true);
18 this.props.onSuccess(data.token);
19
20 }.bind(this));
21 e.preventDefault();
22 },
23 handlePasswordChange: function(event) {
24 this.setState({'password': event.target.value});
25 },
26 handleEmailChange: function(event) {
27 this.setState({'email': event.target.value});
28 },
29 hideMessage: function() {
30 this.setState({'hideMsg': true});
31 },
32 displayMessage: function(msg, ok) {
33 this.setState({'msg': msg, 'msgOk': ok, 'hideMsg': false});
34 },
35 render: function() {
36 var cName = classNames('form-message', {'hidden': this.state.hideMsg, 'message-ok': this.state.msgOk});
37 return (
38 <div className='row justify-content-center sign-in'>
39 <div className='col-lg-offset-4 col-lg-4 col-md-offset-4 col-md-4 col-sm-offset-4 col-sm-4 col-xs-offset-1 col-xs-10'>
40 <form role='form' onSubmit={this.handleSubmit}>
41 <input className='form-control' type='email' placeholder='email' onChange={this.handleEmailChange} />
42 <input className='form-control' type='password' placeholder='password' onChange={this.handlePasswordChange} />
43 <input className='form-control submit' type='submit' value='Sign In' />
44 <div className={cName} ref='message'>{this.state.msg}</div>
45 </form>
46 <a href='#' onClick={App.onInternNavigation.bind(this, '/signup')}><u>Sign up</u></a>
47 </div>
48 </div>
49 );
50 }
51});
52
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 @@
1var Api = require('./api.js').Api;
2var App = require('./app.js');
3var classNames = require('classnames');
4
5module.exports.SignupForm = React.createClass({
6 getInitialState: function() {
7 return {'hideMsg': true, 'msg': '', 'msgOk': false, 'password': '', 'email': ''};
8 },
9 handleSubmit: function(e) {
10 Api.Call('SIGNUP',
11 {
12 'password': this.state.password,
13 'email': this.state.email
14 },
15 function(err, status, data) {
16 if (err) {
17 console.error(err, data);
18 this.displayMessage(App.errorCodeToMessage(err.code), false);
19 return;
20 }
21
22 this.displayMessage('Thank You. Your account is being confirmed. Check your mailbox soon', true);
23 this.props.onSuccess(data.token);
24
25 }.bind(this));
26 e.preventDefault();
27 },
28 handlePasswordChange: function(event) {
29 this.setState({'password': event.target.value});
30 },
31 handleEmailChange: function(event) {
32 this.setState({'email': event.target.value});
33 },
34 hideMessage: function() {
35 this.setState({'hideMsg': true});
36 },
37 displayMessage: function(msg, ok) {
38 this.setState({'msg': msg, 'msgOk': ok, 'hideMsg': false});
39 },
40 render: function() {
41 var cName = classNames('form-message', {'hidden': this.state.hideMsg, 'message-ok': this.state.msgOk});
42 return (
43 <div className='row justify-content-center sign-in'>
44 <div className='col-lg-offset-4 col-lg-4 col-md-offset-4 col-md-4 col-sm-offset-4 col-sm-4 col-xs-offset-1 col-xs-10'>
45 <form role='form' onSubmit={this.handleSubmit}>
46 <input className='form-control' type='email' placeholder='email' onChange={this.handleEmailChange} />
47 <input className='form-control' type='password' placeholder='password' onChange={this.handlePasswordChange} />
48 <input className='form-control submit' type='submit' value='Sign Up' />
49 <div className={cName} ref='message'>{this.state.msg}</div>
50 <a href='#' onClick={App.onInternNavigation.bind(this, '/signin')}><u>Sign In</u></a>
51 </form>
52 </div>
53 </div>
54 );
55 }
56});