UNPKG

coinbase-pro-node-api

Version:
142 lines (141 loc) 3.92 kB
import { EventEmitter } from "node:events"; import WebSocket from "ws"; import { DefaultProductID } from "./public.js"; import { Signer } from "./signer.js"; export const WsUri = "wss://ws-feed.exchange.coinbase.com"; export const SandboxWsUri = "wss://ws-feed-public.sandbox.exchange.coinbase.com"; export const DefaultChannels = [ { name: "full", product_ids: [DefaultProductID] }, { name: "heartbeat", product_ids: [DefaultProductID] }, { name: "status", product_ids: [DefaultProductID] }, ]; export class WebSocketClient extends EventEmitter { ws; wsUri; channels; #key; #secret; #passphrase; constructor({ channels = DefaultChannels, key, secret, passphrase, sandbox = false, wsUri = sandbox ? SandboxWsUri : WsUri, } = {}) { super(); this.channels = channels; this.wsUri = wsUri; if (key && secret && passphrase) { this.#key = key; this.#secret = secret; this.#passphrase = passphrase; } } connect() { return new Promise((resolve, reject) => { switch (this.ws?.readyState) { case WebSocket.CLOSING: case WebSocket.CONNECTING: reject(new Error(`Could not connect. State: ${this.ws.readyState}`)); return; case WebSocket.OPEN: resolve(); return; default: break; } (this.ws = new WebSocket(this.wsUri)) .once("open", resolve) .once("error", reject) .on("message", (data) => { try { const message = JSON.parse(data); if (message.type === "error") { this.emit("error", message); } else { this.emit("message", message); } } catch (error) { this.emit("error", error); } }) .on("open", () => { this.emit("open"); this.subscribe({ channels: this.channels }).catch((error) => { this.emit("error", error); }); }) .on("close", () => { this.emit("close"); }) .on("error", (error) => { this.emit("error", error); }); }); } disconnect() { return new Promise((resolve, reject) => { switch (this.ws?.readyState) { case WebSocket.CLOSED: resolve(); return; case WebSocket.CLOSING: case WebSocket.CONNECTING: reject( new Error(`Could not disconnect. State: ${this.ws.readyState}`) ); return; default: break; } if (!this.ws) { resolve(); } else { this.ws.once("error", reject).once("close", resolve).close(); } }); } subscribe(params) { return this.#send({ ...params, type: "subscribe" }); } unsubscribe(params) { return this.#send({ ...params, type: "unsubscribe" }); } #send(params) { return new Promise((resolve, reject) => { const { ws } = this; if (!ws) { reject(new Error("WebSocket is not initialized")); return; } const message = params; if (this.#key && this.#secret && this.#passphrase) { const timestamp = Date.now() / 1000; const signature = Signer({ timestamp, body: "", method: "GET", url: new URL("/users/self/verify", this.wsUri), key: this.#key, secret: this.#secret, passphrase: this.#passphrase, }); message.key = signature["CB-ACCESS-KEY"]; message.signature = signature["CB-ACCESS-SIGN"]; message.timestamp = signature["CB-ACCESS-TIMESTAMP"]; message.passphrase = signature["CB-ACCESS-PASSPHRASE"]; } const data = JSON.stringify(message); ws.send(data, (error) => { if (error) { reject(error); } else { resolve(); } }); }); } }