1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | var 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);
|
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 |
|
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 |
|
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 |
|
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 |
|
326 | module.exports = NodeBittrexApi;
|