import { WebSocketServer } from 'ws'; import { type Message, MessageType } from '@hnu.de/hl7v2-shared'; import 'dotenv/config'; import { config } from './config'; // ########################################################################### // ### ID Pool // ########################################################################### let availableIds: string[] = []; for (const prefix of config.prefixes) { for (let i = 1; i <= config.poolSize / config.prefixes.length; i++) { const number = i.toString().padStart(config.poolSize.toString().length, '0'); availableIds.push(`${prefix}-${number}`); } } // mixes the array randomly to avoid getting the same ID reassigned function shake(arr: any[]) { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; } availableIds = shake(availableIds); // ########################################################################### // ### Websocket Server // ########################################################################### const wss = new WebSocketServer({ port: config.port }); const clients = new Map(); console.log(`Starting WebSocket server ...`); console.log('Server configuration:', config); wss.on('connection', (ws) => { // check for available IDs if (availableIds.length === 0) { console.log('Connection rejected: No available IDs.'); ws.close(1013, 'Server is full. Please try again later.'); // 1013: Try again later return; } // get ID from pool and assign to station const stationId = availableIds.pop(); if (!stationId) { console.log('Connection rejected: Failed to retrieve ID.'); ws.close(1013, 'Server is full. Please try again later.'); // 1013: Try again later return; } // store client in map clients.set(stationId, ws); // send station ID to client const welcomeMessage = { type: 'assign_id', payload: { stationId: stationId, }, } as Message; ws.send(JSON.stringify(welcomeMessage)); console.log(`Client connected. Assigning ID: ${stationId}`); // listen for messages and defined message handling ws.on('message', (message) => { try { // parse every message to type Message const parsedMessage: Message = JSON.parse(message.toString()); console.log(`Received message from ${stationId}:`, parsedMessage); // We only expect one type of message from clients: 'send_hl7v2' if (parsedMessage.type === MessageType.send_hl7v2) { const { message } = parsedMessage.payload; // TODO: validate message // get sender and recipient ID const recipientId = parseMshField(message, 5); if (!recipientId) { const errorMessage = { type: 'delivery_error', payload: { error: `Message is missing header field 5.`, }, } as Message; ws.send(JSON.stringify(errorMessage)); } // Find the recipient's WebSocket connection in our map. const recipientWs = clients.get(recipientId); // check if recipient exists and is connected if (recipientWs && recipientWs.readyState === WebSocket.OPEN) { const timestamp = new Date().toISOString(); // The recipient is connected. Forward the message. const forwardMessage = { type: MessageType.receive_hl7v2, payload: { message: message, timestamp: timestamp, }, } as Message; recipientWs.send(JSON.stringify(forwardMessage)); console.log(`Forwarded message to ${recipientId}`); const ackMessage = { type: MessageType.delivery_success, payload: { message: message, timestamp: timestamp, }, }; ws.send(JSON.stringify(ackMessage)); console.log(`Sent acknowledgment to ${stationId}`); } else { // The recipient is not connected or not found. console.log(`Recipient ${recipientId} not found or not connected.`); const errorMessage = { type: MessageType.delivery_error, payload: { error: `Station with ID ${recipientId} is not available.`, }, } as Message; ws.send(JSON.stringify(errorMessage)); } } } catch (error) { console.error(`Failed to process message from ${stationId}:`, error); } }); // listen to disconnects to free IDs ws.on('close', () => { console.log(`Client ${stationId} disconnected.`); clients.delete(stationId); if (stationId) { availableIds.push(stationId); } // shake IDs to avoid getting the same ID reassigned availableIds = shake(availableIds); }); ws.on('error', (error) => { console.error(`WebSocket error for client ${stationId}:`, error); }); }); function parseMshField(message: string, fieldIndex: number) { try { const mshLine = message.split(/[\r\n]+/)[0]; if (!mshLine || !mshLine.startsWith('MSH')) return null; const fields = mshLine.split('|'); return fields[fieldIndex] || null; } catch (e) { return null; } } console.log(`WebSocket server started on port ${config.port}`);