Files
bookhoard/web/src/websocket.ts
T
John O'Keefe 8631aa00bf refactor: remove dead code across server, templ pages, and client modules
Deadcode-verified removals (RTA reachability from main, cross-checked
with -test and textual xrefs):

Go server:
- whole files: middleware/transaction.go, middleware/error_handler.go,
  middleware/password_validator.go (live registration machinery kept),
  handlers/sync.go, sync/offline.go
- unrouted endpoints: auth UpdateUsername/UpdateEmail, timezone
  settings handler, OPDS RegisterOPDS, Kobo LibrarySync handler,
  ReaderHandler.ShowReader, JSON-data twins on collections/library
- duplicate book-matching handlers (book_matching.go Handler twins,
  matching.go MatchingHandler twins), superseded helpers
  (router.parseUUID, handlers.ValidateDeviceToken, normalizePath,
  userExists, generateLockID)

Templ/client:
- never-routed pages: api_explorer.templ, queue.templ and their
  client modules api-explorer.ts, api-explorer-docs.ts, queue.ts
- fully orphaned wrapper modules events.ts, dom.ts
- broken clearSyncQueue (called a nonexistent endpoint) and the
  hidden, never-populated sync-queue section on the devices page
- 166+ unused exports demoted to private (knip-verified); ~85 truly
  dead declarations removed via tsc noUnusedLocals fixpoint
- stale 'interactive API Explorer' claims dropped from docs

The docs drift-gate package (internal/docs), templ-bound modules
(dashboard, stats, header, index, login, profile*, bookshelf,
collections, toast-error), sqlc-generated code, and web/static are
intentionally untouched.
2026-10-25 11:06:08 -04:00

124 lines
2.9 KiB
TypeScript

import { getToken } from "./storage";
type MessageListener = (message: any) => void;
let activeWebSocket: WebSocket | null = null;
let reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
let listeners: Set<MessageListener> = new Set();
let currentConfig: { enableReconnect?: boolean; reconnectDelay?: number } = {};
function connectWebSocket(): WebSocket | null {
const token = getToken();
if (!token) {
return null;
}
if (activeWebSocket) {
return activeWebSocket;
}
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");
};
ws.onmessage = (event: MessageEvent) => {
try {
const message = JSON.parse(event.data);
listeners.forEach((listener) => {
try {
listener(message);
} catch (error) {
console.error("Error in WebSocket listener:", error);
}
});
} catch (error) {
console.error("Error parsing WebSocket message:", error);
}
};
ws.onerror = (error: Event) => {
console.error("WebSocket error:", error);
};
ws.onclose = () => {
console.log("WebSocket disconnected");
activeWebSocket = null;
if (currentConfig.enableReconnect !== false) {
const delay = currentConfig.reconnectDelay || 5000;
console.log(`Reconnecting in ${delay / 1000}s...`);
reconnectTimeout = setTimeout(() => {
connectWebSocket();
}, delay);
}
};
activeWebSocket = ws;
return ws;
}
function addListener(listener: MessageListener): void {
listeners.add(listener);
if (!activeWebSocket) {
connectWebSocket();
}
}
function removeListener(listener: MessageListener): void {
listeners.delete(listener);
if (listeners.size === 0) {
disconnectWebSocket();
}
}
function disconnectWebSocket(): void {
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
reconnectTimeout = null;
}
if (activeWebSocket) {
activeWebSocket.close();
activeWebSocket = null;
}
}
function createWebSocket(config: {
onMessage: (data: any) => void;
onOpen?: () => void;
onClose?: () => void;
onError?: (error: Event) => void;
enableReconnect?: boolean;
reconnectDelay?: number;
}): WebSocket | null {
currentConfig = config;
const listener: MessageListener = (message) => {
config.onMessage(message);
};
listeners.add(listener);
if (activeWebSocket) {
activeWebSocket.close();
activeWebSocket = null;
}
const ws = connectWebSocket();
if (ws && config.onOpen) {
if (ws.readyState === WebSocket.OPEN) {
config.onOpen();
} else {
ws.addEventListener("open", () => config.onOpen?.(), { once: true });
}
}
return ws;
}
export { addListener, createWebSocket, removeListener };