Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
2.1.0 September 12, 2016
- Added config.hostname, config.port, and config.secure

2.0.0 August 24, 2016
- Added `accounts` resource
+ `accounts#all`
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ var client = require('button-client-node')('sk-XXX', {

##### Config

* `timeout`: The time in ms for network requests to abort
* `timeout`: The time in ms for network requests to abort. Defaults to false.
* `promise`: A function which accepts a resolver function and returns a promise. Used to integrate with the promise library of your choice (i.e. es6 Promises, Bluebird, Q, etc). If `promise` is supplied and is a function, all API functions will ignore any passed callbacks and instead return a promise.
* `hostname`: Defaults to `api.usebutton.com`
* `port`: Defaults to `443` if `config.secure`, else defaults to `80`.
* `secure`: Whether or not to use HTTPS. Defaults to true. **N.B: Button's API is only exposed through HTTPS. This option is provided purely as a convenience for testing and development.**


#### Node-style Callbacks
Expand Down
27 changes: 21 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
var resources = require('./lib').resources;
var maybePromise = require('./lib').maybePromise;
var request = require('./lib').request;
var merge = require('./lib').merge;
var compact = require('./lib').compact;
var version = require('./package.json').version;

var configDefaults = {
secure: true,
timeout: false
};

module.exports = function client(apiKey, config) {
//
// #client provides the top-level interface to making API requests to Button.
Expand All @@ -11,6 +18,9 @@ module.exports = function client(apiKey, config) {
//
// @param {string} apiKey your Button API key
// @param {Object=} config an optional object
// @param {string=} config.hostname defaults to 'api.usebutton.com'
// @param {number=} config.port defaults to 443 if config.secure else 80
// @param {bool=} config.secure will use HTTPS if true and HTTP if false
// @param {number=} config.timeout a timeout in ms to abort API calls
// @param {Func=} config.promise a function which should return a promise
// @returns {Object} a client
Expand All @@ -19,21 +29,26 @@ module.exports = function client(apiKey, config) {
throw new Error('Must provide a Button API key. Find yours at https://app.usebutton.com/settings/organization');
}

if (!config) {
config = {};
}
config = merge(configDefaults, config);

var requestOptions = {
var requestConfig = merge({
hostname: 'api.usebutton.com',
port: config.secure ? 443 : 80
}, compact({
hostname: config.hostname,
port: config.port
}));

var requestOptions = merge(requestConfig, {
auth: apiKey + ':',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'button-client-node/' + version + ' node/' + process.versions.node
}
};
});

var maybePromiseRequest = maybePromise(
request(config.timeout),
request(config.timeout, config.secure),
config.promise
);

Expand Down
8 changes: 5 additions & 3 deletions lib/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ function merge() {
var res = {};

Array.prototype.slice.call(arguments).forEach(function(obj) {
Object.keys(obj).forEach(function(key) {
res[key] = obj[key];
});
if (Object.prototype.toString.call(obj) === '[object Object]') {
Object.keys(obj).forEach(function(key) {
res[key] = obj[key];
});
}
});

return res;
Expand Down
13 changes: 8 additions & 5 deletions lib/request.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
'use strict';

var https = require('https');
var http = require('http');
var parse = require('url').parse;
var once = require('./once');

function request(timeout) {
function request(timeout, secure) {
//
// request issues an https request. To generate a function that will issue
// network requests, you must call this module with an optional timeout.
// network requests, you must call this module with an optional timeout and
// optional boolean for whether or not to use HTTPS.
//
// The returned function may then be invoked with arguments specific to a
// given request.
//
// ## Usage
//
// request(3000)({
// request(3000, true)({
// method: 'GET',
// path: '/v1/blorp/blorp-1'
// hostname: 'api.bloop.com',
// }, function(err, res) {
// ...
// });
//
// request(3000)({
// request(3000, true)({
// method: 'POST',
// path: '/v1/blorp/blorp-1'
// hostname: 'api.bloop.com',
Expand All @@ -40,7 +43,7 @@ function request(timeout) {

callback = once(callback);

var req = https.request(options);
var req = (secure ? https : http).request(options);

req.on('response', responseHandler(callback));
req.on('error', errorHandler(callback));
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "button-client-node",
"version": "2.0.0",
"version": "2.1.0",
"description": "node.js client for the Button Order API",
"main": "index.js",
"scripts": {
Expand Down
68 changes: 68 additions & 0 deletions test/index-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

var expect = require('expect.js');
var nock = require('nock');

var client = require('../index');

Expand All @@ -20,4 +21,71 @@ describe('client', function() {
expect(typeof client('sk-XXX', {}).orders).to.eql('object');
});

describe('config', function() {
before(function() {
nock.disableNetConnect();
});

after(function() {
nock.enableNetConnect();
});

it('defaults config options', function(done) {
var c = client('sk-XXX').orders;
var orderId = 'btnorder-XXX';
var scope = nock('https://api.usebutton.com:443')
.get('/v1/order/' + orderId)
.reply(200, { meta: { status: 'ok' }, 'object': {} });

c.get(orderId, function(err) {
expect(err).to.be(null);
scope.done();
done();
}.bind(this));
});

it('makes insecure requests', function(done) {
var c = client('sk-XXX', { secure: false }).orders;
var orderId = 'btnorder-XXX';
var scope = nock('http://api.usebutton.com:80')
.get('/v1/order/' + orderId)
.reply(200, { meta: { status: 'ok' }, 'object': {} });

c.get(orderId, function(err) {
expect(err).to.be(null);
scope.done();
done();
}.bind(this));
});

it('overrides the hostname', function(done) {
var c = client('sk-XXX', { hostname: 'staging.usebutton.com' }).orders;
var orderId = 'btnorder-XXX';
var scope = nock('https://staging.usebutton.com:443')
.get('/v1/order/' + orderId)
.reply(200, { meta: { status: 'ok' }, 'object': {} });

c.get(orderId, function(err) {
expect(err).to.be(null);
scope.done();
done();
}.bind(this));
});

it('overrides the port', function(done) {
var c = client('sk-XXX', { port: 1989 }).orders;
var orderId = 'btnorder-XXX';
var scope = nock('https://api.usebutton.com:1989')
.get('/v1/order/' + orderId)
.reply(200, { meta: { status: 'ok' }, 'object': {} });

c.get(orderId, function(err) {
expect(err).to.be(null);
scope.done();
done();
}.bind(this));
});

});

});
5 changes: 5 additions & 0 deletions test/lib/merge-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,9 @@ describe('lib/#merge', function() {
expect(merged).to.eql({ a: 3, b: 3, c: 4 });
});

it('skips non-objects', function() {
var merged = merge({ a : 1 }, undefined, null, 2, false, '', [0, 1, 2], { b: 3 });
expect(merged).to.eql({ a: 1, b: 3 });
});

});
27 changes: 24 additions & 3 deletions test/lib/request-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('lib/#request', function() {
});

beforeEach(function() {
this.request = request();
this.request = request(false, true);
});

it('makes a basic GET request', function(done) {
Expand Down Expand Up @@ -161,7 +161,7 @@ describe('lib/#request', function() {
});

it('handles timeout errors', function(done) {
var timeoutRequest = request(10);
var timeoutRequest = request(10, true);
var path = '/bleep/bloop';
var hostname = 'api.usebutton.com';

Expand All @@ -184,7 +184,7 @@ describe('lib/#request', function() {

it('succeeds if faster than the timeout', function(done) {
var payload = {};
var timeoutRequest = request(1000);
var timeoutRequest = request(1000, true);
var path = '/bleep/bloop';
var hostname = 'api.usebutton.com';

Expand Down Expand Up @@ -399,4 +399,25 @@ describe('lib/#request', function() {
});
});

it('makes insecure requests', function(done) {
var insecureRequest = request(false, false);
var path = '/bleep/bloop';
var hostname = 'api.usebutton.com';

var scope = nock('http://' + hostname + ':80')
.get(path)
.reply(200, successResponse({}));

insecureRequest({
method: 'GET',
path: path,
hostname: hostname
}, function(err, res) {
expect(err).to.be(null);
expect(res.data).to.eql({});
scope.done();
done();
});
});

});
2 changes: 1 addition & 1 deletion test/lib/resources/orders-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('lib/resources/orders', function() {
describe('#get', function() {

beforeEach(function() {
this.orderId = 'btnorder-4c944faaaa747dcb';
this.orderId = 'btnorder-XXX';
this.order = { 'button_order_id': 'btnorder-XXX' };
this.scope = nock('https://api.usebutton.com:443')
.get('/v1/order/' + this.orderId)
Expand Down