coinbase-pro-node-api
Version:
Node.js library for Coinbase Pro
142 lines (141 loc) • 3.92 kB
JavaScript
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();
}
});
});
}
}