tardis-machine 18.4.2 → 18.6.0
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.
- package/dist/ws/subscriptionsmappers.d.ts.map +1 -1
- package/dist/ws/subscriptionsmappers.js +11 -1
- package/dist/ws/subscriptionsmappers.js.map +1 -1
- package/node_modules/tardis-dev/dist/consts.d.ts +1 -1
- package/node_modules/tardis-dev/dist/consts.js +1 -1
- package/node_modules/tardis-dev/dist/consts.js.map +1 -1
- package/node_modules/tardis-dev/dist/downloaddatasets.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/downloaddatasets.js +10 -4
- package/node_modules/tardis-dev/dist/downloaddatasets.js.map +1 -1
- package/node_modules/tardis-dev/dist/handy.d.ts +2 -1
- package/node_modules/tardis-dev/dist/handy.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/handy.js +16 -7
- package/node_modules/tardis-dev/dist/handy.js.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/bitflyer.js +3 -3
- package/node_modules/tardis-dev/dist/mappers/bitflyer.js.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/bitget.js +9 -9
- package/node_modules/tardis-dev/dist/mappers/bitget.js.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/gateiofutures.d.ts +3 -2
- package/node_modules/tardis-dev/dist/mappers/gateiofutures.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/gateiofutures.js +61 -6
- package/node_modules/tardis-dev/dist/mappers/gateiofutures.js.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/index.d.ts +2 -2
- package/node_modules/tardis-dev/dist/realtimefeeds/coinbasederivatives.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/coinbasederivatives.js +3 -11
- package/node_modules/tardis-dev/dist/realtimefeeds/coinbasederivatives.js.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/gateiofutures.d.ts +4 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/gateiofutures.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/gateiofutures.js +42 -7
- package/node_modules/tardis-dev/dist/realtimefeeds/gateiofutures.js.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.d.ts +6 -2
- package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.js +73 -26
- package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.js.map +1 -1
- package/node_modules/tardis-dev/package.json +1 -1
- package/node_modules/tardis-dev/src/consts.ts +1 -1
- package/node_modules/tardis-dev/src/downloaddatasets.ts +13 -4
- package/node_modules/tardis-dev/src/handy.ts +17 -7
- package/node_modules/tardis-dev/src/mappers/bitflyer.ts +3 -3
- package/node_modules/tardis-dev/src/mappers/bitget.ts +9 -9
- package/node_modules/tardis-dev/src/mappers/gateiofutures.ts +89 -7
- package/node_modules/tardis-dev/src/realtimefeeds/coinbasederivatives.ts +3 -11
- package/node_modules/tardis-dev/src/realtimefeeds/gateiofutures.ts +80 -8
- package/node_modules/tardis-dev/src/realtimefeeds/realtimefeed.ts +89 -28
- package/package.json +2 -2
- package/src/ws/subscriptionsmappers.ts +12 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Writable } from 'node:stream'
|
|
2
|
-
import {
|
|
2
|
+
import { getJSON, optimizeFilters } from '../handy.ts'
|
|
3
3
|
import { Filter } from '../types.ts'
|
|
4
4
|
import { MultiConnectionRealTimeFeedBase, PoolingClientBase, RealTimeFeedBase } from './realtimefeed.ts'
|
|
5
5
|
|
|
@@ -12,16 +12,8 @@ export class CoinbaseDerivativesRealTimeFeed extends MultiConnectionRealTimeFeed
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
for (const group of groups) {
|
|
18
|
-
const groupedFilters = wsFilters
|
|
19
|
-
.map((f) => ({
|
|
20
|
-
...f,
|
|
21
|
-
symbols: f.symbols?.filter((s) => group.includes(s))
|
|
22
|
-
}))
|
|
23
|
-
.filter((f) => f.symbols?.length || ['heartbeats', 'subscriptions'].includes(f.channel))
|
|
24
|
-
yield new CoinbaseDerivativesSingleConnectionRealTimeFeed(exchange, groupedFilters, timeoutIntervalMS, onError)
|
|
15
|
+
if (wsFilters.length > 0) {
|
|
16
|
+
yield new CoinbaseDerivativesSingleConnectionRealTimeFeed(exchange, wsFilters, timeoutIntervalMS, onError)
|
|
25
17
|
}
|
|
26
18
|
|
|
27
19
|
const productFilters = filters.filter((f) => f.channel === 'products')
|
|
@@ -9,16 +9,66 @@ export class GateIOFuturesRealTimeFeed extends MultiConnectionRealTimeFeedBase {
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
const inverseContractsFilters = filters.reduce(
|
|
12
|
-
this._only((s) => s.endsWith('
|
|
12
|
+
this._only((s) => s.endsWith('_USD')),
|
|
13
13
|
[] as Filter<string>[]
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
if (linearContractsFilters.length > 0) {
|
|
17
|
-
yield new GateIOFuturesSingleConnectionRealTimeFeed(
|
|
17
|
+
yield new GateIOFuturesSingleConnectionRealTimeFeed(
|
|
18
|
+
'wss://fx-ws.gateio.ws/v4/ws/usdt',
|
|
19
|
+
400,
|
|
20
|
+
exchange,
|
|
21
|
+
linearContractsFilters,
|
|
22
|
+
timeoutIntervalMS,
|
|
23
|
+
onError
|
|
24
|
+
)
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
if (inverseContractsFilters.length > 0) {
|
|
21
|
-
yield new GateIOFuturesSingleConnectionRealTimeFeed(
|
|
28
|
+
yield new GateIOFuturesSingleConnectionRealTimeFeed(
|
|
29
|
+
'wss://fx-ws.gateio.ws/v4/ws/btc',
|
|
30
|
+
400,
|
|
31
|
+
exchange,
|
|
32
|
+
inverseContractsFilters,
|
|
33
|
+
timeoutIntervalMS,
|
|
34
|
+
onError
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const usd1ContractsFilters = filters.reduce(
|
|
39
|
+
this._only((s) => s.endsWith('_USD1')),
|
|
40
|
+
[] as Filter<string>[]
|
|
41
|
+
)
|
|
42
|
+
if (usd1ContractsFilters.length > 0) {
|
|
43
|
+
yield new GateIOFuturesSingleConnectionRealTimeFeed(
|
|
44
|
+
'wss://fx-ws.gateio.ws/v4/ws/usd1',
|
|
45
|
+
400,
|
|
46
|
+
exchange,
|
|
47
|
+
usd1ContractsFilters,
|
|
48
|
+
timeoutIntervalMS,
|
|
49
|
+
onError
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Gate has no public liquidations channel for delivery futures
|
|
54
|
+
if (filters.every((filter) => filter.channel === 'public_liquidates' && filter.symbols!.every((s) => /_USDT_\d{8}$/.test(s)))) {
|
|
55
|
+
throw new Error('Gate.io delivery futures do not support public_liquidates')
|
|
56
|
+
}
|
|
57
|
+
const deliveryContractsFilters = filters
|
|
58
|
+
.filter((filter) => filter.channel !== 'public_liquidates')
|
|
59
|
+
.reduce(
|
|
60
|
+
this._only((s) => /_USDT_\d{8}$/.test(s)),
|
|
61
|
+
[] as Filter<string>[]
|
|
62
|
+
)
|
|
63
|
+
if (deliveryContractsFilters.length > 0) {
|
|
64
|
+
yield new GateIOFuturesSingleConnectionRealTimeFeed(
|
|
65
|
+
'wss://fx-ws.gateio.ws/v4/ws/delivery/usdt',
|
|
66
|
+
100,
|
|
67
|
+
exchange,
|
|
68
|
+
deliveryContractsFilters,
|
|
69
|
+
timeoutIntervalMS,
|
|
70
|
+
onError
|
|
71
|
+
)
|
|
22
72
|
}
|
|
23
73
|
}
|
|
24
74
|
|
|
@@ -27,6 +77,11 @@ export class GateIOFuturesRealTimeFeed extends MultiConnectionRealTimeFeedBase {
|
|
|
27
77
|
if (!current.symbols || current.symbols.length === 0) {
|
|
28
78
|
throw new Error('GateIOFuturesRealTimeFeed requires explicitly specified symbols when subscribing to live feed')
|
|
29
79
|
}
|
|
80
|
+
for (const symbol of current.symbols) {
|
|
81
|
+
if (!/_(USD|USDT|USD1)$/.test(symbol) && !/_USDT_\d{8}$/.test(symbol)) {
|
|
82
|
+
throw new Error(`Unsupported Gate.io futures symbol: ${symbol}`)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
30
85
|
|
|
31
86
|
const symbols = current.symbols.filter(filter)
|
|
32
87
|
if (symbols.length > 0) {
|
|
@@ -41,28 +96,29 @@ export class GateIOFuturesRealTimeFeed extends MultiConnectionRealTimeFeedBase {
|
|
|
41
96
|
}
|
|
42
97
|
|
|
43
98
|
class GateIOFuturesSingleConnectionRealTimeFeed extends RealTimeFeedBase {
|
|
44
|
-
protected readonly wssURL: string
|
|
45
99
|
protected readonly extraHeaders = { 'X-Gate-Size-Decimal': '1' }
|
|
100
|
+
private readonly bookSequences = new Map<string, number>()
|
|
46
101
|
|
|
47
102
|
constructor(
|
|
48
|
-
|
|
103
|
+
protected readonly wssURL: string,
|
|
104
|
+
private readonly orderBookDepth: 100 | 400,
|
|
49
105
|
exchange: string,
|
|
50
106
|
filters: Filter<string>[],
|
|
51
107
|
timeoutIntervalMS?: number,
|
|
52
108
|
onError?: (error: Error) => void
|
|
53
109
|
) {
|
|
54
110
|
super(exchange, filters, timeoutIntervalMS, onError)
|
|
55
|
-
this.wssURL = `wss://fx-ws.gateio.ws/v4/ws/${wsURLSuffix}`
|
|
56
111
|
}
|
|
57
112
|
|
|
58
113
|
protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
|
|
114
|
+
this.bookSequences.clear()
|
|
59
115
|
const payload = filters.flatMap((filter) => {
|
|
60
|
-
if (filter.channel === 'order_book') {
|
|
116
|
+
if (filter.channel === 'order_book' || filter.channel === 'obu') {
|
|
61
117
|
return filter.symbols!.map((symbol) => {
|
|
62
118
|
return {
|
|
63
119
|
event: 'subscribe',
|
|
64
120
|
channel: `futures.${filter.channel}`,
|
|
65
|
-
payload: [symbol, '20', '0'],
|
|
121
|
+
payload: filter.channel === 'obu' ? [`ob.${symbol}.${this.orderBookDepth}`] : [symbol, '20', '0'],
|
|
66
122
|
time: Math.floor(new Date().valueOf() / 1000)
|
|
67
123
|
}
|
|
68
124
|
})
|
|
@@ -81,6 +137,22 @@ class GateIOFuturesSingleConnectionRealTimeFeed extends RealTimeFeedBase {
|
|
|
81
137
|
return payload
|
|
82
138
|
}
|
|
83
139
|
|
|
140
|
+
protected onMessage(message: any) {
|
|
141
|
+
if (message.channel !== 'futures.obu' || (message.event !== 'update' && message.event !== 'all')) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const { result } = message
|
|
146
|
+
const previous = this.bookSequences.get(result.s)
|
|
147
|
+
if (
|
|
148
|
+
!Number.isSafeInteger(result.u) ||
|
|
149
|
+
(result.full !== true && (previous === undefined || result.U !== previous + 1 || result.u < result.U))
|
|
150
|
+
) {
|
|
151
|
+
throw new Error(`Gate.io OBU sequence gap for ${result.s}: previous=${previous}, U=${result.U}, u=${result.u}`)
|
|
152
|
+
}
|
|
153
|
+
this.bookSequences.set(result.s, result.u)
|
|
154
|
+
}
|
|
155
|
+
|
|
84
156
|
protected messageIsError(message: any): boolean {
|
|
85
157
|
if (message.error !== null && message.error !== undefined) {
|
|
86
158
|
return true
|
|
@@ -5,7 +5,7 @@ import { setTimeout as sleep } from 'node:timers/promises'
|
|
|
5
5
|
import { createDebug, type DebugLogger } from '../debug.ts'
|
|
6
6
|
import { getProxyAgent, HttpClientError, ONE_SEC_IN_MS, optimizeFilters } from '../handy.ts'
|
|
7
7
|
import { getMessageParser } from '../parsemessage.ts'
|
|
8
|
-
import { createManagedRealTimeIterator, mergeRealTime, type ManagedRealTimeIterator } from '../realtimeiterator.ts'
|
|
8
|
+
import { closeIterator, createManagedRealTimeIterator, mergeRealTime, type ManagedRealTimeIterator } from '../realtimeiterator.ts'
|
|
9
9
|
import { Exchange, Filter } from '../types.ts'
|
|
10
10
|
|
|
11
11
|
export type RealTimeFeed = {
|
|
@@ -19,7 +19,7 @@ export type RealTimeFeed = {
|
|
|
19
19
|
|
|
20
20
|
let connectionCounter = 1
|
|
21
21
|
|
|
22
|
-
export type RealTimeFeedIterable =
|
|
22
|
+
export type RealTimeFeedIterable = { [Symbol.asyncIterator](): ManagedRealTimeIterator<any> }
|
|
23
23
|
|
|
24
24
|
type RealTimeFeedConnection = {
|
|
25
25
|
id: number
|
|
@@ -95,6 +95,7 @@ export abstract class RealTimeFeedBase implements RealTimeFeedIterable {
|
|
|
95
95
|
let staleConnectionTimerId: NodeJS.Timeout | undefined
|
|
96
96
|
let pingTimerId: NodeJS.Timeout | undefined
|
|
97
97
|
let connectionError: Error | undefined
|
|
98
|
+
let delivered = false
|
|
98
99
|
|
|
99
100
|
try {
|
|
100
101
|
this.manualSnapshotsBuffer.length = 0
|
|
@@ -173,6 +174,7 @@ export abstract class RealTimeFeedBase implements RealTimeFeedIterable {
|
|
|
173
174
|
this.onMessage(messageDeserialized)
|
|
174
175
|
|
|
175
176
|
yield messageDeserialized
|
|
177
|
+
delivered = true
|
|
176
178
|
|
|
177
179
|
if (retries > 0) {
|
|
178
180
|
// reset retries counter as we've received correct message from the connection
|
|
@@ -216,33 +218,22 @@ export abstract class RealTimeFeedBase implements RealTimeFeedIterable {
|
|
|
216
218
|
if (connectionError !== undefined) {
|
|
217
219
|
this._onError?.(connectionError)
|
|
218
220
|
retries++
|
|
219
|
-
|
|
220
|
-
const MAX_DELAY = 32 * 1000
|
|
221
|
-
const httpError = connectionError instanceof HttpClientError ? connectionError : undefined
|
|
222
|
-
const isRateLimited = httpError?.status === 418 || httpError?.status === 429 || connectionError.message.includes('429')
|
|
223
|
-
|
|
224
|
-
if (isRateLimited) {
|
|
225
|
-
const fallbackDelay = httpError?.status === 418 ? 2 * 60 * 1000 : (MAX_DELAY / 2) * retries
|
|
226
|
-
retryDelay = httpError?.retryAfterMS ?? fallbackDelay
|
|
227
|
-
} else {
|
|
228
|
-
retryDelay = Math.min(Math.pow(2, retries - 1) * 1000, MAX_DELAY)
|
|
229
|
-
}
|
|
221
|
+
retryDelay = getRetryDelay(connectionError, retries)
|
|
230
222
|
|
|
231
223
|
this.debug(
|
|
232
|
-
'(connection id: %d) %s real-time feed connection error, retries count: %d, next retry delay: %dms,
|
|
224
|
+
'(connection id: %d) %s real-time feed connection error, retries count: %d, next retry delay: %dms, error message: %o',
|
|
233
225
|
connectionId,
|
|
234
226
|
this._exchange,
|
|
235
227
|
retries,
|
|
236
228
|
retryDelay,
|
|
237
|
-
isRateLimited,
|
|
238
229
|
connectionError
|
|
239
230
|
)
|
|
240
231
|
}
|
|
241
232
|
|
|
242
|
-
yield { __disconnect__: true }
|
|
233
|
+
yield { __disconnect__: true, error: connectionError, delivered }
|
|
243
234
|
|
|
244
235
|
if (retryDelay !== undefined) {
|
|
245
|
-
await
|
|
236
|
+
await wait(retryDelay, this._closeController.signal)
|
|
246
237
|
}
|
|
247
238
|
}
|
|
248
239
|
}
|
|
@@ -343,7 +334,7 @@ export abstract class RealTimeFeedBase implements RealTimeFeedIterable {
|
|
|
343
334
|
|
|
344
335
|
this._send(connection, message)
|
|
345
336
|
if (this.throttleSubscribeMS > 0) {
|
|
346
|
-
await
|
|
337
|
+
await wait(this.throttleSubscribeMS, connection.controller.signal)
|
|
347
338
|
}
|
|
348
339
|
}
|
|
349
340
|
|
|
@@ -352,7 +343,7 @@ export abstract class RealTimeFeedBase implements RealTimeFeedIterable {
|
|
|
352
343
|
const messagesBeforeSnapshots = subscribeMessages.length === 0 ? 0 : subscribeMessages.length + 1
|
|
353
344
|
// Wait for subscription acknowledgements and the first data message before fetching snapshots.
|
|
354
345
|
while (shouldCancel() === false && connection.receivedMessagesCount < messagesBeforeSnapshots) {
|
|
355
|
-
await
|
|
346
|
+
await wait(100, connection.controller.signal)
|
|
356
347
|
}
|
|
357
348
|
|
|
358
349
|
if (shouldCancel()) {
|
|
@@ -411,18 +402,31 @@ export abstract class RealTimeFeedBase implements RealTimeFeedIterable {
|
|
|
411
402
|
this.manualSnapshotsBuffer.length = 0
|
|
412
403
|
}
|
|
413
404
|
}
|
|
405
|
+
}
|
|
414
406
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
407
|
+
async function wait(delayMS: number, signal: AbortSignal) {
|
|
408
|
+
try {
|
|
409
|
+
await sleep(delayMS, undefined, { signal })
|
|
410
|
+
} catch (error) {
|
|
411
|
+
if (signal.aborted === false) {
|
|
412
|
+
throw error
|
|
422
413
|
}
|
|
423
414
|
}
|
|
424
415
|
}
|
|
425
416
|
|
|
417
|
+
function getRetryDelay(error: Error, retries: number) {
|
|
418
|
+
const MAX_DELAY = 32 * 1000
|
|
419
|
+
const httpError = error instanceof HttpClientError ? error : undefined
|
|
420
|
+
const isRateLimited = httpError?.status === 418 || httpError?.status === 429 || error.message.includes('429')
|
|
421
|
+
|
|
422
|
+
if (isRateLimited) {
|
|
423
|
+
const fallbackDelay = httpError?.status === 418 ? 2 * 60 * 1000 : (MAX_DELAY / 2) * retries
|
|
424
|
+
return httpError?.retryAfterMS ?? fallbackDelay
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return Math.min(Math.pow(2, retries - 1) * 1000, MAX_DELAY)
|
|
428
|
+
}
|
|
429
|
+
|
|
426
430
|
function resolveOrAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T | typeof ABORTED> {
|
|
427
431
|
if (signal.aborted) {
|
|
428
432
|
return Promise.resolve(ABORTED)
|
|
@@ -449,7 +453,12 @@ function resolveOrAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T
|
|
|
449
453
|
})
|
|
450
454
|
}
|
|
451
455
|
|
|
456
|
+
// A disconnect resets all normalized state, so when any connection drops, the feed closes all its connections and pollers,
|
|
457
|
+
// yields one disconnect and opens them again after the retry delay of the failed connection.
|
|
452
458
|
export abstract class MultiConnectionRealTimeFeedBase implements RealTimeFeedIterable {
|
|
459
|
+
private readonly _closeController = new AbortController()
|
|
460
|
+
private _connections?: ManagedRealTimeIterator<any>
|
|
461
|
+
|
|
453
462
|
constructor(
|
|
454
463
|
private readonly _exchange: string,
|
|
455
464
|
private readonly _filters: Filter<string>[],
|
|
@@ -458,8 +467,44 @@ export abstract class MultiConnectionRealTimeFeedBase implements RealTimeFeedIte
|
|
|
458
467
|
) {}
|
|
459
468
|
|
|
460
469
|
[Symbol.asyncIterator](): ManagedRealTimeIterator<any> {
|
|
461
|
-
|
|
462
|
-
|
|
470
|
+
return createManagedRealTimeIterator(this._stream(), () => {
|
|
471
|
+
this._closeController.abort()
|
|
472
|
+
return this._connections?.return()
|
|
473
|
+
})
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private async *_stream() {
|
|
477
|
+
let retries = 0
|
|
478
|
+
|
|
479
|
+
while (this._closeController.signal.aborted === false) {
|
|
480
|
+
this._connections = mergeRealTime(
|
|
481
|
+
Array.from(this._getRealTimeFeeds(this._exchange, this._filters, this._timeoutIntervalMS, this._onError), untilDisconnect)
|
|
482
|
+
)
|
|
483
|
+
let disconnect: { error?: Error; delivered?: boolean } | undefined
|
|
484
|
+
|
|
485
|
+
for await (const message of this._connections) {
|
|
486
|
+
if (message.__disconnect__ === true) {
|
|
487
|
+
disconnect = message
|
|
488
|
+
break
|
|
489
|
+
}
|
|
490
|
+
yield message
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (disconnect === undefined) {
|
|
494
|
+
return
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
yield { __disconnect__: true }
|
|
498
|
+
|
|
499
|
+
if (disconnect.delivered === true) {
|
|
500
|
+
retries = 0
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (disconnect.error !== undefined) {
|
|
504
|
+
retries++
|
|
505
|
+
await wait(getRetryDelay(disconnect.error, retries), this._closeController.signal)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
463
508
|
}
|
|
464
509
|
|
|
465
510
|
protected abstract _getRealTimeFeeds(
|
|
@@ -470,6 +515,22 @@ export abstract class MultiConnectionRealTimeFeedBase implements RealTimeFeedIte
|
|
|
470
515
|
): IterableIterator<RealTimeFeedIterable>
|
|
471
516
|
}
|
|
472
517
|
|
|
518
|
+
// A connection stops at its disconnect, so it cannot reconnect on its own before the feed restarts all connections.
|
|
519
|
+
function untilDisconnect(feed: RealTimeFeedIterable) {
|
|
520
|
+
const messages = feed[Symbol.asyncIterator]()
|
|
521
|
+
|
|
522
|
+
async function* stream() {
|
|
523
|
+
for await (const message of messages) {
|
|
524
|
+
yield message
|
|
525
|
+
if (message.__disconnect__ === true) {
|
|
526
|
+
return
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return createManagedRealTimeIterator(stream(), () => closeIterator(messages))
|
|
532
|
+
}
|
|
533
|
+
|
|
473
534
|
export abstract class PoolingClientBase implements RealTimeFeedIterable {
|
|
474
535
|
protected readonly debug: DebugLogger
|
|
475
536
|
private _tid: NodeJS.Timeout | undefined = undefined
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tardis-machine",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.6.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=24.19"
|
|
6
6
|
},
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"license": "MPL-2.0",
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"debug": "^4.4.3",
|
|
79
|
-
"tardis-dev": "^18.
|
|
79
|
+
"tardis-dev": "^18.6.0",
|
|
80
80
|
"ws": "^8.21.3"
|
|
81
81
|
},
|
|
82
82
|
"bundleDependencies": true,
|
|
@@ -439,9 +439,20 @@ const gateIOFuturesMapper: SubscriptionMapper = {
|
|
|
439
439
|
},
|
|
440
440
|
|
|
441
441
|
map: (message: any) => {
|
|
442
|
+
const channel = message.channel.split('.')[1]
|
|
443
|
+
if (channel === 'obu') {
|
|
444
|
+
return [{ channel, symbols: message.payload.map((stream: string) => stream.slice(3, stream.lastIndexOf('.'))) }]
|
|
445
|
+
}
|
|
446
|
+
if (channel === 'order_book') {
|
|
447
|
+
return [{ channel, symbols: [message.payload[0]] }]
|
|
448
|
+
}
|
|
449
|
+
if (channel === 'public_liquidates' && message.payload.includes('!all')) {
|
|
450
|
+
return [{ channel, symbols: [] }]
|
|
451
|
+
}
|
|
452
|
+
|
|
442
453
|
return [
|
|
443
454
|
{
|
|
444
|
-
channel
|
|
455
|
+
channel,
|
|
445
456
|
symbols: message.payload.map((s: any) => {
|
|
446
457
|
if (typeof s === 'string') {
|
|
447
458
|
return s
|