Skip to content

Commit

Permalink
Merge pull request #5 from neuhaus/promises
Browse files Browse the repository at this point in the history
Promises
  • Loading branch information
4ley authored Jan 9, 2018
2 parents 1c8b310 + e476b93 commit 95918dc
Show file tree
Hide file tree
Showing 4 changed files with 412 additions and 322 deletions.
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,27 @@ npm install bitcoinde-api
```javascript
var BitcoindeClient = require('bitcoinde-api');
var bitcoinde = new BitcoindeClient({
key: 'api_key',
secret: 'api_secret'
key: 'api_key',
secret: 'api_secret'
});

// Orderbook
bitcoinde.get('orders', { type: 'sell' }, function(error, result) {
if(error) {
console.error(error);
} else {
bitcoinde.get('orders', { type: 'sell' })
.then((result) => {
console.log(result.orders);
}
});
})
.catch((error) => {
console.error(error);
});

// Account Info
bitcoinde.get('account', null, function(error, result) {
if(error) {
console.error(error);
} else {
bitcoinde.get('account', null)
.then((result) => {
console.log(result.data);
}
});
})
.catch((error) => {
console.error(error);
});

// Catch Error Event
bitcoinde.on('error', function(error) {
Expand Down
318 changes: 156 additions & 162 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,171 +1,165 @@
var util = require('util');
var events = require('events');
var crypto = require('crypto');
var extend = require('util-extend');
var request = require('request');
var querystring = require('querystring');
'use strict';
const util = require('util');
const events = require('events');
const crypto = require('crypto');
const extend = require('util-extend');
const requestPromise = require('request-promise-native');
const querystring = require('query-string'); // sorts keys during stringify

/**
* BitcoindeClient connects to the bitcoin.de API
* @param {object} settings Object required keys "key" and "secret"
*/
function BitcoindeClient(settings) {
var self = this;

var config_default = {
url: 'https://api.bitcoin.de',
version: 'v2',
agent: 'Bitcoin.de NodeJS API Client',
timeoutMS: 20000
};
var config = extend(config_default, settings);
if (!config.key) self.emit('error', new Error('required settings "key" is missing'));
if (!config.secret) self.emit('error', new Error('required settings "secret" is missing'));

/**
* Initialize necessary properties from EventEmitter
*/
events.EventEmitter.call(self);

/**
* Perform GET API request
* @param {String} method API method
* @param {Object} params Arguments to pass to the api call
* @param {Function} callback A callback function to be executed when the request is complete
* @return {Object} The request object
*/
self.get = function(method, params, callback) {
var url = config.url+'/'+config.version+'/'+method;
return rawRequest('get', url, params, callback);
};

/**
* Perform POST API request
* @param {String} method API method
* @param {Object} params Arguments to pass to the api call
* @param {Function} callback A callback function to be executed when the request is complete
* @return {Object} The request object
*/
self.post = function(method, params, callback) {
var url = config.url+'/'+config.version+'/'+method;
return rawRequest('post', url, params, callback);
};

/**
* Perform DELETE API request
* @param {String} method API method
* @param {Object} params Arguments to pass to the api call
* @param {Function} callback A callback function to be executed when the request is complete
* @return {Object} The request object
*/
self.delete = function(method, params, callback) {
var url = config.url+'/'+config.version+'/'+method;
return rawRequest('del', url, params, callback);
};

/**
* Send the actual HTTP request
* @param {String} method HTTP method
* @param {String} url URL to request
* @param {Object} params POST body
* @param {Function} callback A callback function to call when the request is complete
* @return {Object} The request object
*/
var rawRequest = function(method, url, params, callback) {

var nonce = noncer.generate();
md5Query = 'd41d8cd98f00b204e9800998ecf8427e', // empty string hash
options = {
url: url,
timeout: config.timeoutMS
};

if(params) {
switch(method) {
case 'post':
var queryParams = {};
Object.keys(params).sort().forEach(function(idx) { queryParams[idx] = params[idx]; });
md5Query = crypto.createHash('md5').update(querystring.stringify(queryParams)).digest('hex');
options.form = queryParams;
break;
case 'get':
case 'del':
options.url += '?'+querystring.stringify(params);
break;
default:
var err = new Error(method+' not defined');
self.emit('error', err);
return (typeof callback === 'function'? callback.call(self, err, null) : null);
}
}

var signature = crypto.createHmac('sha256', config.secret).update(
(method == 'del'? 'DELETE' : method.toUpperCase())+'#'+options.url+'#'+config.key+'#'+nonce+'#'+md5Query
).digest('hex');

options.headers = {
'User-Agent': config.agent,
'X-API-KEY': config.key,
'X-API-NONCE': nonce,
'X-API-SIGNATURE': signature
};

var req = request[method](options, function(error, response, body) {
if(typeof callback === 'function') {
var data, err;

if(error) {
err = new Error('Error in server response: '+JSON.stringify(error));
self.emit('error', err);
return callback.call(self, err, null);
}

try {
data = JSON.parse(body);
} catch(e) {
err = new Error('Could not understand response from server: '+body);
self.emit('error', err);
return callback.call(self, err, null);
}

if(data.errors && data.errors.length) {
err = new Error('Bitcoin.de API returned error: '+data.errors[0].message);
self.emit('error', err);
return callback.call(self, err, data.errors);
} else {
return callback.call(self, null, data);
}
}
});

return req;
};

/**
* Nonce generator
*/
var noncer = new (function() {

// if you call Date.now() to fast it will generate
// the same ms, helper to make sure the nonce is
// truly unique (supports up to 999 calls per ms).
this.generate = function() {

var now = Date.now();

this.counter = (now === this.last? this.counter + 1 : 0);
this.last = now;

// add padding to nonce
var padding =
this.counter < 10 ? '000' :
this.counter < 100 ? '00' :
this.counter < 1000 ? '0' : '';

return now+padding+this.counter;
};
})();
};
var self = this;

let config_default = {
url: 'https://api.bitcoin.de',
version: 'v2',
agent: 'Bitcoin.de NodeJS API Client',
timeoutMS: 20000
};
let config = extend(config_default, settings);
if (!config.key) self.emit('error', new Error('required settings "key" is missing'));
if (!config.secret) self.emit('error', new Error('required settings "secret" is missing'));

/**
* Initialize necessary properties from EventEmitter
*/
events.EventEmitter.call(self);

/**
* Perform GET API request
* @param {String} action API action
* @param {Object} params Arguments to pass to the api call
* @return {Object} The request object
*/
self.get = function(action, params) {
let url = config.url+'/'+config.version+'/'+action;
return rawRequest('get', url, params);
};

/**
* Perform POST API request
* @param {String} action API action
* @param {Object} params Arguments to pass to the api call
* @return {Object} The request object
*/
self.post = function(action, params) {
let url = config.url+'/'+config.version+'/'+action;
return rawRequest('post', url, params);
};

/**
* Perform DELETE API request
* @param {String} action API action
* @param {Object} params Arguments to pass to the api call
* @return {Object} The request object
*/
self.delete = function(action, params) {
let url = config.url+'/'+config.version+'/'+action;
return rawRequest('delete', url, params);
};

/**
* Send the actual HTTP request
* @param {String} method HTTP method
* @param {String} url URL to request
* @param {Object} params POST body or Querystring
* @return {Object} The request object
*/
let rawRequest = function(method, url, params) {

let nonce = noncer.generate();
let md5Query = 'd41d8cd98f00b204e9800998ecf8427e'; // empty string hash
let options = {
url: url,
timeout: config.timeoutMS,
json: true
};

if (params) {
switch(method) {
case 'post':
md5Query = crypto.createHash('md5')
.update(querystring.stringify(params))
.digest('hex');
options.form = queryParams;
options.method = 'POST';
break;
case 'get':
options.url += '?'+querystring.stringify(params);
break;
case 'delete':
options.url += '?'+querystring.stringify(params);
options.method = 'DELETE';
break;
default:
return new Promise((resolve, reject) => {
let err = new Error('Method ' + method + ' not defined');
self.emit('error', err);
reject(err);
});
}
}

let signature = crypto.createHmac('sha256', config.secret)
.update([method.toUpperCase(), options.url, config.key, nonce, md5Query].join('#'))
.digest('hex');

options.headers = {
'User-Agent' : config.agent,
'X-API-KEY' : config.key,
'X-API-NONCE' : nonce,
'X-API-SIGNATURE': signature
};

return new Promise((resolve, reject) => {
requestPromise(options)
.then((data) => {
if(data.errors && data.errors.length) {
// can we do something better with data.errors?
let err = new Error('bitcoin.de API returned error: ' + data.errors[0].message);
self.emit('error', err);
reject(err);
} else {
resolve(data);
}
})
.catch((error) => {
let err = new Error('Error in server response: ' + JSON.stringify(error));
self.emit('error', err);
reject(err);
});

});
}; // rawRequest

/**
* Nonce generator
*/
let noncer = new (function() {

// if you call Date.now() too fast it will generate
// the same ms, helper to make sure the nonce is
// truly unique (supports up to 999 calls per ms).
this.generate = function() {

let now = Date.now();

this.counter = (now === this.last? this.counter + 1 : 0);
this.last = now;

// add padding to nonce
let padding =
this.counter < 10 ? '000' :
this.counter < 100 ? '00' :
this.counter < 1000 ? '0' : '';

return now + padding + this.counter;
};
})();
}

util.inherits(BitcoindeClient, events.EventEmitter);

Expand Down
Loading

0 comments on commit 95918dc

Please sign in to comment.