btc-api-node 1.12.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of btc-api-node might be problematic. Click here for more details.
- package/.istanbul.yml +53 -0
- package/.travis.yml +5 -0
- package/CHANGELOG +33 -0
- package/LICENSE.md +21 -0
- package/README.md +211 -0
- package/doc/order.md +160 -0
- package/doc/rest2.md +573 -0
- package/doc/ws2.md +925 -0
- package/examples/bfx.js +26 -0
- package/examples/rest2/order_history.js +29 -0
- package/examples/rest2/symbols.js +15 -0
- package/examples/rest2/tickers.js +24 -0
- package/examples/rest2/trade_history.js +28 -0
- package/examples/ws2/auth.js +31 -0
- package/examples/ws2/calc.js +33 -0
- package/examples/ws2/cancel_all.js +35 -0
- package/examples/ws2/cancel_all_buf.js +39 -0
- package/examples/ws2/candles.js +36 -0
- package/examples/ws2/info_events.js +40 -0
- package/examples/ws2/oc_multi.js +50 -0
- package/examples/ws2/order_books.js +37 -0
- package/examples/ws2/orders.js +67 -0
- package/examples/ws2/ox_multi.js +61 -0
- package/examples/ws2/sequencing.js +23 -0
- package/examples/ws2/ticker.js +20 -0
- package/examples/ws2/trades.js +27 -0
- package/index.js +24 -0
- package/lib/model.js +25 -0
- package/lib/models/alert.js +25 -0
- package/lib/models/balance_info.js +21 -0
- package/lib/models/candle.js +33 -0
- package/lib/models/funding_credit.js +61 -0
- package/lib/models/funding_info.js +16 -0
- package/lib/models/funding_loan.js +64 -0
- package/lib/models/funding_offer.js +60 -0
- package/lib/models/funding_trade.js +33 -0
- package/lib/models/index.js +23 -0
- package/lib/models/margin_info.js +29 -0
- package/lib/models/notification.js +31 -0
- package/lib/models/order.js +288 -0
- package/lib/models/order_book.js +214 -0
- package/lib/models/position.js +43 -0
- package/lib/models/tick.js +83 -0
- package/lib/models/trade.js +43 -0
- package/lib/models/trade_tick.js +29 -0
- package/lib/models/wallet.js +34 -0
- package/lib/transports/rest.js +391 -0
- package/lib/transports/rest2.js +597 -0
- package/lib/transports/ws.js +323 -0
- package/lib/transports/ws2.js +1729 -0
- package/lib/util/gen_auth_sig.js +23 -0
- package/lib/util/index.js +11 -0
- package/lib/util/is_snapshot.js +5 -0
- package/lib/util/nonce.js +5 -0
- package/package.json +39 -0
- package/test/fixtures/response-ticker-funding.json +1 -0
- package/test/fixtures/response-ticker-pairs.json +1 -0
- package/test/fixtures/response-trades-funding.json +1 -0
- package/test/fixtures/response-trades-pairs.json +1 -0
- package/test/fixtures/response-ws-1-orderbook-R0.json +51 -0
- package/test/fixtures/response-ws2-server-order-book-P0.json +1 -0
- package/test/fixtures/response-ws2-server-order-book-P1.json +1 -0
- package/test/fixtures/response-ws2-server-order-book-R0.json +1 -0
- package/test/fixtures/response-ws2-server-ticker-funding.json +1 -0
- package/test/fixtures/response-ws2-server-trades.json +1 -0
- package/test/helpers/test_model.js +71 -0
- package/test/index.js +131 -0
- package/test/lib/models/alert.js +12 -0
- package/test/lib/models/balance_info.js +12 -0
- package/test/lib/models/candle.js +12 -0
- package/test/lib/models/funding_credit.js +17 -0
- package/test/lib/models/funding_info.js +7 -0
- package/test/lib/models/funding_loan.js +17 -0
- package/test/lib/models/funding_offer.js +17 -0
- package/test/lib/models/funding_trade.js +15 -0
- package/test/lib/models/margin_info.js +15 -0
- package/test/lib/models/notification.js +14 -0
- package/test/lib/models/order.js +395 -0
- package/test/lib/models/order_book.js +188 -0
- package/test/lib/models/position.js +15 -0
- package/test/lib/models/tick.js +34 -0
- package/test/lib/models/trade.js +16 -0
- package/test/lib/models/trade_tick.js +14 -0
- package/test/lib/models/wallet.js +14 -0
- package/test/lib/transports/rest-1-integration.js +131 -0
- package/test/lib/transports/rest-2-integration.js +80 -0
- package/test/lib/transports/rest-2-issue-80-argument-length.js +61 -0
- package/test/lib/transports/rest-2-smoke-test.js +49 -0
- package/test/lib/transports/rest-2-unit.js +26 -0
- package/test/lib/transports/rest1.js +152 -0
- package/test/lib/transports/ws-1-handle-channel.js +83 -0
- package/test/lib/transports/ws-1-parsing.js +40 -0
- package/test/lib/transports/ws-1-test.js +275 -0
- package/test/lib/transports/ws2-integration.js +259 -0
- package/test/lib/transports/ws2-unit.js +1295 -0
- package/test/lib/util/is_snapshot.js +20 -0
- package/test/lib/util/nonce.js +20 -0
|
@@ -0,0 +1,1729 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('events')
|
|
4
|
+
const debug = require('debug')('bitfinex:ws')
|
|
5
|
+
const WebSocket = require('ws')
|
|
6
|
+
const Promise = require('bluebird')
|
|
7
|
+
const CbQ = require('cbq')
|
|
8
|
+
const _Throttle = require('lodash.throttle')
|
|
9
|
+
const { genAuthSig, nonce } = require('../util')
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
BalanceInfo,
|
|
13
|
+
FundingCredit,
|
|
14
|
+
FundingInfo,
|
|
15
|
+
FundingLoan,
|
|
16
|
+
FundingOffer,
|
|
17
|
+
FundingTrade,
|
|
18
|
+
MarginInfo,
|
|
19
|
+
Notification,
|
|
20
|
+
Order,
|
|
21
|
+
Position,
|
|
22
|
+
Trade,
|
|
23
|
+
TradeTick,
|
|
24
|
+
Wallet,
|
|
25
|
+
OrderBook,
|
|
26
|
+
Candle,
|
|
27
|
+
Tick
|
|
28
|
+
} = require('../models')
|
|
29
|
+
|
|
30
|
+
const WS_URL = 'wss://api.bitfinex.com/ws/2'
|
|
31
|
+
const MAX_CALC_OPS = 8
|
|
32
|
+
const INFO_CODES = {
|
|
33
|
+
SERVER_RESTART: 20051,
|
|
34
|
+
MAINTENANCE_START: 20060,
|
|
35
|
+
MAINTENANCE_END: 20061
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Communicates with v2 of the Bitfinex WebSocket API
|
|
40
|
+
*/
|
|
41
|
+
class WSv2 extends EventEmitter {
|
|
42
|
+
/**
|
|
43
|
+
* Instantiate a new ws2 transport. Does not auto-open
|
|
44
|
+
*
|
|
45
|
+
* @param {string} opts.apiKey
|
|
46
|
+
* @param {string} opts.apiSecret
|
|
47
|
+
* @param {string} opts.url - ws connection url
|
|
48
|
+
* @param {number} opts.orderOpBufferDelay - multi-order op batching timeout
|
|
49
|
+
* @param {boolean} opts.transform - if true, packets are converted to models
|
|
50
|
+
* @param {Object} opts.agent - optional node agent for ws connection (proxy)
|
|
51
|
+
* @param {boolean} opts.manageOrderBooks - enable local OB persistence
|
|
52
|
+
* @param {boolean} opts.manageCandles - enable local candle persistence
|
|
53
|
+
* @param {boolean} opts.seqAudit - enable sequence numbers & verification
|
|
54
|
+
* @param {boolean} opts.autoReconnect - if true, we will reconnect on close
|
|
55
|
+
* @param {number} opts.reconnectDelay - optional, defaults to 1000 (ms)
|
|
56
|
+
* @param {number} opts.packetWDDelay - watch-dog forced reconnection delay
|
|
57
|
+
*/
|
|
58
|
+
constructor (opts = { apiKey: '', apiSecret: '', url: WS_URL }) {
|
|
59
|
+
super()
|
|
60
|
+
|
|
61
|
+
this._apiKey = opts.apiKey || ''
|
|
62
|
+
this._apiSecret = opts.apiSecret || ''
|
|
63
|
+
this._agent = opts.agent
|
|
64
|
+
this._url = opts.url || WS_URL
|
|
65
|
+
this._transform = opts.transform === true
|
|
66
|
+
this._orderOpBufferDelay = opts.orderOpBufferDelay || -1
|
|
67
|
+
this._orderOpBuffer = []
|
|
68
|
+
this._orderOpTimeout = null
|
|
69
|
+
this._seqAudit = opts.seqAudit === true
|
|
70
|
+
this._autoReconnect = opts.autoReconnect === true
|
|
71
|
+
this._reconnectDelay = opts.reconnectDelay || 1000
|
|
72
|
+
this._manageOrderBooks = opts.manageOrderBooks === true
|
|
73
|
+
this._manageCandles = opts.manageCandles === true
|
|
74
|
+
this._packetWDDelay = opts.packetWDDelay
|
|
75
|
+
this._packetWDTimeout = null
|
|
76
|
+
this._packetWDLastTS = 0
|
|
77
|
+
this._orderBooks = {}
|
|
78
|
+
this._candles = {}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* {
|
|
82
|
+
* [groupID]: {
|
|
83
|
+
* [eventName]: [{
|
|
84
|
+
* modelClass: ..,
|
|
85
|
+
* filter: { symbol: 'tBTCUSD' }, // only works w/ serialize
|
|
86
|
+
* cb: () => {}
|
|
87
|
+
* }]
|
|
88
|
+
* }
|
|
89
|
+
* }
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
this._listeners = {}
|
|
93
|
+
this._infoListeners = {} // { [code]: <listeners> }
|
|
94
|
+
this._subscriptionRefs = {}
|
|
95
|
+
this._channelMap = {}
|
|
96
|
+
this._orderBooks = {}
|
|
97
|
+
this._eventCallbacks = new CbQ()
|
|
98
|
+
this._isAuthenticated = false
|
|
99
|
+
this._wasEverAuthenticated = false // used for auto-auth on reconnect
|
|
100
|
+
this._lastPubSeq = -1
|
|
101
|
+
this._lastAuthSeq = -1
|
|
102
|
+
this._isOpen = false
|
|
103
|
+
this._ws = null
|
|
104
|
+
this._isClosing = false // used to block reconnect on direct close() call
|
|
105
|
+
this._isReconnecting = false
|
|
106
|
+
|
|
107
|
+
this._onWSOpen = this._onWSOpen.bind(this)
|
|
108
|
+
this._onWSClose = this._onWSClose.bind(this)
|
|
109
|
+
this._onWSError = this._onWSError.bind(this)
|
|
110
|
+
this._onWSMessage = this._onWSMessage.bind(this)
|
|
111
|
+
this._triggerPacketWD = this._triggerPacketWD.bind(this)
|
|
112
|
+
this._sendCalc = _Throttle(this._sendCalc.bind(this), 1000 / MAX_CALC_OPS)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Opens a connection to the API server. Rejects with an error if a
|
|
117
|
+
* connection is already open. Resolves on success
|
|
118
|
+
*
|
|
119
|
+
* @return {Promise} p
|
|
120
|
+
*/
|
|
121
|
+
open () {
|
|
122
|
+
if (this._isOpen || this._ws !== null) {
|
|
123
|
+
return Promise.reject(new Error('already open'))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this._ws = new WebSocket(this._url, {
|
|
127
|
+
agent: this._agent
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
if (this._seqAudit) {
|
|
131
|
+
this._ws.once('open', this.enableSequencing.bind(this))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this._ws.on('message', this._onWSMessage)
|
|
135
|
+
this._ws.on('open', this._onWSOpen)
|
|
136
|
+
this._ws.on('error', this._onWSError)
|
|
137
|
+
this._ws.on('close', this._onWSClose)
|
|
138
|
+
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
this._ws.once('open', () => resolve())
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Closes the active connection. If there is none, rejects with a promise.
|
|
146
|
+
* Resolves on success
|
|
147
|
+
*
|
|
148
|
+
* @param {number} code - passed to ws
|
|
149
|
+
* @param {string} reason - passed to ws
|
|
150
|
+
* @return {Promise}
|
|
151
|
+
*/
|
|
152
|
+
close (code, reason) {
|
|
153
|
+
if (!this._isOpen || this._ws === null) {
|
|
154
|
+
return Promise.reject(new Error('not open'))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this._isClosing = true
|
|
158
|
+
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
this._ws.once('close', () => resolve())
|
|
161
|
+
this._ws.close(code, reason)
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Generates & sends an authentication packet to the server; if already
|
|
167
|
+
* authenticated, rejects with an error. Resolves on success
|
|
168
|
+
*
|
|
169
|
+
* @param {number} calc - optional, default is 0
|
|
170
|
+
* @return {Promise} p
|
|
171
|
+
*/
|
|
172
|
+
auth (calc = 0) {
|
|
173
|
+
if (this._isAuthenticated) {
|
|
174
|
+
return Promise.reject(new Error('already authenticated'))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const authNonce = nonce()
|
|
178
|
+
const authPayload = `AUTH${authNonce}${authNonce}`
|
|
179
|
+
const { sig } = genAuthSig(this._apiSecret, authPayload)
|
|
180
|
+
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
this._ws.once('auth', () => resolve())
|
|
183
|
+
|
|
184
|
+
this.send({
|
|
185
|
+
event: 'auth',
|
|
186
|
+
apiKey: this._apiKey,
|
|
187
|
+
authSig: sig,
|
|
188
|
+
authPayload,
|
|
189
|
+
authNonce,
|
|
190
|
+
calc
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Utility method to close & re-open the ws connection. Re-authenticates if
|
|
197
|
+
* previously authenticated
|
|
198
|
+
*
|
|
199
|
+
* @return {Promise} p - resolves on completion
|
|
200
|
+
*/
|
|
201
|
+
reconnect () {
|
|
202
|
+
if (!this._ws) return this.open()
|
|
203
|
+
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
this._ws.once('close', () => {
|
|
206
|
+
this.open()
|
|
207
|
+
|
|
208
|
+
if (!this._wasEverAuthenticated) {
|
|
209
|
+
return resolve()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this._ws.once('open', this.auth.bind(this))
|
|
213
|
+
this._ws.once('auth', () => resolve())
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
this.close()
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Trigger the packet watch-dog; called when we haven't seen a new WS packet
|
|
222
|
+
* for longer than our WD duration (if provided)
|
|
223
|
+
* @private
|
|
224
|
+
*/
|
|
225
|
+
_triggerPacketWD () {
|
|
226
|
+
if (!this._packetWDDelay || !this._isOpen) return
|
|
227
|
+
|
|
228
|
+
debug(
|
|
229
|
+
'packet delay watchdog triggered [last packet %dms ago]',
|
|
230
|
+
Date.now() - this._packetWDLastTS
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
this._packetWDTimeout = null
|
|
234
|
+
this.reconnect()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Reset the packet watch-dog timeout. Should be called on every new WS packet
|
|
239
|
+
* if the watch-dog is enabled
|
|
240
|
+
* @private
|
|
241
|
+
*/
|
|
242
|
+
_resetPacketWD () {
|
|
243
|
+
if (!this._packetWDDelay) return
|
|
244
|
+
if (this._packetWDTimeout !== null) {
|
|
245
|
+
clearTimeout(this._packetWDTimeout)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!this._isOpen) return
|
|
249
|
+
|
|
250
|
+
this._packetWDTimeout = setTimeout(
|
|
251
|
+
this._triggerPacketWD,
|
|
252
|
+
this._packetWDDelay
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @private
|
|
258
|
+
*/
|
|
259
|
+
_onWSOpen () {
|
|
260
|
+
this._isOpen = true
|
|
261
|
+
this._isReconnecting = false
|
|
262
|
+
this._packetWDLastTS = Date.now()
|
|
263
|
+
this.emit('open')
|
|
264
|
+
|
|
265
|
+
debug('connection open')
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* @private
|
|
270
|
+
*/
|
|
271
|
+
_onWSClose () {
|
|
272
|
+
this._isOpen = false
|
|
273
|
+
this._isAuthenticated = false
|
|
274
|
+
this._lastAuthSeq = -1
|
|
275
|
+
this._lastPubSeq = -1
|
|
276
|
+
this._ws = null
|
|
277
|
+
this.emit('close')
|
|
278
|
+
|
|
279
|
+
debug('connection closed')
|
|
280
|
+
|
|
281
|
+
if (this._autoReconnect && !this._isClosing) {
|
|
282
|
+
setTimeout(this.reconnect.bind(this), this._reconnectDelay)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this._isClosing = false
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @private
|
|
290
|
+
*/
|
|
291
|
+
_onWSError (err) {
|
|
292
|
+
this.emit('error', err)
|
|
293
|
+
|
|
294
|
+
debug('error: %j', err)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @param {Array} arrN - notification in ws array format
|
|
299
|
+
* @private
|
|
300
|
+
*/
|
|
301
|
+
_onWSNotification (arrN) {
|
|
302
|
+
const status = arrN[6]
|
|
303
|
+
const msg = arrN[7]
|
|
304
|
+
|
|
305
|
+
if (!arrN[4]) return
|
|
306
|
+
|
|
307
|
+
if (arrN[1] === 'on-req') {
|
|
308
|
+
const [,, cid] = arrN[4]
|
|
309
|
+
const k = `order-new-${cid}`
|
|
310
|
+
|
|
311
|
+
if (status === 'SUCCESS') {
|
|
312
|
+
return this._eventCallbacks.trigger(k, null, arrN[4])
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
this._eventCallbacks.trigger(k, new Error(`${status}: ${msg}`), arrN[4])
|
|
316
|
+
} else if (arrN[1] === 'oc-req') {
|
|
317
|
+
const [id] = arrN[4]
|
|
318
|
+
const k = `order-cancel-${id}`
|
|
319
|
+
|
|
320
|
+
if (status === 'SUCCESS') {
|
|
321
|
+
return this._eventCallbacks.trigger(k, null, arrN[4])
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
this._eventCallbacks.trigger(k, new Error(`${status}: ${msg}`), arrN[4])
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* @param {string} msgJSON
|
|
330
|
+
* @param {string} flags
|
|
331
|
+
* @private
|
|
332
|
+
*/
|
|
333
|
+
_onWSMessage (msgJSON, flags) {
|
|
334
|
+
this._packetWDLastTS = Date.now()
|
|
335
|
+
this._resetPacketWD()
|
|
336
|
+
|
|
337
|
+
let msg
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
msg = JSON.parse(msgJSON)
|
|
341
|
+
} catch (e) {
|
|
342
|
+
this.emit('error', `invalid message JSON: ${msgJSON}`)
|
|
343
|
+
return
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const seq = msg[msg.length - 1]
|
|
347
|
+
|
|
348
|
+
if (this._seqAudit && seq !== 0 && !isNaN(seq)) {
|
|
349
|
+
if (msg[0] === 0) {
|
|
350
|
+
if (this._lastAuthSeq !== -1 && seq > (this._lastAuthSeq + 1)) {
|
|
351
|
+
this.emit('error', new Error(
|
|
352
|
+
`invalid auth seq #; last ${this._lastAuthSeq}, got ${seq}`
|
|
353
|
+
))
|
|
354
|
+
|
|
355
|
+
this._lastAuthSeq = seq
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
this._lastAuthSeq = seq
|
|
360
|
+
} else {
|
|
361
|
+
if (this._lastPubSeq !== -1 && seq > (this._lastPubSeq + 1)) {
|
|
362
|
+
this.emit('error', new Error(
|
|
363
|
+
`invalid seq #; last ${this._lastPubSeq}, got ${seq}`
|
|
364
|
+
))
|
|
365
|
+
|
|
366
|
+
this._lastPubSeq = seq
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
this._lastPubSeq = seq
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
this.emit('message', msg, flags)
|
|
375
|
+
|
|
376
|
+
if (Array.isArray(msg)) {
|
|
377
|
+
this._handleChannelMessage(msg)
|
|
378
|
+
} else if (msg.event) {
|
|
379
|
+
this._handleEventMessage(msg)
|
|
380
|
+
} else {
|
|
381
|
+
debug('recv unidentified message: %j', msg)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @param {array} msg
|
|
387
|
+
* @private
|
|
388
|
+
*/
|
|
389
|
+
_handleChannelMessage (msg) {
|
|
390
|
+
const [chanId] = msg
|
|
391
|
+
const channelData = this._channelMap[chanId]
|
|
392
|
+
|
|
393
|
+
if (!channelData) {
|
|
394
|
+
debug('recv msg from unknown channel %d: %j', chanId, msg)
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
debug('recv msg: %j', msg)
|
|
399
|
+
|
|
400
|
+
if (msg.length < 2) return
|
|
401
|
+
if (msg[1] === 'hb') return // TODO: optionally track seq
|
|
402
|
+
|
|
403
|
+
if (channelData.channel === 'book') {
|
|
404
|
+
return this._handleOBMessage(msg, channelData)
|
|
405
|
+
} else if (channelData.channel === 'trades') {
|
|
406
|
+
return this._handleTradeMessage(msg, channelData)
|
|
407
|
+
} else if (channelData.channel === 'ticker') {
|
|
408
|
+
return this._handleTickerMessage(msg, channelData)
|
|
409
|
+
} else if (channelData.channel === 'candles') {
|
|
410
|
+
return this._handleCandleMessage(msg, channelData)
|
|
411
|
+
} else if (channelData.channel === 'auth') {
|
|
412
|
+
return this._handleAuthMessage(msg, channelData)
|
|
413
|
+
} else {
|
|
414
|
+
this._propagateMessageToListeners(msg)
|
|
415
|
+
this.emit(channelData.channel, msg)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Called for messages from the 'book' channel. Might be an update or a
|
|
421
|
+
* snapshot
|
|
422
|
+
*
|
|
423
|
+
* @param {Array|Array[]} msg
|
|
424
|
+
* @param {Object} chanData - entry from _channelMap
|
|
425
|
+
* @private
|
|
426
|
+
*/
|
|
427
|
+
_handleOBMessage (msg, chanData) {
|
|
428
|
+
const { symbol } = chanData
|
|
429
|
+
let data = msg[1]
|
|
430
|
+
|
|
431
|
+
if (this._manageOrderBooks) {
|
|
432
|
+
const err = this._updateManagedOB(symbol, data)
|
|
433
|
+
|
|
434
|
+
if (err) {
|
|
435
|
+
this.emit('error', err)
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
data = this._orderBooks[symbol]
|
|
440
|
+
} else if (data.length > 0 && !Array.isArray(data[0])) {
|
|
441
|
+
data = [data] // always pass on an array of entries
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (this._transform) {
|
|
445
|
+
data = new OrderBook(data)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const internalMessage = [chanData.chanId, 'orderbook', data]
|
|
449
|
+
internalMessage.filterOverride = [
|
|
450
|
+
chanData.symbol,
|
|
451
|
+
chanData.prec,
|
|
452
|
+
chanData.len
|
|
453
|
+
]
|
|
454
|
+
|
|
455
|
+
this._propagateMessageToListeners(internalMessage, false)
|
|
456
|
+
this.emit('orderbook', symbol, data)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* @param {string} symbol
|
|
461
|
+
* @param {number[]|number[][]} data
|
|
462
|
+
* @return {Error} err - null on success
|
|
463
|
+
* @private
|
|
464
|
+
*/
|
|
465
|
+
_updateManagedOB (symbol, data) {
|
|
466
|
+
if (Array.isArray(data[0])) { // snapshot, new OB
|
|
467
|
+
if (this._orderBooks[symbol]) {
|
|
468
|
+
return new Error(`recv snapshot for known OB: ${symbol}`)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
this._orderBooks[symbol] = data
|
|
472
|
+
return null
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// entry, needs to be applied to OB
|
|
476
|
+
if (!this._orderBooks[symbol]) {
|
|
477
|
+
return new Error(`recv update for unknown OB: ${symbol}`)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const success = OrderBook.updateArrayOBWith(this._orderBooks[symbol], data)
|
|
481
|
+
|
|
482
|
+
if (!success) {
|
|
483
|
+
return new Error(
|
|
484
|
+
`ob update for unknown price level: ${JSON.stringify(data)}`
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return null
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Returns an up-to-date copy of the order book for the specified symbol, or
|
|
493
|
+
* null if no OB is managed for that symbol.
|
|
494
|
+
* Set `manageOrderBooks: true` in the constructor to use.
|
|
495
|
+
*
|
|
496
|
+
* @param {string} symbol
|
|
497
|
+
* @return {OrderBook} ob - null if not found
|
|
498
|
+
*/
|
|
499
|
+
getOB (symbol) {
|
|
500
|
+
if (!this._orderBooks[symbol]) return null
|
|
501
|
+
|
|
502
|
+
return new OrderBook(this._orderBooks[symbol])
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* @param {Array} msg
|
|
507
|
+
* @param {Object} chanData
|
|
508
|
+
* @private
|
|
509
|
+
*/
|
|
510
|
+
_handleTradeMessage (msg, chanData) {
|
|
511
|
+
const eventName = msg.length === 3 ? msg[1] : 'trades'
|
|
512
|
+
let payload = msg[msg.length - 1]
|
|
513
|
+
|
|
514
|
+
if (!Array.isArray(payload[0])) {
|
|
515
|
+
payload = [payload]
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const model = msg[0] === 0 ? Trade : TradeTick // auth trades have more data
|
|
519
|
+
const data = this._transform ? model.unserialize(payload) : payload
|
|
520
|
+
const internalMessage = [chanData.chanId, eventName, data]
|
|
521
|
+
internalMessage.filterOverride = [chanData.pair]
|
|
522
|
+
|
|
523
|
+
this._propagateMessageToListeners(internalMessage, false)
|
|
524
|
+
this.emit('trades', chanData.pair, data)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* @param {Array} msg
|
|
529
|
+
* @param {Object} chanData
|
|
530
|
+
* @private
|
|
531
|
+
*/
|
|
532
|
+
_handleTickerMessage (msg, chanData) {
|
|
533
|
+
let data = msg[1]
|
|
534
|
+
|
|
535
|
+
if (this._transform) {
|
|
536
|
+
msg[1].splice(0, 0, chanData.symbol)
|
|
537
|
+
data = Tick.unserialize(msg[1])
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const internalMessage = [chanData.chanId, 'ticker', data]
|
|
541
|
+
internalMessage.filterOverride = [chanData.symbol]
|
|
542
|
+
|
|
543
|
+
this._propagateMessageToListeners(internalMessage, false)
|
|
544
|
+
this.emit('ticker', chanData.symbol, data)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Called for messages from a 'candles' channel. Might be an update or
|
|
549
|
+
* snapshot.
|
|
550
|
+
*
|
|
551
|
+
* @param {Array|Array[]} msg
|
|
552
|
+
* @param {Object} chanData - entry from _channelMap
|
|
553
|
+
* @private
|
|
554
|
+
*/
|
|
555
|
+
_handleCandleMessage (msg, chanData) {
|
|
556
|
+
const { key } = chanData
|
|
557
|
+
let data = msg[1]
|
|
558
|
+
|
|
559
|
+
if (this._manageCandles) {
|
|
560
|
+
const err = this._updateManagedCandles(key, data)
|
|
561
|
+
|
|
562
|
+
if (err) {
|
|
563
|
+
this.emit('error', err)
|
|
564
|
+
return
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
data = this._candles[key]
|
|
568
|
+
} else if (data.length > 0 && !Array.isArray(data[0])) {
|
|
569
|
+
data = [data] // always pass on an array of candles
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (this._transform) {
|
|
573
|
+
data = Candle.unserialize(data)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const internalMessage = [chanData.chanId, 'candle', data]
|
|
577
|
+
internalMessage.filterOverride = [chanData.key]
|
|
578
|
+
|
|
579
|
+
this._propagateMessageToListeners(internalMessage, false)
|
|
580
|
+
this.emit('candle', data, key)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* @param {string} symbol
|
|
585
|
+
* @param {number[]|number[][]} data
|
|
586
|
+
* @return {Error} err - null on success
|
|
587
|
+
* @private
|
|
588
|
+
*/
|
|
589
|
+
_updateManagedCandles (key, data) {
|
|
590
|
+
if (Array.isArray(data[0])) { // snapshot, new candles
|
|
591
|
+
if (this._candles[key]) {
|
|
592
|
+
return new Error(`recv snapshot for known candles: ${key}`)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
data.sort((a, b) => b[0] - a[0])
|
|
596
|
+
|
|
597
|
+
this._candles[key] = data
|
|
598
|
+
return null
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// entry, needs to be applied to candle set
|
|
602
|
+
if (!this._candles[key]) {
|
|
603
|
+
return new Error(`recv update for unknown candles: ${key}`)
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const candles = this._candles[key]
|
|
607
|
+
let updated = false
|
|
608
|
+
|
|
609
|
+
for (let i = 0; i < candles.length; i++) {
|
|
610
|
+
if (data[0] === candles[i][0]) {
|
|
611
|
+
candles[i] = data
|
|
612
|
+
updated = true
|
|
613
|
+
break
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (!updated) {
|
|
618
|
+
candles.unshift(data)
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return null
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Fetch a reference to the full set of synced candles for the specified key.
|
|
626
|
+
* Set `manageCandles: true` in the constructor to use.
|
|
627
|
+
*
|
|
628
|
+
* @param {string} key
|
|
629
|
+
* @return {Array} candles - empty array if none exist
|
|
630
|
+
*/
|
|
631
|
+
getCandles (key) {
|
|
632
|
+
return this._candles[key] || []
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* @param {Array} msg
|
|
637
|
+
* @param {Object} chanData
|
|
638
|
+
* @private
|
|
639
|
+
*/
|
|
640
|
+
_handleAuthMessage (msg, chanData) {
|
|
641
|
+
if (msg[1] === 'n') {
|
|
642
|
+
this._onWSNotification(msg[2])
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
this._propagateMessageToListeners(msg)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* @param {Array} msg
|
|
650
|
+
* @param {boolean} transform - defaults to internal flag
|
|
651
|
+
* @private
|
|
652
|
+
*/
|
|
653
|
+
_propagateMessageToListeners (msg, transform = this._transform) {
|
|
654
|
+
const listenerGroups = Object.values(this._listeners)
|
|
655
|
+
|
|
656
|
+
for (let i = 0; i < listenerGroups.length; i++) {
|
|
657
|
+
WSv2._notifyListenerGroup(listenerGroups[i], msg, transform, this)
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Applies filtering & transform to a packet before sending it out to matching
|
|
663
|
+
* listeners in the group.
|
|
664
|
+
*
|
|
665
|
+
* @param {Object} lGroup - listener group to parse & notify
|
|
666
|
+
* @param {Object} msg - passed to each matched listener
|
|
667
|
+
* @param {boolean} transform - whether or not to instantiate a model
|
|
668
|
+
* @param {WSv2} ws - instance to pass to models if transforming
|
|
669
|
+
* @private
|
|
670
|
+
*/
|
|
671
|
+
static _notifyListenerGroup (lGroup, msg, transform, ws) {
|
|
672
|
+
const [, eventName, data] = msg
|
|
673
|
+
let filterByData
|
|
674
|
+
|
|
675
|
+
// Catch-all can't filter/transform
|
|
676
|
+
WSv2._notifyCatchAllListeners(lGroup, msg)
|
|
677
|
+
|
|
678
|
+
if (!lGroup[eventName] || lGroup[eventName].length === 0) return
|
|
679
|
+
|
|
680
|
+
const listeners = lGroup[eventName].filter((listener) => {
|
|
681
|
+
const { filter } = listener
|
|
682
|
+
|
|
683
|
+
if (!filter) return true
|
|
684
|
+
|
|
685
|
+
// inspect snapshots for matching packets
|
|
686
|
+
if (Array.isArray(data[0])) {
|
|
687
|
+
const matchingData = data.filter((item) => {
|
|
688
|
+
filterByData = msg.filterOverride ? msg.filterOverride : item
|
|
689
|
+
|
|
690
|
+
return WSv2._payloadPassesFilter(filterByData, filter)
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
return matchingData.length !== 0
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// inspect single packet
|
|
697
|
+
filterByData = msg.filterOverride ? msg.filterOverride : data
|
|
698
|
+
|
|
699
|
+
return WSv2._payloadPassesFilter(filterByData, filter)
|
|
700
|
+
})
|
|
701
|
+
|
|
702
|
+
if (listeners.length === 0) return
|
|
703
|
+
|
|
704
|
+
listeners.forEach(({ cb, modelClass }) => {
|
|
705
|
+
const ModelClass = modelClass
|
|
706
|
+
|
|
707
|
+
if (!transform || data.length === 0) {
|
|
708
|
+
cb(data)
|
|
709
|
+
} else if (Array.isArray(data[0])) {
|
|
710
|
+
cb(data.map((entry) => {
|
|
711
|
+
return new ModelClass(entry, ws)
|
|
712
|
+
}))
|
|
713
|
+
} else {
|
|
714
|
+
cb(new ModelClass(data, ws))
|
|
715
|
+
}
|
|
716
|
+
})
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* @param {Array} payload
|
|
721
|
+
* @param {Object} filter
|
|
722
|
+
* @return {boolean} pass
|
|
723
|
+
* @private
|
|
724
|
+
*/
|
|
725
|
+
static _payloadPassesFilter (payload, filter) {
|
|
726
|
+
const filterIndices = Object.keys(filter)
|
|
727
|
+
|
|
728
|
+
for (let k = 0; k < filterIndices.length; k++) {
|
|
729
|
+
if (!filter[filterIndices[k]]) continue // no value provided
|
|
730
|
+
|
|
731
|
+
if (payload[+filterIndices[k]] !== filter[filterIndices[k]]) {
|
|
732
|
+
return false
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return true
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* @param {Object} lGroup - listener group keyed by event ('' in this case)
|
|
741
|
+
* @param {*} data - packet to pass to listeners
|
|
742
|
+
* @private
|
|
743
|
+
*/
|
|
744
|
+
static _notifyCatchAllListeners (lGroup, data) {
|
|
745
|
+
if (!lGroup['']) return
|
|
746
|
+
|
|
747
|
+
for (let j = 0; j < lGroup[''].length; j++) {
|
|
748
|
+
lGroup[''][j].cb(data)
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* @param {Object} msg
|
|
754
|
+
* @private
|
|
755
|
+
*/
|
|
756
|
+
_handleEventMessage (msg) {
|
|
757
|
+
if (msg.event === 'auth') {
|
|
758
|
+
return this._handleAuthEvent(msg)
|
|
759
|
+
} else if (msg.event === 'subscribed') {
|
|
760
|
+
return this._handleSubscribedEvent(msg)
|
|
761
|
+
} else if (msg.event === 'unsubscribed') {
|
|
762
|
+
return this._handleUnsubscribedEvent(msg)
|
|
763
|
+
} else if (msg.event === 'info') {
|
|
764
|
+
return this._handleInfoEvent(msg)
|
|
765
|
+
} else if (msg.event === 'conf') {
|
|
766
|
+
return this._handleConfigEvent(msg)
|
|
767
|
+
} else if (msg.event === 'error') {
|
|
768
|
+
return this._handleErrorEvent(msg)
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
debug('recv unknown event message: %j', msg)
|
|
772
|
+
return null
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* @param {Object} msg
|
|
777
|
+
* @private
|
|
778
|
+
*/
|
|
779
|
+
_handleConfigEvent (msg) {
|
|
780
|
+
if (msg.status !== 'OK') {
|
|
781
|
+
debug('config failed: %j', msg)
|
|
782
|
+
return this.emit('error', msg)
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* @param {Object} msg
|
|
788
|
+
* @private
|
|
789
|
+
*/
|
|
790
|
+
_handleErrorEvent (msg) {
|
|
791
|
+
this.emit('error', msg)
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* @param {Object} msg
|
|
796
|
+
* @private
|
|
797
|
+
*/
|
|
798
|
+
_handleAuthEvent (msg) {
|
|
799
|
+
if (msg.status !== 'OK') {
|
|
800
|
+
debug('auth failed: %j', msg)
|
|
801
|
+
return this.emit('error', msg)
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
this._channelMap[msg.chanId] = { channel: 'auth' }
|
|
805
|
+
this._isAuthenticated = true
|
|
806
|
+
this._wasEverAuthenticated = true
|
|
807
|
+
|
|
808
|
+
this.emit('auth', msg)
|
|
809
|
+
debug('authenticated!')
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* @param {Object} msg
|
|
814
|
+
* @private
|
|
815
|
+
*/
|
|
816
|
+
_handleSubscribedEvent (msg) {
|
|
817
|
+
this._channelMap[msg.chanId] = msg
|
|
818
|
+
|
|
819
|
+
debug('subscribed to %s [%d]', msg.channel, msg.chanId)
|
|
820
|
+
this.emit('subscribed', msg)
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* @param {Object} msg
|
|
825
|
+
* @private
|
|
826
|
+
*/
|
|
827
|
+
_handleUnsubscribedEvent (msg) {
|
|
828
|
+
delete this._channelMap[msg.chanId]
|
|
829
|
+
|
|
830
|
+
debug('unsubscribed from %s [%d]', msg.channel, msg.chanId)
|
|
831
|
+
this.emit('unsubscribed', msg)
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* @param {Object} msg
|
|
836
|
+
* @private
|
|
837
|
+
*/
|
|
838
|
+
_handleInfoEvent (msg) {
|
|
839
|
+
if (msg.version) {
|
|
840
|
+
if (msg.version !== 2) {
|
|
841
|
+
const err = new Error(`server not running API v2: v${msg.version}`)
|
|
842
|
+
|
|
843
|
+
this.emit('error', err)
|
|
844
|
+
this.close()
|
|
845
|
+
return
|
|
846
|
+
} else {
|
|
847
|
+
debug('server running API v2')
|
|
848
|
+
}
|
|
849
|
+
} else if (msg.code && this._infoListeners[msg.code]) {
|
|
850
|
+
this._infoListeners[msg.code].forEach(cb => cb(msg))
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
this.emit('info', msg)
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Subscribes and tracks subscriptions per channel/identifier pair. If
|
|
858
|
+
* already subscribed to the specified pair, nothing happens.
|
|
859
|
+
*
|
|
860
|
+
* @param {string} channel
|
|
861
|
+
* @param {string} identifier - for uniquely identifying the ref count
|
|
862
|
+
* @param {Object} payload - merged with sub packet
|
|
863
|
+
* @return {boolean} subSent
|
|
864
|
+
*/
|
|
865
|
+
managedSubscribe (channel = '', identifier = '', payload = {}) {
|
|
866
|
+
const key = `${channel}:${identifier}`
|
|
867
|
+
|
|
868
|
+
if (this._subscriptionRefs[key]) {
|
|
869
|
+
this._subscriptionRefs[key]++
|
|
870
|
+
return false
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
this._subscriptionRefs[key] = 1
|
|
874
|
+
this.subscribe(channel, payload)
|
|
875
|
+
|
|
876
|
+
return true
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* @param {string} channel
|
|
881
|
+
* @param {string} identifier
|
|
882
|
+
* @return {boolean} unsubSent
|
|
883
|
+
*/
|
|
884
|
+
managedUnsubscribe (channel = '', identifier = '') {
|
|
885
|
+
const key = `${channel}:${identifier}`
|
|
886
|
+
const chanId = this._chanIdByIdentifier(channel, identifier)
|
|
887
|
+
|
|
888
|
+
if (chanId === null || isNaN(this._subscriptionRefs[key])) return false
|
|
889
|
+
|
|
890
|
+
this._subscriptionRefs[key]--
|
|
891
|
+
if (this._subscriptionRefs[key] > 0) return false
|
|
892
|
+
|
|
893
|
+
this.unsubscribe(chanId)
|
|
894
|
+
delete this._subscriptionRefs[key]
|
|
895
|
+
|
|
896
|
+
return true
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* @param {Object} opts
|
|
901
|
+
* @param {number} opts.chanId
|
|
902
|
+
* @param {string} opts.channel - optional
|
|
903
|
+
* @param {string} opts.symbol - optional
|
|
904
|
+
* @param {string} opts.key - optional
|
|
905
|
+
* @return {Object} chanData - null if not found
|
|
906
|
+
*/
|
|
907
|
+
getChannelData ({ chanId, channel, symbol, key }) {
|
|
908
|
+
const id = chanId || this._chanIdByIdentifier(channel, symbol || key)
|
|
909
|
+
|
|
910
|
+
return this._channelMap[id] || null
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* @param {string} channel
|
|
915
|
+
* @param {string} identifier
|
|
916
|
+
* @private
|
|
917
|
+
*/
|
|
918
|
+
_chanIdByIdentifier (channel, identifier) {
|
|
919
|
+
const channelIds = Object.keys(this._channelMap)
|
|
920
|
+
let chan
|
|
921
|
+
|
|
922
|
+
for (let i = 0; i < channelIds.length; i++) {
|
|
923
|
+
chan = this._channelMap[channelIds[i]]
|
|
924
|
+
|
|
925
|
+
if (chan.channel === channel && (
|
|
926
|
+
chan.symbol === identifier ||
|
|
927
|
+
chan.key === identifier
|
|
928
|
+
)) {
|
|
929
|
+
return channelIds[i]
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return null
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* @param {string} key
|
|
938
|
+
* @private
|
|
939
|
+
*/
|
|
940
|
+
_getEventPromise (key) {
|
|
941
|
+
return new Promise((resolve, reject) => {
|
|
942
|
+
this._eventCallbacks.push(key, (err, res) => {
|
|
943
|
+
if (err) {
|
|
944
|
+
return reject(err)
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
resolve(res)
|
|
948
|
+
})
|
|
949
|
+
})
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Send a packet to the WS server
|
|
954
|
+
*
|
|
955
|
+
* @param {*} msg - packet, gets stringified
|
|
956
|
+
*/
|
|
957
|
+
send (msg) {
|
|
958
|
+
if (!this._ws) {
|
|
959
|
+
return this.emit('error', new Error('ws not open'))
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
debug('sending %j', msg)
|
|
963
|
+
this._ws.send(JSON.stringify(msg))
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Configures the seq flag to enable sequencing (packet number) for this
|
|
968
|
+
* connection. When enabled, the seq number will be the last value of
|
|
969
|
+
* channel packet arrays.
|
|
970
|
+
*
|
|
971
|
+
* @param {Object} args
|
|
972
|
+
* @param {boolean} args.audit - if true, an error is emitted on invalid seq
|
|
973
|
+
*/
|
|
974
|
+
enableSequencing (args = { audit: true }) {
|
|
975
|
+
this._seqAudit = args.audit === true
|
|
976
|
+
|
|
977
|
+
this.send({
|
|
978
|
+
event: 'conf',
|
|
979
|
+
flags: 65536
|
|
980
|
+
})
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Register a callback in case of a ws server restart message; Use this to
|
|
985
|
+
* call reconnect() if needed. (code 20051)
|
|
986
|
+
*
|
|
987
|
+
* @param {method} cb
|
|
988
|
+
*/
|
|
989
|
+
onServerRestart (cb) {
|
|
990
|
+
this.onInfoMessage(WSv2.info.SERVER_RESTART, cb)
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Register a callback in case of a 'maintenance started' message from the
|
|
995
|
+
* server. This is a good time to pause server packets until maintenance ends
|
|
996
|
+
*
|
|
997
|
+
* @param {method} cb
|
|
998
|
+
*/
|
|
999
|
+
onMaintenanceStart (cb) {
|
|
1000
|
+
this.onInfoMessage(WSv2.info.MAINTENANCE_START, cb)
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Register a callback to be notified of a maintenance period ending
|
|
1005
|
+
*
|
|
1006
|
+
* @param {method} cb
|
|
1007
|
+
*/
|
|
1008
|
+
onMaintenanceEnd (cb) {
|
|
1009
|
+
this.onInfoMessage(WSv2.info.MAINTENANCE_END, cb)
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* @param {string} channel
|
|
1014
|
+
* @param {Object} payload - optional extra packet data
|
|
1015
|
+
*/
|
|
1016
|
+
subscribe (channel, payload) {
|
|
1017
|
+
this.send(Object.assign({
|
|
1018
|
+
event: 'subscribe',
|
|
1019
|
+
channel
|
|
1020
|
+
}, payload))
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* @param {string} symbol
|
|
1025
|
+
* @return {boolean} subscribed
|
|
1026
|
+
*/
|
|
1027
|
+
subscribeTicker (symbol) {
|
|
1028
|
+
return this.managedSubscribe('ticker', symbol, { symbol })
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* @param {string} symbol
|
|
1033
|
+
* @return {boolean} subscribed
|
|
1034
|
+
*/
|
|
1035
|
+
subscribeTrades (symbol) {
|
|
1036
|
+
return this.managedSubscribe('trades', symbol, { symbol })
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* @param {string} symbol
|
|
1041
|
+
* @param {string} prec - P0, P1, P2, or P3 (default P0)
|
|
1042
|
+
* @param {string} len - 25 or 100 (default 25)
|
|
1043
|
+
* @return {boolean} subscribed
|
|
1044
|
+
*/
|
|
1045
|
+
subscribeOrderBook (symbol, prec = 'P0', len = '25') {
|
|
1046
|
+
return this.managedSubscribe('book', symbol, { symbol, len, prec })
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* @param {string} key
|
|
1051
|
+
* @return {boolean} subscribed
|
|
1052
|
+
*/
|
|
1053
|
+
subscribeCandles (key) {
|
|
1054
|
+
return this.managedSubscribe('candles', key, { key })
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* @param {number} chanId
|
|
1059
|
+
*/
|
|
1060
|
+
unsubscribe (chanId) {
|
|
1061
|
+
this.send({
|
|
1062
|
+
event: 'unsubscribe',
|
|
1063
|
+
chanId: +chanId
|
|
1064
|
+
})
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* @param {string} symbol
|
|
1069
|
+
* @return {boolean} unsubscribed
|
|
1070
|
+
*/
|
|
1071
|
+
unsubscribeTicker (symbol) {
|
|
1072
|
+
return this.managedUnsubscribe('ticker', symbol)
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* @param {string} symbol
|
|
1077
|
+
* @return {boolean} unsubscribed
|
|
1078
|
+
*/
|
|
1079
|
+
unsubscribeTrades (symbol) {
|
|
1080
|
+
return this.managedUnsubscribe('trades', symbol)
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* @param {string} symbol
|
|
1085
|
+
* @param {string} prec - P0, P1, P2, or P3 (default P0)
|
|
1086
|
+
* @param {string} len - 25 or 100 (default 25)
|
|
1087
|
+
* @return {boolean} unsubscribed
|
|
1088
|
+
*/
|
|
1089
|
+
unsubscribeOrderBook (symbol, prec = 'P0', len = '25') {
|
|
1090
|
+
return this.managedUnsubscribe('book', symbol)
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* @param {string} symbol
|
|
1095
|
+
* @param {string} frame - time frame
|
|
1096
|
+
* @return {boolean} unsubscribed
|
|
1097
|
+
*/
|
|
1098
|
+
unsubscribeCandles (symbol, frame) {
|
|
1099
|
+
return this.managedUnsubscribe('candles', `trade:${frame}:${symbol}`)
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* @param {string} cbGID
|
|
1104
|
+
*/
|
|
1105
|
+
removeListeners (cbGID) {
|
|
1106
|
+
delete this._listeners[cbGID]
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* @param {string[]} prefixes
|
|
1111
|
+
*/
|
|
1112
|
+
requestCalc (prefixes) {
|
|
1113
|
+
this._sendCalc([0, 'calc', null, prefixes.map(p => [p])])
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Throttled call to ws.send, max 8 op/s
|
|
1118
|
+
*
|
|
1119
|
+
* @param {Array} msg
|
|
1120
|
+
* @private
|
|
1121
|
+
*/
|
|
1122
|
+
_sendCalc (msg) {
|
|
1123
|
+
debug('req calc: %j', msg)
|
|
1124
|
+
|
|
1125
|
+
this._ws.send(msg)
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* Sends a new order to the server and resolves the returned promise once the
|
|
1130
|
+
* order submit is confirmed. Emits an error if not authenticated. The order
|
|
1131
|
+
* can be either an array, key/value map, or Order object instance.
|
|
1132
|
+
*
|
|
1133
|
+
* @param {Object|Array} order
|
|
1134
|
+
* @return {Promise} p - resolves on submit notification
|
|
1135
|
+
*/
|
|
1136
|
+
submitOrder (order) {
|
|
1137
|
+
if (!this._isAuthenticated) {
|
|
1138
|
+
return Promise.reject(new Error('not authenticated'))
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
const packet = Array.isArray(order)
|
|
1142
|
+
? order
|
|
1143
|
+
: order instanceof Order
|
|
1144
|
+
? order.toNewOrderPacket()
|
|
1145
|
+
: new Order(order).toNewOrderPacket()
|
|
1146
|
+
|
|
1147
|
+
this._sendOrderPacket([0, 'on', null, packet])
|
|
1148
|
+
|
|
1149
|
+
return this._getEventPromise(`order-new-${packet.cid}`)
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* Cancels an order by ID and resolves the returned promise once the cancel is
|
|
1154
|
+
* confirmed. Emits an error if not authenticated. The ID can be passed as a
|
|
1155
|
+
* number, or taken from an order array/object.
|
|
1156
|
+
*
|
|
1157
|
+
* @param {Object|Array|number} order
|
|
1158
|
+
* @return {Promise} p
|
|
1159
|
+
*/
|
|
1160
|
+
cancelOrder (order) {
|
|
1161
|
+
if (!this._isAuthenticated) {
|
|
1162
|
+
return Promise.reject(new Error('not authenticated'))
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
const id = typeof order === 'number'
|
|
1166
|
+
? order
|
|
1167
|
+
: Array.isArray(order)
|
|
1168
|
+
? order[0]
|
|
1169
|
+
: order.id
|
|
1170
|
+
|
|
1171
|
+
debug(`cancelling order ${id}`)
|
|
1172
|
+
this._sendOrderPacket([0, 'oc', null, { id }])
|
|
1173
|
+
|
|
1174
|
+
return this._getEventPromise(`order-cancel-${id}`)
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* Cancels multiple orders, returns a promise that resolves once all
|
|
1179
|
+
* operations are confirmed.
|
|
1180
|
+
*
|
|
1181
|
+
* @see cancelOrder
|
|
1182
|
+
*
|
|
1183
|
+
* @param {Object[]|Array[]|number[]} orders
|
|
1184
|
+
* @return {Promise} p
|
|
1185
|
+
*/
|
|
1186
|
+
cancelOrders (orders) {
|
|
1187
|
+
if (!this._isAuthenticated) {
|
|
1188
|
+
return Promise.reject(new Error('not authenticated'))
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
return Promise.all(orders.map((order) => {
|
|
1192
|
+
return this.cancelOrder(order)
|
|
1193
|
+
}))
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Sends the op payloads to the server as an 'ox_multi' command. A promise is
|
|
1198
|
+
* returned and resolves immediately if authenticated, as no confirmation is
|
|
1199
|
+
* available for this message type.
|
|
1200
|
+
*
|
|
1201
|
+
* @param {Object[]} opPayloads
|
|
1202
|
+
* @return {Promise} p - rejects if not authenticated
|
|
1203
|
+
*/
|
|
1204
|
+
submitOrderMultiOp (opPayloads) {
|
|
1205
|
+
if (!this._isAuthenticated) {
|
|
1206
|
+
return Promise.reject(new Error('not authenticated'))
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
this.send([0, 'ox_multi', null, opPayloads])
|
|
1210
|
+
|
|
1211
|
+
return Promise.resolve() // TODO: multi-op tracking
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* @param {array} packet
|
|
1216
|
+
* @private
|
|
1217
|
+
*/
|
|
1218
|
+
_sendOrderPacket (packet) {
|
|
1219
|
+
if (this._hasOrderBuff()) {
|
|
1220
|
+
this._ensureOrderBuffTimeout()
|
|
1221
|
+
this._orderOpBuffer.push(packet)
|
|
1222
|
+
} else {
|
|
1223
|
+
this.send(packet)
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* @return {boolean} buffEnabled
|
|
1229
|
+
* @private
|
|
1230
|
+
*/
|
|
1231
|
+
_hasOrderBuff () {
|
|
1232
|
+
return this._orderOpBufferDelay > 0
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* @private
|
|
1237
|
+
*/
|
|
1238
|
+
_ensureOrderBuffTimeout () {
|
|
1239
|
+
if (this._orderOpTimeout !== null) return
|
|
1240
|
+
|
|
1241
|
+
this._orderOpTimeout = setTimeout(
|
|
1242
|
+
this._flushOrderOps.bind(this),
|
|
1243
|
+
this._orderOpBufferDelay
|
|
1244
|
+
)
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/**
|
|
1248
|
+
* Splits the op buffer into packets of max 15 ops each, and sends them down
|
|
1249
|
+
* the wire.
|
|
1250
|
+
*
|
|
1251
|
+
* @return {Promise} p - resolves after send
|
|
1252
|
+
* @private
|
|
1253
|
+
*/
|
|
1254
|
+
_flushOrderOps () {
|
|
1255
|
+
this._orderOpTimeout = null
|
|
1256
|
+
|
|
1257
|
+
const packets = this._orderOpBuffer.map(p => [p[1], p[3]])
|
|
1258
|
+
this._orderOpBuffer = []
|
|
1259
|
+
|
|
1260
|
+
if (packets.length <= 15) {
|
|
1261
|
+
return this.submitOrderMultiOp(packets)
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
const promises = []
|
|
1265
|
+
|
|
1266
|
+
while (packets.length > 0) {
|
|
1267
|
+
const opPackets = packets.splice(0, Math.min(packets.length, 15))
|
|
1268
|
+
promises.push(this.submitOrderMultiOp(opPackets))
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
return Promise.all(promises)
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
/**
|
|
1275
|
+
* @return {boolean} authenticated
|
|
1276
|
+
*/
|
|
1277
|
+
isAuthenticated () {
|
|
1278
|
+
return this._isAuthenticated
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
* @return {boolean} open
|
|
1283
|
+
*/
|
|
1284
|
+
isOpen () {
|
|
1285
|
+
return this._isOpen
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* Adds a listener to the internal listener set, with an optional grouping
|
|
1290
|
+
* for batch unsubscribes (GID) & automatic ws packet matching (filterKey)
|
|
1291
|
+
*
|
|
1292
|
+
* @param {string} eventName - as received on ws stream
|
|
1293
|
+
* @param {Object} filter - map of index & value in ws packet
|
|
1294
|
+
* @param {object} modelClass - model to use for serialization
|
|
1295
|
+
* @param {string} cbGID - listener group ID for mass removal
|
|
1296
|
+
* @param {method} cb - listener
|
|
1297
|
+
* @private
|
|
1298
|
+
*/
|
|
1299
|
+
_registerListener (eventName, filter, modelClass, cbGID, cb) {
|
|
1300
|
+
if (!cbGID) cbGID = null
|
|
1301
|
+
|
|
1302
|
+
if (!this._listeners[cbGID]) {
|
|
1303
|
+
this._listeners[cbGID] = { [eventName]: [] }
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
const listeners = this._listeners[cbGID]
|
|
1307
|
+
|
|
1308
|
+
if (!listeners[eventName]) {
|
|
1309
|
+
listeners[eventName] = []
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
const l = {
|
|
1313
|
+
cb,
|
|
1314
|
+
modelClass,
|
|
1315
|
+
filter
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
listeners[eventName].push(l)
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/**
|
|
1322
|
+
* Registers a new callback to be called when a matching info message is
|
|
1323
|
+
* received.
|
|
1324
|
+
*
|
|
1325
|
+
* @param {number} code - from WSv2.info.*
|
|
1326
|
+
* @param {method} cb
|
|
1327
|
+
*/
|
|
1328
|
+
onInfoMessage (code, cb) {
|
|
1329
|
+
if (!this._infoListeners[code]) {
|
|
1330
|
+
this._infoListeners[code] = []
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
this._infoListeners[code].push(cb)
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
/**
|
|
1337
|
+
* @param {Object} opts
|
|
1338
|
+
* @param {string} opts.cbGID - callback group id
|
|
1339
|
+
* @param {Method} cb
|
|
1340
|
+
*/
|
|
1341
|
+
onMessage ({ cbGID }, cb) {
|
|
1342
|
+
this._registerListener('', null, null, cbGID, cb)
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
/**
|
|
1346
|
+
* @param {Object} opts
|
|
1347
|
+
* @param {string} opts.key - candle set key, i.e. trade:30m:tBTCUSD
|
|
1348
|
+
* @param {string} opts.cbGID - callback group id
|
|
1349
|
+
* @param {Method} cb
|
|
1350
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-public-candle
|
|
1351
|
+
*/
|
|
1352
|
+
onCandle ({ key, cbGID }, cb) {
|
|
1353
|
+
this._registerListener('candle', { 0: key }, Candle, cbGID, cb)
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* @param {Object} opts
|
|
1358
|
+
* @param {string} opts.symbol
|
|
1359
|
+
* @param {string} opts.prec
|
|
1360
|
+
* @param {string} opts.len
|
|
1361
|
+
* @param {string} opts.cbGID - callback group id
|
|
1362
|
+
* @param {Method} cb
|
|
1363
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-public-order-books
|
|
1364
|
+
*/
|
|
1365
|
+
onOrderBook ({ symbol, prec, len, cbGID }, cb) {
|
|
1366
|
+
this._registerListener('orderbook', {
|
|
1367
|
+
0: symbol,
|
|
1368
|
+
1: prec,
|
|
1369
|
+
2: len
|
|
1370
|
+
}, OrderBook, cbGID, cb)
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
/**
|
|
1374
|
+
* @param {Object} opts
|
|
1375
|
+
* @param {string} opts.pair
|
|
1376
|
+
* @param {string} opts.cbGID - callback group id
|
|
1377
|
+
* @param {Method} cb
|
|
1378
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-public-trades
|
|
1379
|
+
*/
|
|
1380
|
+
onTrades ({ pair, cbGID }, cb) {
|
|
1381
|
+
this._registerListener('trades', { 0: pair }, TradeTick, cbGID, cb)
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
* @param {Object} opts
|
|
1386
|
+
* @param {string} opts.symbol
|
|
1387
|
+
* @param {string} opts.cbGID - callback group id
|
|
1388
|
+
* @param {Method} cb
|
|
1389
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-public-ticker
|
|
1390
|
+
*/
|
|
1391
|
+
onTicker ({ symbol, cbGID }, cb) {
|
|
1392
|
+
this._registerListener('ticker', { 0: symbol }, Tick, cbGID, cb)
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/**
|
|
1396
|
+
* @param {Object} opts
|
|
1397
|
+
* @param {string} opts.symbol
|
|
1398
|
+
* @param {number} opts.gid
|
|
1399
|
+
* @param {string} opts.cbGID - callback group id
|
|
1400
|
+
* @param {Method} cb
|
|
1401
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-orders
|
|
1402
|
+
*/
|
|
1403
|
+
onOrderSnapshot ({ symbol, gid, cbGID }, cb) {
|
|
1404
|
+
this._registerListener('os', { 3: symbol, 1: gid }, Order, cbGID, cb)
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/**
|
|
1408
|
+
* @param {Object} opts
|
|
1409
|
+
* @param {string} opts.symbol
|
|
1410
|
+
* @param {number} opts.gid
|
|
1411
|
+
* @param {string} opts.cbGID - callback group id
|
|
1412
|
+
* @param {Method} cb
|
|
1413
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-orders
|
|
1414
|
+
*/
|
|
1415
|
+
onOrderNew ({ symbol, gid, cbGID }, cb) {
|
|
1416
|
+
this._registerListener('on', { 3: symbol, 1: gid }, Order, cbGID, cb)
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* @param {Object} opts
|
|
1421
|
+
* @param {string} opts.symbol
|
|
1422
|
+
* @param {number} opts.gid
|
|
1423
|
+
* @param {number} opts.cid
|
|
1424
|
+
* @param {string} opts.cbGID - callback group id
|
|
1425
|
+
* @param {Method} cb
|
|
1426
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-orders
|
|
1427
|
+
*/
|
|
1428
|
+
onOrderUpdate ({ symbol, gid, cid, cbGID }, cb) {
|
|
1429
|
+
this._registerListener('ou', { 3: symbol, 1: gid, 2: cid }, Order, cbGID, cb)
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
/**
|
|
1433
|
+
* @param {Object} opts
|
|
1434
|
+
* @param {string} opts.symbol
|
|
1435
|
+
* @param {number} opts.gid
|
|
1436
|
+
* @param {number} opts.cid
|
|
1437
|
+
* @param {string} opts.cbGID - callback group id
|
|
1438
|
+
* @param {Method} cb
|
|
1439
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-orders
|
|
1440
|
+
*/
|
|
1441
|
+
onOrderClose ({ symbol, gid, cid, cbGID }, cb) {
|
|
1442
|
+
this._registerListener('oc', { 3: symbol, 1: gid, 2: cid }, Order, cbGID, cb)
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* @param {Object} opts
|
|
1447
|
+
* @param {string} opts.symbol
|
|
1448
|
+
* @param {string} opts.cbGID - callback group id
|
|
1449
|
+
* @param {Method} cb
|
|
1450
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-position
|
|
1451
|
+
*/
|
|
1452
|
+
onPositionSnapshot ({ symbol, cbGID }, cb) {
|
|
1453
|
+
this._registerListener('ps', { 0: symbol }, Position, cbGID, cb)
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/**
|
|
1457
|
+
* @param {Object} opts
|
|
1458
|
+
* @param {string} opts.symbol
|
|
1459
|
+
* @param {string} opts.cbGID - callback group id
|
|
1460
|
+
* @param {Method} cb
|
|
1461
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-position
|
|
1462
|
+
*/
|
|
1463
|
+
onPositionNew ({ symbol, cbGID }, cb) {
|
|
1464
|
+
this._registerListener('pn', { 0: symbol }, Position, cbGID, cb)
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
/**
|
|
1468
|
+
* @param {Object} opts
|
|
1469
|
+
* @param {string} opts.symbol
|
|
1470
|
+
* @param {string} opts.cbGID - callback group id
|
|
1471
|
+
* @param {Method} cb
|
|
1472
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-position
|
|
1473
|
+
*/
|
|
1474
|
+
onPositionUpdate ({ symbol, cbGID }, cb) {
|
|
1475
|
+
this._registerListener('pu', { 0: symbol }, Position, cbGID, cb)
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* @param {Object} opts
|
|
1480
|
+
* @param {string} opts.symbol
|
|
1481
|
+
* @param {string} opts.cbGID - callback group id
|
|
1482
|
+
* @param {Method} cb
|
|
1483
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-position
|
|
1484
|
+
*/
|
|
1485
|
+
onPositionClose ({ symbol, cbGID }, cb) {
|
|
1486
|
+
this._registerListener('pc', { 0: symbol }, Position, cbGID, cb)
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/**
|
|
1490
|
+
* @param {Object} opts
|
|
1491
|
+
* @param {string} opts.pair
|
|
1492
|
+
* @param {string} opts.cbGID - callback group id
|
|
1493
|
+
* @param {Method} cb
|
|
1494
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-trades
|
|
1495
|
+
*/
|
|
1496
|
+
onTradeEntry ({ pair, cbGID }, cb) {
|
|
1497
|
+
this._registerListener('te', { 0: pair }, Trade, cbGID, cb)
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
/**
|
|
1501
|
+
* @param {Object} opts
|
|
1502
|
+
* @param {string} opts.pair
|
|
1503
|
+
* @param {string} opts.cbGID - callback group id
|
|
1504
|
+
* @param {Method} cb
|
|
1505
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-trades
|
|
1506
|
+
*/
|
|
1507
|
+
onTradeUpdate ({ pair, cbGID }, cb) {
|
|
1508
|
+
this._registerListener('tu', { 0: pair }, Trade, cbGID, cb)
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/**
|
|
1512
|
+
* @param {Object} opts
|
|
1513
|
+
* @param {string} opts.symbol
|
|
1514
|
+
* @param {string} opts.cbGID - callback group id
|
|
1515
|
+
* @param {Method} cb
|
|
1516
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-offers
|
|
1517
|
+
*/
|
|
1518
|
+
onFundingOfferSnapshot ({ symbol, cbGID }, cb) {
|
|
1519
|
+
this._registerListener('fos', { 1: symbol }, FundingOffer, cbGID, cb)
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
/**
|
|
1523
|
+
* @param {Object} opts
|
|
1524
|
+
* @param {string} opts.symbol
|
|
1525
|
+
* @param {string} opts.cbGID - callback group id
|
|
1526
|
+
* @param {Method} cb
|
|
1527
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-offers
|
|
1528
|
+
*/
|
|
1529
|
+
onFundingOfferNew ({ symbol, cbGID }, cb) {
|
|
1530
|
+
this._registerListener('fon', { 1: symbol }, FundingOffer, cbGID, cb)
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
/**
|
|
1534
|
+
* @param {Object} opts
|
|
1535
|
+
* @param {string} opts.symbol
|
|
1536
|
+
* @param {string} opts.cbGID - callback group id
|
|
1537
|
+
* @param {Method} cb
|
|
1538
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-offers
|
|
1539
|
+
*/
|
|
1540
|
+
onFundingOfferUpdate ({ symbol, cbGID }, cb) {
|
|
1541
|
+
this._registerListener('fou', { 1: symbol }, FundingOffer, cbGID, cb)
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
/**
|
|
1545
|
+
* @param {Object} opts
|
|
1546
|
+
* @param {string} opts.symbol
|
|
1547
|
+
* @param {string} opts.cbGID - callback group id
|
|
1548
|
+
* @param {Method} cb
|
|
1549
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-offers
|
|
1550
|
+
*/
|
|
1551
|
+
onFundingOfferClose ({ symbol, cbGID }, cb) {
|
|
1552
|
+
this._registerListener('foc', { 1: symbol }, FundingOffer, cbGID, cb)
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
/**
|
|
1556
|
+
* @param {Object} opts
|
|
1557
|
+
* @param {string} opts.symbol
|
|
1558
|
+
* @param {string} opts.cbGID - callback group id
|
|
1559
|
+
* @param {Method} cb
|
|
1560
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-credits
|
|
1561
|
+
*/
|
|
1562
|
+
onFundingCreditSnapshot ({ symbol, cbGID }, cb) {
|
|
1563
|
+
this._registerListener('fcs', { 1: symbol }, FundingCredit, cbGID, cb)
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
/**
|
|
1567
|
+
* @param {Object} opts
|
|
1568
|
+
* @param {string} opts.symbol
|
|
1569
|
+
* @param {string} opts.cbGID - callback group id
|
|
1570
|
+
* @param {Method} cb
|
|
1571
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-credits
|
|
1572
|
+
*/
|
|
1573
|
+
onFundingCreditNew ({ symbol, cbGID }, cb) {
|
|
1574
|
+
this._registerListener('fcn', { 1: symbol }, FundingCredit, cbGID, cb)
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
/**
|
|
1578
|
+
* @param {Object} opts
|
|
1579
|
+
* @param {string} opts.symbol
|
|
1580
|
+
* @param {string} opts.cbGID - callback group id
|
|
1581
|
+
* @param {Method} cb
|
|
1582
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-credits
|
|
1583
|
+
*/
|
|
1584
|
+
onFundingCreditUpdate ({ symbol, cbGID }, cb) {
|
|
1585
|
+
this._registerListener('fcu', { 1: symbol }, FundingCredit, cbGID, cb)
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* @param {Object} opts
|
|
1590
|
+
* @param {string} opts.symbol
|
|
1591
|
+
* @param {string} opts.cbGID - callback group id
|
|
1592
|
+
* @param {Method} cb
|
|
1593
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-credits
|
|
1594
|
+
*/
|
|
1595
|
+
onFundingCreditClose ({ symbol, cbGID }, cb) {
|
|
1596
|
+
this._registerListener('fcc', { 1: symbol }, FundingCredit, cbGID, cb)
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
/**
|
|
1600
|
+
* @param {Object} opts
|
|
1601
|
+
* @param {string} opts.symbol
|
|
1602
|
+
* @param {string} opts.cbGID - callback group id
|
|
1603
|
+
* @param {Method} cb
|
|
1604
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-loans
|
|
1605
|
+
*/
|
|
1606
|
+
onFundingLoanSnapshot ({ symbol, cbGID }, cb) {
|
|
1607
|
+
this._registerListener('fls', { 1: symbol }, FundingLoan, cbGID, cb)
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
/**
|
|
1611
|
+
* @param {Object} opts
|
|
1612
|
+
* @param {string} opts.symbol
|
|
1613
|
+
* @param {string} opts.cbGID - callback group id
|
|
1614
|
+
* @param {Method} cb
|
|
1615
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-loans
|
|
1616
|
+
*/
|
|
1617
|
+
onFundingLoanNew ({ symbol, cbGID }, cb) {
|
|
1618
|
+
this._registerListener('fln', { 1: symbol }, FundingLoan, cbGID, cb)
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
/**
|
|
1622
|
+
* @param {Object} opts
|
|
1623
|
+
* @param {string} opts.symbol
|
|
1624
|
+
* @param {string} opts.cbGID - callback group id
|
|
1625
|
+
* @param {Method} cb
|
|
1626
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-loans
|
|
1627
|
+
*/
|
|
1628
|
+
onFundingLoanUpdate ({ symbol, cbGID }, cb) {
|
|
1629
|
+
this._registerListener('flu', { 1: symbol }, FundingLoan, cbGID, cb)
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
/**
|
|
1633
|
+
* @param {Object} opts
|
|
1634
|
+
* @param {string} opts.symbol
|
|
1635
|
+
* @param {string} opts.cbGID - callback group id
|
|
1636
|
+
* @param {Method} cb
|
|
1637
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-loans
|
|
1638
|
+
*/
|
|
1639
|
+
onFundingLoanClose ({ symbol, cbGID }, cb) {
|
|
1640
|
+
this._registerListener('flc', { 1: symbol }, FundingLoan, cbGID, cb)
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/**
|
|
1644
|
+
* @param {Object} opts
|
|
1645
|
+
* @param {string} opts.cbGID - callback group id
|
|
1646
|
+
* @param {Method} cb
|
|
1647
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-wallets
|
|
1648
|
+
*/
|
|
1649
|
+
onWalletSnapshot ({ cbGID }, cb) {
|
|
1650
|
+
this._registerListener('ws', null, Wallet, cbGID, cb)
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
/**
|
|
1654
|
+
* @param {Object} opts
|
|
1655
|
+
* @param {string} opts.cbGID - callback group id
|
|
1656
|
+
* @param {Method} cb
|
|
1657
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-wallets
|
|
1658
|
+
*/
|
|
1659
|
+
onWalletUpdate ({ cbGID }, cb) {
|
|
1660
|
+
this._registerListener('wu', null, Wallet, cbGID, cb)
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
/**
|
|
1664
|
+
* @param {Object} opts
|
|
1665
|
+
* @param {string} opts.cbGID - callback group id
|
|
1666
|
+
* @param {Method} cb
|
|
1667
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-balance
|
|
1668
|
+
*/
|
|
1669
|
+
onBalanceInfoUpdate ({ cbGID }, cb) {
|
|
1670
|
+
this._registerListener('bu', null, BalanceInfo, cbGID, cb)
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
/**
|
|
1674
|
+
* @param {Object} opts
|
|
1675
|
+
* @param {string} opts.cbGID - callback group id
|
|
1676
|
+
* @param {Method} cb
|
|
1677
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-margin
|
|
1678
|
+
*/
|
|
1679
|
+
onMarginInfoUpdate ({ cbGID }, cb) {
|
|
1680
|
+
this._registerListener('miu', null, MarginInfo, cbGID, cb)
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
/**
|
|
1684
|
+
* @param {Object} opts
|
|
1685
|
+
* @param {string} opts.cbGID - callback group id
|
|
1686
|
+
* @param {Method} cb
|
|
1687
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-funding
|
|
1688
|
+
*/
|
|
1689
|
+
onFundingInfoUpdate ({ cbGID }, cb) {
|
|
1690
|
+
this._registerListener('fiu', null, FundingInfo, cbGID, cb)
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
/**
|
|
1694
|
+
* @param {Object} opts
|
|
1695
|
+
* @param {string} opts.symbol
|
|
1696
|
+
* @param {string} opts.cbGID - callback group id
|
|
1697
|
+
* @param {Method} cb
|
|
1698
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-funding-trades
|
|
1699
|
+
*/
|
|
1700
|
+
onFundingTradeEntry ({ symbol, cbGID }, cb) {
|
|
1701
|
+
this._registerListener('fte', { 1: symbol }, FundingTrade, cbGID, cb)
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* @param {Object} opts
|
|
1706
|
+
* @param {string} opts.symbol
|
|
1707
|
+
* @param {string} opts.cbGID - callback group id
|
|
1708
|
+
* @param {Method} cb
|
|
1709
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-funding-trades
|
|
1710
|
+
*/
|
|
1711
|
+
onFundingTradeUpdate ({ symbol, cbGID }, cb) {
|
|
1712
|
+
this._registerListener('ftu', { 1: symbol }, FundingTrade, cbGID, cb)
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
/**
|
|
1716
|
+
* @param {Object} opts
|
|
1717
|
+
* @param {string} opts.type
|
|
1718
|
+
* @param {string} opts.cbGID - callback group id
|
|
1719
|
+
* @param {Method} cb
|
|
1720
|
+
* @see https://docs.bitfinex.com/v2/reference#ws-auth-notifications
|
|
1721
|
+
*/
|
|
1722
|
+
onNotification ({ type, cbGID }, cb) {
|
|
1723
|
+
this._registerListener('n', { 1: type }, Notification, cbGID, cb)
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
WSv2.info = INFO_CODES
|
|
1728
|
+
|
|
1729
|
+
module.exports = WSv2
|