So basically I'm using a connect.ts a custom hook for leveraging a single socket connection by not making new connections each time whenever I need to send a message from anywhere from my app.
I have added logic for reconnection in here as well, and it is working perfectly fine and I'm exporting latest socket instance from hook.
This is connect.ts hook.
import { useWallet } from "@solana/wallet-adapter-react";
import React from "react";
const socketInstances: Record<string, WebSocket | null> = {};
export function useWebSocket() {
const [initData, setInitData] = React.useState<any>(null);
const socketInstanceRef = React.useRef<WebSocket | null>(null);
const messageListenerRef = React.useRef<
((event: MessageEvent) => void) | null
>(null);
const { disconnect } = useWallet();
// inital messages handing
const handleWebSocketMessage = React.useCallback((event: MessageEvent) => {
try {
const data = JSON.parse(event.data);
if (data && Object.keys(data).length > 0) {
data.action === "someAction" && setInitData(data);
if (data.description === "token not found in database") {
disconnect();
localStorage.clear();
}
}
} catch (error) {
console.error("Error parsing WebSocket message:", error);
}
}, []);
// connection making
const connectWebSocket = React.useCallback(() => {
if (
socketInstanceRef.current &&
socketInstanceRef.current.readyState === WebSocket.CLOSED
) {
socketInstanceRef.current = new WebSocket(
"socketURL",
"protocol"
);
socketInstanceRef.current.onopen = () => {
console.log(
`Connected to the WebSocket server for URL: socketURL`
);
};
socketInstanceRef.current.onclose = () => {
console.log(
`Disconnected from the WebSocket server for URL: socketURL`
);
// Reconnect when the connection is closed
setTimeout(connectWebSocket, 1000);
};
// Update the socketInstances object with the new instance
socketInstances["socketURL"] = socketInstanceRef.current;
}
}, []);
React.useEffect(() => {
if (!socketInstances["socketURL"]) {
// Create a new WebSocket instance and add event listeners
socketInstanceRef.current = new WebSocket(
"socketURL",
"protocol"
);
socketInstanceRef.current.onopen = () => {
console.log(
`Connected to the WebSocket server for URL: socketURL`
);
};
socketInstanceRef.current.onclose = () => {
console.log(
`Disconnected from the WebSocket server for URL: socketURL`
);
// Reconnect when the connection is closed
setTimeout(connectWebSocket, 1000);
};
// Update the socketInstances object with the new instance
socketInstances["socketURL"] = socketInstanceRef.current;
} else {
// Update the socketInstanceRef.current when the WebSocket reconnects
socketInstanceRef.current = socketInstances["socketURL"];
}
if (messageListenerRef.current) {
socketInstanceRef.current?.removeEventListener(
"message",
messageListenerRef.current
);
}
messageListenerRef.current = handleWebSocketMessage;
if (messageListenerRef.current) {
socketInstanceRef.current?.addEventListener(
"message",
messageListenerRef.current
);
}
return () => {
if (messageListenerRef.current) {
socketInstanceRef.current?.removeEventListener(
"message",
messageListenerRef.current
);
}
};
}, [handleWebSocketMessage, connectWebSocket]);
return {
socketInstance: socketInstanceRef.current,
initData: initData,
};
}
// Export the socketInstances object for access to existing socket instances
export { socketInstances };
So after that when I come to another component and use useEffect hook to get initial data related to that specific page.
const socketData = useWebSocket();
const socketInstance = socketData?.socketInstance;
useEffect(() => {
if (socketInstance?.readyState === 1 && connected) {
socketInstance.send(
JSON.stringify({
action: "getserverinfo",
token: user?.token,
data: {
user: {
address: `${publicKey?.toBase58()}`,
chainid: 0,
},
},
})
);
socketInstance.onmessage = (event: { data: string }) => {
if (JSON.parse(event.data).action === "getserverinfo") {
setAddressToDeposit(JSON.parse(event.data).data?.server.address);
}
};
}
}, [publicKey, socketInstance, connected]);
It works fine as well but whenever I wait here in another page for socket to get disconnect for some reason and gets reconnect again according to connnect.ts reconnection logic. And I try to run a function which is sending message to same socket connection like that
const handleDeposit = async () => {
if (!publicKey) throw new WalletNotConnectedError();
if (!coinsToDeposit) throw new Error("Please enter a valid amount");
if (!addressToDeposit) throw new Error("Please refresh the page");
if (socketInstance?.readyState !== 1)
throw new Error(
`Please connect to the server ${socketInstance}, ${connected}`
);
if (socketInstance?.readyState === 1 && connected) {
transferUSDC(coinsToDeposit).then((signature) => {
console.log(signature);
if (signature) {
socketInstance?.send(
JSON.stringify({
action: "deposit",
token: user?.token,
data: {
user: {
address: `${publicKey?.toBase58()}`,
chainid: 0,
},
amount: coinsToDeposit * 1000000,
signature,
},
})
);
}
});
} else {
console.log("socketInstance", socketInstance);
}
};
It doesn't send message and If I try to know the readyState it is 3, but initially when the useEffect on top of it ran it was 1 after reconnection it became 3, How I know that a reconnect happened, I know from network tab, and console which I added in connect.ts hook.
I tried binding socketInstance?.readyState to a useEffect as well, But the useEffect doesn't run when it get's reconnect even tho the readyState was changed.
Can you guys please help me why I'm getting 3 and handleDepost is failing because of that, It should be 1 because I already handled reconnection, and if socketInstance is changing why the useEffect is not running based on that, I added it in dependency array as well. Below is the code for that.
useEffect(() => {
console.log("socketInstance", socketInstance);
}, [socketInstance]);
FYI guys last 3 code blocks are from same separate component. Thanks
It was resolved by shifting up my whole refs to states and using it as a wrapper around my app with useContext. It makes sure to update my app whenever there is a message or update from the socket instance (in the global state).