Movatterモバイル変換


[0]ホーム

URL:


Requestable.js

/** * @file * @copyright  2016 Yahoo Inc. * @license    Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. *             Github.js is freely distributable. */import axios from 'axios';import debug from 'debug';import {Base64} from 'js-base64';const log = debug('github:request');/** * The error structure returned when a network call fails */class ResponseError extends Error {   /**    * Construct a new ResponseError    * @param {string} message - an message to return instead of the the default error message    * @param {string} path - the requested path    * @param {Object} response - the object returned by Axios    */   constructor(message, path, response) {      super(message);      this.path = path;      this.request = response.config;      this.response = (response || {}).response || response;      this.status = response.status;   }}/** * Requestable wraps the logic for making http requests to the API */class Requestable {   /**    * Either a username and password or an oauth token for Github    * @typedef {Object} Requestable.auth    * @prop {string} [username] - the Github username    * @prop {string} [password] - the user's password    * @prop {token} [token] - an OAuth token    */   /**    * Initialize the http internals.    * @param {Requestable.auth} [auth] - the credentials to authenticate to Github. If auth is    *                                  not provided request will be made unauthenticated    * @param {string} [apiBase=https://api.github.com] - the base Github API URL    * @param {string} [AcceptHeader=v3] - the accept header for the requests    */   constructor(auth, apiBase, AcceptHeader) {      this.__apiBase = apiBase || 'https://api.github.com';      this.__auth = {         token: auth.token,         username: auth.username,         password: auth.password,      };      this.__AcceptHeader = AcceptHeader || 'v3';      if (auth.token) {         this.__authorizationHeader = 'token ' + auth.token;      } else if (auth.username && auth.password) {         this.__authorizationHeader = 'Basic ' + Base64.encode(auth.username + ':' + auth.password);      }   }   /**    * Compute the URL to use to make a request.    * @private    * @param {string} path - either a URL relative to the API base or an absolute URL    * @return {string} - the URL to use    */   __getURL(path) {      let url = path;      if (path.indexOf('//') === -1) {         url = this.__apiBase + path;      }      let newCacheBuster = 'timestamp=' + new Date().getTime();      return url.replace(/(timestamp=\d+)/, newCacheBuster);   }   /**    * Compute the headers required for an API request.    * @private    * @param {boolean} raw - if the request should be treated as JSON or as a raw request    * @param {string} AcceptHeader - the accept header for the request    * @return {Object} - the headers to use in the request    */   __getRequestHeaders(raw, AcceptHeader) {      let headers = {         'Content-Type': 'application/json;charset=UTF-8',         'Accept': 'application/vnd.github.' + (AcceptHeader || this.__AcceptHeader),      };      if (raw) {         headers.Accept += '.raw';      }      headers.Accept += '+json';      if (this.__authorizationHeader) {         headers.Authorization = this.__authorizationHeader;      }      return headers;   }   /**    * Sets the default options for API requests    * @protected    * @param {Object} [requestOptions={}] - the current options for the request    * @return {Object} - the options to pass to the request    */   _getOptionsWithDefaults(requestOptions = {}) {      if (!(requestOptions.visibility || requestOptions.affiliation)) {         requestOptions.type = requestOptions.type || 'all';      }      requestOptions.sort = requestOptions.sort || 'updated';      requestOptions.per_page = requestOptions.per_page || '100'; // eslint-disable-line      return requestOptions;   }   /**    * if a `Date` is passed to this function it will be converted to an ISO string    * @param {*} date - the object to attempt to cooerce into an ISO date string    * @return {string} - the ISO representation of `date` or whatever was passed in if it was not a date    */   _dateToISO(date) {      if (date && (date instanceof Date)) {         date = date.toISOString();      }      return date;   }   /**    * A function that receives the result of the API request.    * @callback Requestable.callback    * @param {Requestable.Error} error - the error returned by the API or `null`    * @param {(Object|true)} result - the data returned by the API or `true` if the API returns `204 No Content`    * @param {Object} request - the raw {@linkcode https://github.com/mzabriskie/axios#response-schema Response}    */   /**    * Make a request.    * @param {string} method - the method for the request (GET, PUT, POST, DELETE)    * @param {string} path - the path for the request    * @param {*} [data] - the data to send to the server. For HTTP methods that don't have a body the data    *                   will be sent as query parameters    * @param {Requestable.callback} [cb] - the callback for the request    * @param {boolean} [raw=false] - if the request should be sent as raw. If this is a falsy value then the    *                              request will be made as JSON    * @return {Promise} - the Promise for the http request    */   _request(method, path, data, cb, raw) {      const url = this.__getURL(path);      const AcceptHeader = (data || {}).AcceptHeader;      if (AcceptHeader) {         delete data.AcceptHeader;      }      const headers = this.__getRequestHeaders(raw, AcceptHeader);      let queryParams = {};      const shouldUseDataAsParams = data && (typeof data === 'object') && methodHasNoBody(method);      if (shouldUseDataAsParams) {         queryParams = data;         data = undefined;      }      const config = {         url: url,         method: method,         headers: headers,         params: queryParams,         data: data,         responseType: raw ? 'text' : 'json',      };      log(`${config.method} to ${config.url}`);      const requestPromise = axios(config).catch(callbackErrorOrThrow(cb, path));      if (cb) {         requestPromise.then((response) => {            if (response.data && Object.keys(response.data).length > 0) {               // When data has results               cb(null, response.data, response);            } else if (config.method !== 'GET' && Object.keys(response.data).length < 1) {               // True when successful submit a request and receive a empty object               cb(null, (response.status < 300), response);            } else {               cb(null, response.data, response);            }         });      }      return requestPromise;   }   /**    * Make a request to an endpoint the returns 204 when true and 404 when false    * @param {string} path - the path to request    * @param {Object} data - any query parameters for the request    * @param {Requestable.callback} cb - the callback that will receive `true` or `false`    * @param {method} [method=GET] - HTTP Method to use    * @return {Promise} - the promise for the http request    */   _request204or404(path, data, cb, method = 'GET') {      return this._request(method, path, data)         .then(function success(response) {            if (cb) {               cb(null, true, response);            }            return true;         }, function failure(response) {            if (response.response.status === 404) {               if (cb) {                  cb(null, false, response);               }               return false;            }            if (cb) {               cb(response);            }            throw response;         });   }   /**    * Make a request and fetch all the available data. Github will paginate responses so for queries    * that might span multiple pages this method is preferred to {@link Requestable#request}    * @param {string} path - the path to request    * @param {Object} options - the query parameters to include    * @param {Requestable.callback} [cb] - the function to receive the data. The returned data will always be an array.    * @param {Object[]} results - the partial results. This argument is intended for interal use only.    * @return {Promise} - a promise which will resolve when all pages have been fetched    * @deprecated This will be folded into {@link Requestable#_request} in the 2.0 release.    */   _requestAllPages(path, options, cb, results) {      results = results || [];      return this._request('GET', path, options)         .then((response) => {            let thisGroup;            if (response.data instanceof Array) {               thisGroup = response.data;            } else if (response.data.items instanceof Array) {               thisGroup = response.data.items;            } else {               let message = `cannot figure out how to append ${response.data} to the result set`;               throw new ResponseError(message, path, response);            }            results.push(...thisGroup);            const nextUrl = getNextPage(response.headers.link);            if (nextUrl && typeof options.page !== 'number') {               log(`getting next page: ${nextUrl}`);               return this._requestAllPages(nextUrl, options, cb, results);            }            if (cb) {               cb(null, results, response);            }            response.data = results;            return response;         }).catch(callbackErrorOrThrow(cb, path));   }}module.exports = Requestable;// ////////////////////////// ////  Private helper functions  //// ////////////////////////// //const METHODS_WITH_NO_BODY = ['GET', 'HEAD', 'DELETE'];function methodHasNoBody(method) {   return METHODS_WITH_NO_BODY.indexOf(method) !== -1;}function getNextPage(linksHeader = '') {   const links = linksHeader.split(/\s*,\s*/); // splits and strips the urls   return links.reduce(function(nextUrl, link) {      if (link.search(/rel="next"/) !== -1) {         return (link.match(/<(.*)>/) || [])[1];      }      return nextUrl;   }, undefined);}function callbackErrorOrThrow(cb, path) {   return function handler(object) {      let error;      if (object.hasOwnProperty('config')) {         const {response: {status, statusText}, config: {method, url}} = object;         let message = (`${status} error making request ${method} ${url}: "${statusText}"`);         error = new ResponseError(message, path, object);         log(`${message} ${JSON.stringify(object.data)}`);      } else {         error = object;      }      if (cb) {         log('going to error callback');         cb(error);      } else {         log('throwing error');         throw error;      }   };}


[8]ページ先頭

©2009-2025 Movatter.jp