UNPKG

10.5 kBJavaScriptView Raw
1/* ============================================================
2 * node.bittrex.api
3 * https://github.com/n0mad01/node.bittrex.api
4 *
5 * ============================================================
6 * Copyright 2014-2017, Adrian Soluch - http://soluch.us/
7 * Released under the MIT License
8 * ============================================================ */
9var NodeBittrexApi = function() {
10
11 'use strict';
12
13 var request = require('request'),
14 hmac_sha512 = require('./hmac-sha512.js'),
15 JSONStream = require('JSONStream'),
16 es = require('event-stream'),
17 jsonic = require('jsonic'),
18 signalR = require('signalr-client'),
19 wsclient;
20
21 var start,
22 request_options = {
23 method: 'GET',
24 agent: false,
25 headers: {
26 'User-Agent': 'Mozilla/4.0 (compatible; Node Bittrex API)',
27 'Content-type': 'application/x-www-form-urlencoded'
28 }
29 };
30
31 var opts = {
32 baseUrl: 'https://bittrex.com/api/v1.1',
33 baseUrlv2: 'https://bittrex.com/Api/v2.0',
34 websockets_baseurl: 'wss://socket.bittrex.com/signalr',
35 websockets_hubs: ['CoreHub'],
36 apikey: 'APIKEY',
37 apisecret: 'APISECRET',
38 verbose: false,
39 cleartext: false,
40 stream: false,
41 inverse_callback_arguments: false,
42 };
43
44 var getNonce = function() {
45 return Math.floor(new Date().getTime() / 1000);
46 };
47
48 var extractOptions = function(options) {
49 var o = Object.keys(options),
50 i;
51 for (i = 0; i < o.length; i++) {
52 opts[o[i]] = options[o[i]];
53 }
54 };
55
56 var apiCredentials = function(uri) {
57 var options = {
58 apikey: opts.apikey,
59 nonce: getNonce()
60 };
61
62 return setRequestUriGetParams(uri, options);
63 };
64
65 var setRequestUriGetParams = function(uri, options) {
66 var op;
67 if (typeof(uri) === 'object') {
68 op = uri;
69 uri = op.uri;
70 } else {
71 op = request_options;
72 }
73
74
75 var o = Object.keys(options),
76 i;
77 for (i = 0; i < o.length; i++) {
78 uri = updateQueryStringParameter(uri, o[i], options[o[i]]);
79 }
80
81 op.headers.apisign = hmac_sha512.HmacSHA512(uri, opts.apisecret); // setting the HMAC hash `apisign` http header
82 op.uri = uri;
83
84 return op;
85 };
86
87 var updateQueryStringParameter = function(uri, key, value) {
88 var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
89 var separator = uri.indexOf('?') !== -1 ? "&" : "?";
90
91 if (uri.match(re)) {
92 uri = uri.replace(re, '$1' + key + "=" + value + '$2');
93 } else {
94 uri = uri + separator + key + "=" + value;
95 }
96
97 return uri;
98 };
99
100 var sendRequestCallback = function(callback, op) {
101 start = Date.now();
102
103 switch (opts.stream) {
104 case true:
105 request(op)
106 .pipe(JSONStream.parse('*'))
107 .pipe(es.mapSync(function(data) {
108 callback(data);
109 ((opts.verbose) ? console.log("streamed from " + op.uri + " in: %ds", (Date.now() - start) / 1000) : '');
110 }));
111 break;
112 case false:
113 request(op, function(error, result, body) {
114 ((opts.verbose) ? console.log("requested from " + op.uri + " in: %ds", (Date.now() - start) / 1000) : '');
115 if (!body || !result || result.statusCode != 200) {
116 var errorObj = {
117 success: false,
118 message: 'URL request error',
119 error: error,
120 result: result,
121 };
122 return ((opts.inverse_callback_arguments) ?
123 callback(errorObj, null) :
124 callback(null, errorObj));
125 } else {
126 result = JSON.parse(body);
127 if (!result.success) {
128 // error returned by bittrex API - forward the result as an error
129 return ((opts.inverse_callback_arguments) ?
130 callback(result, null) :
131 callback(null, result));
132 }
133 return ((opts.inverse_callback_arguments) ?
134 callback(null, ((opts.cleartext) ? body : result)) :
135 callback(((opts.cleartext) ? body : result), null));
136 }
137 });
138 break;
139 }
140 };
141
142 var publicApiCall = function(url, callback, options) {
143 var op = request_options;
144 if (!options) {
145 op.uri = url;
146 }
147 sendRequestCallback(callback, (!options) ? op : setRequestUriGetParams(url, options));
148 };
149
150 var credentialApiCall = function(url, callback, options) {
151 if (options) {
152 options = setRequestUriGetParams(apiCredentials(url), options);
153 }
154 sendRequestCallback(callback, options);
155 };
156
157 var connectws = function(callback) {
158 wsclient = new signalR.client(
159 opts.websockets_baseurl,
160 opts.websockets_hubs
161 );
162 wsclient.serviceHandlers = {
163 bound: function() {
164 ((opts.verbose) ? console.log('Websocket bound') : '');
165 },
166 connectFailed: function(error) {
167 ((opts.verbose) ? console.log('Websocket connectFailed: ', error) : '');
168 },
169 disconnected: function() {
170 ((opts.verbose) ? console.log('Websocket disconnected') : '');
171 },
172 onerror: function(error) {
173 ((opts.verbose) ? console.log('Websocket onerror: ', error) : '');
174 },
175 bindingError: function(error) {
176 ((opts.verbose) ? console.log('Websocket bindingError: ', error) : '');
177 },
178 connectionLost: function(error) {
179 ((opts.verbose) ? console.log('Connection Lost: ', error) : '');
180 },
181 reconnecting: function(retry) {
182 ((opts.verbose) ? console.log('Websocket Retrying: ', retry) : '');
183 // change to true to stop retrying
184 return false;
185 }
186 };
187 return wsclient;
188 };
189
190 var setMessageReceivedWs = function(callback) {
191 wsclient.serviceHandlers.messageReceived = function(message) {
192 try {
193 var data = jsonic(message.utf8Data);
194 if (data && data.M) {
195 data.M.forEach(function(M) {
196 callback(M);
197 });
198 } else {
199 // ((opts.verbose) ? console.log('Unhandled data', data) : '');
200 callback({'unhandled_data' : data});
201 }
202 } catch (e) {
203 ((opts.verbose) ? console.error(e) : '');
204 }
205 return false;
206 };
207 };
208
209 var setConnectedWs = function(markets) {
210 wsclient.serviceHandlers.connected = function(connection) {
211 markets.forEach(function(market) {
212 wsclient.call('CoreHub', 'SubscribeToExchangeDeltas', market).done(function(err, result) {
213 if (err) {
214 return console.error(err);
215 }
216
217 if (result === true) {
218 ((opts.verbose) ? console.log('Subscribed to ' + market) : '');
219 }
220 });
221 });
222 ((opts.verbose) ? console.log('Websocket connected') : '');
223 };
224 };
225
226 return {
227 options: function(options) {
228 extractOptions(options);
229 },
230 websockets: {
231 client: function(callback) {
232 return connectws();
233 },
234 listen: function(callback) {
235 var client = connectws();
236 setMessageReceivedWs(callback);
237 return client;
238 },
239 subscribe: function(markets, callback) {
240 var client = connectws();
241 setConnectedWs(markets);
242 setMessageReceivedWs(callback);
243 return client;
244 }
245 },
246 sendCustomRequest: function(request_string, callback, credentials) {
247 var op;
248
249 if (credentials === true) {
250 op = apiCredentials(request_string);
251 } else {
252 op = request_options;
253 op.uri = request_string;
254 }
255 sendRequestCallback(callback, op);
256 },
257 getmarkets: function(callback) {
258 publicApiCall(opts.baseUrl + '/public/getmarkets', callback, null);
259 },
260 getcurrencies: function(callback) {
261 publicApiCall(opts.baseUrl + '/public/getcurrencies', callback, null);
262 },
263 getticker: function(options, callback) {
264 publicApiCall(opts.baseUrl + '/public/getticker', callback, options);
265 },
266 getmarketsummaries: function(callback) {
267 publicApiCall(opts.baseUrl + '/public/getmarketsummaries', callback, null);
268 },
269 getmarketsummary: function(options, callback) {
270 publicApiCall(opts.baseUrl + '/public/getmarketsummary', callback, options);
271 },
272 getorderbook: function(options, callback) {
273 publicApiCall(opts.baseUrl + '/public/getorderbook', callback, options);
274 },
275 getmarkethistory: function(options, callback) {
276 publicApiCall(opts.baseUrl + '/public/getmarkethistory', callback, options);
277 },
278 getcandles: function(options, callback) {
279 publicApiCall(opts.baseUrlv2 + '/pub/market/GetTicks', callback, options);
280 },
281 buylimit: function(options, callback) {
282 credentialApiCall(opts.baseUrl + '/market/buylimit', callback, options);
283 },
284 buymarket: function(options, callback) {
285 credentialApiCall(opts.baseUrl + '/market/buymarket', callback, options);
286 },
287 selllimit: function(options, callback) {
288 credentialApiCall(opts.baseUrl + '/market/selllimit', callback, options);
289 },
290 sellmarket: function(options, callback) {
291 credentialApiCall(opts.baseUrl + '/market/sellmarket', callback, options);
292 },
293 cancel: function(options, callback) {
294 credentialApiCall(opts.baseUrl + '/market/cancel', callback, options);
295 },
296 getopenorders: function(options, callback) {
297 credentialApiCall(opts.baseUrl + '/market/getopenorders', callback, options);
298 },
299 getbalances: function(callback) {
300 credentialApiCall(opts.baseUrl + '/account/getbalances', callback, {});
301 },
302 getbalance: function(options, callback) {
303 credentialApiCall(opts.baseUrl + '/account/getbalance', callback, options);
304 },
305 getwithdrawalhistory: function(options, callback) {
306 credentialApiCall(opts.baseUrl + '/account/getwithdrawalhistory', callback, options);
307 },
308 getdepositaddress: function(options, callback) {
309 credentialApiCall(opts.baseUrl + '/account/getdepositaddress', callback, options);
310 },
311 getdeposithistory: function(options, callback) {
312 credentialApiCall(opts.baseUrl + '/account/getdeposithistory', callback, options);
313 },
314 getorderhistory: function(options, callback) {
315 credentialApiCall(opts.baseUrl + '/account/getorderhistory', callback, options);
316 },
317 getorder: function(options, callback) {
318 credentialApiCall(opts.baseUrl + '/account/getorder', callback, options);
319 },
320 withdraw: function(options, callback) {
321 credentialApiCall(opts.baseUrl + '/account/withdraw', callback, options);
322 }
323 };
324}();
325
326module.exports = NodeBittrexApi;