Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

heroku-client

Package Overview
Dependencies
Maintainers
4
Versions
72
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

heroku-client - npm Package Compare versions

Comparing version 2.4.3 to 3.0.0-alpha1

index.js

608

lib/request.js

@@ -1,395 +0,317 @@

'use strict';
'use strict'
var http = require('http');
var https = require('https');
var tunnel = require('tunnel-agent');
var extend = require('util')._extend;
var q = require('q');
var URL = require('./url');
var pjson = require('../package.json');
var http = require('http')
var https = require('https')
var tunnel = require('tunnel-agent')
var extend = require('util')._extend
var URL = require('./url')
var pjson = require('../package.json')
var fs = require('fs')
var path = require('path')
module.exports = Request;
/*
* Create an object capable of making API
* calls. Accepts custom request options and
* a callback function.
* Object capable of making API calls.
*/
function Request(options, callback) {
this.options = options || {};
this.debug = options.debug;
this.debugHeaders = options.debugHeaders;
if (this.debug) {
q.longStackSupport = true;
}
var url = URL(options.host || 'https://api.heroku.com');
this.host = url.host;
this.port = url.port;
this.secure = url.secure;
this.partial = options.partial;
this.callback = callback;
this.deferred = q.defer();
this.userAgent = options.userAgent || 'node-heroku-client/' + pjson.version;
this.parseJSON = options.hasOwnProperty('parseJSON') ? options.parseJSON : true;
this.nextRange = 'id ]..; max=1000';
this.logger = options.logger;
this.cache = options.cache;
this.middleware = options.middleware || function (_, cb) {cb();};
if (process.env.HEROKU_HTTP_PROXY_HOST) {
var tunnelFunc;
if (this.secure) {
tunnelFunc = tunnel.httpsOverHttp;
class Request {
constructor (options) {
this.options = options || {}
this.debug = options.debug
this.debugHeaders = options.debugHeaders
var url = URL(options.host || 'https://api.heroku.com')
this.host = url.host
this.port = url.port
this.secure = url.secure
this.partial = options.partial
this.userAgent = options.userAgent || 'node-heroku-client/' + pjson.version
this.parseJSON = options.hasOwnProperty('parseJSON') ? options.parseJSON : true
this.nextRange = 'id ..; max=1000'
this.logger = options.logger
this.middleware = options.middleware || function (_, cb) { cb() }
this.certs = getCerts(this.debug)
if (process.env.HEROKU_HTTP_PROXY_HOST) {
var tunnelFunc
if (this.secure) {
tunnelFunc = tunnel.httpsOverHttp
} else {
tunnelFunc = tunnel.httpOverHttp
}
var agentOpts = {
proxy: {
host: process.env.HEROKU_HTTP_PROXY_HOST,
port: process.env.HEROKU_HTTP_PROXY_PORT || 8080,
proxyAuth: process.env.HEROKU_HTTP_PROXY_AUTH
}
}
if (this.certs.length > 0) {
agentOpts.ca = this.certs
}
this.agent = tunnelFunc(agentOpts)
} else {
tunnelFunc = tunnel.httpOverHttp;
}
this.agent = tunnelFunc({
proxy: {
host: process.env.HEROKU_HTTP_PROXY_HOST,
port: process.env.HEROKU_HTTP_PROXY_PORT || 8080,
proxyAuth: process.env.HEROKU_HTTP_PROXY_AUTH
if (this.secure) {
this.agent = new https.Agent({ maxSockets: Number(process.env.HEROKU_CLIENT_MAX_SOCKETS) || 5000 })
} else {
this.agent = new http.Agent({ maxSockets: Number(process.env.HEROKU_CLIENT_MAX_SOCKETS) || 5000 })
}
});
} else {
if (this.secure) {
this.agent = new https.Agent({ maxSockets: Number(process.env.HEROKU_CLIENT_MAX_SOCKETS) || 5000 });
} else {
this.agent = new http.Agent({ maxSockets: Number(process.env.HEROKU_CLIENT_MAX_SOCKETS) || 5000 });
}
}
}
/*
* Perform the actual API request.
*/
request () {
return new Promise((resolve, reject) => {
this.resolve = resolve
this.reject = reject
var headers = extend({
'Accept': 'application/vnd.heroku+json; version=3',
'Content-type': 'application/json',
'User-Agent': this.userAgent,
'Range': this.nextRange
}, this.options.headers)
// remove null|undefined headers
for (var k in Object.keys(headers)) {
if (headers[k] === null || headers[k] === undefined) {
delete headers[k]
}
}
/*
* Instantiate a Request object and makes a
* request, returning the request promise.
*/
Request.request = function request(options, callback) {
var req = new Request(options, function (err, body) {
if (callback) { callback(err, body); }
});
var requestOptions = {
agent: this.agent,
host: this.host,
port: this.port,
path: this.options.path,
auth: this.options.auth || ':' + this.options.token,
method: this.options.method || 'GET',
rejectUnauthorized: this.options.rejectUnauthorized,
headers: headers
}
if (this.certs.length > 0) {
requestOptions.ca = this.certs
}
return req.request();
};
let req
if (this.secure) {
req = https.request(requestOptions, this.handleResponse.bind(this))
} else {
req = http.request(requestOptions, this.handleResponse.bind(this))
}
/*
* Check for a cached response, then
* perform an API request. Return the
* request object's promise.
*/
Request.prototype.request = function request() {
this.getCache(this.performRequest.bind(this));
return this.deferred.promise;
};
this.logRequest(req)
this.writeBody(req)
this.setRequestTimeout(req)
/*
* Perform the actual API request.
*/
Request.prototype.performRequest = function performRequest() {
var req;
req.on('error', reject)
this.options.headers = this.options.headers || {};
var headers = extend({
'Accept': 'application/vnd.heroku+json; version=3',
'Content-type': 'application/json',
'User-Agent': this.userAgent,
'Range': this.nextRange
}, this.options.headers);
req.end()
})
}
// remove null|undefined headers
for (var k in headers) {
if (headers.hasOwnProperty(k)) {
if (headers[k] === null || headers[k] === undefined) {
delete headers[k];
/*
* Handle an API response, returning the API response.
*/
handleResponse (res) {
this.middleware(res, () => {
this.logResponse(res)
if (res.statusCode === 304) {
this.updateAggregate(this.cachedResponse.body)
this.resolve(this.aggregate)
return
}
}
concat(res).then((data) => {
if (this.debug) {
console.error('<-- ' + data)
}
if (res.statusCode.toString().match(/^2\d{2}$/)) {
this.handleSuccess(res, data)
} else {
this.handleFailure(res, data)
}
})
})
}
var requestOptions = {
agent: this.agent,
host: this.host,
port: this.port,
path: this.options.path,
auth: this.options.auth || ':' + this.options.token,
method: this.options.method || 'GET',
rejectUnauthorized: this.options.rejectUnauthorized,
headers: headers
};
if (this.cachedResponse) {
headers['If-None-Match'] = this.cachedResponse.etag;
logRequest (req) {
if (this.debug) {
console.error('--> ' + req.method + ' ' + req.path)
}
if (this.debugHeaders) {
printHeaders(req._headers)
}
}
if (this.secure) {
req = https.request(requestOptions, this.handleResponse.bind(this));
} else {
req = http.request(requestOptions, this.handleResponse.bind(this));
/*
* Log the API response.
*/
logResponse (res) {
if (this.logger) {
this.logger.log({
status: res.statusCode,
content_length: res.headers['content-length'],
request_id: res.headers['request-id']
})
}
if (this.debug) {
console.error('<-- ' + res.statusCode + ' ' + res.statusMessage)
}
if (this.debugHeaders) {
printHeaders(res.headers)
}
}
this.logRequest(req);
this.writeBody(req);
this.setRequestTimeout(req);
req.on('error', this.handleError.bind(this));
req.end();
return this.deferred.promise;
};
/*
* Handle an API response, returning the API response.
*/
Request.prototype.handleResponse = function handleResponse(res) {
var self = this;
this.middleware(res, function () {
self.logResponse(res);
if (res.statusCode === 304 && self.cachedResponse) {
if (self.cachedResponse.nextRange) {
self.nextRequest(self.cachedResponse.nextRange, self.cachedResponse.body);
} else {
self.updateAggregate(self.cachedResponse.body);
self.deferred.resolve(self.aggregate);
self.callback(null, self.aggregate);
/*
* If the request options include a body,
* write the body to the request and set
* an appropriate 'Content-length' header.
*/
writeBody (req) {
if (this.options.body) {
var body = this.options.body
if (this.options.json !== false) { body = JSON.stringify(body) }
if (this.debug) {
console.error('--> ' + body)
}
return;
}
concat(res, function (data) {
if (self.debug) {
console.error('<-- ' + data);
}
if (res.statusCode.toString().match(/^2\d{2}$/)) {
self.handleSuccess(res, data);
} else {
self.handleFailure(res, data);
}
});
});
};
function printHeaders (headers) {
var key;
var value;
for (key in headers) {
if (headers.hasOwnProperty(key)) {
value = key.toUpperCase() === 'AUTHORIZATION' ? 'REDACTED' : headers[key];
console.error(' ' + key + '=' + value);
req.setHeader('Content-length', Buffer.byteLength(body, 'utf8'))
req.write(body)
} else {
req.setHeader('Content-length', 0)
}
}
}
Request.prototype.logRequest = function logRequest(req) {
if (this.debug) {
console.error('--> ' + req.method + ' ' + req.path);
}
if (this.debugHeaders) {
printHeaders(req._headers);
}
};
/*
* If the request options include a timeout,
* set the timeout and provide a callback
* function in case the request exceeds the
* timeout period.
*/
setRequestTimeout (req) {
if (!this.options.timeout) return
/*
* Log the API response.
*/
Request.prototype.logResponse = function logResponse(res) {
if (this.logger) {
this.logger.log({
status : res.statusCode,
content_length: res.headers['content-length'],
request_id : res.headers['request-id']
});
req.setTimeout(this.options.timeout, () => {
var err = new Error('Request took longer than ' + this.options.timeout + 'ms to complete.')
req.abort()
this.reject(err)
})
}
if (this.debug) {
console.error('<-- ' + res.statusCode + ' ' + res.statusMessage);
}
if (this.debugHeaders) {
printHeaders(res.headers);
}
};
/*
* If the request options include a body,
* write the body to the request and set
* an appropriate 'Content-length' header.
*/
Request.prototype.writeBody = function writeBody(req) {
if (this.options.body) {
var body = this.options.body;
if (this.options.json !== false) { body = JSON.stringify(body); }
if (this.debug) {
console.error('--> ' + body);
/*
* Get the request body, and parse it (or not) as appropriate.
* - Parse JSON by default.
* - If parseJSON is `false`, it will not parse.
*/
parseBody (body) {
if (this.parseJSON) {
return JSON.parse(body || '{}')
} else {
return body
}
req.setHeader('Content-length', Buffer.byteLength(body, 'utf8'));
req.write(body);
} else {
req.setHeader('Content-length', 0);
}
};
/*
* In the event of a non-successful API request,
* fail with an appropriate error message and
* status code.
*/
handleFailure (res, buffer) {
var message = 'Expected response to be successful, got ' + res.statusCode
var err
/*
* If the request options include a timeout,
* set the timeout and provide a callback
* function in case the request exceeds the
* timeout period.
*/
Request.prototype.setRequestTimeout = function setRequestTimeout(req) {
if (!this.options.timeout) { return; }
err = new Error(message)
err.statusCode = res.statusCode
err.body = this.parseBody(buffer)
req.setTimeout(this.options.timeout, function () {
var err = new Error('Request took longer than ' + this.options.timeout + 'ms to complete.');
this.reject(err)
}
req.abort();
/*
* In the event of a successful API response,
* respond with the response body.
*/
handleSuccess (res, buffer) {
var body = this.parseBody(buffer)
this.deferred.reject(err);
this.callback(err);
}.bind(this));
};
if (!this.partial && res.headers['next-range']) {
this.nextRequest(res.headers['next-range'], body)
} else {
this.updateAggregate(body)
this.resolve(this.aggregate)
}
}
/*
* Get the request body, and parse it (or not) as appropriate.
* - Parse JSON by default.
* - If parseJSON is `false`, it will not parse.
*/
Request.prototype.parseBody = function parseBody(body) {
if (this.parseJSON) {
return JSON.parse(body || '{}');
} else {
return body;
/*
* Since this request isn't the full response (206 or
* 304 with a cached Next-Range), perform the next
* request for more data.
*/
nextRequest (nextRange, body) {
this.updateAggregate(body)
this.nextRange = nextRange
// The initial range header passed in (if there was one), is no longer valid, and should no longer take precedence
delete (this.options.headers.Range)
this.request()
}
};
/*
* In the event of an error in performing
* the API request, reject the deferred
* object and return an error to the callback.
*/
Request.prototype.handleError = function handleError(err) {
this.deferred.reject(err);
this.callback(err);
};
/*
* If given an object, sets aggregate to object,
* otherwise concats array onto aggregate.
*/
updateAggregate (aggregate) {
if (aggregate instanceof Array) {
this.aggregate = this.aggregate || []
this.aggregate = this.aggregate.concat(aggregate)
} else {
this.aggregate = aggregate
}
}
}
function sslCertFile () {
return process.env.SSL_CERT_FILE ? [process.env.SSL_CERT_FILE] : []
}
/*
* In the event of a non-successful API request,
* fail with an appropriate error message and
* status code.
*/
Request.prototype.handleFailure = function handleFailure(res, buffer) {
var callback = this.callback;
var deferred = this.deferred;
var message = 'Expected response to be successful, got ' + res.statusCode;
var err;
err = new Error(message);
err.statusCode = res.statusCode;
err.body = this.parseBody(buffer);
deferred.reject(err);
callback(err);
};
/*
* In the event of a successful API response,
* respond with the response body.
*/
Request.prototype.handleSuccess = function handleSuccess(res, buffer) {
var callback = this.callback;
var deferred = this.deferred;
var body = this.parseBody(buffer);
this.setCache(res, body);
if (!this.partial && res.headers['next-range']) {
this.nextRequest(res.headers['next-range'], body);
function sslCertDir () {
var certDir = process.env.SSL_CERT_DIR
if (certDir) {
return fs.readdirSync(certDir).map((f) => path.join(certDir, f))
} else {
this.updateAggregate(body);
deferred.resolve(this.aggregate);
callback(null, this.aggregate);
return []
}
};
}
function getCerts (debug) {
var filenames = sslCertFile().concat(sslCertDir())
/*
* Since this request isn't the full response (206 or
* 304 with a cached Next-Range), perform the next
* request for more data.
*/
Request.prototype.nextRequest = function nextRequest(nextRange, body) {
this.updateAggregate(body);
this.nextRange = nextRange;
// The initial range header passed in (if there was one), is no longer valid, and should no longer take precedence
delete (this.options.headers.Range);
this.request();
};
/*
* If given an object, sets aggregate to object,
* otherwise concats array onto aggregate.
*/
Request.prototype.updateAggregate = function updateAggregate(aggregate) {
if (aggregate instanceof Array) {
this.aggregate = this.aggregate || [];
this.aggregate = this.aggregate.concat(aggregate);
} else {
this.aggregate = aggregate;
if (filenames.length > 0 && debug) {
console.error('Adding the following trusted certificate authorities')
}
};
/*
* If the cache client is alive, get the
* cached response from the cache.
*/
Request.prototype.getCache = function getCache(callback) {
if (!this.cache) { return callback(null); }
var key = this.getCacheKey();
var self = this;
this.cache.store.get(key, function (err, res) {
if (err) { return self.deferred.reject(err); }
self.cachedResponse = res ? self.cache.encryptor.decrypt(res.toString()) : res;
callback();
});
};
return filenames.map(function (filename) {
if (debug) {
console.error(' ' + filename)
}
return fs.readFileSync(filename)
})
}
/*
* If the cache client is alive, write the
* provided response and body to the cache.
*/
Request.prototype.setCache = function setCache(res, body) {
if ((!this.cache) || !(res.headers.etag)) { return; }
var key = this.getCacheKey();
var value = {
body : body,
etag : res.headers.etag,
nextRange: res.headers['next-range']
};
if (this.debug) {
console.error('<-- writing to cache');
function printHeaders (headers) {
var key
var value
for (key in headers) {
if (headers.hasOwnProperty(key)) {
value = key.toUpperCase() === 'AUTHORIZATION' ? 'REDACTED' : headers[key]
console.error(' ' + key + '=' + value)
}
}
}
value = this.cache.encryptor.encrypt(value);
this.cache.store.set(key, value);
};
function concat (stream) {
return new Promise((resolve) => {
var strings = []
stream.on('data', (data) => strings.push(data))
stream.on('end', () => resolve(strings.join('')))
})
}
/*
* Returns a cache key comprising the request path,
* the 'Next Range' header, and the user's API token.
*/
Request.prototype.getCacheKey = function getCacheKey() {
var path = JSON.stringify([this.options.path, this.nextRange, this.options.token]);
return this.cache.encryptor.hmac(path);
};
function concat (stream, callback) {
var strings = [];
stream.on('data', function (data) {
strings.push(data);
});
stream.on('end', function () {
callback(strings.join(''));
});
}
module.exports = Request

@@ -1,22 +0,22 @@

'use strict';
'use strict'
var url = require('url');
var url = require('url')
module.exports = function(u) {
module.exports = function (u) {
if (u.indexOf('http') !== 0 && u.indexOf('https') !== 0) {
u = 'https://' + u;
u = 'https://' + u
}
var uu = url.parse(u);
var port = uu.port;
var uu = url.parse(u)
var port = uu.port
if (!port) {
if (uu.protocol === 'https:') {
port = '443';
port = '443'
} else {
port = '80';
port = '80'
}
}
var secure = uu.protocol === 'https:' || uu.port === '443';
var secure = uu.protocol === 'https:' || uu.port === '443'
return { host: uu.hostname, port: parseInt(port), secure: secure };
};
return { host: uu.hostname, port: parseInt(port), secure: secure }
}
{
"name": "heroku-client",
"description": "A wrapper for the Heroku v3 API",
"version": "2.4.3",
"author": "Jonathan Clem",
"version": "3.0.0-alpha1",
"author": "Jeff Dickey",
"bugs": {

@@ -11,2 +11,3 @@ "url": "https://github.com/heroku/node-heroku-client/issues"

"Jonathan Clem",
"Jeff Dickey",
"Ray McDermott",

@@ -23,24 +24,10 @@ "Bob Zoller",

"devDependencies": {
"jasmine-node": "^1.14.5",
"jshint": "^2.5.3",
"simple-encryptor": "^1.0.3"
"ava": "0.14.0",
"codecov": "1.0.1",
"nock": "8.0.0",
"nyc": "6.4.4",
"standard": "7.1.0"
},
"jshintConfig": {
"curly": true,
"eqeqeq": true,
"forin": true,
"globals": {
"afterEach": false,
"before": false,
"beforeEach": false,
"describe": false,
"expect": false,
"it": false,
"jasmine": false,
"spyOn": false
},
"node": true,
"quotmark": "single",
"undef": true,
"unused": true
"engines": {
"node": ">=4.0.0"
},

@@ -51,3 +38,2 @@ "keywords": [

"license": "MIT",
"main": "./lib/heroku.js",
"repository": {

@@ -58,4 +44,4 @@ "type": "git",

"scripts": {
"test": "jasmine-node spec && jshint lib spec"
"test": "nyc ava && standard"
}
}

@@ -1,3 +0,7 @@

# heroku-client [![Build Status](https://travis-ci.org/heroku/node-heroku-client.png?branch=master)](https://travis-ci.org/heroku/node-heroku-client)
# heroku-client
[![Build Status](https://travis-ci.org/heroku/node-heroku-client.png?branch=master)](https://travis-ci.org/heroku/node-heroku-client)
[![codecov](https://codecov.io/gh/heroku/node-heroku-client/branch/master/graph/badge.svg)](https://codecov.io/gh/heroku/node-heroku-client)
[![Code Climate](https://codeclimate.com/github/heroku/node-heroku-client/badges/gpa.svg)](https://codeclimate.com/github/heroku/node-heroku-client)
A wrapper around the [v3 Heroku API][platform-api-reference].

@@ -4,0 +8,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc