273 lines
6.9 KiB
JavaScript

import React, {
createContext,
useContext,
useCallback,
useRef,
useState,
useEffect,
} from "react";
import { useWebSocket } from "../hooks/useWebSocket";
import { useCellLocks } from "../hooks/useCellLocks";
import { toast } from "react-toastify";
const RealtimeContext = createContext(null);
export const useRealtime = () => {
const context = useContext(RealtimeContext);
if (!context) {
throw new Error("useRealtime must be used within RealtimeProvider");
}
return context;
};
export const RealtimeProvider = ({
children,
formId,
sheetName,
direction,
userId,
}) => {
// Формируем URL (кастомные секреты на фронте пока недоступны, делаем через REACT_APP_ROOT_PATH)
const wsUrl = `${(process.env.REACT_APP_API_URL || process.env.REACT_APP_API_URL || process.env.REACT_APP_ROOT_PATH || "ws://localhost:8000").replace(/^http/, "ws")}/api/v1/ws/form/${formId}/sheet/${sheetName}${direction ? `?direction=${direction}` : ""}`; console.log()
const handleMessage = useCallback((data) => {
// Обработка ошибок
if (data.error) {
toast.error("Server error:", data.error);
onErrorRef.current?.(data.error);
return;
}
switch (data.event) {
case "cell_updated":
console.log(onCellUpdateRef.current);
if (data.result && onCellUpdateRef.current) {
const updatedCells = Array.isArray(data.result)
? data.result
: [data.result];
onCellUpdateRef.current(updatedCells);
}
break;
case "row_added":
if (data.result && onRowAddRef.current) {
onRowAddRef.current(data);
}
break;
case "row_deleted":
if (data.result && onRowDeleteRef.current) {
onRowDeleteRef.current(data);
}
break;
default:
console.log("Unknown event:", data.event);
}
}, []);
const { isConnected, sendMessage, disconnect, lastError } = useWebSocket(
wsUrl,
handleMessage,
);
const {
isCellLocked,
tryLockCell,
unlockCell,
getCellLockInfo,
releaseAllLocks,
} = useCellLocks(null);
const onCellUpdateRef = useRef(null);
const onRowAddRef = useRef(null);
const onRowDeleteRef = useRef(null);
const onErrorRef = useRef(null);
const onAuthSuccessRef = useRef(null);
const authAttemptedRef = useRef(false);
const reconnectTimerRef = useRef(null);
// Функция для получения токена из localStorage
const getAccessToken = useCallback(() => {
try {
const token = localStorage.getItem("access_token");
if (!token) {
console.warn("No access token found in localStorage");
return null;
}
return token;
} catch (error) {
console.error("Error reading token from localStorage:", error);
return null;
}
}, []);
const sendLogin = useCallback(() => {
const token = getAccessToken();
if (!token) {
console.error("Cannot login: no token available");
onErrorRef.current?.("Токен авторизации не найден");
return false;
}
const loginMessage = {
event: "user_login",
data: { token },
};
return sendMessage(loginMessage);
}, [getAccessToken, sendMessage]);
useEffect(() => {
if (isConnected && !authAttemptedRef.current) {
authAttemptedRef.current = true;
console.log("WebSocket connected, sending login...");
setTimeout(() => {
sendLogin();
}, 100);
}
}, [isConnected, sendLogin]);
useEffect(() => {
if (!isConnected) {
authAttemptedRef.current = false;
}
}, [isConnected]);
const updateCell = useCallback(
async (row, column, value) => {
const columnId = column.id;
const rowId = row.id;
if (!isConnected) {
onErrorRef.current?.("WebSocket не подключен");
return false;
}
const lineId = row.original?.data?.line_id || null;
const colId = columnId.slice(5);
const message = {
event: "cell_updated",
data: {
line_id: lineId,
column: colId,
value: value,
},
};
const sent = sendMessage(message);
if (!sent) {
return false;
}
return true;
},
[isConnected, sendMessage],
);
const addRow = useCallback(
async (rowData) => {
if (!isConnected) {
onErrorRef.current?.("Нет подключения или авторизации");
return false;
}
const message = {
event: "row_added",
data: rowData,
sheet: sheetName,
form_id: formId,
direction: direction,
};
return sendMessage(message);
},
[isConnected, sendMessage],
);
const deleteRow = useCallback(
async (rowId) => {
if (!isConnected) {
onErrorRef.current?.("Нет подключения или авторизации");
return false;
}
const message = {
event: "row_deleted",
data: { row_id: rowId },
sheet: sheetName,
form_id: formId,
direction: direction,
};
return sendMessage(message);
},
[isConnected, sendMessage],
);
const subscribeToCellUpdates = useCallback((callback) => {
onCellUpdateRef.current = callback;
return () => {
onCellUpdateRef.current = null;
};
}, []);
const subscribeToRowAdds = useCallback((callback) => {
onRowAddRef.current = callback;
return () => {
onRowAddRef.current = null;
};
}, []);
const subscribeToRowDeletes = useCallback((callback) => {
onRowDeleteRef.current = callback;
return () => {
onRowDeleteRef.current = null;
};
}, []);
const subscribeToErrors = useCallback((callback) => {
onErrorRef.current = callback;
return () => {
onErrorRef.current = null;
};
}, []);
const subscribeToAuthSuccess = useCallback((callback) => {
onAuthSuccessRef.current = callback;
return () => {
onAuthSuccessRef.current = null;
};
}, []);
const retryLogin = useCallback(() => {
if (isConnected) {
authAttemptedRef.current = false;
sendLogin();
}
}, [isConnected, sendLogin]);
return (
<RealtimeContext.Provider
value={{
isConnected,
error: lastError,
updateCell,
addRow,
deleteRow,
isCellLocked: (rowId, columnId) =>
isCellLocked(rowId, columnId, userId),
getCellLockInfo: (rowId, columnId) => getCellLockInfo(rowId, columnId),
tryLockCell: (rowId, columnId) => tryLockCell(rowId, columnId, userId),
subscribeToCellUpdates,
subscribeToRowAdds,
subscribeToRowDeletes,
subscribeToErrors,
subscribeToAuthSuccess,
retryLogin,
releaseAllLocks: () => releaseAllLocks(userId),
}}
>
{children}
</RealtimeContext.Provider>
);
};