feature/IO-3096-GlobalNotifications - Checkpoint - Notification Center
This commit is contained in:
191
client/src/contexts/SocketIO/useSocket.jsx
Normal file
191
client/src/contexts/SocketIO/useSocket.jsx
Normal file
@@ -0,0 +1,191 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import SocketIO from "socket.io-client";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
import { store } from "../../redux/store";
|
||||
import { addAlerts, setWssStatus } from "../../redux/application/application.actions";
|
||||
import client from "../../utils/GraphQLClient";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
|
||||
const useSocket = (bodyshop) => {
|
||||
const socketRef = useRef(null);
|
||||
const [clientId, setClientId] = useState(null);
|
||||
const notification = useNotification();
|
||||
|
||||
useEffect(() => {
|
||||
const initializeSocket = async (token) => {
|
||||
if (!bodyshop || !bodyshop.id) return;
|
||||
|
||||
const endpoint = import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "";
|
||||
|
||||
const socketInstance = SocketIO(endpoint, {
|
||||
path: "/wss",
|
||||
withCredentials: true,
|
||||
auth: { token, bodyshopId: bodyshop.id },
|
||||
reconnectionAttempts: Infinity,
|
||||
reconnectionDelay: 2000,
|
||||
reconnectionDelayMax: 10000
|
||||
});
|
||||
|
||||
socketRef.current = socketInstance;
|
||||
|
||||
const handleBodyshopMessage = (message) => {
|
||||
if (!message || !message.type) return;
|
||||
switch (message.type) {
|
||||
case "alert-update":
|
||||
store.dispatch(addAlerts(message.payload));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!import.meta.env.DEV) return;
|
||||
console.log(`Received message for bodyshop ${bodyshop.id}:`, message);
|
||||
};
|
||||
|
||||
const handleConnect = () => {
|
||||
socketInstance.emit("join-bodyshop-room", bodyshop.id);
|
||||
setClientId(socketInstance.id);
|
||||
store.dispatch(setWssStatus("connected"));
|
||||
console.log("Socket connected, ID:", socketInstance.id);
|
||||
};
|
||||
|
||||
const handleReconnect = () => {
|
||||
store.dispatch(setWssStatus("connected"));
|
||||
};
|
||||
|
||||
const handleConnectionError = (err) => {
|
||||
console.error("Socket connection error:", err);
|
||||
if (err.message.includes("auth/id-token-expired")) {
|
||||
console.warn("Token expired, refreshing...");
|
||||
auth.currentUser?.getIdToken(true).then((newToken) => {
|
||||
socketInstance.auth = { token: newToken };
|
||||
socketInstance.connect();
|
||||
});
|
||||
} else {
|
||||
store.dispatch(setWssStatus("error"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisconnect = (reason) => {
|
||||
console.warn("Socket disconnected:", reason);
|
||||
store.dispatch(setWssStatus("disconnected"));
|
||||
if (!socketInstance.connected && reason !== "io server disconnect") {
|
||||
setTimeout(() => {
|
||||
if (socketInstance.disconnected) {
|
||||
console.log("Manually triggering reconnection...");
|
||||
socketInstance.connect();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNotification = (data) => {
|
||||
const { jobId, bodyShopId, notifications, notificationId } = data;
|
||||
console.log("handleNotification - Received", { jobId, bodyShopId, notificationId, notifications });
|
||||
|
||||
const newNotification = {
|
||||
id: notificationId,
|
||||
jobid: jobId,
|
||||
associationid: null,
|
||||
scenario_text: notifications.map((notif) => notif.body),
|
||||
fcm_text: notifications.map((notif) => notif.body).join(". ") + ".",
|
||||
scenario_meta: notifications.map((notif) => notif.variables || {}),
|
||||
created_at: new Date(notifications[0].timestamp).toISOString(),
|
||||
read: null,
|
||||
__typename: "notifications"
|
||||
};
|
||||
|
||||
try {
|
||||
client.cache.modify({
|
||||
id: "ROOT_QUERY",
|
||||
fields: {
|
||||
notifications(existing = []) {
|
||||
if (existing.some((n) => n.id === newNotification.id)) return existing;
|
||||
console.log("Adding to cache", newNotification);
|
||||
return [newNotification, ...existing];
|
||||
},
|
||||
notifications_aggregate(existing = { aggregate: { count: 0 } }) {
|
||||
// Fallback if aggregate isn’t in schema yet
|
||||
if (!existing) return existing;
|
||||
console.log("Updating unread count", existing.aggregate.count + 1);
|
||||
return {
|
||||
...existing,
|
||||
aggregate: {
|
||||
...existing.aggregate,
|
||||
count: existing.aggregate.count + 1
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
notification.info({
|
||||
message: "New Notification",
|
||||
description: (
|
||||
<ul>
|
||||
{notifications.map((notif, index) => (
|
||||
<li key={index}>{notif.body}</li>
|
||||
))}
|
||||
</ul>
|
||||
),
|
||||
placement: "topRight",
|
||||
duration: 5
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in handleNotification:", error);
|
||||
}
|
||||
};
|
||||
|
||||
socketInstance.on("connect", handleConnect);
|
||||
socketInstance.on("reconnect", handleReconnect);
|
||||
socketInstance.on("connect_error", handleConnectionError);
|
||||
socketInstance.on("disconnect", handleDisconnect);
|
||||
socketInstance.on("bodyshop-message", handleBodyshopMessage);
|
||||
|
||||
socketInstance.on("message", (message) => {
|
||||
console.log("Raw socket message:", message);
|
||||
try {
|
||||
if (typeof message === "string" && message.startsWith("42")) {
|
||||
const parsedMessage = JSON.parse(message.slice(2));
|
||||
const [event, data] = parsedMessage;
|
||||
if (event === "notification") handleNotification(data);
|
||||
} else if (Array.isArray(message)) {
|
||||
const [event, data] = message;
|
||||
if (event === "notification") handleNotification(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing socket message:", error);
|
||||
}
|
||||
});
|
||||
|
||||
socketInstance.on("notification", handleNotification);
|
||||
};
|
||||
|
||||
const unsubscribe = auth.onIdTokenChanged(async (user) => {
|
||||
if (user) {
|
||||
const token = await user.getIdToken();
|
||||
if (socketRef.current) {
|
||||
socketRef.current.emit("update-token", { token, bodyshopId: bodyshop.id });
|
||||
} else {
|
||||
initializeSocket(token);
|
||||
}
|
||||
} else {
|
||||
if (socketRef.current) {
|
||||
socketRef.current.disconnect();
|
||||
socketRef.current = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
if (socketRef.current) {
|
||||
socketRef.current.disconnect();
|
||||
socketRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [bodyshop, client.cache]);
|
||||
|
||||
return { socket: socketRef.current, clientId };
|
||||
};
|
||||
|
||||
export default useSocket;
|
||||
Reference in New Issue
Block a user