feat(system): implement graceful shutdown and expand websocket events

Improve system reliability and real-time capabilities by implementing a robust lifecycle management system and adding new broadcast events for attachments and voice recordings.

- Implement asynchronous graceful shutdown in backend to close HTTP, WebSocket, Redis, and database connections.
- Add new WebSocket broadcast events: `attachment_created`, `voice_recording_started`, `voice_recording_stopped`, `voice_recording_uploaded`, and `analysis_queue_status`.
- Refactor media status handling to use boolean `playing` state instead of string-based status.
- Centralize `PageResult` and `VoiceRecording` types to improve consistency between frontend and backend.
- Update frontend API client to include `listRecordings` and handle new WebSocket event types.
- Fix type mismatches in voice command handling and media status reporting.
This commit is contained in:
MythEclipse
2026-06-09 16:56:44 +07:00
parent 3a7b005d95
commit 7108f6bb47
14 changed files with 161 additions and 90 deletions
+21
View File
@@ -11,6 +11,9 @@ interface BroadcastEvent {
timestamp: string;
}
// Track the active WebSocket server for lifecycle management
let _wss: WebSocketServer | null = null;
async function sendInitialStates(ws: WebSocket): Promise<void> {
// Send initial user state
ws.send(
@@ -51,10 +54,18 @@ async function sendInitialStates(ws: WebSocket): Promise<void> {
}
}
export function closeWebSocketServer(): void {
if (!_wss) return;
logger.info("Closing WebSocket server");
_wss.close(() => logger.info("WebSocket server closed"));
_wss = null;
}
export function createWebSocketServer(server: Server): WebSocketServer {
const clients = new Set<WebSocket>();
const wss = new WebSocketServer({ server, path: "/ws" });
_wss = wss;
wss.on("connection", (ws: WebSocket) => {
clients.add(ws);
@@ -188,12 +199,22 @@ export function createWebSocketServer(server: Server): WebSocketServer {
broadcast({ type: "message_deleted", data }),
messageAnalyzed: (data: unknown) =>
broadcast({ type: "message_analyzed", data }),
attachmentCreated: (data: unknown) =>
broadcast({ type: "attachment_created", data }),
attachmentUploaded: (data: unknown) =>
broadcast({ type: "attachment_uploaded", data }),
voiceRecordingStarted: (data: unknown) =>
broadcast({ type: "voice_recording_started", data }),
voiceRecordingStopped: (data: unknown) =>
broadcast({ type: "voice_recording_stopped", data }),
voiceRecordingUploaded: (data: unknown) =>
broadcast({ type: "voice_recording_uploaded", data }),
voicePcmData: (data: unknown) =>
broadcast({ type: "voice_pcm_data", data }),
voiceActiveUser: (data: unknown) =>
broadcast({ type: "voice_active_user", data }),
analysisQueueStatus: (data: unknown) =>
broadcast({ type: "analysis_queue_status", data }),
raw: (type: string, data: unknown) => broadcast({ type, data }),
binary: broadcastBinary,
});