HL7v2/packages/server/index.ts

164 lines
5.5 KiB
TypeScript

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) {
// 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 ${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}`);