src/api/communication/index.js
/**
* This file contains the settings and the communication mechanism for the
* ITSLanguage REST API.
*/
// Headers
const CONTENT_TYPE = 'Content-Type';
const AUTHORIZATION = 'Authorization';
// Content-Types
const APPLICATION_JSON = 'application/json';
/**
* The settings to use for the communication with the ITSLanguage API.
*/
export const settings = {
apiUrl: 'https://api.itslanguage.nl',
wsUrl: null,
authorizationToken: null
};
/**
* Update the settings with the `newSettings`.
*
* @param {Object} newSettings - The settings to inject/update.
*
* @throws {Error} - When the given `newSettings` is something other than a
* object.
*/
export function updateSettings(newSettings) {
if (!newSettings || !(newSettings instanceof Object)) {
throw new Error('Please, only provide objects as settings.');
}
Object.assign(settings, newSettings);
}
/**
* Parse the response of a fetch request.
*
* Try to parse the given response body as JSON, if it isn't Leave the
* response as is.
*
* @param {Response} response - The response to parse.
*
* @throws {Promise.<String>} - When the requets was not okay and the contents
* of the response isn't json.
* @throws {Promise.<Object>} - When the requets was not okay and the contents
* of the response is json.
*
* @returns {Promise.<Object>} - The contents of a JSON response or the
* response itself if the body is something other
* than JSON.
*/
function handleResponse(response) {
const responseContentType = response.headers.get(CONTENT_TYPE);
// The ITSLanguage API should return JSON. If t
if (responseContentType && responseContentType.includes(APPLICATION_JSON)) {
return response.json().then(json => {
if (response.ok) {
return json;
}
return Promise.reject(json);
});
}
if (!response.ok) {
return Promise.reject(`${response.status}: ${response.statusText}`);
}
return response;
}
/**
* Perform an HTTP request for the given method, url, body, and headers.
*
* In case the given url is a partial url, meaning it starts with a `/`, the
* base URL to the ITSLanguage API is prepended.
*
* When a Object instance is provided as body, it'll be transformed into JSON.
* Unless it is either a `URLSearchParams` or a `FormData` object. Anything
* else is sent as plain text.
*
* @param {string} method - The request METHOD ('GET', 'POST', 'PUT', 'DELETE').
* @param {string} url - The location to send the request to.
* @param {*} [body] - Anything which needs to be sent somewhere.
* @param {Headers} [headers] - Extra headers to send with the request.
*
* @returns {Promise.<Object>} The response of the made request.
*/
export function request(method, url, body, headers) {
const requestHeaders = headers || new Headers();
let requestBody = body;
if (!(body instanceof URLSearchParams || body instanceof FormData) && body instanceof Object) {
requestHeaders.set('Content-Type', 'application/json');
requestBody = JSON.stringify(body);
}
let requestURL = url;
// XXX remove the URL truthy check when all tests are properly written. Now
// it happens way to often that the URL is omitted without any good reason.
if (url && url.startsWith('/')) {
requestURL = `${settings.apiUrl}${url}`;
}
const requestOptions = {
method,
headers: requestHeaders,
body: requestBody
};
return fetch(requestURL, requestOptions).then(handleResponse);
}
/**
* Build a bearer token from the `authorizationToken` in the settings object.
*
* @throws {Error} When no authorizationToken is set.
*
* @returns {string} The generated bearer token.
*/
function getBearerToken() {
if (!settings.authorizationToken) {
throw new Error('Please authenticate first.');
}
return `Bearer ${settings.authorizationToken}`;
}
/**
* Perform an HTTP request with the desired method, body, and headers to the
* given partial ITSLanguage API URL.
*
* This request will add the ``Authorization`` header to the request.
*
* This function only allows to make calls to the ITSLanguage API.
*
* @param {string} method - The request METHOD ('GET', 'POST', 'PUT', 'DELETE').
* @param {string} url - The location to send the request to.
* @param {*} [body] - Anything which needs to be sent somewhere.
* @param {Headers} [headers] - Extra headers to send with the request.
*
* @throws {Promise.<string>} - When the given `url` param is not a partial
* URL, or when there is no authorisation token
* availible.
*
* @returns {Promise.<Object>} - The response from the ITSLanguage API.
*/
export function authorisedRequest(method, url, body, headers) {
// XXX remove the URL truthy check when all parts of the SDK no longer build
// a complete url by themselves using the "private" settings object of their
// connection reference.
if (url && (!url.startsWith('/') && !url.startsWith(settings.apiUrl))) {
return Promise.reject('Only relative ITSLanguage API URLs are allowed.');
}
try {
const requestHeaders = headers || new Headers();
requestHeaders.set(AUTHORIZATION, getBearerToken());
return request(method, url, body, requestHeaders);
} catch (unauthorised) {
return Promise.reject(unauthorised.message);
}
}