Home Reference Source

core/request/request.js

import Promise from 'es6-promise';
import qs from 'qs';
import assign from 'lodash/assign';
import isString from 'lodash/isString';
import isNumber from 'lodash/isNumber';
import { Client } from '../client';
import { KinveyError, NoResponseError } from '../errors';
import { isDefined, appendQuery, randomString } from '../utils';
import { Response } from './response';
import { Headers } from './headers';

/**
 * @private
 */
export const RequestMethod = {
  GET: 'GET',
  POST: 'POST',
  PATCH: 'PATCH',
  PUT: 'PUT',
  DELETE: 'DELETE'
};
Object.freeze(RequestMethod);

/**
 * @private
 */
export class Request {
  constructor(options = {}) {
    options = assign({
      followRedirect: true
    }, options);

    this.id = randomString();
    this.client = options.client;
    this.method = options.method || RequestMethod.GET;
    this.headers = options.headers || new Headers();
    this.url = options.url || '';
    this.body = options.body || options.data;
    this.timeout = isDefined(options.timeout) ? options.timeout : this.client.defaultTimeout;
    this.followRedirect = options.followRedirect === true;
    this.cache = options.cache === true;
  }

  get client() {
    return this._client || Client.sharedInstance();
  }

  set client(client) {
    if (client) {
      if ((client instanceof Client) === false) {
        throw new KinveyError('client must be an instance of the Client class.');
      }
    }

    this._client = client;
  }

  get method() {
    return this._method;
  }

  set method(method) {
    if (!isString(method)) {
      method = String(method);
    }

    method = method.toUpperCase();
    switch (method) {
      case RequestMethod.GET:
      case RequestMethod.POST:
      case RequestMethod.PATCH:
      case RequestMethod.PUT:
      case RequestMethod.DELETE:
        this._method = method;
        break;
      default:
        throw new KinveyError('Invalid request method. Only GET, POST, PATCH, PUT, and DELETE are allowed.');
    }
  }

  get headers() {
    return this._headers;
  }

  set headers(headers) {
    if (!(headers instanceof Headers)) {
      headers = new Headers(headers);
    }

    this._headers = headers;
  }

  get url() {
    // If `cache` is true, add a cache busting query string.
    // This is useful for Android < 4.0 which caches all requests aggressively.
    if (this.cache === true) {
      return appendQuery(this._url, qs.stringify({
        _: Math.random().toString(36).substr(2)
      }));
    }

    return this._url;
  }

  set url(urlString) {
    this._url = urlString;
  }

  get data() {
    return this.body;
  }

  set data(data) {
    this.body = data;
  }

  get timeout() {
    return this._timeout;
  }

  set timeout(timeout) {
    timeout = parseInt(timeout, 10);

    if (isNumber(timeout) === false || isNaN(timeout)) {
      throw new KinveyError('Invalid timeout. Timeout must be a number.');
    }

    this._timeout = timeout;
  }

  get followRedirect() {
    return this._followRedirect;
  }

  set followRedirect(followRedirect) {
    this._followRedirect = !!followRedirect;
  }

  get cache() {
    return this._cache;
  }

  set cache(cache) {
    this._cache = !!cache;
  }

  execute() {
    if (isDefined(this.rack) === false) {
      Log.error('Unable to execute the request. Please provide a rack to execute the request.');
      return Promise.reject(
        new KinveyError('Unable to execute the request. Please provide a rack to execute the request.')
      );
    }

    return this.rack.execute(this.toPlainObject())
      .then((response) => {
        if (isDefined(response) === false) {
          throw new NoResponseError();
        }

        if ((response instanceof Response) === false) {
          response = new Response({
            statusCode: response.statusCode,
            headers: response.headers,
            data: response.data
          });
        }

        return response;
      });
  }

  cancel() {
    return this.rack.cancel();
  }

  toPlainObject() {
    return {
      id: this.id,
      method: this.method,
      headers: this.headers.toPlainObject(),
      url: this.url,
      body: this.body,
      timeout: this.timeout,
      followRedirect: this.followRedirect,
      encryptionKey: this.client.encryptionKey
    };
  }
}