feat: Enhance LoginComponent and SocketManager for improved session management and reauthentication
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user