UNPKG

@sotatech/nest-quickfix

Version:

A powerful NestJS implementation of the FIX (Financial Information eXchange) protocol. Provides high-performance, reliable messaging for financial trading applications with built-in session management, message validation, and recovery mechanisms.

173 lines 7.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AcceptorMessageHandler = void 0; const logout_1 = require("./../protocol/messages/logout"); const common_1 = require("@nestjs/common"); const fields_1 = require("../fields"); const reject_message_1 = require("../message/reject.message"); const session_1 = require("../session/session"); const fix_date_1 = require("../common/date/fix.date"); class AcceptorMessageHandler { constructor(sessionManager, config, metadataExplorer, roomManager) { this.sessionManager = sessionManager; this.config = config; this.metadataExplorer = metadataExplorer; this.roomManager = roomManager; this.logger = new common_1.Logger(AcceptorMessageHandler.name); } async handleLogon(socket, message) { try { await this.validateAndHandleLogon(socket, message); } catch (error) { this.logger.error('Error handling logon:', error); socket.destroy(); throw error; } } async handleNonLogonMessage(socket, message) { const sessionId = this.buildSessionId(message); const session = this.sessionManager.getSessionById(sessionId); if (!session) { await this.handleNoSessionFound(socket, message, sessionId); return; } return session.handleMessage(message); } async createNewSession(socket, logon) { const { senderCompId, targetCompId } = this.extractCompIds(logon); this.logger.debug(`Creating new session for ${senderCompId}->${targetCompId}`); const sessionConfig = this.buildSessionConfig(logon, senderCompId, targetCompId); const session = new session_1.Session(sessionConfig, socket, this.roomManager, this.sessionManager); this.sessionManager.registerSession(session); return session; } async setupSessionHandlers(session) { const metadata = this.metadataExplorer.explore(); metadata.forEach((meta) => { this.registerSessionHandlers(session, meta); }); } extractCompIds(message) { return { senderCompId: message.getField(fields_1.Fields.SenderCompID), targetCompId: message.getField(fields_1.Fields.TargetCompID), }; } sendReject(socket, message, reason) { const reject = new reject_message_1.RejectMessage(message.getField(fields_1.Fields.MsgSeqNum), reason, message.getField(fields_1.Fields.MsgType)); socket.write(reject.toString()); } async validateLogon(message) { this.validateRequiredFields(message); const targetCompId = message.getField(fields_1.Fields.TargetCompID); if (targetCompId !== this.config.TargetCompID) { this.logger.warn(`Invalid TargetCompID received: ${targetCompId}. Expected: ${this.config.TargetCompID}`); return { isValid: false, message: 'Invalid TargetCompID' }; } if (!this.config?.auth) return { isValid: true }; return this.validateAuthentication(message); } async validateAndHandleLogon(socket, message) { try { const { isValid, message: errorMsg } = await this.validateLogon(message); if (!isValid) { await this.sendLogoutAndClose(socket, message, errorMsg || 'Invalid logon message'); return; } } catch (error) { await this.sendLogoutAndClose(socket, message, error.message); return; } const session = await this.createNewSession(socket, message); await this.setupSessionHandlers(session); this.setupDisconnectHandler(socket, session); await session.handleMessage(message); } setupDisconnectHandler(socket, session) { socket.once('close', () => { this.logger.debug(`Socket closed for session ${session.getSessionId()}`); session.handleDisconnect(); this.sessionManager.removeSession(session.getSessionId()); }); } async sendLogoutAndClose(socket, message, reason) { const logoutMsg = this.createLogoutMessage(message, reason); socket.write(logoutMsg.toString()); await new Promise((resolve) => setTimeout(resolve, 100)); socket.destroy(); } createLogoutMessage(message, reason) { const logoutMsg = new logout_1.LogoutMessage(reason); const sendingTime = fix_date_1.FixDateFormat.formatDateTime(new Date()); logoutMsg.setField(fields_1.Fields.SenderCompID, message.getField(fields_1.Fields.TargetCompID)); logoutMsg.setField(fields_1.Fields.TargetCompID, message.getField(fields_1.Fields.SenderCompID)); logoutMsg.setField(fields_1.Fields.BeginString, this.config.BeginString); logoutMsg.setField(fields_1.Fields.MsgSeqNum, 1); logoutMsg.setField(fields_1.Fields.SendingTime, sendingTime); return logoutMsg; } buildSessionId(message) { const senderCompId = message.getField(fields_1.Fields.SenderCompID); const targetCompId = message.getField(fields_1.Fields.TargetCompID); return `${senderCompId}->${targetCompId}`; } async handleNoSessionFound(socket, message, sessionId) { this.logger.warn(`Received message without logon from ${sessionId}`); await this.sendLogoutAndClose(socket, message, 'Session not logged on. Please logon first.'); } buildSessionConfig(logon, senderCompId, targetCompId) { return { senderCompId, targetCompId, heartbeatInterval: parseInt(logon.getField(fields_1.Fields.HeartBtInt)), beginString: this.config.BeginString, appName: this.config.application.name, }; } registerSessionHandlers(session, meta) { if (meta.onLogon) session.registerLogonHandler(meta.onLogon); if (meta.onLogout) session.registerLogoutHandler(meta.onLogout); if (meta.onConnected) session.registerConnectedHandler(meta.onConnected); if (meta.onDisconnected) session.registerDisconnectedHandler(meta.onDisconnected); if (meta.onMessage) { session.registerMessageHandler(meta.onMessage.handler, meta.onMessage.msgType); } } validateRequiredFields(message) { const requiredFields = [ fields_1.Fields.SenderCompID, fields_1.Fields.TargetCompID, fields_1.Fields.HeartBtInt, fields_1.Fields.EncryptMethod, ]; for (const field of requiredFields) { if (!message.hasField(field)) { throw new Error(`Missing required field: ${field}`); } } } async validateAuthentication(message) { const account = message.getField(fields_1.Fields.Account); const senderCompId = message.getField(fields_1.Fields.SenderCompID); const isValid = await this.config.auth.validateCredentials(message); if (!isValid) { this.logger.error(`Invalid credentials for ${senderCompId}`); return { isValid: false, message: 'Invalid credentials' }; } const allowedSenderCompIds = await this.config.auth.getAllowedSenderCompIds(account); if (!allowedSenderCompIds.includes(senderCompId)) { this.logger.error(`SenderCompID ${senderCompId} not allowed for ${account}`); return { isValid: false, message: 'Invalid sender comp ID' }; } return { isValid: true }; } } exports.AcceptorMessageHandler = AcceptorMessageHandler; //# sourceMappingURL=message.handler.js.map