Files
nas-tools/dashboard/frontend/src/lib/opc-ws.js
T
Gan, Jimmy ddaf3c6cee security: Sprint 00 — critical security fixes (OPC WS auth, Transmission creds, SECRET_KEY, passkey rate limit)
- Add JWT token auth to OPC WebSocket (unauthenticated → 403, per-user tracking)
- Externalize Transmission RPC credentials to TRANSMISSION_USER/PASS env vars
- Remove hardcoded SECRET_KEY fallback from dev compose
- Rate-limit passkey register options endpoint at 5/minute
- Add PDD (docs/improvement-plan.md) and sprint specs (specs/)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 13:31:25 +08:00

90 lines
1.9 KiB
JavaScript

/**
* OPC WebSocket Client - Real-time updates
*/
import { getToken } from "./api.js";
let ws = null;
let reconnectTimer = null;
let listeners = new Set();
export function connect() {
if (ws && ws.readyState === WebSocket.OPEN) {
return;
}
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const token = getToken();
const tokenParam = token ? `?token=${encodeURIComponent(token)}` : "";
const wsUrl = `${protocol}//${window.location.host}/ws/opc${tokenParam}`;
ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log("OPC WebSocket connected");
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
// Send ping every 30 seconds to keep connection alive
const pingInterval = setInterval(() => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send("ping");
} else {
clearInterval(pingInterval);
}
}, 30000);
};
ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
notifyListeners(message);
} catch (e) {
console.error("Failed to parse WebSocket message:", e);
}
};
ws.onerror = (error) => {
console.error("WebSocket error:", error);
};
ws.onclose = () => {
console.log("OPC WebSocket disconnected");
ws = null;
// Reconnect after 5 seconds
reconnectTimer = setTimeout(() => {
console.log("Reconnecting WebSocket...");
connect();
}, 5000);
};
}
export function disconnect() {
if (ws) {
ws.close();
ws = null;
}
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
}
export function subscribe(callback) {
listeners.add(callback);
return () => listeners.delete(callback);
}
function notifyListeners(message) {
listeners.forEach((callback) => {
try {
callback(message);
} catch (e) {
console.error("Listener error:", e);
}
});
}