okex-api
Version:
A modern, asynchronous, and easy-to-use [Okx API](https://www.okx.com/docs-v5) wrapper for Node.js/bun/Web Browser/Cloudflare workers.
190 lines (189 loc) • 6.69 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WsPrivate = exports.WsPublic = void 0;
const utils_1 = require("./utils");
const wsEvents = new Set(['error', 'login', 'subscribe', 'unsubscribe', 'channel-conn-count']);
class WsApiEvent extends Event {
data;
constructor(type, data) {
super(type);
this.data = data;
}
}
class WsChannelEvent extends Event {
arg;
data;
constructor(type, arg, data) {
super(type);
this.arg = arg;
this.data = data;
}
}
class WsApi extends EventTarget {
url;
ws;
shouldReconnect = true;
constructor(url) {
super();
this.url = url;
}
addEventListener(type, listener, options) {
super.addEventListener(type, listener, options);
}
connect() {
let pingInterval;
const ws = new WebSocket(this.url);
ws.onmessage = (event) => {
const data = event.data;
if (data !== 'pong') {
const event = JSON.parse(data);
if (wsEvents.has(event.event)) {
this.dispatchEvent(new WsApiEvent(event.event, event));
}
else if (event.op && event.id) {
this.dispatchEvent(new WsApiEvent(event.op + '-' + event.id, event));
}
else if (event.arg) {
const ev = event;
this.dispatchEvent(new WsChannelEvent(ev.arg.channel, ev.arg, ev.data));
}
}
};
ws.onopen = () => {
this.ws = ws;
this.dispatchEvent(new Event('open'));
pingInterval = setInterval(() => ws.send('ping'), 10 * 1000);
};
ws.onclose = (event) => {
this.dispatchEvent(new CloseEvent('close', { code: event.code, reason: event.reason }));
clearInterval(pingInterval);
this.ws = undefined;
if (this.shouldReconnect) {
setTimeout(() => this.connect(), 1000);
}
};
ws.onerror = (event) => {
this.dispatchEvent(new ErrorEvent('error', { error: event.error }));
ws.close();
};
}
close() {
this.shouldReconnect = false;
if (this.ws)
this.ws.close();
}
async subscribe(wsEvents) {
const wsEventArgs = Array.isArray(wsEvents) ? wsEvents : [wsEvents];
await this.send({ op: 'subscribe', args: wsEventArgs });
}
async unsubscribe(wsEvents) {
const wsEventArgs = Array.isArray(wsEvents) ? wsEvents : [wsEvents];
await this.send({ op: 'unsubscribe', args: wsEventArgs });
}
async waitForReady(timeout = 10 * 1000) {
if (!this.ws) {
return new Promise((resolve, reject) => {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
reject(new Error('Timeout'));
}, timeout);
this.addEventListener('open', () => {
resolve(undefined);
clearTimeout(timeoutId);
}, { once: true, signal: controller.signal });
});
}
}
async send(data) {
if (typeof data !== 'string')
data = JSON.stringify(data);
await this.waitForReady();
this.ws.send(data);
}
}
class WsPublic extends WsApi {
static create(baseURL = 'wss://ws.okx.com:8443') {
return new WsPublic(baseURL);
}
constructor(baseURL = 'wss://ws.okx.com:8443') {
super(baseURL + '/ws/v5/public');
}
}
exports.WsPublic = WsPublic;
class WsPrivate extends WsApi {
credentials;
static async create(apiKey, apiSecret, passphrase, baseURL = 'wss://ws.okx.com:8443') {
const credentials = await utils_1.APICredentials.create(apiKey, apiSecret, passphrase, true);
if (credentials)
return new WsPrivate(credentials, baseURL);
else
throw new Error('No credentials');
}
ready = false;
constructor(credentials, baseURL = 'wss://ws.okx.com:8443') {
super(baseURL + '/ws/v5/private');
this.credentials = credentials;
this.addEventListener('open', async () => {
await super.waitForReady();
this.ws.send(await this.credentials.getWsLoginMessage());
});
this.addEventListener('login', (event) => {
if (event.data.code === '0') {
this.ready = true;
this.dispatchEvent(new Event('ready'));
}
else {
this.dispatchEvent(new ErrorEvent('error', { error: new Error(event.data.msg) }));
}
});
}
connect() {
this.ready = false;
super.connect();
}
async waitForReady(timeout) {
await super.waitForReady(timeout);
if (!this.ready) {
await new Promise((resolve, reject) => {
const resolveController = new AbortController();
const rejectController = new AbortController();
this.addEventListener('ready', () => {
resolve(undefined);
rejectController.abort();
}, { once: true, signal: resolveController.signal });
this.addEventListener('error', (err) => {
reject(err.error || err.data);
resolveController.abort();
}, { once: true, signal: rejectController.signal });
});
}
}
async sendTradeOp(op) {
const promise = new Promise((resolve, reject) => {
this.addEventListener(`${op.op}-${op.id}`, (event) => {
if (event.data.code === '0') {
resolve(event.data);
}
else {
reject(event.data);
}
}, { once: true });
});
await this.send(op);
return await promise;
}
order(arg) {
return this.sendTradeOp({ op: 'order', id: crypto.randomUUID().replace(/-/g, ''), args: [arg] });
}
batchOrder(args) {
return this.sendTradeOp({ op: 'batch-orders', id: crypto.randomUUID().replace(/-/g, ''), args });
}
cancelOrder(arg) {
return this.sendTradeOp({ op: 'cancel-order', id: crypto.randomUUID().replace(/-/g, ''), args: [arg] });
}
batchCancelOrder(args) {
return this.sendTradeOp({ op: 'batch-cancel-orders', id: crypto.randomUUID().replace(/-/g, ''), args });
}
}
exports.WsPrivate = WsPrivate;