diff --git a/CHANGELOG.md b/CHANGELOG.md index 112e179..0f816be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` diff --git a/README.md b/README.md index 756847e..af3afcc 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/index.js b/index.js index 4d3a3e6..5165423 100644 --- a/index.js +++ b/index.js @@ -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. @@ -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 @@ -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 ); diff --git a/lib/merge.js b/lib/merge.js index d4092df..5c08c3d 100644 --- a/lib/merge.js +++ b/lib/merge.js @@ -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; diff --git a/lib/request.js b/lib/request.js index fefb9e4..e66438a 100644 --- a/lib/request.js +++ b/lib/request.js @@ -1,19 +1,22 @@ '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', @@ -21,7 +24,7 @@ function request(timeout) { // ... // }); // - // request(3000)({ + // request(3000, true)({ // method: 'POST', // path: '/v1/blorp/blorp-1' // hostname: 'api.bloop.com', @@ -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)); diff --git a/package.json b/package.json index 8547dc8..77710b1 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/test/index-test.js b/test/index-test.js index 4b3cc9f..24af83d 100644 --- a/test/index-test.js +++ b/test/index-test.js @@ -1,6 +1,7 @@ 'use strict'; var expect = require('expect.js'); +var nock = require('nock'); var client = require('../index'); @@ -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)); + }); + + }); + }); diff --git a/test/lib/merge-test.js b/test/lib/merge-test.js index a1f846b..110a886 100644 --- a/test/lib/merge-test.js +++ b/test/lib/merge-test.js @@ -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 }); + }); + }); diff --git a/test/lib/request-test.js b/test/lib/request-test.js index 622a69d..0c6a74f 100644 --- a/test/lib/request-test.js +++ b/test/lib/request-test.js @@ -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) { @@ -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'; @@ -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'; @@ -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(); + }); + }); + }); diff --git a/test/lib/resources/orders-test.js b/test/lib/resources/orders-test.js index 0e09500..270ead4 100644 --- a/test/lib/resources/orders-test.js +++ b/test/lib/resources/orders-test.js @@ -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)