scratch-www/src/lib/api.js

104 lines
3.6 KiB
JavaScript
Raw Normal View History

const defaults = require('lodash.defaults');
const xhr = require('xhr');
const jar = require('./jar');
const log = require('./log');
const 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.
*
* @param {object} opts optional xhr args (see above)
* @param {Function} callback [description]
*/
module.exports = (opts, callback) => {
defaults(opts, {
host: process.env.API_HOST,
headers: {},
responseType: 'json',
useCsrf: false
});
if (opts.host === '') {
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 ? '?' : '&');
}
if (opts.formData) {
opts.body = urlParams(opts.formData);
opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
const apiRequest = options => {
if (options.host !== '') {
if ('withCredentials' in new XMLHttpRequest()) {
options.useXDR = false;
} else {
// For IE < 10, we must use XDR for cross-domain requests. XDR does not support
// custom headers.
options.useXDR = true;
delete options.headers;
if (options.authentication) {
const authenticationParams = [`x-token=${options.authentication}`];
const parts = options.uri.split('?');
const qs = (parts[1] || '')
.split('&')
.concat(authenticationParams)
.join('&');
options.uri = `${parts[0]}?${qs}`;
}
}
}
xhr(options, (err, res, body) => {
if (err) log.error(err);
if (options.responseType === 'json' && typeof body === 'string') {
// IE doesn't parse responses as JSON without the json attribute,
// even with responseType: 'json'.
// See https://github.com/Raynos/xhr/issues/123
try {
body = JSON.parse(body);
} catch (e) {
// Not parseable anyway, don't worry about it
}
}
// 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 (e) {
// do nothing
}
callback(err, body, res);
});
};
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/', (err, csrftoken) => {
if (err) return log.error('Error while retrieving CSRF token', err);
opts.headers['X-CSRFToken'] = csrftoken;
apiRequest(opts);
});
} else {
apiRequest(opts);
}
};