feature/IO-3586-Socket-Reconnect-Issues - Fix

This commit is contained in:
Dave
2026-03-02 10:41:24 -05:00
parent 88e943f43d
commit 1fa6280876
7 changed files with 258 additions and 52 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import SocketIO from "socket.io-client";
import { auth } from "../../firebase/firebase.utils";
import { store } from "../../redux/store";
@@ -18,6 +18,7 @@ import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
import { INITIAL_NOTIFICATIONS, SocketContext } from "./useSocket.js";
const LIMIT = INITIAL_NOTIFICATIONS;
const TOKEN_SYNC_INTERVAL_MS = 10 * 60 * 1000;
/**
* Socket Provider - Scenario Notifications / Web Socket related items
@@ -30,6 +31,7 @@ const LIMIT = INITIAL_NOTIFICATIONS;
*/
const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
const socketRef = useRef(null);
const tokenSyncIntervalRef = useRef(null);
const [clientId, setClientId] = useState(null);
const [isConnected, setIsConnected] = useState(false);
const notification = useNotification();
@@ -147,6 +149,30 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
onError: (err) => console.error("MARK_ALL_NOTIFICATIONS_READ error:", err)
});
const reconnectSocket = useCallback(
async ({ forceRefreshToken = true } = {}) => {
const socketInstance = socketRef.current;
if (!socketInstance || !auth.currentUser || !bodyshop?.id) return false;
try {
const token = await auth.currentUser.getIdToken(forceRefreshToken);
socketInstance.auth = { token, bodyshopId: bodyshop.id };
if (socketInstance.connected) {
socketInstance.emit("update-token", { token, bodyshopId: bodyshop.id });
}
socketInstance.disconnect();
socketInstance.connect();
return true;
} catch (error) {
console.error("Socket reconnect failed:", error?.message || error);
return false;
}
},
[bodyshop?.id]
);
useEffect(() => {
const initializeSocket = async (token) => {
if (!bodyshop?.id || socketRef.current) return;
@@ -254,25 +280,60 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
}
};
const syncCurrentTokenToSocket = async () => {
try {
if (!auth.currentUser || !bodyshop?.id) return;
const token = await auth.currentUser.getIdToken();
socketInstance.auth = { token, bodyshopId: bodyshop.id };
socketInstance.emit("update-token", { token, bodyshopId: bodyshop.id });
} catch (error) {
console.error("Failed to sync token to socket:", error?.message || error);
}
};
const forceRefreshAndSyncToken = async () => {
try {
if (!auth.currentUser || !bodyshop?.id) return;
const token = await auth.currentUser.getIdToken(true);
socketInstance.auth = { token, bodyshopId: bodyshop.id };
socketInstance.emit("update-token", { token, bodyshopId: bodyshop.id });
} catch (error) {
console.error("Failed to force-refresh token for socket:", error?.message || error);
}
};
const handleConnect = () => {
socketInstance.emit("join-bodyshop-room", bodyshop.id);
syncCurrentTokenToSocket();
setClientId(socketInstance.id);
setIsConnected(true);
store.dispatch(setWssStatus("connected"));
};
const handleReconnect = () => {
forceRefreshAndSyncToken();
setIsConnected(true);
store.dispatch(setWssStatus("connected"));
};
const handleTokenUpdated = ({ success, error }) => {
if (success) return;
const err = String(error || "");
if (/stale token|id-token-expired/i.test(err)) {
forceRefreshAndSyncToken();
}
};
const handleConnectionError = (err) => {
console.error("Socket connection error:", err);
setIsConnected(false);
if (err.message.includes("auth/id-token-expired")) {
if (err?.message?.includes("auth/id-token-expired")) {
console.warn("Token expired, refreshing...");
auth.currentUser?.getIdToken(true).then((newToken) => {
socketInstance.auth = { token: newToken };
socketInstance.auth = { token: newToken, bodyshopId: bodyshop.id };
if (socketInstance.connected) {
socketInstance.emit("update-token", { token: newToken, bodyshopId: bodyshop.id });
}
socketInstance.connect();
});
} else {
@@ -513,10 +574,23 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
socketInstance.on("notification", handleNotification);
socketInstance.on("sync-notification-read", handleSyncNotificationRead);
socketInstance.on("sync-all-notifications-read", handleSyncAllNotificationsRead);
socketInstance.on("token-updated", handleTokenUpdated);
if (tokenSyncIntervalRef.current) {
clearInterval(tokenSyncIntervalRef.current);
}
tokenSyncIntervalRef.current = setInterval(() => {
if (!socketInstance.connected) return;
syncCurrentTokenToSocket();
}, TOKEN_SYNC_INTERVAL_MS);
};
const unsubscribe = auth.onIdTokenChanged(async (user) => {
if (!user) {
if (tokenSyncIntervalRef.current) {
clearInterval(tokenSyncIntervalRef.current);
tokenSyncIntervalRef.current = null;
}
socketRef.current?.disconnect();
socketRef.current = null;
setIsConnected(false);
@@ -525,7 +599,10 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
const token = await user.getIdToken();
if (socketRef.current) {
socketRef.current.emit("update-token", { token, bodyshopId: bodyshop.id });
socketRef.current.auth = { token, bodyshopId: bodyshop.id };
if (socketRef.current.connected) {
socketRef.current.emit("update-token", { token, bodyshopId: bodyshop.id });
}
} else {
initializeSocket(token).catch((err) =>
console.error("Something went wrong Initializing Sockets:", err?.message || "")
@@ -535,6 +612,10 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
return () => {
unsubscribe();
if (tokenSyncIntervalRef.current) {
clearInterval(tokenSyncIntervalRef.current);
tokenSyncIntervalRef.current = null;
}
if (socketRef.current) {
socketRef.current.disconnect();
socketRef.current = null;
@@ -549,6 +630,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
socket: socketRef.current,
clientId,
isConnected,
reconnectSocket,
markNotificationRead,
markAllNotificationsRead,
scenarioNotificationsOn: Realtime_Notifications_UI?.treatment === "on"