feat: Enhance LoginComponent and SocketManager for improved session management and reauthentication

This commit is contained in:
sebseb7
2026-03-19 05:44:03 +01:00
parent f0e4a94dfc
commit a93aa22a76
2 changed files with 129 additions and 18 deletions

View File

@@ -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,

View File

@@ -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);