mexc-futures-sdk
Version:
Unofficial TypeScript SDK for MEXC Futures trading with maintenance bypass. Uses browser session tokens to work 24/7 even during API downtime.
670 lines âĸ 20.9 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MexcFuturesWebSocket = void 0;
const ws_1 = __importDefault(require("ws"));
const events_1 = require("events");
const crypto = __importStar(require("crypto"));
const logger_1 = require("./utils/logger");
class MexcFuturesWebSocket extends events_1.EventEmitter {
constructor(config) {
super();
this.ws = null;
this.pingTimer = null;
this.reconnectTimer = null;
this.isConnected = false;
this.isLoggedIn = false;
this.wsUrl = "wss://contract.mexc.com/edge";
this.config = {
autoReconnect: true,
reconnectInterval: 5000,
pingInterval: 15000, // 15 seconds (recommended 10-20s)
...config,
};
this.logger = new logger_1.Logger(config.logLevel);
}
/**
* Connect to WebSocket
*/
connect() {
return new Promise((resolve, reject) => {
try {
this.logger.info("đ Connecting to MEXC Futures WebSocket...");
this.ws = new ws_1.default(this.wsUrl);
this.ws.on("open", () => {
this.logger.info("â
WebSocket connected");
this.isConnected = true;
this.startPing();
this.emit("connected");
resolve();
});
this.ws.on("message", (data) => {
try {
const message = JSON.parse(data.toString());
this.handleMessage(message);
}
catch (error) {
this.logger.error("â Error parsing WebSocket message:", error);
this.emit("error", error);
}
});
this.ws.on("close", (code, reason) => {
this.logger.warn(`đ WebSocket closed: ${code} ${reason}`);
this.isConnected = false;
this.isLoggedIn = false;
this.stopPing();
this.emit("disconnected", { code, reason });
if (this.config.autoReconnect) {
this.scheduleReconnect();
}
});
this.ws.on("error", (error) => {
this.logger.error("â WebSocket error:", error);
this.emit("error", error);
reject(error);
});
}
catch (error) {
reject(error);
}
});
}
/**
* Disconnect from WebSocket
*/
disconnect() {
this.logger.info("đ Disconnecting WebSocket...");
this.config.autoReconnect = false;
this.stopPing();
this.clearReconnectTimer();
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.isConnected = false;
this.isLoggedIn = false;
}
/**
* Login to access private data streams
* @param subscribe - false to cancel default push of all private data
*/
async login(subscribe = true) {
if (!this.isConnected) {
throw new Error("WebSocket not connected");
}
// Generate signature for login using API Key and Secret Key
const reqTime = Date.now().toString();
// For WebSocket, signature is HMAC SHA256 of (apiKey + timestamp) using secret key
const signatureString = `${this.config.apiKey}${reqTime}`;
const signature = crypto
.createHmac("sha256", this.config.secretKey)
.update(signatureString)
.digest("hex");
const loginMessage = {
subscribe,
method: "login",
param: {
apiKey: this.config.apiKey,
signature: signature,
reqTime,
},
};
this.send(loginMessage);
}
/**
* Set personal data filters
* @param filters - Array of filters to apply
*/
setPersonalFilter(filters) {
if (!this.isLoggedIn) {
throw new Error("Must login first before setting filters");
}
const filterMessage = {
method: "personal.filter",
param: {
filters: filters || [], // Empty array means all data
},
};
this.send(filterMessage);
}
// ==================== PRIVATE DATA SUBSCRIPTIONS ====================
/**
* Subscribe to specific order updates for symbols
*/
subscribeToOrders(symbols) {
this.setPersonalFilter([
{
filter: "order",
rules: symbols,
},
]);
}
/**
* Subscribe to order deals (executions) for symbols
*/
subscribeToOrderDeals(symbols) {
this.setPersonalFilter([
{
filter: "order.deal",
rules: symbols,
},
]);
}
/**
* Subscribe to position updates for symbols
*/
subscribeToPositions(symbols) {
this.setPersonalFilter([
{
filter: "position",
rules: symbols,
},
]);
}
/**
* Subscribe to asset (balance) updates
*/
subscribeToAssets() {
this.setPersonalFilter([
{
filter: "asset",
},
]);
}
/**
* Subscribe to ADL level updates
*/
subscribeToADLLevels() {
this.setPersonalFilter([
{
filter: "adl.level",
},
]);
}
/**
* Subscribe to multiple data types with custom filters
*/
subscribeToMultiple(filters) {
this.setPersonalFilter(filters);
}
/**
* Subscribe to all private data (default after login)
*/
subscribeToAll() {
this.setPersonalFilter([]);
}
// ==================== PUBLIC DATA SUBSCRIPTIONS ====================
/**
* Subscribe to all tickers (all contracts)
* @param gzip - Whether to compress the data (default: false)
*/
subscribeToAllTickers(gzip = false) {
const message = {
method: "sub.tickers",
param: {},
gzip,
};
this.send(message);
}
/**
* Unsubscribe from all tickers
*/
unsubscribeFromAllTickers() {
const message = {
method: "unsub.tickers",
param: {},
};
this.send(message);
}
/**
* Subscribe to ticker for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
subscribeToTicker(symbol) {
const message = {
method: "sub.ticker",
param: {
symbol,
},
};
this.send(message);
}
/**
* Unsubscribe from ticker for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
unsubscribeFromTicker(symbol) {
const message = {
method: "unsub.ticker",
param: {
symbol,
},
};
this.send(message);
}
/**
* Subscribe to trades for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
subscribeToDeals(symbol) {
const message = {
method: "sub.deal",
param: {
symbol,
},
};
this.send(message);
}
/**
* Unsubscribe from trades for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
unsubscribeFromDeals(symbol) {
const message = {
method: "unsub.deal",
param: {
symbol,
},
};
this.send(message);
}
/**
* Subscribe to incremental depth for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
* @param compress - Whether to compress the data (default: false)
*/
subscribeToDepth(symbol, compress = false) {
const message = {
method: "sub.depth",
param: {
symbol,
compress,
},
};
this.send(message);
}
/**
* Subscribe to full depth for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
* @param limit - Depth limit (5, 10, or 20)
*/
subscribeToFullDepth(symbol, limit = 20) {
const message = {
method: "sub.depth.full",
param: {
symbol,
limit,
},
};
this.send(message);
}
/**
* Unsubscribe from incremental depth for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
unsubscribeFromDepth(symbol) {
const message = {
method: "unsub.depth",
param: {
symbol,
},
};
this.send(message);
}
/**
* Unsubscribe from full depth for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
unsubscribeFromFullDepth(symbol) {
const message = {
method: "usub.depth.full",
param: {
symbol,
},
};
this.send(message);
}
/**
* Subscribe to kline/candlestick data for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
* @param interval - Kline interval
*/
subscribeToKline(symbol, interval) {
const message = {
method: "sub.kline",
param: {
symbol,
interval,
},
};
this.send(message);
}
/**
* Unsubscribe from kline/candlestick data for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
unsubscribeFromKline(symbol) {
const message = {
method: "unsub.kline",
param: {
symbol,
},
};
this.send(message);
}
/**
* Subscribe to funding rate for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
subscribeToFundingRate(symbol) {
const message = {
method: "sub.funding.rate",
param: {
symbol,
},
};
this.send(message);
}
/**
* Unsubscribe from funding rate for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
unsubscribeFromFundingRate(symbol) {
const message = {
method: "unsub.funding.rate",
param: {
symbol,
},
};
this.send(message);
}
/**
* Subscribe to index price for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
subscribeToIndexPrice(symbol) {
const message = {
method: "sub.index.price",
param: {
symbol,
},
};
this.send(message);
}
/**
* Unsubscribe from index price for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
unsubscribeFromIndexPrice(symbol) {
const message = {
method: "unsub.index.price",
param: {
symbol,
},
};
this.send(message);
}
/**
* Subscribe to fair price for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
subscribeToFairPrice(symbol) {
const message = {
method: "sub.fair.price",
param: {
symbol,
},
};
this.send(message);
}
/**
* Unsubscribe from fair price for specific contract
* @param symbol - Contract symbol (e.g., "BTC_USDT")
*/
unsubscribeFromFairPrice(symbol) {
const message = {
method: "unsub.fair.price",
param: {
symbol,
},
};
this.send(message);
}
/**
* Send message to WebSocket
*/
send(message) {
if (this.ws && this.isConnected) {
const messageString = JSON.stringify(message);
this.logger.debug("âĄī¸ Sending WebSocket message:", messageString);
this.ws.send(messageString);
}
else {
this.logger.error("â Cannot send message: WebSocket not connected or not ready");
}
}
/**
* Handle incoming WebSocket messages
*/
handleMessage(message) {
this.logger.debug("âŦ
ī¸ Received WebSocket message:", JSON.stringify(message));
// DEBUG: Uncomment next line for detailed message debugging
// console.log("đ RAW MESSAGE:", JSON.stringify(message, null, 2));
// Handle pong response
if (message.channel === "pong") {
this.emit("pong", message.data);
return;
}
// Handle login response
if (message.channel === "rs.login") {
if (message.data === "success" || message.data?.code === 0) {
this.isLoggedIn = true;
this.logger.info("â
WebSocket login successful");
this.emit("login", message);
}
else {
this.isLoggedIn = false;
this.logger.error("â WebSocket login failed:", message.data);
this.emit("login_failed", message.data);
}
return;
}
// Handle filter response
if (message.channel === "rs.personal.filter") {
if (message.data === "success" || message.data?.code === 0) {
this.logger.info("â
WebSocket filter set successfully");
this.emit("filter_set", message.data);
}
else {
this.logger.error("â WebSocket filter set failed:", message.data);
this.emit("filter_failed", message.data);
}
return;
}
// Handle subscription confirmations
if (message.channel?.startsWith("rs.sub.")) {
const streamType = message.channel.replace("rs.sub.", "");
this.logger.info(`â
Subscribed to ${streamType}`);
this.emit("subscribed", { type: streamType, data: message.data });
return;
}
// Handle unsubscription confirmations
if (message.channel?.startsWith("rs.unsub.")) {
const streamType = message.channel.replace("rs.unsub.", "");
this.logger.info(`â
Unsubscribed from ${streamType}`);
this.emit("unsubscribed", { type: streamType, data: message.data });
return;
}
// Handle error responses
if (message.channel === "rs.error") {
this.logger.error("â WebSocket error response:", message.data);
this.emit("error", new Error(message.data));
return;
}
// Handle private data updates
this.handleDataUpdate(message);
}
/**
* Handle data updates (orders, positions, market data, etc.)
*/
handleDataUpdate(message) {
const { channel, data } = message;
// Handle public data updates
switch (channel) {
case "push.tickers":
this.emit("tickers", data);
break;
case "push.ticker":
this.emit("ticker", data);
break;
case "push.deal":
this.emit("deal", data);
break;
case "push.depth":
this.emit("depth", data);
break;
case "push.kline":
this.emit("kline", data);
break;
case "push.funding.rate":
this.emit("fundingRate", data);
break;
case "push.index.price":
this.emit("indexPrice", data);
break;
case "push.fair.price":
this.emit("fairPrice", data);
break;
// Handle private data updates with push.personal.* channels
case "push.personal.order":
this.emit("orderUpdate", data);
break;
case "push.personal.order.deal":
this.emit("orderDeal", data);
break;
case "push.personal.position":
this.emit("positionUpdate", data);
break;
case "push.personal.asset":
this.emit("assetUpdate", data);
break;
case "push.personal.stop.order":
this.emit("stopOrder", data);
break;
case "push.personal.stop.planorder":
this.emit("stopPlanOrder", data);
break;
case "push.personal.liquidate.risk":
this.emit("liquidateRisk", data);
break;
case "push.personal.adl.level":
this.emit("adlLevel", data);
break;
case "push.personal.risk.limit":
this.emit("riskLimit", data);
break;
case "push.personal.plan.order":
this.emit("planOrder", data);
break;
default:
// If not handled by any case above, emit as generic message
// Uncomment for debugging unhandled messages
// console.log("đĨ Other message detected:", JSON.stringify(message, null, 2));
this.emit("message", message);
break;
}
}
/**
* Start ping timer
*/
startPing() {
this.stopPing();
this.pingTimer = setInterval(() => {
if (this.isConnected) {
this.logger.debug("âĄī¸ Sending ping");
this.send({ method: "ping" });
}
}, this.config.pingInterval);
}
/**
* Stop ping timer
*/
stopPing() {
if (this.pingTimer) {
clearInterval(this.pingTimer);
this.pingTimer = null;
this.logger.debug("âšī¸ Stopped ping timer");
}
}
/**
* Schedule reconnection
*/
scheduleReconnect() {
this.clearReconnectTimer();
this.logger.info(`đ Scheduling reconnect in ${this.config.reconnectInterval}ms...`);
this.reconnectTimer = setTimeout(() => {
this.logger.info("đ Reconnecting...");
this.connect().catch((error) => {
this.logger.error("â Reconnect failed:", error);
});
}, this.config.reconnectInterval);
}
/**
* Clear reconnect timer
*/
clearReconnectTimer() {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
}
/**
* Get connection status
*/
get connected() {
return this.isConnected;
}
/**
* Get login status
*/
get loggedIn() {
return this.isLoggedIn;
}
}
exports.MexcFuturesWebSocket = MexcFuturesWebSocket;
//# sourceMappingURL=websocket.js.map