Initial commit

This commit is contained in:
Markus Thielker 2025-07-24 15:08:39 +02:00
commit d9cff2e70c
72 changed files with 2878 additions and 0 deletions

160
packages/server/index.ts Normal file
View file

@ -0,0 +1,160 @@
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}`);