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}`); } } 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 user const userId = availableIds.pop(); if (!userId) { 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(userId, ws); setTimeout(() => { // send user ID to client const welcomeMessage = { type: 'assign_id', payload: { userId: userId, }, } as Message; ws.send(JSON.stringify(welcomeMessage)); console.log(`Client connected. Assigning ID: ${userId}`); }, 0); ws.on('message', (message) => { try { const parsedMessage: Message = JSON.parse(message.toString()); console.log(`Received message from ${userId}:`, 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); if (recipientWs && recipientWs.readyState === WebSocket.OPEN) { // The recipient is connected. Forward the message. const forwardMessage = { type: MessageType.receive_hl7v2, payload: { message: message, timestamp: new Date().toISOString(), }, } as Message; recipientWs.send(JSON.stringify(forwardMessage)); console.log(`Forwarded message to ${recipientId}`); } 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 ${userId}:`, error); } }); // listen to disconnects to free IDs ws.on('close', () => { console.log(`Client ${userId} disconnected.`); clients.delete(userId); if (userId) { availableIds.push(userId); } availableIds = shake(availableIds); }); ws.on('error', (error) => { console.error(`WebSocket error for client ${userId}:`, 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}`);