Source: lib/remote.js

'use strict';

const querystring = require('querystring');

const fetch = require('node-fetch');
const Resource = require('./resource');

const CONTENT_TYPE = 'application/json';

/**
 * Interact with a remote {@link Resource}.
 * @type {Remote}
 * @property {Object} config
 * @property {Boolean} secure
 */
class Remote extends Resource {
  /**
   * An in-memory representation of a node in our network.
   * @param       {Object} target - Target object.
   * @param       {String} target.host - Named host, e.g. "localhost".
   * @param       {String} target.secure - Require TLS session.
   * @constructor
   */
  constructor (config = {}) {
    super(config);

    this.config = Object.assign({
      authority: config.host || 'localhost',
      entropy: Math.random(),
      secure: true,
      port: 443
    }, config);

    this.secure = this.config.secure;

    return this;
  }

  /**
   * Enumerate the available Resources on the remote host.
   * @return {Configuration}
   */
  async enumerate () {
    let options = await this._OPTIONS('/');
    let results = [];

    for (let name in options) {
      let definition = options[name];
      results.push({
        name: definition.name,
        description: definition.description,
        components: Object.assign({
          list: 'maki-resource-list',
          view: 'maki-resource-view'
        }, definition.components),
        routes: definition.routes,
        attributes: definition.attributes,
        names: definition.names
      });
    }

    return options;
  }

  /**
   * HTTP PUT against the configured Authority.
   * @param  {String} path - HTTP Path to request.
   * @param  {Object} obj - Map of parameters to supply.
   * @return {Mixed}        [description]
   */
  async _PUT (key, obj) {
    let self = this;
    let host = self.config.authority;
    let port = self.config.port;
    let protocol = (!self.secure) ? 'http' : 'https';
    let url = `${protocol}://${host}:${port}${key}`;

    let result = null;

    try {
      result = await fetch(url, {
        method: 'put',
        headers: {
          'Accept': CONTENT_TYPE
        },
        body: obj
      });
    } catch (e) {
      console.error('[REMOTE]', 'exception:', e);
    }

    return result;
  }

  /**
   * HTTP GET against the configured Authority.
   * @param  {String} path - HTTP Path to request.
   * @param  {Object} params - Map of parameters to supply.
   * @return {Mixed}        [description]
   */
  async _GET (key, params) {
    let self = this;
    let host = self.config.authority;
    let port = self.config.port;
    let protocol = (!self.secure) ? 'http' : 'https';
    let url = `${protocol}://${host}:${port}${key}`;
    let result = null;
    let headers = {
      'Accept': CONTENT_TYPE
    };

    if (this.config.username && this.config.password) {
      headers['Authorization'] = `Basic ${Buffer.from([
        this.config.username,
        this.config.password
      ].join(':')).toString('base64')}`;
    }

    if (params) {
      url += '?' + querystring.stringify(params);
    }

    try {
      result = await fetch(url, {
        method: 'get',
        headers: headers
      });
    } catch (e) {
      console.error('[REMOTE]', 'exception:', e);
    }

    return result;
  }

  /**
   * HTTP POST against the configured Authority.
   * @param  {String} path - HTTP Path to request.
   * @param  {Object} params - Map of parameters to supply.
   * @return {Mixed}        [description]
   */
  async _POST (key, obj, params) {
    let self = this;
    let host = self.config.authority;
    let port = self.config.port;
    let protocol = (!self.secure) ? 'http' : 'https';
    let url = `${protocol}://${host}:${port}${key}`;
    let result = null;

    try {
      result = await fetch(url, {
        method: 'post',
        headers: {
          'Accept': CONTENT_TYPE
        },
        body: obj
      });
    } catch (e) {
      console.error('[REMOTE]', 'exception:', e);
    }

    /* try {
      let response = await rest.request('POST', url, {
        headers: {
          'Accept': CONTENT_TYPE
        },
        payload: obj,
        // TODO: report to `wreck` as NOT WORKING
        redirect303: true
      });

      if (response.statusCode === 303) {
        result = await self._GET(response.headers.location);
      } else {
        result = await rest.read(response, { json: true });
      }
    } catch (e) {
      console.error('[REMOTE]', 'exception:', e);
    } */

    return result;
  }

  /**
   * HTTP OPTIONS on the configured Authority.
   * @param  {String} path - HTTP Path to request.
   * @param  {Object} params - Map of parameters to supply.
   * @return {Object} - Full description of remote resource.
   */
  async _OPTIONS (key, params) {
    let self = this;
    let host = self.config.authority;
    let port = self.config.port;
    let protocol = (!self.secure) ? 'http' : 'https';
    let url = `${protocol}://${host}:${port}${key}`;
    let result = null;

    try {
      result = await fetch(url, {
        method: 'options',
        headers: {
          'Accept': CONTENT_TYPE
        }
      });
    } catch (e) {
      console.error('[REMOTE]', 'exception:', e);
    }

    return result;
  }

  /**
   * HTTP PATCH on the configured Authority.
   * @param  {String} path - HTTP Path to request.
   * @param  {Object} params - Map of parameters to supply.
   * @return {Object} - Full description of remote resource.
   */
  async _PATCH (key, params) {
    let self = this;
    let host = self.config.authority;
    let port = self.config.port;
    let protocol = (!self.secure) ? 'http' : 'https';
    let url = `${protocol}://${host}:${port}${key}`;
    let result = null;

    try {
      result = await fetch(url, {
        method: 'patch',
        headers: {
          'Accept': CONTENT_TYPE
        },
        body: params
      });
    } catch (e) {
      console.error('[REMOTE]', 'exception:', e);
    }

    return result;
  }

  /**
   * HTTP DELETE on the configured Authority.
   * @param  {String} path - HTTP Path to request.
   * @param  {Object} params - Map of parameters to supply.
   * @return {Object} - Full description of remote resource.
   */
  async _DELETE (key, params) {
    let self = this;
    let host = self.config.authority;
    let port = self.config.port;
    let protocol = (!self.secure) ? 'http' : 'https';
    let url = `${protocol}://${host}:${port}${key}`;
    let result = null;

    try {
      result = await fetch(url, {
        method: 'delete',
        headers: {
          'Accept': CONTENT_TYPE
        }
      });
    } catch (e) {
      console.error('[REMOTE]', 'exception:', e);
    }

    return result;
  }
}

module.exports = Remote;