Files
bookhoard/web/src/websocket.ts
T
john-okeefe 4ed5c24f84 feat(websocket): add reusable WebSocket connection helper utility
- Create createWebSocket() helper for WebSocket connections with authentication
- Support automatic reconnection with configurable delay
- Include error handling and logging
- Export disconnectWebSocket() for cleanup

This helper provides a centralized way to create WebSocket connections
with JWT token authentication from localStorage. Although not currently
used in the application (we opted for server-side token injection in templates),
it provides a reusable utility for future WebSocket integrations.

Features:
- Automatic token retrieval from localStorage
- Configurable reconnection behavior (enabled by default)
- Error handling with try-catch on all callbacks
- Connection cleanup and management
- Type-safe configuration interface

Available for future use in client-side WebSocket scenarios or as a reference
implementation.
2026-03-12 15:45:05 -04:00

93 lines
2.5 KiB
TypeScript

import { getToken } from "./storage";
interface WebSocketConfig {
onMessage: (data: any) => void;
onOpen?: () => void;
onClose?: () => void;
onError?: (error: Event) => void;
enableReconnect?: boolean;
reconnectDelay?: number;
}
let activeWebSocket: WebSocket | null = null;
let reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
function createWebSocket(config: WebSocketConfig): WebSocket | null {
const token = getToken();
if (!token) {
console.warn(
"No authentication token found - skipping WebSocket connection",
);
return null;
}
// Clean up existing connection
if (activeWebSocket) {
activeWebSocket.close();
}
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
reconnectTimeout = null;
}
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}/ws/sync?token=${token}`;
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log("WebSocket connected");
if (config.onOpen) {
try {
config.onOpen();
} catch (error) {
console.error("Error in onOpen callback:", error);
}
}
};
ws.onmessage = (event: MessageEvent) => {
try {
const message = JSON.parse(event.data);
config.onMessage(message);
} catch (error) {
console.error("Error parsing WebSocket message:", error);
console.error("Raw message:", event.data);
}
};
ws.onerror = (error: Event) => {
console.error("WebSocket error:", error);
if (config.onError) {
try {
config.onError(error);
} catch (err) {
console.error("Error in onError callback:", err);
}
}
};
ws.onclose = () => {
console.log("WebSocket disconnected");
if (config.onClose) {
try {
config.onClose();
} catch (error) {
console.error("Error in onClose callback:", error);
}
}
// Auto-reconnect if enabled
if (config.enableReconnect !== false) {
const delay = config.reconnectDelay || 5000;
console.log(`Reconnecting in ${delay / 1000}s...`);
reconnectTimeout = setTimeout(() => {
createWebSocket(config);
}, delay);
}
};
activeWebSocket = ws;
return ws;
}
function disconnectWebSocket(): void {
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
reconnectTimeout = null;
}
if (activeWebSocket) {
activeWebSocket.close();
activeWebSocket = null;
}
}
export { createWebSocket, disconnectWebSocket };