okex-api
Version:
okex api for both node & frontend
160 lines (131 loc) • 5.03 kB
JavaScript
const crypto = require('crypto');
const { EventEmitter } = require('events');
const WS = require('async-ws');
const Signer = require('../signer');
class WsApi extends EventEmitter {
// constructor(apiKey, apiSecret, passphrase, opt = {}) {
constructor(apiKey, apiSecret, passphrase) {
super();
const processMsg = message => {
if (message.data) message = message.data;
if (message.toString) message = message.toString();
if (message !== 'pong') {
const data = JSON.parse(message);
if (data.event) {
this.emit(data.event, data);
} else if (data.op) {
this.emit(`op:${data.op}`, data.data || data.args, { id: data.id, msg: data.msg, code: data.code });
} else if (data.arg) {
this.emit(data.arg.channel, data.data, data.arg);
}
}
};
this._public = new WS('wss://ws.okx.com:8443/ws/v5/public');
this._public.on('error', console.error);
this._public.on('message', processMsg);
if (apiSecret) {
this.update(apiKey, apiSecret, passphrase);
this._private = new WS('wss://ws.okx.com:8443/ws/v5/private');
this._private.on('error', console.error);
this._private.on('open', () => {
const [timestamp, sign] = this.signer.sign('/users/self/verify');
this._private.send(JSON.stringify({
op: 'login',
args: [{
apiKey: this.apiKey,
passphrase: this.passphrase,
timestamp,
sign
}]
}));
});
this._private.on('message', processMsg);
this.on('login', () => this.loggedIn = true);
this._orders = new Map();
this.on('orders', ([order]) => {
this._orders.set(order.ordId, order);
});
}
setInterval(() => {
if (this._public._ready) this._public.send('ping');
if (this._private && this._private._ready) this._private.send('ping')
}, 15000);
}
update(newApiKey, newApiSecret, newPassphrase) {
this.signer = new Signer(newApiSecret);
this.apiKey = newApiKey;
this.passphrase = newPassphrase;
}
async subscribePublic(channel, sendOnReconnect = false) {
const msg = JSON.stringify({ op: 'subscribe', args: Array.isArray(channel)? channel : [channel] });
if (sendOnReconnect) this._public.on('open', () => this._public.send(msg));
if (!sendOnReconnect || this._public._ready) await this._public.send(msg);
}
async subscribePrivate(channel, sendOnReconnect = false) {
const msg = JSON.stringify({ op: 'subscribe', args: Array.isArray(channel)? channel : [channel] });
if (sendOnReconnect) this.on('login', () => this._private.send(msg));
if (this.loggedIn) await this._private.send(msg);
else this.once('login', () => this._private.send(msg));
}
waitForOp(op, opId, timeout = 5000) {
op = `op:${op}`;
return new Promise((resolve, reject) => {
const tm = setTimeout(() => reject('timeout'), timeout);
const listener = (data, { id, code, msg }) => {
if (id === opId) {
clearTimeout(tm);
this.off(op, listener);
code *= 1;
if (code === 0 || code === 2) resolve(data);
else reject({ code, msg, data });
}
};
this.on(op, listener);
});
}
batchOrder(orders) {
const id = crypto.randomBytes(16).toString('hex');
this._private.send(JSON.stringify({ id, op: 'batch-orders', args: orders }));
return this.waitForOp('batch-orders', id);
}
batchCancelOrders(orders) {
const id = crypto.randomBytes(16).toString('hex');
this._private.send(JSON.stringify({ id, op: 'batch-cancel-orders', args: orders }));
return this.waitForOp('batch-cancel-orders', id);
}
waitForOrders(orders, states = ['canceled', 'filled'], timeout = 5000) {
return new Promise((resolve, reject) => {
let allOk = true;
for (const order of orders) {
Object.assign(order, this._orders.get(order.ordId) || {});
this._orders.delete(order.ordId);
if (states.indexOf(order.state) < 0 && order.sCode == 0) allOk = false;
}
if (allOk) return resolve(orders);
const listener = ([orderData]) => {
let ok = true;
for (const order of orders) {
if (order.ordId === orderData.ordId) {
Object.assign(order, orderData);
this._orders.delete(order.ordId);
}
if (states.indexOf(order.state) < 0 && order.sCode == 0) ok = false;
}
if (ok) {
clearTimeout(tid);
this.off('orders', listener);
resolve(orders);
}
};
const tid = setTimeout(() => {
this.off('orders', listener);
reject('timeout');
}, timeout);
this.on('orders', listener);
});
}
toOrder(instId, side, posSide, ordType, sz/* size */, px/* price */, clOrdId, tdMode = 'cross') {
return { instId, tdMode, clOrdId, side, posSide, ordType, sz, px };
}
}
module.exports = WsApi;