mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-01 07:19:30 +00:00
* respect section hiddenFields when detecting config overrides * change audio events to audio detection to match docs * add field messages for object and review genai * add more config messages * more messages * add guard to prevent race when adding camera dynamically * fix duplicate websocket messages from zombie connection under react strict mode detach ws event handlers before close() in WsProvider cleanup so a CONNECTING socket's deferred onclose can't schedule a reconnect after the next mount resets the unmounted guard, which was spawning a second live ws and duplicating every message * fix double event publishes for stationary objects with attributes
86 lines
2.2 KiB
TypeScript
86 lines
2.2 KiB
TypeScript
import { baseUrl } from "./baseUrl";
|
|
import { ReactNode, useCallback, useEffect, useRef } from "react";
|
|
import { WsSendContext } from "./wsContext";
|
|
import type { Update } from "./wsContext";
|
|
import { processWsMessage, resetWsStore } from "./ws";
|
|
|
|
export function WsProvider({ children }: { children: ReactNode }) {
|
|
const wsUrl = `${baseUrl.replace(/^http/, "ws")}ws`;
|
|
const wsRef = useRef<WebSocket | null>(null);
|
|
const reconnectTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
const reconnectAttempt = useRef(0);
|
|
const unmounted = useRef(false);
|
|
|
|
const sendJsonMessage = useCallback((msg: unknown) => {
|
|
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
wsRef.current.send(JSON.stringify(msg));
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
unmounted.current = false;
|
|
|
|
function connect() {
|
|
if (unmounted.current) return;
|
|
|
|
const ws = new WebSocket(wsUrl);
|
|
wsRef.current = ws;
|
|
|
|
ws.onopen = () => {
|
|
reconnectAttempt.current = 0;
|
|
ws.send(
|
|
JSON.stringify({ topic: "onConnect", message: "", retain: false }),
|
|
);
|
|
};
|
|
|
|
ws.onmessage = (event: MessageEvent) => {
|
|
processWsMessage(event.data as string);
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
if (unmounted.current) return;
|
|
const delay = Math.min(1000 * 2 ** reconnectAttempt.current, 30000);
|
|
reconnectAttempt.current++;
|
|
reconnectTimer.current = setTimeout(connect, delay);
|
|
};
|
|
|
|
ws.onerror = () => {
|
|
ws.close();
|
|
};
|
|
}
|
|
|
|
connect();
|
|
|
|
return () => {
|
|
unmounted.current = true;
|
|
if (reconnectTimer.current) {
|
|
clearTimeout(reconnectTimer.current);
|
|
}
|
|
const ws = wsRef.current;
|
|
if (ws) {
|
|
ws.onopen = null;
|
|
ws.onmessage = null;
|
|
ws.onclose = null;
|
|
ws.onerror = null;
|
|
ws.close();
|
|
}
|
|
resetWsStore();
|
|
};
|
|
}, [wsUrl]);
|
|
|
|
const send = useCallback(
|
|
(message: Update) => {
|
|
sendJsonMessage({
|
|
topic: message.topic,
|
|
payload: message.payload,
|
|
retain: message.retain,
|
|
});
|
|
},
|
|
[sendJsonMessage],
|
|
);
|
|
|
|
return (
|
|
<WsSendContext.Provider value={send}>{children}</WsSendContext.Provider>
|
|
);
|
|
}
|