158 lines
5.2 KiB
TypeScript
158 lines
5.2 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}`);
|
|
}
|
|
}
|
|
|
|
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}`);
|
|
|
|
ws.on('message', (message) => {
|
|
try {
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
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}`);
|