UNPKG

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
"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