@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
JavaScript
;
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