This commit is contained in:
chamikaJ
2025-04-17 18:28:54 +05:30
parent f583291d8a
commit 8825b0410a
2837 changed files with 241385 additions and 127578 deletions

View File

@@ -0,0 +1,8 @@
export const SOCKET_CONFIG = {
url: import.meta.env.VITE_SOCKET_URL || 'ws://localhost:3000',
options: {
transports: ['websocket'],
path: '/socket',
upgrade: true,
},
};

View File

@@ -0,0 +1,149 @@
import React, { createContext, useContext, useEffect, useState, useRef } from 'react';
import io, { Socket } from 'socket.io-client';
import { useTranslation } from 'react-i18next';
import { SOCKET_CONFIG } from './config';
import logger from '@/utils/errorLogger';
import { Modal, message } from 'antd';
import { SocketEvents } from '@/shared/socket-events';
import { getUserSession } from '@/utils/session-helper';
interface SocketContextType {
socket: Socket | null;
connected: boolean;
modalContextHolder: React.ReactElement<any>;
}
const SocketContext = createContext<SocketContextType | null>(null);
export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { t } = useTranslation('common');
const socketRef = useRef<Socket | null>(null);
const [connected, setConnected] = useState(false);
const [modal, contextHolder] = Modal.useModal();
const profile = getUserSession(); // Adjust based on your Redux structure
const [messageApi, messageContextHolder] = message.useMessage(); // Add message API
const hasShownConnectedMessage = useRef(false); // Add ref to track if message was shown
// Initialize socket connection
useEffect(() => {
// Only create a new socket if one doesn't exist
if (!socketRef.current) {
socketRef.current = io(SOCKET_CONFIG.url, {
...SOCKET_CONFIG.options,
reconnection: true,
reconnectionAttempts: Infinity,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
timeout: 20000,
});
}
const socket = socketRef.current;
// Set up event listeners before connecting
socket.on('connect', () => {
logger.info('Socket connected');
setConnected(true);
// Only show connected message once
if (!hasShownConnectedMessage.current) {
messageApi.success(t('connection-restored'));
hasShownConnectedMessage.current = true;
}
});
// Emit login event
if (profile && profile.id) {
socket.emit(SocketEvents.LOGIN.toString(), profile.id);
socket.once(SocketEvents.LOGIN.toString(), () => {
logger.info('Socket login success');
});
}
socket.on('connect_error', error => {
logger.error('Connection error', { error });
setConnected(false);
messageApi.error(t('connection-lost'));
// Reset the connected message flag on error
hasShownConnectedMessage.current = false;
});
socket.on('disconnect', () => {
logger.info('Socket disconnected');
setConnected(false);
messageApi.loading(t('reconnecting'));
// Reset the connected message flag on disconnect
hasShownConnectedMessage.current = false;
// Emit logout event
if (profile && profile.id) {
socket.emit(SocketEvents.LOGOUT.toString(), profile.id);
}
});
// Add team-related socket events
socket.on(SocketEvents.INVITATIONS_UPDATE.toString(), (message: string) => {
logger.info(message);
});
socket.on(
SocketEvents.TEAM_MEMBER_REMOVED.toString(),
(data: { teamId: string; message: string }) => {
if (!data) return;
if (profile && profile.team_id === data.teamId) {
modal.confirm({
title: 'You no longer have permissions to stay on this team!',
content: data.message,
closable: false,
cancelButtonProps: { disabled: true },
onOk: () => window.location.reload(),
});
}
}
);
// Connect after setting up listeners
socket.connect();
// Cleanup function
return () => {
if (socket) {
// Remove all listeners first
socket.off('connect');
socket.off('connect_error');
socket.off('disconnect');
socket.off(SocketEvents.INVITATIONS_UPDATE.toString());
socket.off(SocketEvents.TEAM_MEMBER_REMOVED.toString());
socket.removeAllListeners();
// Then close the connection
socket.close();
socketRef.current = null;
hasShownConnectedMessage.current = false; // Reset on unmount
}
};
}, [messageApi, t]); // Add messageApi and t to dependencies
const value = {
socket: socketRef.current,
connected,
modalContextHolder: contextHolder,
};
return (
<SocketContext.Provider value={value}>
{messageContextHolder}
{children}
</SocketContext.Provider>
);
};
export const useSocket = () => {
const context = useContext(SocketContext);
if (!context) {
throw new Error('useSocket must be used within a SocketProvider');
}
return context;
};