2023-12-08 16:33:22 +03:00
|
|
|
import { baseUrl } from "./baseUrl";
|
2024-02-27 19:05:28 +03:00
|
|
|
import { useCallback, useEffect, useState } from "react";
|
2023-12-08 16:33:22 +03:00
|
|
|
import useWebSocket, { ReadyState } from "react-use-websocket";
|
2024-04-30 16:09:50 +03:00
|
|
|
import {
|
2024-10-10 22:28:43 +03:00
|
|
|
EmbeddingsReindexProgressType,
|
2024-04-30 16:09:50 +03:00
|
|
|
FrigateCameraState,
|
|
|
|
|
FrigateEvent,
|
|
|
|
|
FrigateReview,
|
2024-10-07 23:30:45 +03:00
|
|
|
ModelState,
|
2024-04-30 16:09:50 +03:00
|
|
|
ToggleableSetting,
|
|
|
|
|
} from "@/types/ws";
|
2024-02-22 05:27:02 +03:00
|
|
|
import { FrigateStats } from "@/types/stats";
|
2024-02-27 19:05:28 +03:00
|
|
|
import { createContainer } from "react-tracked";
|
2024-07-11 18:25:33 +03:00
|
|
|
import useDeepMemo from "@/hooks/use-deep-memo";
|
2023-12-08 16:33:22 +03:00
|
|
|
|
2024-02-27 19:05:28 +03:00
|
|
|
type Update = {
|
2023-12-08 16:33:22 +03:00
|
|
|
topic: string;
|
2024-02-29 01:23:56 +03:00
|
|
|
payload: unknown;
|
2023-12-08 16:33:22 +03:00
|
|
|
retain: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-27 19:05:28 +03:00
|
|
|
type WsState = {
|
2024-02-29 01:23:56 +03:00
|
|
|
[topic: string]: unknown;
|
2023-12-08 16:33:22 +03:00
|
|
|
};
|
|
|
|
|
|
2024-02-27 19:05:28 +03:00
|
|
|
type useValueReturn = [WsState, (update: Update) => void];
|
2023-12-08 16:33:22 +03:00
|
|
|
|
2024-02-27 19:05:28 +03:00
|
|
|
function useValue(): useValueReturn {
|
|
|
|
|
const wsUrl = `${baseUrl.replace(/^http/, "ws")}ws`;
|
2023-12-08 16:33:22 +03:00
|
|
|
|
2024-02-27 19:05:28 +03:00
|
|
|
// main state
|
2024-08-06 18:08:43 +03:00
|
|
|
|
|
|
|
|
const [hasCameraState, setHasCameraState] = useState(false);
|
2024-02-27 19:05:28 +03:00
|
|
|
const [wsState, setWsState] = useState<WsState>({});
|
2023-12-08 16:33:22 +03:00
|
|
|
|
2024-02-27 19:05:28 +03:00
|
|
|
useEffect(() => {
|
2024-08-06 18:08:43 +03:00
|
|
|
if (hasCameraState) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const activityValue: string = wsState["camera_activity"] as string;
|
|
|
|
|
|
|
|
|
|
if (!activityValue) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cameraActivity: { [key: string]: object } = JSON.parse(activityValue);
|
|
|
|
|
|
|
|
|
|
if (!cameraActivity) {
|
2024-02-27 19:05:28 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2023-12-08 16:33:22 +03:00
|
|
|
|
2024-02-27 19:05:28 +03:00
|
|
|
const cameraStates: WsState = {};
|
2023-12-08 16:33:22 +03:00
|
|
|
|
2024-08-06 18:08:43 +03:00
|
|
|
Object.entries(cameraActivity).forEach(([name, state]) => {
|
|
|
|
|
const { record, detect, snapshots, audio, autotracking } =
|
|
|
|
|
// @ts-expect-error we know this is correct
|
|
|
|
|
state["config"];
|
|
|
|
|
cameraStates[`${name}/recordings/state`] = record ? "ON" : "OFF";
|
|
|
|
|
cameraStates[`${name}/detect/state`] = detect ? "ON" : "OFF";
|
|
|
|
|
cameraStates[`${name}/snapshots/state`] = snapshots ? "ON" : "OFF";
|
|
|
|
|
cameraStates[`${name}/audio/state`] = audio ? "ON" : "OFF";
|
|
|
|
|
cameraStates[`${name}/ptz_autotracker/state`] = autotracking
|
2024-05-16 17:32:39 +03:00
|
|
|
? "ON"
|
|
|
|
|
: "OFF";
|
2024-02-27 19:05:28 +03:00
|
|
|
});
|
|
|
|
|
|
2024-10-21 05:38:11 +03:00
|
|
|
setWsState((prevState) => ({
|
|
|
|
|
...prevState,
|
|
|
|
|
...cameraStates,
|
|
|
|
|
}));
|
2024-11-07 17:25:13 +03:00
|
|
|
|
|
|
|
|
if (Object.keys(cameraStates).length > 0) {
|
|
|
|
|
setHasCameraState(true);
|
|
|
|
|
}
|
2024-02-29 01:23:56 +03:00
|
|
|
// we only want this to run initially when the config is loaded
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
2024-08-06 18:08:43 +03:00
|
|
|
}, [wsState]);
|
2024-02-27 19:05:28 +03:00
|
|
|
|
|
|
|
|
// ws handler
|
2023-12-08 16:33:22 +03:00
|
|
|
const { sendJsonMessage, readyState } = useWebSocket(wsUrl, {
|
|
|
|
|
onMessage: (event) => {
|
2024-02-27 19:05:28 +03:00
|
|
|
const data: Update = JSON.parse(event.data);
|
|
|
|
|
|
|
|
|
|
if (data) {
|
2024-10-21 05:38:11 +03:00
|
|
|
setWsState((prevState) => ({
|
|
|
|
|
...prevState,
|
|
|
|
|
[data.topic]: data.payload,
|
|
|
|
|
}));
|
2024-02-27 19:05:28 +03:00
|
|
|
}
|
2023-12-08 16:33:22 +03:00
|
|
|
},
|
2024-04-30 16:27:39 +03:00
|
|
|
onOpen: () => {
|
|
|
|
|
sendJsonMessage({
|
|
|
|
|
topic: "onConnect",
|
|
|
|
|
message: "",
|
|
|
|
|
retain: false,
|
|
|
|
|
});
|
|
|
|
|
},
|
2024-11-07 17:25:13 +03:00
|
|
|
onClose: () => {
|
|
|
|
|
setHasCameraState(false);
|
|
|
|
|
},
|
2023-12-08 16:33:22 +03:00
|
|
|
shouldReconnect: () => true,
|
2024-06-04 23:00:04 +03:00
|
|
|
retryOnError: true,
|
2023-12-08 16:33:22 +03:00
|
|
|
});
|
|
|
|
|
|
2024-02-27 19:05:28 +03:00
|
|
|
const setState = useCallback(
|
|
|
|
|
(message: Update) => {
|
|
|
|
|
if (readyState === ReadyState.OPEN) {
|
|
|
|
|
sendJsonMessage({
|
|
|
|
|
topic: message.topic,
|
|
|
|
|
payload: message.payload,
|
|
|
|
|
retain: message.retain,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-02-29 01:23:56 +03:00
|
|
|
[readyState, sendJsonMessage],
|
2023-12-08 16:33:22 +03:00
|
|
|
);
|
2024-02-27 19:05:28 +03:00
|
|
|
|
|
|
|
|
return [wsState, setState];
|
2023-12-08 16:33:22 +03:00
|
|
|
}
|
|
|
|
|
|
2024-02-27 19:05:28 +03:00
|
|
|
export const {
|
|
|
|
|
Provider: WsProvider,
|
|
|
|
|
useTrackedState: useWsState,
|
|
|
|
|
useUpdate: useWsUpdate,
|
|
|
|
|
} = createContainer(useValue, { defaultState: {}, concurrentMode: true });
|
|
|
|
|
|
2023-12-08 16:33:22 +03:00
|
|
|
export function useWs(watchTopic: string, publishTopic: string) {
|
2024-02-27 19:05:28 +03:00
|
|
|
const state = useWsState();
|
|
|
|
|
const sendJsonMessage = useWsUpdate();
|
2023-12-08 16:33:22 +03:00
|
|
|
|
2024-02-27 19:05:28 +03:00
|
|
|
const value = { payload: state[watchTopic] || null };
|
2023-12-08 16:33:22 +03:00
|
|
|
|
|
|
|
|
const send = useCallback(
|
2024-02-29 01:23:56 +03:00
|
|
|
(payload: unknown, retain = false) => {
|
2024-02-27 19:05:28 +03:00
|
|
|
sendJsonMessage({
|
|
|
|
|
topic: publishTopic || watchTopic,
|
|
|
|
|
payload,
|
|
|
|
|
retain,
|
|
|
|
|
});
|
2023-12-08 16:33:22 +03:00
|
|
|
},
|
2024-02-29 01:23:56 +03:00
|
|
|
[sendJsonMessage, watchTopic, publishTopic],
|
2023-12-08 16:33:22 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return { value, send };
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-29 16:08:00 +03:00
|
|
|
export function useDetectState(camera: string): {
|
2024-02-10 15:30:53 +03:00
|
|
|
payload: ToggleableSetting;
|
|
|
|
|
send: (payload: ToggleableSetting, retain?: boolean) => void;
|
2023-12-29 16:08:00 +03:00
|
|
|
} {
|
2023-12-08 16:33:22 +03:00
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
send,
|
|
|
|
|
} = useWs(`${camera}/detect/state`, `${camera}/detect/set`);
|
2024-02-29 01:23:56 +03:00
|
|
|
return { payload: payload as ToggleableSetting, send };
|
2023-12-08 16:33:22 +03:00
|
|
|
}
|
|
|
|
|
|
2023-12-29 16:08:00 +03:00
|
|
|
export function useRecordingsState(camera: string): {
|
2024-02-10 15:30:53 +03:00
|
|
|
payload: ToggleableSetting;
|
|
|
|
|
send: (payload: ToggleableSetting, retain?: boolean) => void;
|
2023-12-29 16:08:00 +03:00
|
|
|
} {
|
2023-12-08 16:33:22 +03:00
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
send,
|
|
|
|
|
} = useWs(`${camera}/recordings/state`, `${camera}/recordings/set`);
|
2024-02-29 01:23:56 +03:00
|
|
|
return { payload: payload as ToggleableSetting, send };
|
2023-12-08 16:33:22 +03:00
|
|
|
}
|
|
|
|
|
|
2023-12-29 16:08:00 +03:00
|
|
|
export function useSnapshotsState(camera: string): {
|
2024-02-10 15:30:53 +03:00
|
|
|
payload: ToggleableSetting;
|
|
|
|
|
send: (payload: ToggleableSetting, retain?: boolean) => void;
|
2023-12-29 16:08:00 +03:00
|
|
|
} {
|
2023-12-08 16:33:22 +03:00
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
send,
|
|
|
|
|
} = useWs(`${camera}/snapshots/state`, `${camera}/snapshots/set`);
|
2024-02-29 01:23:56 +03:00
|
|
|
return { payload: payload as ToggleableSetting, send };
|
2023-12-08 16:33:22 +03:00
|
|
|
}
|
|
|
|
|
|
2023-12-29 16:08:00 +03:00
|
|
|
export function useAudioState(camera: string): {
|
2024-02-10 15:30:53 +03:00
|
|
|
payload: ToggleableSetting;
|
|
|
|
|
send: (payload: ToggleableSetting, retain?: boolean) => void;
|
2023-12-29 16:08:00 +03:00
|
|
|
} {
|
2023-12-08 16:33:22 +03:00
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
send,
|
|
|
|
|
} = useWs(`${camera}/audio/state`, `${camera}/audio/set`);
|
2024-02-29 01:23:56 +03:00
|
|
|
return { payload: payload as ToggleableSetting, send };
|
2023-12-08 16:33:22 +03:00
|
|
|
}
|
|
|
|
|
|
2024-05-16 17:32:39 +03:00
|
|
|
export function useAutotrackingState(camera: string): {
|
|
|
|
|
payload: ToggleableSetting;
|
|
|
|
|
send: (payload: ToggleableSetting, retain?: boolean) => void;
|
|
|
|
|
} {
|
|
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
send,
|
|
|
|
|
} = useWs(`${camera}/ptz_autotracker/state`, `${camera}/ptz_autotracker/set`);
|
|
|
|
|
return { payload: payload as ToggleableSetting, send };
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-29 16:08:00 +03:00
|
|
|
export function usePtzCommand(camera: string): {
|
|
|
|
|
payload: string;
|
|
|
|
|
send: (payload: string, retain?: boolean) => void;
|
|
|
|
|
} {
|
2023-12-08 16:33:22 +03:00
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
send,
|
|
|
|
|
} = useWs(`${camera}/ptz`, `${camera}/ptz`);
|
2024-02-29 01:23:56 +03:00
|
|
|
return { payload: payload as string, send };
|
2023-12-08 16:33:22 +03:00
|
|
|
}
|
|
|
|
|
|
2023-12-29 16:08:00 +03:00
|
|
|
export function useRestart(): {
|
|
|
|
|
payload: string;
|
|
|
|
|
send: (payload: string, retain?: boolean) => void;
|
|
|
|
|
} {
|
2023-12-08 16:33:22 +03:00
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
send,
|
|
|
|
|
} = useWs("restart", "restart");
|
2024-02-29 01:23:56 +03:00
|
|
|
return { payload: payload as string, send };
|
2023-12-08 16:33:22 +03:00
|
|
|
}
|
2023-12-29 16:08:00 +03:00
|
|
|
|
|
|
|
|
export function useFrigateEvents(): { payload: FrigateEvent } {
|
|
|
|
|
const {
|
|
|
|
|
value: { payload },
|
2024-02-27 16:37:39 +03:00
|
|
|
} = useWs("events", "");
|
2024-02-29 01:23:56 +03:00
|
|
|
return { payload: JSON.parse(payload as string) };
|
2024-02-27 16:37:39 +03:00
|
|
|
}
|
|
|
|
|
|
2024-07-11 18:25:33 +03:00
|
|
|
export function useFrigateReviews(): FrigateReview {
|
2024-02-27 16:37:39 +03:00
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
} = useWs("reviews", "");
|
2024-07-11 18:25:33 +03:00
|
|
|
return useDeepMemo(JSON.parse(payload as string));
|
2023-12-29 16:08:00 +03:00
|
|
|
}
|
|
|
|
|
|
2024-07-11 18:25:33 +03:00
|
|
|
export function useFrigateStats(): FrigateStats {
|
2024-02-22 05:27:02 +03:00
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
} = useWs("stats", "");
|
2024-07-11 18:25:33 +03:00
|
|
|
return useDeepMemo(JSON.parse(payload as string));
|
2024-02-22 05:27:02 +03:00
|
|
|
}
|
|
|
|
|
|
2024-05-14 16:38:03 +03:00
|
|
|
export function useInitialCameraState(
|
|
|
|
|
camera: string,
|
2024-05-21 02:26:17 +03:00
|
|
|
revalidateOnFocus: boolean,
|
2024-05-14 16:38:03 +03:00
|
|
|
): {
|
2024-04-30 16:09:50 +03:00
|
|
|
payload: FrigateCameraState;
|
|
|
|
|
} {
|
|
|
|
|
const {
|
|
|
|
|
value: { payload },
|
2024-05-14 16:38:03 +03:00
|
|
|
send: sendCommand,
|
|
|
|
|
} = useWs("camera_activity", "onConnect");
|
2024-07-11 18:25:33 +03:00
|
|
|
|
|
|
|
|
const data = useDeepMemo(JSON.parse(payload as string));
|
2024-05-14 16:38:03 +03:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2024-05-21 02:26:17 +03:00
|
|
|
let listener = undefined;
|
|
|
|
|
if (revalidateOnFocus) {
|
2024-05-14 16:38:03 +03:00
|
|
|
sendCommand("onConnect");
|
2024-05-21 02:26:17 +03:00
|
|
|
listener = () => {
|
|
|
|
|
if (document.visibilityState == "visible") {
|
|
|
|
|
sendCommand("onConnect");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
addEventListener("visibilitychange", listener);
|
2024-05-14 16:38:03 +03:00
|
|
|
}
|
2024-05-21 02:26:17 +03:00
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
if (listener) {
|
|
|
|
|
removeEventListener("visibilitychange", listener);
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-05-14 16:38:03 +03:00
|
|
|
// only refresh when onRefresh value changes
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
2024-05-21 02:26:17 +03:00
|
|
|
}, [revalidateOnFocus]);
|
2024-05-14 16:38:03 +03:00
|
|
|
|
2024-04-30 16:09:50 +03:00
|
|
|
return { payload: data ? data[camera] : undefined };
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-07 23:30:45 +03:00
|
|
|
export function useModelState(
|
|
|
|
|
model: string,
|
|
|
|
|
revalidateOnFocus: boolean = true,
|
|
|
|
|
): { payload: ModelState } {
|
|
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
send: sendCommand,
|
|
|
|
|
} = useWs("model_state", "modelState");
|
|
|
|
|
|
|
|
|
|
const data = useDeepMemo(JSON.parse(payload as string));
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
let listener = undefined;
|
|
|
|
|
if (revalidateOnFocus) {
|
|
|
|
|
sendCommand("modelState");
|
|
|
|
|
listener = () => {
|
|
|
|
|
if (document.visibilityState == "visible") {
|
|
|
|
|
sendCommand("modelState");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
addEventListener("visibilitychange", listener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
if (listener) {
|
|
|
|
|
removeEventListener("visibilitychange", listener);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// we know that these deps are correct
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
}, [revalidateOnFocus]);
|
|
|
|
|
|
|
|
|
|
return { payload: data ? data[model] : undefined };
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 22:28:43 +03:00
|
|
|
export function useEmbeddingsReindexProgress(
|
|
|
|
|
revalidateOnFocus: boolean = true,
|
|
|
|
|
): {
|
|
|
|
|
payload: EmbeddingsReindexProgressType;
|
|
|
|
|
} {
|
|
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
send: sendCommand,
|
|
|
|
|
} = useWs("embeddings_reindex_progress", "embeddingsReindexProgress");
|
|
|
|
|
|
|
|
|
|
const data = useDeepMemo(JSON.parse(payload as string));
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
let listener = undefined;
|
|
|
|
|
if (revalidateOnFocus) {
|
|
|
|
|
sendCommand("embeddingsReindexProgress");
|
|
|
|
|
listener = () => {
|
|
|
|
|
if (document.visibilityState == "visible") {
|
|
|
|
|
sendCommand("embeddingsReindexProgress");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
addEventListener("visibilitychange", listener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
if (listener) {
|
|
|
|
|
removeEventListener("visibilitychange", listener);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// we know that these deps are correct
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
}, [revalidateOnFocus]);
|
|
|
|
|
|
|
|
|
|
return { payload: data };
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-29 16:08:00 +03:00
|
|
|
export function useMotionActivity(camera: string): { payload: string } {
|
|
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
} = useWs(`${camera}/motion`, "");
|
2024-02-29 01:23:56 +03:00
|
|
|
return { payload: payload as string };
|
2023-12-29 16:08:00 +03:00
|
|
|
}
|
|
|
|
|
|
2024-02-10 15:30:53 +03:00
|
|
|
export function useAudioActivity(camera: string): { payload: number } {
|
2023-12-29 16:08:00 +03:00
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
} = useWs(`${camera}/audio/rms`, "");
|
2024-02-29 01:23:56 +03:00
|
|
|
return { payload: payload as number };
|
2023-12-29 16:08:00 +03:00
|
|
|
}
|
2024-04-19 14:34:07 +03:00
|
|
|
|
|
|
|
|
export function useMotionThreshold(camera: string): {
|
|
|
|
|
payload: string;
|
|
|
|
|
send: (payload: number, retain?: boolean) => void;
|
|
|
|
|
} {
|
|
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
send,
|
|
|
|
|
} = useWs(
|
|
|
|
|
`${camera}/motion_threshold/state`,
|
|
|
|
|
`${camera}/motion_threshold/set`,
|
|
|
|
|
);
|
|
|
|
|
return { payload: payload as string, send };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useMotionContourArea(camera: string): {
|
|
|
|
|
payload: string;
|
|
|
|
|
send: (payload: number, retain?: boolean) => void;
|
|
|
|
|
} {
|
|
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
send,
|
|
|
|
|
} = useWs(
|
|
|
|
|
`${camera}/motion_contour_area/state`,
|
|
|
|
|
`${camera}/motion_contour_area/set`,
|
|
|
|
|
);
|
|
|
|
|
return { payload: payload as string, send };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useImproveContrast(camera: string): {
|
|
|
|
|
payload: ToggleableSetting;
|
|
|
|
|
send: (payload: string, retain?: boolean) => void;
|
|
|
|
|
} {
|
|
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
send,
|
|
|
|
|
} = useWs(
|
|
|
|
|
`${camera}/improve_contrast/state`,
|
|
|
|
|
`${camera}/improve_contrast/set`,
|
|
|
|
|
);
|
|
|
|
|
return { payload: payload as ToggleableSetting, send };
|
|
|
|
|
}
|
2024-09-24 17:14:51 +03:00
|
|
|
|
|
|
|
|
export function useEventUpdate(): { payload: string } {
|
|
|
|
|
const {
|
|
|
|
|
value: { payload },
|
|
|
|
|
} = useWs("event_update", "");
|
|
|
|
|
return useDeepMemo(JSON.parse(payload as string));
|
|
|
|
|
}
|