tardis-dev 13.11.0 → 13.13.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/consts.d.ts +2 -1
- package/dist/consts.d.ts.map +1 -1
- package/dist/consts.js +15 -2
- package/dist/consts.js.map +1 -1
- package/dist/mappers/binance.d.ts +1 -1
- package/dist/mappers/binance.js +2 -2
- package/dist/mappers/binance.js.map +1 -1
- package/dist/mappers/bybit.d.ts +1 -1
- package/dist/mappers/bybitspot.d.ts +1 -1
- package/dist/mappers/cryptocom.d.ts +1 -1
- package/dist/mappers/huobi.d.ts +3 -3
- package/dist/mappers/index.d.ts +4 -4
- package/dist/mappers/index.d.ts.map +1 -1
- package/dist/mappers/index.js +9 -4
- package/dist/mappers/index.js.map +1 -1
- package/dist/mappers/woox.d.ts +111 -0
- package/dist/mappers/woox.d.ts.map +1 -0
- package/dist/mappers/woox.js +236 -0
- package/dist/mappers/woox.js.map +1 -0
- package/dist/realtimefeeds/index.d.ts.map +1 -1
- package/dist/realtimefeeds/index.js +3 -1
- package/dist/realtimefeeds/index.js.map +1 -1
- package/dist/realtimefeeds/woox.d.ts +11 -0
- package/dist/realtimefeeds/woox.d.ts.map +1 -0
- package/dist/realtimefeeds/woox.js +65 -0
- package/dist/realtimefeeds/woox.js.map +1 -0
- package/package.json +1 -1
- package/src/consts.ts +16 -2
- package/src/mappers/binance.ts +3 -3
- package/src/mappers/index.ts +9 -4
- package/src/mappers/woox.ts +322 -0
- package/src/realtimefeeds/index.ts +3 -1
- package/src/realtimefeeds/woox.ts +70 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { upperCaseSymbols } from '../handy'
|
|
2
|
+
import { BookChange, BookTicker, DerivativeTicker, Exchange, FilterForExchange, Liquidation, Trade } from '../types'
|
|
3
|
+
import { Mapper, PendingTickerInfoHelper } from './mapper'
|
|
4
|
+
|
|
5
|
+
export const wooxTradesMapper: Mapper<'woo-x', Trade> = {
|
|
6
|
+
canHandle(message: WooxTradeMessage) {
|
|
7
|
+
return message.topic !== undefined && message.topic.endsWith('@trade')
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
getFilters(symbols?: string[]) {
|
|
11
|
+
symbols = upperCaseSymbols(symbols)
|
|
12
|
+
|
|
13
|
+
return [
|
|
14
|
+
{
|
|
15
|
+
channel: 'trade',
|
|
16
|
+
symbols
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
*map(message: WooxTradeMessage, localTimestamp: Date): IterableIterator<Trade> {
|
|
22
|
+
const timestamp = new Date(message.ts)
|
|
23
|
+
|
|
24
|
+
yield {
|
|
25
|
+
type: 'trade',
|
|
26
|
+
symbol: message.data.symbol,
|
|
27
|
+
exchange: 'woo-x',
|
|
28
|
+
id: undefined,
|
|
29
|
+
price: message.data.price,
|
|
30
|
+
amount: message.data.size,
|
|
31
|
+
side: message.data.side === 'SELL' ? 'sell' : 'buy',
|
|
32
|
+
timestamp,
|
|
33
|
+
localTimestamp: localTimestamp
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class WooxBookChangeMapper implements Mapper<'woo-x', BookChange> {
|
|
39
|
+
private readonly _symbolToDepthInfoMapping: { [key: string]: LocalDepthInfo } = {}
|
|
40
|
+
|
|
41
|
+
canHandle(message: WooxOrderbookMessage | WooxOrderbookupdateMessage) {
|
|
42
|
+
if ('id' in message) {
|
|
43
|
+
return message.id.endsWith('@orderbook')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if ('topic' in message) {
|
|
47
|
+
return message.topic.endsWith('@orderbookupdate')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getFilters(symbols?: string[]) {
|
|
54
|
+
symbols = upperCaseSymbols(symbols)
|
|
55
|
+
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
channel: 'orderbook',
|
|
59
|
+
symbols
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
channel: 'orderbookupdate',
|
|
63
|
+
symbols
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
*map(message: WooxOrderbookMessage | WooxOrderbookupdateMessage, localTimestamp: Date): IterableIterator<BookChange> {
|
|
69
|
+
const symbol = message.data.symbol
|
|
70
|
+
|
|
71
|
+
if (this._symbolToDepthInfoMapping[symbol] === undefined) {
|
|
72
|
+
this._symbolToDepthInfoMapping[symbol] = {
|
|
73
|
+
bufferedUpdates: []
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const symbolDepthInfo = this._symbolToDepthInfoMapping[symbol]
|
|
78
|
+
const snapshotAlreadyProcessed = symbolDepthInfo.snapshotProcessed
|
|
79
|
+
|
|
80
|
+
// first check if received message is snapshot and process it as such if it is
|
|
81
|
+
if ('id' in message && message.success) {
|
|
82
|
+
yield {
|
|
83
|
+
type: 'book_change',
|
|
84
|
+
symbol,
|
|
85
|
+
exchange: 'woo-x',
|
|
86
|
+
isSnapshot: true,
|
|
87
|
+
bids: message.data.bids.map(this._mapBookLevel),
|
|
88
|
+
asks: message.data.asks.map(this._mapBookLevel),
|
|
89
|
+
timestamp: new Date(message.data.ts),
|
|
90
|
+
localTimestamp
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// mark given symbol depth info that has snapshot processed
|
|
94
|
+
symbolDepthInfo.lastUpdateTimestamp = message.data.ts
|
|
95
|
+
symbolDepthInfo.snapshotProcessed = true
|
|
96
|
+
|
|
97
|
+
// if there were any depth updates buffered, let's proccess those
|
|
98
|
+
for (const update of symbolDepthInfo.bufferedUpdates) {
|
|
99
|
+
const bookChange = this._mapBookDepthUpdate(update, localTimestamp, symbolDepthInfo, symbol)
|
|
100
|
+
if (bookChange !== undefined) {
|
|
101
|
+
yield bookChange
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// remove all buffered updates
|
|
105
|
+
symbolDepthInfo.bufferedUpdates = []
|
|
106
|
+
} else if (snapshotAlreadyProcessed) {
|
|
107
|
+
// snapshot was already processed let's map the message as normal book_change
|
|
108
|
+
const bookChange = this._mapBookDepthUpdate(message as WooxOrderbookupdateMessage, localTimestamp, symbolDepthInfo, symbol)
|
|
109
|
+
if (bookChange !== undefined) {
|
|
110
|
+
yield bookChange
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
// if snapshot hasn't been yet processed and we've got depthUpdate message, let's buffer it for later processing
|
|
114
|
+
symbolDepthInfo.bufferedUpdates.push(message as WooxOrderbookupdateMessage)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private _mapBookDepthUpdate(
|
|
119
|
+
wooxBookUpdate: WooxOrderbookupdateMessage,
|
|
120
|
+
localTimestamp: Date,
|
|
121
|
+
depthInfo: LocalDepthInfo,
|
|
122
|
+
symbol: string
|
|
123
|
+
): BookChange | undefined {
|
|
124
|
+
if (wooxBookUpdate.data.prevTs < depthInfo.lastUpdateTimestamp!) {
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
type: 'book_change',
|
|
130
|
+
symbol,
|
|
131
|
+
exchange: 'woo-x',
|
|
132
|
+
isSnapshot: false,
|
|
133
|
+
bids: wooxBookUpdate.data.bids.map(this._mapBookLevel),
|
|
134
|
+
asks: wooxBookUpdate.data.asks.map(this._mapBookLevel),
|
|
135
|
+
timestamp: new Date(wooxBookUpdate.ts),
|
|
136
|
+
localTimestamp
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private _mapBookLevel(level: [number, number]) {
|
|
141
|
+
const price = level[0]
|
|
142
|
+
const amount = level[1]
|
|
143
|
+
|
|
144
|
+
return { price, amount }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export class WooxDerivativeTickerMapper implements Mapper<'woo-x', DerivativeTicker> {
|
|
149
|
+
private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
|
|
150
|
+
private readonly _indexPrices = new Map<string, number>()
|
|
151
|
+
|
|
152
|
+
canHandle(message: WooxTradeMessage | WooxEstFundingRate | WooxMarkPrice | WooxIndexPrice | WooxOpenInterest) {
|
|
153
|
+
if (message.topic === undefined) {
|
|
154
|
+
return false
|
|
155
|
+
}
|
|
156
|
+
const symbol = (message.data && message.data.symbol) || ''
|
|
157
|
+
const isPerp = symbol.startsWith('PERP_')
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
(message.topic.endsWith('@trade') && isPerp) ||
|
|
161
|
+
(message.topic.endsWith('@markprice') && isPerp) ||
|
|
162
|
+
(message.topic.endsWith('@estfundingrate') && isPerp) ||
|
|
163
|
+
message.topic.endsWith('@indexprice') ||
|
|
164
|
+
(message.topic.endsWith('@openinterest') && isPerp)
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
getFilters(symbols?: string[]) {
|
|
169
|
+
symbols = upperCaseSymbols(symbols)
|
|
170
|
+
const spotSymbols = symbols !== undefined ? symbols.map((s) => s.replace('PERP_', 'SPOT_')) : []
|
|
171
|
+
return [
|
|
172
|
+
{
|
|
173
|
+
channel: 'trade',
|
|
174
|
+
symbols
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
channel: 'markprice',
|
|
178
|
+
symbols
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
channel: 'estfundingrate',
|
|
182
|
+
symbols
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
channel: 'openinterest',
|
|
186
|
+
symbols
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
channel: 'indexprice',
|
|
190
|
+
symbols: spotSymbols
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
*map(message: any, localTimestamp: Date): IterableIterator<DerivativeTicker> {
|
|
196
|
+
if (message.topic.endsWith('@indexprice')) {
|
|
197
|
+
this._indexPrices.set(message.data.symbol.replace('SPOT_', 'PERP_'), message.data.price)
|
|
198
|
+
} else {
|
|
199
|
+
const symbol = message.data.symbol
|
|
200
|
+
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(symbol, 'woo-x')
|
|
201
|
+
|
|
202
|
+
const lastIndexPrice = this._indexPrices.get(symbol)
|
|
203
|
+
if (lastIndexPrice !== undefined) {
|
|
204
|
+
pendingTickerInfo.updateIndexPrice(lastIndexPrice)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (message.topic.endsWith('@markprice')) {
|
|
208
|
+
pendingTickerInfo.updateMarkPrice(message.data.price)
|
|
209
|
+
pendingTickerInfo.updateTimestamp(new Date(message.ts))
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (message.topic.endsWith('@trade')) {
|
|
213
|
+
pendingTickerInfo.updateLastPrice(message.data.price)
|
|
214
|
+
pendingTickerInfo.updateTimestamp(new Date(message.ts))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (message.topic.endsWith('@estfundingrate')) {
|
|
218
|
+
pendingTickerInfo.updateFundingRate(message.data.fundingRate)
|
|
219
|
+
pendingTickerInfo.updateFundingTimestamp(new Date(message.data.fundingTs))
|
|
220
|
+
pendingTickerInfo.updateTimestamp(new Date(message.ts))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (message.topic.endsWith('@openinterest')) {
|
|
224
|
+
pendingTickerInfo.updateOpenInterest(message.data.openInterest)
|
|
225
|
+
pendingTickerInfo.updateTimestamp(new Date(message.ts))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (pendingTickerInfo.hasChanged()) {
|
|
229
|
+
yield pendingTickerInfo.getSnapshot(localTimestamp)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export class WooxBookTickerMapper implements Mapper<'woo-x', BookTicker> {
|
|
236
|
+
canHandle(message: WooxTradeMessage) {
|
|
237
|
+
return message.topic !== undefined && message.topic.endsWith('@bbo')
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
getFilters(symbols?: string[]) {
|
|
241
|
+
symbols = upperCaseSymbols(symbols)
|
|
242
|
+
|
|
243
|
+
return [
|
|
244
|
+
{
|
|
245
|
+
channel: 'bbo',
|
|
246
|
+
symbols
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
*map(wooxBBOMessage: WooxBBOMessage, localTimestamp: Date) {
|
|
252
|
+
const wooxBookTicker = wooxBBOMessage.data
|
|
253
|
+
|
|
254
|
+
const ticker: BookTicker = {
|
|
255
|
+
type: 'book_ticker',
|
|
256
|
+
symbol: wooxBookTicker.symbol,
|
|
257
|
+
exchange: 'woo-x',
|
|
258
|
+
askAmount: wooxBookTicker.askSize !== undefined ? wooxBookTicker.askSize : undefined,
|
|
259
|
+
askPrice: wooxBookTicker.ask !== undefined ? wooxBookTicker.ask : undefined,
|
|
260
|
+
|
|
261
|
+
bidPrice: wooxBookTicker.bid !== undefined ? wooxBookTicker.bid : undefined,
|
|
262
|
+
bidAmount: wooxBookTicker.bidSize !== undefined ? wooxBookTicker.bidSize : undefined,
|
|
263
|
+
timestamp: new Date(wooxBBOMessage.ts),
|
|
264
|
+
localTimestamp: localTimestamp
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
yield ticker
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
type LocalDepthInfo = {
|
|
272
|
+
bufferedUpdates: WooxOrderbookupdateMessage[]
|
|
273
|
+
snapshotProcessed?: boolean
|
|
274
|
+
lastUpdateTimestamp?: number
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
type WooxTradeMessage = {
|
|
278
|
+
topic: 'PERP_GALA_USDT@trade'
|
|
279
|
+
ts: 1674431999995
|
|
280
|
+
data: { symbol: 'PERP_GALA_USDT'; price: 0.048756; size: 4109; side: 'SELL'; source: 0 }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
type WooxOrderbookupdateMessage = {
|
|
284
|
+
topic: 'PERP_BTC_USDT@orderbookupdate'
|
|
285
|
+
ts: 1674432000020
|
|
286
|
+
data: {
|
|
287
|
+
symbol: 'PERP_BTC_USDT'
|
|
288
|
+
prevTs: 1674432000030
|
|
289
|
+
asks: [[22712.7, 15.4675]]
|
|
290
|
+
bids: [[22708.0, 4.503]]
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
type WooxOrderbookMessage = {
|
|
295
|
+
id: 'PERP_BTC_USDT@orderbook'
|
|
296
|
+
event: 'request'
|
|
297
|
+
success: true
|
|
298
|
+
ts: 1674432000034
|
|
299
|
+
data: {
|
|
300
|
+
symbol: 'PERP_BTC_USDT'
|
|
301
|
+
ts: 1674432000020
|
|
302
|
+
asks: [[22712.7, 15.4675], [26772.1, 0.248]]
|
|
303
|
+
bids: [[22708.0, 4.503], [18555.0, 0.002]]
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
type WooxBBOMessage = {
|
|
308
|
+
topic: 'SPOT_ETH_USDT@bbo'
|
|
309
|
+
ts: 1674431999996
|
|
310
|
+
data: { symbol: 'SPOT_ETH_USDT'; ask: 1627.61; askSize: 38.3755; bid: 1627.26; bidSize: 20.424926 }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
type WooxMarkPrice = { topic: 'PERP_BTC_USDT@markprice'; ts: 1674432000007; data: { symbol: 'PERP_BTC_USDT'; price: 22711.11 } }
|
|
314
|
+
|
|
315
|
+
type WooxEstFundingRate = {
|
|
316
|
+
topic: 'PERP_BTC_USDT@estfundingrate'
|
|
317
|
+
ts: 1674432059002
|
|
318
|
+
data: { symbol: 'PERP_BTC_USDT'; fundingRate: 0.00000782; fundingTs: 1674435600005 }
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
type WooxIndexPrice = { topic: 'SPOT_BTC_USDT@indexprice'; ts: 1674432000024; data: { symbol: 'SPOT_BTC_USDT'; price: 22708.44 } }
|
|
322
|
+
type WooxOpenInterest = { topic: 'PERP_BTC_USDT@openinterest'; ts: 1674432013624; data: { symbol: 'PERP_BTC_USDT'; openInterest: 83.2241 } }
|
|
@@ -45,6 +45,7 @@ import { BybitSpotRealTimeFeed } from './bybitspot'
|
|
|
45
45
|
import { CryptoComRealTimeFeed } from './cryptocom'
|
|
46
46
|
import { KucoinRealTimeFeed } from './kucoin'
|
|
47
47
|
import { BitnomialRealTimeFeed } from './bitnomial'
|
|
48
|
+
import { WooxRealTimeFeed } from './woox'
|
|
48
49
|
|
|
49
50
|
export * from './realtimefeed'
|
|
50
51
|
|
|
@@ -98,7 +99,8 @@ const realTimeFeedsMap: {
|
|
|
98
99
|
'crypto-com': CryptoComRealTimeFeed,
|
|
99
100
|
'crypto-com-derivatives': CryptoComRealTimeFeed,
|
|
100
101
|
kucoin: KucoinRealTimeFeed,
|
|
101
|
-
bitnomial: BitnomialRealTimeFeed
|
|
102
|
+
bitnomial: BitnomialRealTimeFeed,
|
|
103
|
+
'woo-x': WooxRealTimeFeed
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
export function getRealTimeFeedFactory(exchange: Exchange): RealTimeFeed {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { wait } from '../handy'
|
|
2
|
+
import { Filter } from '../types'
|
|
3
|
+
import { RealTimeFeedBase } from './realtimefeed'
|
|
4
|
+
|
|
5
|
+
export class WooxRealTimeFeed extends RealTimeFeedBase {
|
|
6
|
+
protected wssURL = 'wss://wss.woo.org/ws/stream/OqdphuyCtYWxwzhxyLLjOWNdFP7sQt8RPWzmb5xY'
|
|
7
|
+
|
|
8
|
+
protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
|
|
9
|
+
return filters
|
|
10
|
+
.filter((filter) => filter.channel !== 'orderbook')
|
|
11
|
+
.map((filter) => {
|
|
12
|
+
if (!filter.symbols || filter.symbols.length === 0) {
|
|
13
|
+
throw new Error('WooxRealTimeFeed requires explicitly specified symbols when subscribing to live feed')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return filter.symbols.map((symbol) => {
|
|
17
|
+
return {
|
|
18
|
+
id: `${symbol}@${filter.channel}`,
|
|
19
|
+
topic: `${symbol}@${filter.channel}`,
|
|
20
|
+
event: 'subscribe'
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
.flatMap((f) => f)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected async provideManualSnapshots(filters: Filter<string>[], shouldCancel: () => boolean) {
|
|
28
|
+
const orderbookFilter = filters.find((f) => f.channel === 'orderbook')
|
|
29
|
+
if (!orderbookFilter) {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await wait(200)
|
|
34
|
+
|
|
35
|
+
for (let symbol of orderbookFilter.symbols!) {
|
|
36
|
+
if (shouldCancel()) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.send({
|
|
41
|
+
id: `${symbol}@orderbook`,
|
|
42
|
+
event: 'request',
|
|
43
|
+
params: {
|
|
44
|
+
type: 'orderbook',
|
|
45
|
+
symbol
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
await wait(1)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.debug('sent orderbook requests for: %s', orderbookFilter.symbols)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected messageIsError(message: any): boolean {
|
|
56
|
+
return message.success === false || message.errorMsg !== undefined
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected messageIsHeartbeat(message: any): boolean {
|
|
60
|
+
return message.event === 'ping'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
protected onMessage(msg: any) {
|
|
64
|
+
if (msg.event === 'ping') {
|
|
65
|
+
this.send({
|
|
66
|
+
event: 'ping'
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|