socket notification
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
import { getToken } from "@/config/func";
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import type { Socket } from "socket.io-client";
|
||||
import { io } from "socket.io-client";
|
||||
import type { NotificationPayload } from "@/types/notification.types";
|
||||
|
||||
export type { NotificationPayload };
|
||||
|
||||
export interface NotificationSocketOptions {
|
||||
serverUrl?: string;
|
||||
token: string;
|
||||
autoConnect?: boolean;
|
||||
onNotification?: (notification: NotificationPayload) => void;
|
||||
onJoined?: (data: { room: string; message: string }) => void;
|
||||
onError?: (error: { message: string }) => void;
|
||||
onConnect?: () => void;
|
||||
onDisconnect?: (reason: string) => void;
|
||||
}
|
||||
|
||||
export interface NotificationSocketState {
|
||||
isConnected: boolean;
|
||||
isConnecting: boolean;
|
||||
room: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export function useNotificationSocket(options: NotificationSocketOptions) {
|
||||
const {
|
||||
serverUrl = import.meta.env.VITE_SOCKET_URL,
|
||||
token = getToken(),
|
||||
autoConnect = true,
|
||||
onNotification,
|
||||
onJoined,
|
||||
onError,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
} = options;
|
||||
|
||||
const socketRef = useRef<Socket | null>(null);
|
||||
const [state, setState] = useState<NotificationSocketState>({
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
room: null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
// Store callbacks in refs to avoid recreating socket on callback changes
|
||||
const callbacksRef = useRef({
|
||||
onNotification,
|
||||
onJoined,
|
||||
onError,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
callbacksRef.current = {
|
||||
onNotification,
|
||||
onJoined,
|
||||
onError,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
};
|
||||
}, [onNotification, onJoined, onError, onConnect, onDisconnect]);
|
||||
|
||||
const connect = useCallback(() => {
|
||||
if (socketRef.current?.connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
error: "Token is required for connection",
|
||||
isConnecting: false,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
setState((prev) => ({ ...prev, isConnecting: true, error: null }));
|
||||
|
||||
// Clean token - remove "Bearer " prefix if present
|
||||
const cleanToken = token.startsWith("Bearer ") ? token.substring(7) : token;
|
||||
|
||||
const socket = io(`${serverUrl}/notifications`, {
|
||||
transports: ["websocket", "polling"],
|
||||
reconnection: true,
|
||||
reconnectionDelay: 1000,
|
||||
reconnectionAttempts: 5,
|
||||
auth: {
|
||||
token: cleanToken,
|
||||
},
|
||||
});
|
||||
|
||||
socket.on("connect", () => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
isConnected: true,
|
||||
isConnecting: false,
|
||||
error: null,
|
||||
}));
|
||||
callbacksRef.current.onConnect?.();
|
||||
});
|
||||
|
||||
socket.on("disconnect", (reason: string) => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
room: null,
|
||||
}));
|
||||
callbacksRef.current.onDisconnect?.(reason);
|
||||
});
|
||||
|
||||
socket.on("connect_error", (error: Error) => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
error: error.message || "Connection failed",
|
||||
}));
|
||||
callbacksRef.current.onError?.({
|
||||
message: error.message || "Connection failed",
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("joined", (data: { room: string; message: string }) => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
room: data.room,
|
||||
}));
|
||||
callbacksRef.current.onJoined?.(data);
|
||||
});
|
||||
|
||||
socket.on("notifications", (data: NotificationPayload) => {
|
||||
callbacksRef.current.onNotification?.(data);
|
||||
});
|
||||
|
||||
socket.on("error", (error: { message: string }) => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
error: error.message,
|
||||
}));
|
||||
callbacksRef.current.onError?.(error);
|
||||
});
|
||||
|
||||
socketRef.current = socket;
|
||||
}, [serverUrl, token]);
|
||||
|
||||
const disconnect = useCallback(() => {
|
||||
if (socketRef.current) {
|
||||
socketRef.current.disconnect();
|
||||
socketRef.current = null;
|
||||
setState({
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
room: null,
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoConnect && token) {
|
||||
connect();
|
||||
}
|
||||
|
||||
return () => {
|
||||
disconnect();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [autoConnect, token]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
connect,
|
||||
disconnect,
|
||||
socket: socketRef.current,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user