From d7df1e980fe0c840b382b86caca0956c5273154c Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Mon, 6 Jun 2016 10:09:01 -0400 Subject: [PATCH] Move api mixin to lib, remove mixin The mixin doesn't gain us anything except complexity --- .../languagechooser/languagechooser.jsx | 4 - src/components/navigation/www/navigation.jsx | 11 +-- src/lib/api.js | 79 +++++++++++++++++++ src/lib/url-params.js | 14 ++++ src/mixins/api.jsx | 76 ------------------ src/redux/conference-details.js | 2 +- src/redux/conference-schedule.js | 2 +- src/redux/session.js | 2 +- src/views/splash/splash.jsx | 22 +++--- 9 files changed, 109 insertions(+), 103 deletions(-) create mode 100644 src/lib/api.js create mode 100644 src/lib/url-params.js delete mode 100644 src/mixins/api.jsx diff --git a/src/components/languagechooser/languagechooser.jsx b/src/components/languagechooser/languagechooser.jsx index 8fbd213b5..0fbc0662a 100644 --- a/src/components/languagechooser/languagechooser.jsx +++ b/src/components/languagechooser/languagechooser.jsx @@ -1,7 +1,6 @@ var classNames = require('classnames'); var React = require('react'); -var Api = require('../../mixins/api.jsx'); var jar = require('../../lib/jar.js'); var languages = require('../../../languages.json'); var Form = require('../forms/form.jsx'); @@ -12,9 +11,6 @@ var Select = require('../forms/select.jsx'); */ var LanguageChooser = React.createClass({ type: 'LanguageChooser', - mixins: [ - Api - ], getDefaultProps: function () { return { languages: languages, diff --git a/src/components/navigation/www/navigation.jsx b/src/components/navigation/www/navigation.jsx index f7319d2d6..c786fad79 100644 --- a/src/components/navigation/www/navigation.jsx +++ b/src/components/navigation/www/navigation.jsx @@ -7,7 +7,7 @@ var injectIntl = ReactIntl.injectIntl; var sessionActions = require('../../../redux/session.js'); -var Api = require('../../../mixins/api.jsx'); +var api = require('../../../lib/api'); var Avatar = require('../../avatar/avatar.jsx'); var Button = require('../../forms/button.jsx'); var Dropdown = require('../../dropdown/dropdown.jsx'); @@ -24,9 +24,6 @@ Modal.setAppElement(document.getElementById('view')); var Navigation = React.createClass({ type: 'Navigation', - mixins: [ - Api - ], getInitialState: function () { return { accountNavOpen: false, @@ -85,7 +82,7 @@ var Navigation = React.createClass({ return '/users/' + this.props.session.session.user.username + '/'; }, getMessageCount: function () { - this.api({ + api({ method: 'get', uri: '/users/' + this.props.session.session.user.username + '/messages/count' }, function (err, body) { @@ -110,7 +107,7 @@ var Navigation = React.createClass({ handleLogIn: function (formData, callback) { this.setState({'loginError': null}); formData['useMessages'] = true; - this.api({ + api({ method: 'post', host: '', uri: '/accounts/login/', @@ -142,7 +139,7 @@ var Navigation = React.createClass({ }, handleLogOut: function (e) { e.preventDefault(); - this.api({ + api({ host: '', method: 'post', uri: '/accounts/logout/', diff --git a/src/lib/api.js b/src/lib/api.js new file mode 100644 index 000000000..0f8df9d41 --- /dev/null +++ b/src/lib/api.js @@ -0,0 +1,79 @@ +var defaults = require('lodash.defaults'); +var xhr = require('xhr'); + +var jar = require('./jar'); +var log = require('./log'); +var urlParams = require('./url-params'); + +/** + * Helper method that constructs requests to the scratch api. + * Custom arguments: + * - useCsrf [boolean] – handles unique csrf token retrieval for POST requests. This prevents + * CSRF forgeries (see: https://www.squarefree.com/securitytips/web-developers.html#CSRF) + * + * It also takes in other arguments specified in the xhr library spec. + */ + +module.exports = function (opts, callback) { + defaults(opts, { + host: process.env.API_HOST, + headers: {}, + json: {}, + useCsrf: false + }); + + defaults(opts.headers, { + 'X-Requested-With': 'XMLHttpRequest' + }); + + opts.uri = opts.host + opts.uri; + + if (opts.params) { + opts.uri = [opts.uri, urlParams(opts.params)] + .join(opts.uri.indexOf('?') === -1 ? '?' : '&'); + } + + var apiRequest = function (opts) { + if (opts.host !== '') { + // For IE < 10, we must use XDR for cross-domain requests. XDR does not support + // custom headers. + defaults(opts, {useXDR: true}); + delete opts.headers; + if (opts.authentication) { + var authenticationParams = ['x-token=' + opts.authentication]; + var parts = opts.uri.split('?'); + var qs = (parts[1] || '').split('&').concat(authenticationParams).join('&'); + opts.uri = parts[0] + '?' + qs; + + } + } + xhr(opts, function (err, res, body) { + if (err) log.error(err); + // Legacy API responses come as lists, and indicate to redirect the client like + // [{success: true, redirect: "/location/to/redirect"}] + try { + if ('redirect' in body[0]) window.location = body[0].redirect; + } catch (err) { + // do nothing + } + callback(err, body, res); + }); + }.bind(this); + + if (typeof jar.get('scratchlanguage') !== 'undefined') { + opts.headers['Accept-Language'] = jar.get('scratchlanguage') + ', en;q=0.8'; + } + if (opts.authentication) { + opts.headers['X-Token'] = opts.authentication; + } + if (opts.useCsrf) { + jar.use('scratchcsrftoken', '/csrf_token/', function (err, csrftoken) { + if (err) return log.error('Error while retrieving CSRF token', err); + opts.json.csrftoken = csrftoken; + opts.headers['X-CSRFToken'] = csrftoken; + apiRequest(opts); + }.bind(this)); + } else { + apiRequest(opts); + } +}; diff --git a/src/lib/url-params.js b/src/lib/url-params.js new file mode 100644 index 000000000..0fcab02e8 --- /dev/null +++ b/src/lib/url-params.js @@ -0,0 +1,14 @@ +/* Turn an object into an url param string + * urlParams({a: 1, b: 2, c: 3}) + * // a=1&b=2&c=3 + */ +module.exports = function urlParams (values) { + return Object + .keys(values) + .map(function (key) { + return [key, values[key]] + .map(encodeURIComponent) + .join('='); + }) + .join('&'); +}; diff --git a/src/mixins/api.jsx b/src/mixins/api.jsx deleted file mode 100644 index 47ee8faff..000000000 --- a/src/mixins/api.jsx +++ /dev/null @@ -1,76 +0,0 @@ -var defaults = require('lodash.defaults'); -var xhr = require('xhr'); - -var jar = require('../lib/jar.js'); -var log = require('../lib/log.js'); - -/** - * Component mixin that constructs requests to the scratch api. - * Custom arguments: - * - useCsrf [boolean] – handles unique csrf token retrieval for POST requests. This prevents - * CSRF forgeries (see: https://www.squarefree.com/securitytips/web-developers.html#CSRF) - * - * It also takes in other arguments specified in the xhr library spec. - */ -var Api = { - api: function (opts, callback) { - defaults(opts, { - host: window.env.API_HOST, - headers: {}, - json: {}, - useCsrf: false - }); - - defaults(opts.headers, { - 'X-Requested-With': 'XMLHttpRequest' - }); - - opts.uri = opts.host + opts.uri; - - var apiRequest = function (opts) { - if (opts.host !== '') { - // For IE < 10, we must use XDR for cross-domain requests. XDR does not support - // custom headers. - defaults(opts, {useXDR: true}); - delete opts.headers; - if (opts.authentication) { - var authenticationParams = ['x-token=' + opts.authentication]; - var parts = opts.uri.split('?'); - var qs = (parts[1] || '').split('&').concat(authenticationParams).join('&'); - opts.uri = parts[0] + '?' + qs; - - } - } - xhr(opts, function (err, res, body) { - if (err) log.error(err); - // Legacy API responses come as lists, and indicate to redirect the client like - // [{success: true, redirect: "/location/to/redirect"}] - try { - if ('redirect' in body[0]) window.location = body[0].redirect; - } catch (err) { - // do nothing - } - callback(err, body); - }); - }.bind(this); - - if (typeof jar.get('scratchlanguage') !== 'undefined') { - opts.headers['Accept-Language'] = jar.get('scratchlanguage') + ', en;q=0.8'; - } - if (opts.authentication) { - opts.headers['X-Token'] = opts.authentication; - } - if (opts.useCsrf) { - jar.use('scratchcsrftoken', '/csrf_token/', function (err, csrftoken) { - if (err) return log.error('Error while retrieving CSRF token', err); - opts.json.csrftoken = csrftoken; - opts.headers['X-CSRFToken'] = csrftoken; - apiRequest(opts); - }.bind(this)); - } else { - apiRequest(opts); - } - } -}; - -module.exports = Api; diff --git a/src/redux/conference-details.js b/src/redux/conference-details.js index 0d46b1282..0e3cf4b25 100644 --- a/src/redux/conference-details.js +++ b/src/redux/conference-details.js @@ -1,5 +1,5 @@ var keyMirror = require('keymirror'); -var api = require('../mixins/api.jsx').api; +var api = require('../lib/api'); var Types = keyMirror({ SET_DETAILS: null, diff --git a/src/redux/conference-schedule.js b/src/redux/conference-schedule.js index 874226d29..82f5166ee 100644 --- a/src/redux/conference-schedule.js +++ b/src/redux/conference-schedule.js @@ -1,5 +1,5 @@ var keyMirror = require('keymirror'); -var api = require('../mixins/api.jsx').api; +var api = require('../lib/api'); var Types = keyMirror({ SET_SCHEDULE: null, diff --git a/src/redux/session.js b/src/redux/session.js index 9b53435a8..16be21028 100644 --- a/src/redux/session.js +++ b/src/redux/session.js @@ -1,7 +1,7 @@ var keyMirror = require('keymirror'); var defaults = require('lodash.defaults'); -var api = require('../mixins/api.jsx').api; +var api = require('../lib/api'); var tokenActions = require('./token.js'); var Types = keyMirror({ diff --git a/src/views/splash/splash.jsx b/src/views/splash/splash.jsx index 8fa0cf30c..316b96b6b 100644 --- a/src/views/splash/splash.jsx +++ b/src/views/splash/splash.jsx @@ -2,13 +2,12 @@ var connect = require('react-redux').connect; var injectIntl = require('react-intl').injectIntl; var omit = require('lodash.omit'); var React = require('react'); -var render = require('../../lib/render.jsx'); +var api = require('../../lib/api'); +var render = require('../../lib/render.jsx'); var sessionActions = require('../../redux/session.js'); var shuffle = require('../../lib/shuffle.js').shuffle; -var Api = require('../../mixins/api.jsx'); - var Activity = require('../../components/activity/activity.jsx'); var AdminPanel = require('../../components/adminpanel/adminpanel.jsx'); var DropdownBanner = require('../../components/dropdown-banner/banner.jsx'); @@ -25,9 +24,6 @@ require('./splash.scss'); var Splash = injectIntl(React.createClass({ type: 'Splash', - mixins: [ - Api - ], getInitialState: function () { return { projectCount: 14000000, // gets the shared project count @@ -90,42 +86,42 @@ var Splash = injectIntl(React.createClass({ } }, getActivity: function () { - this.api({ + api({ uri: '/proxy/users/' + this.props.session.session.user.username + '/activity?limit=5' }, function (err, body) { if (!err) this.setState({activity: body}); }.bind(this)); }, getFeaturedGlobal: function () { - this.api({ + api({ uri: '/proxy/featured' }, function (err, body) { if (!err) this.setState({featuredGlobal: body}); }.bind(this)); }, getFeaturedCustom: function () { - this.api({ + api({ uri: '/proxy/users/' + this.props.session.session.user.id + '/featured' }, function (err, body) { if (!err) this.setState({featuredCustom: body}); }.bind(this)); }, getNews: function () { - this.api({ + api({ uri: '/news?limit=3' }, function (err, body) { if (!err) this.setState({news: body}); }.bind(this)); }, getProjectCount: function () { - this.api({ + api({ uri: '/projects/count/all' }, function (err, body) { if (!err) this.setState({projectCount: body.count}); }.bind(this)); }, refreshHomepageCache: function () { - this.api({ + api({ host: '', uri: '/scratch_admin/homepage/clear-cache/', method: 'post', @@ -161,7 +157,7 @@ var Splash = injectIntl(React.createClass({ this.setState({emailConfirmationModalOpen: false}); }, handleDismiss: function (cue) { - this.api({ + api({ host: '', uri: '/site-api/users/set-template-cue/', method: 'post',