273 lines
6.9 KiB
JavaScript
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>
|
|
);
|
|
};
|