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

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