UNPKG

cryptomarket

Version:

The CryptoMarket for Node.js

154 lines (140 loc) 4.98 kB
import { EventEmitter } from "events"; import WebSocket from "ws"; import { ResponsePromiseFactory } from "./ResponsePromiseFactory"; import { WSResponse } from "./WSResponse"; import { fromCamelCaseToSnakeCase } from "../paramStyleConverter"; export class WSClientBase { private uri: string; private subscriptionKeys: { [x: string]: any }; private multiResponsePromiseFactory: ResponsePromiseFactory; private nextId: number; private pingTimeout: NodeJS.Timeout; private requestTimeoutMs?: number protected ws: WebSocket; protected emitter: EventEmitter; constructor(uri: string, subscriptionKeys: { [x: string]: any } = {}, requestTimeoutMs?: number) { this.uri = uri; this.requestTimeoutMs = requestTimeoutMs; this.subscriptionKeys = subscriptionKeys; this.emitter = new EventEmitter(); this.nextId = 1; this.ws = new WebSocket(null); this.pingTimeout = setTimeout(() => { }); this.multiResponsePromiseFactory = new ResponsePromiseFactory(this.emitter); } protected getNextId(): number { if (this.nextId < 1) this.nextId = 1; const next = this.nextId; this.nextId++; return next; } protected heartbeat() { clearTimeout(this.pingTimeout); this.pingTimeout = setTimeout(() => { this.ws.terminate(); }, 30_000 + 1_000); } /** * Connects the client with the server * * Initializes the internal websocket, this method does not authenticate the client so it does not permit user related calls * * @returns A prommise that resolves after the connection */ public async connect(): Promise<void> { return new Promise<void>((resolve) => { if (this.ws.readyState === this.ws.OPEN) this.ws.close(); this.ws = new WebSocket(this.uri) .on("close", () => { clearTimeout(this.pingTimeout); }) .on("ping", () => { this.heartbeat(); }) .on("message", (msg: any) => { this.handle({ msgJson: msg }); }) .once("open", () => { this.heartbeat; resolve(); }); }); } /** * Close the client connection with the server * * @returns A promise that resolve after the closure */ public close(): Promise<void> { const promise = new Promise<void>((resolve) => { this.ws.once("close", () => { resolve(); }); }); this.ws.close(); return promise; } protected sendSubscription({ method, callback, params = {}, }: { method: string; callback: (...args: any[]) => void; params?: {}; }): Promise<unknown> { const { key } = this.subscriptionKeys[method]; this.emitter.on(key, callback); return this.request({ method, params }); } protected async sendUnsubscription({ method, params = {}, }: { method: any; params?: {}; }): Promise<Boolean> { const { key } = this.subscriptionKeys[method] this.emitter.removeAllListeners(key) const response = await this.request({ method, params }); return response as Boolean; } protected request({ method, params: paramsRaw = {} }: { method: any; params?: {}; }): Promise<unknown> { const params = fromCamelCaseToSnakeCase(paramsRaw) const { id, promise } = this.multiResponsePromiseFactory.newOneResponsePromise(); const payload = { method, params, id }; this.ws.send(JSON.stringify(payload)); return withTimeout(this.requestTimeoutMs, promise); } protected requestList({ method, params: paramsRaw = {}, responseCount = 1 }: { method: any; params?: {}; responseCount?: number; }): Promise<unknown> { const params = fromCamelCaseToSnakeCase(paramsRaw) const { id, promise } = this.multiResponsePromiseFactory.newMultiResponsePromise(responseCount); const payload = { method, params, id }; this.ws.send(JSON.stringify(payload)); return withTimeout(this.requestTimeoutMs, promise); } protected handle({ msgJson }: { msgJson: string }): void { const message = JSON.parse(msgJson); if ("id" in message) { this.handleResponse(message); } else if ("method" in message) { this.handleNotification(message); } } protected handleNotification({ method, params }: { method: string; params: any; }): void { const { key, type } = this.subscriptionKeys[method]; this.emitter.emit(key, params, type); } protected handleResponse(response: WSResponse): void { const id = response["id"]; if (id === null) return; this.emitter.emit(id, response); } } const withTimeout = (millis: number | undefined, promise: any) => { if (millis === undefined || millis === null) { return promise } let timeout: NodeJS.Timeout; const timeoutPromise = new Promise((resolve, reject) => timeout = setTimeout(() => reject(new Error(`Timed out after ${millis} ms.`)), millis)) return Promise.race([promise, timeoutPromise]) .finally(() => { if (timeout) { clearTimeout(timeout) } }) };