Home Reference Source Test Repository

src/Connection/RestConnection.js

/**
 * RestConnection
 *
 * This sends queries to a given endpoint over HTTP using
 * RESTful conventions. If additional query methods are
 * called, these are encoded in the (http) query string.
 *
 * The endpoint *must* be set before this connection is used.
 */
export default class RestConnection {

    /**
     * Create a new RestConnection
     *
     * @param  {string} [endpoint]
     */
    constructor(endpoint) {
        /**
         * The base URL for this connection.
         *
         * @protected
         * @type {string}
         */
        this.endpoint = endpoint;
    }

    /**
     * Run an INSERT query.
     *
     * @param  {Object} data
     * @return {Promise}
     */
    create(data) {
        return this
            .sendRequest(null, 'post', data)
            .then(response => this.unwrap(response));
    }

    /**
     * Run a SELECT query.
     *
     * @param  {number|Array} idOrQuery
     * @return {Promise}
     */
    read(idOrQuery) {
        return this
            .sendRequest(idOrQuery)
            .then(response => this.unwrap(response));
    }

    /**
     * Run an UPDATE query.
     *
     * @param  {number|Array} idOrQuery
     * @param  {Object} data
     * @return {Promise}
     */
    update(idOrQuery, data) {
        return this
            .sendRequest(idOrQuery, 'put', data)
            .then(response => this.unwrap(response));
    }


    /**
     * Run a DELETE query.
     *
     * @param  {number|Array} idOrQuery
     * @return {Promise}
     */
    delete(idOrQuery) {
        return this
            .sendRequest(idOrQuery, 'delete')
            .then(response => response.status === 200);
    }

    /**
     * Wrapper around window.fetch
     *
     * @param  {number} [id]
     * @param  {array} [queryStack]
     * @param  {string} [method]
     * @param  {Object} [data]
     * @return {Promise}
     */
    _fetch(id, queryStack, method, data) {
        return fetch(
            this.url(id, queryStack),
            this._makeInit(method, data)
        );
    }

    /**
     * Send an HTTP request and return a Promise.
     *
     * @param  {string|number|Array} [urlSuffix]
     * @param  {string} [method]
     * @param  {Object} [body]
     * @return {Promise}
     */
    sendRequest(urlSuffix, method, body) {
        return fetch(this.buildUrl(urlSuffix), this.buildOptions(method, body));
    }

    /**
     * Get endpoint URL with the entity ID or query data appended.
     *
     * @param  {string|number|array} suffix
     * @return {string}
     */
    buildUrl(suffix) {
        if ( ! this.endpoint) {
            throw 'Endpoint must be set before using this connection';
        }

        let url = this.endpoint;

        if (Array.isArray(suffix)) {
            if (suffix.length) {
                url += '?query='+JSON.stringify(suffix);
            }
        } else if (suffix) {
            url += '/'+suffix;
        }

        return url;
    }

    /**
     * Get an options hash for the fetch `init` parameter.
     *
     * @see  https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#Parameters
     * @param  {string} [method]
     * @param  {Object} [data]
     * @param  {Object} [options]
     * @return {Object}
     */
    buildOptions(method, body, options) {
        let defaults = {
            credentials: 'same-origin', // to send our session cookie
            headers: {
                'Accept': 'application/json',
                'X-XSRF-TOKEN': getCsrfToken()
            }
        };

        if (method) {
            defaults.method = method;
        }

        if (body) {
            defaults.headers['Content-Type'] = 'application/json';
            defaults.body = JSON.stringify(body);
        }

        return Object.assign(defaults, options || {});
    }

    /**
     * Take a fetch response and extract the JSON.
     *
     * @param  {Response} response
     * @return {Object}
     */
    unwrap(response) {
        return response.json();
    }

    /**
     * Get a URL to the endpoint.
     *
     * @param  {number} [id]
     * @param  {array} [query]
     * @return {string}
     */
    url(id, query) {
        if ( ! this.endpoint) {
            throw 'Endpoint must be set before using this connection';
        }

        let url = this.endpoint;

        if (id) {
            url += '/'+id;
        }

        if (query && query.length) {
            return `${url}?query=${JSON.stringify(query)}`;
        }

        return url;
    }

    /**
     * Make an options hash for the fetch `init` parameter.
     *
     * @see  https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#Parameters
     * @param  {string} [method]
     * @param  {Object} [data]
     * @param  {Object} [options]
     * @return {Object}
     */
    _makeInit(method, data, options) {

        let defaults = {
            credentials: 'same-origin', // to send our session cookie
            headers: {
                'Accept': 'application/json',
                'X-XSRF-TOKEN': getCsrfToken()
            }
        };

        if (method) {
            defaults.method = method;
        }

        if (data) {
            defaults.headers['Content-Type'] = 'application/json';
            defaults.body = JSON.stringify(data);
        }

        return Object.assign(defaults, options || {});
    }
}

function getCsrfToken()
{
    if (typeof document === 'undefined') return;

    return decodeURIComponent((document.cookie.match('(^|; )XSRF-TOKEN=([^;]*)') || 0)[2]);
}