socket notification
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
VITE_TOKEN_NAME = 'dmnu_a_t'
|
VITE_TOKEN_NAME = 'dmnu_a_t'
|
||||||
VITE_REFRESH_TOKEN_NAME = 'dmnu-a-rt'
|
VITE_REFRESH_TOKEN_NAME = 'dmnu-a-rt'
|
||||||
VITE_BASE_URL = 'https://dmenuplus-api.dev.danakcorp.com'
|
VITE_BASE_URL = 'http://192.168.99.242:4000'
|
||||||
# VITE_BASE_URL = 'http://192.168.1.112:4001'
|
# VITE_BASE_URL = 'http://192.168.1.112:4001'
|
||||||
# VITE_SOCKET_URL = 'https://dmenuplus-api.dev.danakcorp.com/pager'
|
# VITE_SOCKET_URL = 'https://dmenuplus-api.dev.danakcorp.com/pager'
|
||||||
VITE_SOCKET_URL = 'http://192.168.99.242:4000'
|
VITE_SOCKET_URL = 'http://192.168.99.242:4000'
|
||||||
@@ -1,19 +1,13 @@
|
|||||||
import { getToken } from '@/config/func'
|
import { getToken } from '@/config/func'
|
||||||
import { useSocket } from '@/pages/pager/hooks/useSocket'
|
import { useSocket } from '@/pages/pager/hooks/useSocket'
|
||||||
import { type FC, useEffect, useCallback } from 'react'
|
import { type FC, useEffect, useCallback } from 'react'
|
||||||
|
import type { NotificationPayload } from '@/types/notification.types'
|
||||||
|
|
||||||
const Notification: FC = () => {
|
const Notification: FC = () => {
|
||||||
|
|
||||||
// توکن بدون Bearer prefix (مثل کد HTML)
|
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
// اضافه کردن /notifications namespace به URL
|
|
||||||
const socketUrl = `${import.meta.env.VITE_SOCKET_URL}/notifications`
|
const socketUrl = `${import.meta.env.VITE_SOCKET_URL}/notifications`
|
||||||
const { connect, socket, isConnected, error } = useSocket(socketUrl, token!)
|
const { connect, socket, error } = useSocket(socketUrl, token!)
|
||||||
|
|
||||||
// لاگ کردن وضعیت اتصال
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('🔄 Notification: وضعیت اتصال:', isConnected ? 'متصل ✅' : 'قطع ❌')
|
|
||||||
}, [isConnected])
|
|
||||||
|
|
||||||
// لاگ کردن خطاها
|
// لاگ کردن خطاها
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -24,52 +18,31 @@ const Notification: FC = () => {
|
|||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('🔌 Notification: تلاش برای اتصال...', socketUrl)
|
|
||||||
connect()
|
connect()
|
||||||
}, [connect])
|
}, [connect])
|
||||||
|
|
||||||
const handleGetNotification = useCallback((data: unknown) => {
|
const handleGetNotification = useCallback((data: NotificationPayload) => {
|
||||||
|
|
||||||
console.log('✅ Notification: دریافت نوتیفیکیشنها:', data)
|
console.log('✅ Notification: دریافت نوتیفیکیشنها:', data)
|
||||||
// alert('دریافت نوتیفیکیشنها: ' + JSON.stringify(data))
|
// alert('دریافت نوتیفیکیشنها: ' + JSON.stringify(data))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isConnected) {
|
|
||||||
console.log('✅ Notification: متصل شد! ارسال درخواست...')
|
|
||||||
socket?.emit('get:notifications', { limit: 50 })
|
|
||||||
}
|
|
||||||
}, [isConnected, socket])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (socket) {
|
if (socket) {
|
||||||
console.log('📡 Notification: تنظیم listener برای notifications:list')
|
|
||||||
|
|
||||||
const errorHandler = (error: unknown) => {
|
socket.on('joined', () => null)
|
||||||
console.error('❌ Notification Error:', error)
|
socket.on('notifications', handleGetNotification)
|
||||||
}
|
|
||||||
|
|
||||||
const exceptionHandler = (error: unknown) => {
|
|
||||||
console.error('❌ Notification Exception:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on('notifications:list', handleGetNotification)
|
|
||||||
socket.on('error', errorHandler)
|
|
||||||
socket.on('exception', exceptionHandler)
|
|
||||||
|
|
||||||
// cleanup function
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.off('notifications:list', handleGetNotification)
|
socket.off('notifications', handleGetNotification)
|
||||||
socket.off('error', errorHandler)
|
|
||||||
socket.off('exception', exceptionHandler)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [socket, handleGetNotification])
|
}, [socket, handleGetNotification])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return null
|
||||||
<div></div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Notification
|
export default Notification
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface NotificationPayload {
|
||||||
|
subject: string;
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user