UNPKG

@crypdex/hydro-sdk

Version:

Javascript SDK for the Hydro API

214 lines (192 loc) 7.48 kB
import * as WebSocket from "ws" import { Channel, ChannelName } from "../models/Channel" import { Order, Side } from "../models/Order"; import { Orderbook, OrderbookLevel } from "../models/Orderbook"; import { Ticker } from "../models/Ticker" import { Trade } from "../models/Trade"; import { PriceLevel } from "../models/PriceLevel"; /** * The Hydro API provides a websocket connection that can push updates * about the exchange to clients. This watcher lets you create a listener * function to handle updates, and a subscription function to choose which * channels you wish to get updates on. * * See https://docs.ddex.io/#websocket */ export class HydroWatcher { private static SOCKET_URL: string = "wss://ws.ddex.io" private listener: HydroListener private socket?: WebSocket private messageQueue: string[] = [] /** * Initialize a new watcher with a set of listener functions that will * be called when a message of that type is received. The watcher will * not connect to the server until you subscribe to at least one channel. * * @param listener Object containing the functions to handle the updates you care about */ constructor(listener: HydroListener) { this.listener = listener } /** * Subscribe to a channel and begin receiving updates from the exchange * for a set of market ids * * See https://docs.ddex.io/#subscribe * * @param channel The name of the channel you want to subscribe to * @param marketIds A list of market ids you want updates for */ public subscribe(channel: ChannelName, marketIds: string[]) { this.initIfNeeded() this.sendMessage(this.generateMessage("subscribe", channel, marketIds)) } /** * Unsubscribe to stop receiving updates from the exchange for a particular * channel/market ids * * See https://docs.ddex.io/#unsubscribe * * @param channel The name of the channel you want to unsubscribe from * @param marketIds A list of market ids you no longer want updates for */ public unsubscribe(channel: ChannelName, marketIds: string[]) { this.initIfNeeded() this.sendMessage(this.generateMessage("unsubscribe", channel, marketIds)) } private initIfNeeded() { if (!this.socket || this.socket.readyState === WebSocket.CLOSED) { this.socket = new WebSocket(HydroWatcher.SOCKET_URL) this.socket.on('message', (message: string) => { this.receiveMessage(message) }) this.socket.on('close', () => { this.initIfNeeded() }) this.socket.on('open', () => { while (this.messageQueue.length > 0) { let message = this.messageQueue.shift() this.socket && this.socket.send(message) } }) } } private sendMessage(message: string) { if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(message) } else { this.messageQueue.push(message) } } private receiveMessage(message: string) { if (!this.listener) { return } const json = JSON.parse(message) switch(json.type) { case "subscriptions": return this.listener.subscriptionsUpdate && this.listener.subscriptionsUpdate(json.channels.map((channel: any) => new Channel(channel))) case "ticker": return this.listener.tickerUpdate && this.listener.tickerUpdate(new Ticker(json)) case "level2OrderbookSnapshot": return this.listener.orderbookSnapshot && this.listener.orderbookSnapshot(new Orderbook(json)) case "level2OrderbookUpdate": return json.changes && json.changes.forEach((change: any) => { this.listener.orderbookUpdate && this.listener.orderbookUpdate(change.side, new PriceLevel(change)) }) case "level3OrderbookSnapshot": return this.listener.fullSnapshot && this.listener.fullSnapshot(new Orderbook(json), json.sequence) case "receive": return this.listener.orderReceived && this.listener.orderReceived(new Order(json), json.sequence, new Date(json.time)) case "open": return this.listener.orderOpened && this.listener.orderOpened(new Order(json), json.sequence, new Date(json.time)) case "done": return this.listener.orderDone && this.listener.orderDone(new Order(json), json.sequence, new Date(json.time)) case "change": return this.listener.orderChanged && this.listener.orderChanged(new Order(json), json.sequence, new Date(json.time)) case "trade": return this.listener.tradeBegin && this.listener.tradeBegin(new Trade(json), json.sequence, new Date(json.time)) case "trade_success": return this.listener.tradeSuccess && this.listener.tradeSuccess(new Trade(json), json.sequence, new Date(json.time)) } } private generateMessage(type: string, channel: ChannelName, marketIds: string[]): string { return JSON.stringify({ type: type, channels: [ { name: channel, marketIds: marketIds, } ] }) } } export interface HydroListener { /** * Received whenever you subscribe or unsubscribe to tell you your * current list of channels and market ids you are watching */ subscriptionsUpdate?: (channels: Channel[]) => void /** * Received when subscribed to the 'ticker' channel and ticker * data is updated */ tickerUpdate?: (ticker: Ticker) => void /** * Received when subscribed to the 'orderbook' channel right * after you subscribe */ orderbookSnapshot?: (orderbook: Orderbook) => void /** * Received when subscribed to the 'orderbook' channel and there * are changes to the orderbook */ orderbookUpdate?: (side: Side, priceLevel: PriceLevel) => void /** * Received when subscribed to the 'full' channel right after * you subscribe */ fullSnapshot?: (orderbook: Orderbook, sequence: number) => void /** * Received when subscribed to the 'full' channel and a new order * has been created */ orderReceived?: (order: Order, sequence: number, time: Date) => void /** * Received when subscribed to the 'full' channel and a new order * has been created, but not immediately fulfilled */ orderOpened?: (order: Order, sequence: number, time: Date) => void /** * Received when subscribed to the 'full' channel and an order is * being taken off the orderbook, either due to being completely * fulfilled or because it was cancelled */ orderDone?: (order: Order, sequence: number, time: Date) => void /** * Received when subscribed to the 'full' channel and an order is * being updated with new data, usually because it was partially * fulfilled */ orderChanged?: (order: Order, sequence: number, time: Date) => void /** * Received when subscribed to the 'full' channel and two orders * have been matched, creating a trade */ tradeBegin?: (trade: Trade, sequence: number, time: Date) => void /** * Received when subscribed to the 'full' channel and a trade has * been successfully validated on the blockchain */ tradeSuccess?: (trade: Trade, sequence: number, time: Date) => void }