From a93aa22a76459fd4100f09a776a0a446d6c8b6cf Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Thu, 19 Mar 2026 05:44:03 +0100 Subject: [PATCH] feat: Enhance LoginComponent and SocketManager for improved session management and reauthentication --- src/components/LoginComponent.js | 43 ++++++++----- src/services/SocketManager.js | 104 ++++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 18 deletions(-) diff --git a/src/components/LoginComponent.js b/src/components/LoginComponent.js index 7dff3d6..9a366cc 100644 --- a/src/components/LoginComponent.js +++ b/src/components/LoginComponent.js @@ -28,6 +28,27 @@ import GoogleIcon from '@mui/icons-material/Google'; // Lazy load GoogleAuthProvider const GoogleAuthProvider = lazy(() => import('../providers/GoogleAuthProvider.js')); +const getTokenFromAuthResponse = (response) => + response?.token || + response?.accessToken || + response?.jwt || + response?.user?.token || + response?.user?.accessToken || + null; + +const persistSessionAuth = (response) => { + if (response?.user) { + sessionStorage.setItem('user', JSON.stringify(response.user)); + } + + const token = getTokenFromAuthResponse(response); + if (token) { + sessionStorage.setItem('authToken', token); + } else { + sessionStorage.removeItem('authToken'); + } +}; + // Function to check if user is logged in export const isUserLoggedIn = () => { const storedUser = sessionStorage.getItem('user'); @@ -77,6 +98,7 @@ function cartsAreIdentical(cartA, cartB) { export class LoginComponent extends Component { constructor(props) { super(props); + const { isLoggedIn, user, isAdmin } = isUserLoggedIn(); this.state = { open: false, tabValue: 0, @@ -86,9 +108,9 @@ export class LoginComponent extends Component { error: '', loading: false, success: '', - isLoggedIn: false, - isAdmin: false, - user: null, + isLoggedIn, + isAdmin, + user, anchorEl: null, showGoogleAuth: false, cartSyncOpen: false, @@ -102,16 +124,6 @@ export class LoginComponent extends Component { componentDidMount() { // Make the open function available globally window.openLoginDrawer = this.handleOpen; - - // Check if user is logged in - const { isLoggedIn: userIsLoggedIn, user: storedUser } = isUserLoggedIn(); - if (userIsLoggedIn) { - this.setState({ - user: storedUser, - isAdmin: !!storedUser.admin, - isLoggedIn: true - }); - } if (this.props.open) { this.setState({ open: true }); @@ -190,7 +202,7 @@ export class LoginComponent extends Component { window.socketManager.emit('verifyUser', { email, password }, (response) => { console.log('LoginComponent: verifyUser', response); if (response.success) { - sessionStorage.setItem('user', JSON.stringify(response.user)); + persistSessionAuth(response); this.setState({ user: response.user, isLoggedIn: true, @@ -306,6 +318,7 @@ export class LoginComponent extends Component { window.socketManager.emit('logout', (response) => { if(response.success){ sessionStorage.removeItem('user'); + sessionStorage.removeItem('authToken'); window.dispatchEvent(new CustomEvent('userLoggedIn')); this.props.navigate('/'); this.setState({ @@ -362,7 +375,7 @@ export class LoginComponent extends Component { window.socketManager.emit('verifyGoogleUser', { credential: credentialResponse.credential }, (response) => { console.log('google respo',response); if (response.success) { - sessionStorage.setItem('user', JSON.stringify(response.user)); + persistSessionAuth(response); this.setState({ isLoggedIn: true, isAdmin: !!response.user.admin, diff --git a/src/services/SocketManager.js b/src/services/SocketManager.js index ef64373..05eb8d4 100644 --- a/src/services/SocketManager.js +++ b/src/services/SocketManager.js @@ -9,9 +9,82 @@ class SocketManager { this.on = this.on.bind(this); this.off = this.off.bind(this); this.connectPromise = null; + this.reauthPromise = null; this.pendingListeners = new Map(); } + _getStoredUser() { + const storedUser = sessionStorage.getItem('user'); + if (!storedUser) { + return null; + } + + try { + return JSON.parse(storedUser); + } catch (error) { + console.error('Failed to parse stored user for socket reauth:', error); + sessionStorage.removeItem('user'); + return null; + } + } + + _buildSocketAuth() { + const token = sessionStorage.getItem('authToken'); + const user = this._getStoredUser(); + if (!token && !user) { + return {}; + } + + // Provide both nested and flat shapes for backend compatibility. + return { + token, + user, + userId: user ? user.id : undefined, + email: user ? user.email : undefined + }; + } + + _reauthenticate() { + if (!this._socket || !this._socket.connected) { + return Promise.resolve(); + } + + const token = sessionStorage.getItem('authToken'); + if (!token) { + return Promise.resolve(); + } + + if (this.reauthPromise) { + return this.reauthPromise; + } + + this.reauthPromise = new Promise((resolve) => { + let settled = false; + const done = () => { + if (settled) return; + settled = true; + this.reauthPromise = null; + resolve(); + }; + + // Don't block emits indefinitely if backend ignores this event. + const timeoutId = setTimeout(done, 800); + + this._socket.emit('verifyToken', { token }, (response) => { + clearTimeout(timeoutId); + if (response && response.success && response.user) { + sessionStorage.setItem('user', JSON.stringify(response.user)); + } + if (response && response.success && response.token) { + sessionStorage.setItem('authToken', response.token); + } + done(); + }); + }); + + return this.reauthPromise; + } + // Lazily import socket.io-client and create the socket on first use. // Subsequent calls return the same promise. _ensureSocket() { @@ -22,6 +95,20 @@ class SocketManager { this._socket = io('', { transports: ['websocket', 'polling'], autoConnect: false, + withCredentials: true, + auth: this._buildSocketAuth() + }); + + // Always refresh auth data before reconnect attempts. + if (this._socket.io && this._socket.io.on) { + this._socket.io.on('reconnect_attempt', () => { + this._socket.auth = this._buildSocketAuth(); + }); + } + + // Re-authenticate every time a new socket connection is established. + this._socket.on('connect', () => { + this.reauthPromise = this._reauthenticate(); }); // Register any listeners that arrived before the socket was ready @@ -81,9 +168,12 @@ class SocketManager { this.connectPromise = this._ensureSocket().then( (socket) => new Promise((resolve, reject) => { + socket.auth = this._buildSocketAuth(); socket.connect(); socket.once('connect', () => { - resolve(); + this._reauthenticate() + .then(() => resolve()) + .catch(() => resolve()); }); socket.once('connect_error', (error) => { this.connectPromise = null; @@ -112,8 +202,16 @@ class SocketManager { reject(error); }); } else { - socket.emit(event, ...args); - resolve(); + const emitNow = () => { + socket.emit(event, ...args); + resolve(); + }; + + if (this.reauthPromise) { + this.reauthPromise.then(emitNow).catch(emitNow); + } else { + emitNow(); + } } }) .catch(reject);