Source: lib/travis-status-http.js

  1. /**
  2. * @copyright Copyright 2016 Kevin Locke <kevin@kevinlocke.name>
  3. * @license MIT
  4. */
  5. 'use strict';
  6. const TravisHttp = require('travis-ci/lib/travis-http');
  7. const caseless = require('caseless');
  8. const { inherits } = require('util');
  9. const request = require('request');
  10. const requestPackageJson = require('request/package.json');
  11. const constants = require('./constants');
  12. const packageJson = require('../package.json');
  13. const trimSlash = require('./trim-slash');
  14. /** Default <code>User-Agent</code> header to send with API requests.
  15. * @const
  16. * @private
  17. */
  18. const DEFAULT_USER_AGENT = `node-travis-status/${packageJson.version
  19. } Request/${requestPackageJson.version
  20. } Node.js/${process.version}`;
  21. /** Creates an instance of the travis-ci HTTP agent with a given endpoint
  22. * and request options.
  23. *
  24. * This class has the following features above <code>TravisHttp</code>:
  25. * - Uses a newer version of request (for gzip and proxy support)
  26. * - Supports caller-specified API endpoints (e.g. for enterprise or local use)
  27. * - Supports caller-specified request options (e.g. pooling, strictSSL, proxy,
  28. * tunnel, timeouts, etc.)
  29. * - Improved error values which are real Error instances and include the
  30. * HTTP response information, regardless of content.
  31. *
  32. * @param {string=} endpoint Travis CI API endpoint (base URL).
  33. * (default: {@link TravisStatusChecker.ORG_URI})
  34. * @param {Object=} options Options to pass to the <code>request</code>
  35. * constructor.
  36. */
  37. function TravisStatusHttp(endpoint, options) {
  38. if (endpoint && typeof endpoint !== 'string') {
  39. throw new TypeError('endpoint must be a string');
  40. }
  41. endpoint = endpoint && trimSlash(endpoint);
  42. if (options && typeof options !== 'object') {
  43. throw new TypeError('options must be an object');
  44. }
  45. options = { gzip: true, ...options };
  46. options.headers = { ...options.headers };
  47. // Careful about providing default values for case-insensitive headers
  48. const caselessHeaders = caseless(options.headers);
  49. // The Travis CI API docs say
  50. // "Always set the Accept header to application/vnd.travis-ci.2+json"
  51. // but the API actually sends Content-Type application/json.
  52. // Declare that we accept either.
  53. if (!caselessHeaders.has('Accept')) {
  54. options.headers.Accept =
  55. 'application/vnd.travis-ci.2+json, application/json';
  56. }
  57. if (!caselessHeaders.has('User-Agent')) {
  58. options.headers['User-Agent'] = DEFAULT_USER_AGENT;
  59. }
  60. TravisHttp.call(
  61. this,
  62. endpoint === constants.PRO_URI,
  63. options.headers,
  64. );
  65. this._endpoint = endpoint || constants.ORG_URI;
  66. // Set this._headers as TravisHttp does
  67. this._headers = options.headers;
  68. delete options.headers;
  69. this._options = options;
  70. }
  71. inherits(TravisStatusHttp, TravisHttp);
  72. TravisStatusHttp.prototype._getHeaders = function _getHeaders() {
  73. let headers = this._headers;
  74. const token = this._getAccessToken();
  75. if (token) {
  76. let quotedToken;
  77. // From https://tools.ietf.org/html/rfc7235#section-2.1
  78. if (/^[A-Za-z0-9._~+/-]+$/.test(token)) {
  79. // The token is a valid HTTP token68 and doesn't require quoting
  80. quotedToken = token;
  81. } else {
  82. // Convert it to a quoted string
  83. // Note: No validation is done to ensure it doesn't have prohibited
  84. // control characters, since there's no way to quote them and I'm more
  85. // likely to screw up unicode handling than catch real errors.
  86. quotedToken = `"${token.replace(/["\\]/g, '\\$&')}"`;
  87. }
  88. headers = { ...headers };
  89. caseless(headers).del('Authorization');
  90. headers.Authorization = `token ${quotedToken}`;
  91. }
  92. return headers;
  93. };
  94. TravisStatusHttp.prototype.request = function(method, path, data, callback) {
  95. if (typeof data === 'function') {
  96. callback = data;
  97. data = undefined;
  98. }
  99. const options = {
  100. ...this._options,
  101. method,
  102. url: this._endpoint + path,
  103. headers: this._getHeaders(),
  104. };
  105. if (data instanceof Buffer) {
  106. options.body = data;
  107. } else {
  108. options.json = data || true;
  109. }
  110. return request(options, (errRequest, res, body) => {
  111. if (errRequest) {
  112. callback(errRequest);
  113. return;
  114. }
  115. let err;
  116. if (res.statusCode >= 400) {
  117. err = new Error(res.statusMessage);
  118. }
  119. if (typeof body === 'string') {
  120. try {
  121. body = JSON.parse(body);
  122. } catch (errJson) {
  123. err = err || errJson;
  124. }
  125. }
  126. // Note: This error handling deviates from travis-ci (which returns body
  127. // or statusCode as err). I think this is much more sane.
  128. if (err) {
  129. err.statusCode = res.statusCode;
  130. err.statusMessage = res.statusMessage;
  131. err.headers = res.headers;
  132. err.body = body;
  133. callback(err);
  134. return;
  135. }
  136. callback(null, body);
  137. });
  138. };
  139. module.exports = TravisStatusHttp;