fix(room-ws): try reconnect with existing token before requesting new

On auto-reconnect (scheduleReconnect), attempt connection with the stored
wsToken first. If the WS closes immediately (server rejected the token),
fall back to fetching a fresh token and retrying. Only requests a new
token when the existing one fails or when connect() is called manually
with forceNewToken=true.
This commit is contained in:
ZhenYi 2026-04-17 21:40:07 +08:00
parent 309bc50e86
commit b70d91866c

View File

@ -133,7 +133,7 @@ export class RoomWsClient {
return new Set(this.subscribedProjects); return new Set(this.subscribedProjects);
} }
async connect(): Promise<void> { async connect(forceNewToken = false): Promise<void> {
if (this.ws && this.status === 'open') { if (this.ws && this.status === 'open') {
return; return;
} }
@ -141,28 +141,31 @@ export class RoomWsClient {
this.shouldReconnect = true; this.shouldReconnect = true;
this.setStatus('connecting'); this.setStatus('connecting');
// Fetch a fresh token for each connection attempt (backend consumes token on use) // Fetch a fresh token unless we have a valid existing one and not forcing.
try { // When forceNewToken=false (reconnect path), try existing token first.
const tokenResp = await fetch(`${this.baseUrl}/api/ws/token`, { if (forceNewToken || !this.wsToken) {
method: 'POST', try {
credentials: 'include', const tokenResp = await fetch(`${this.baseUrl}/api/ws/token`, {
}); method: 'POST',
if (!tokenResp.ok) { credentials: 'include',
const text = await tokenResp.text().catch(() => ''); });
console.error(`[RoomWs] Token fetch failed: ${tokenResp.status} ${tokenResp.statusText}${text}`); if (!tokenResp.ok) {
throw new Error(`Token fetch failed: ${tokenResp.status}`); const text = await tokenResp.text().catch(() => '');
console.error(`[RoomWs] Token fetch failed: ${tokenResp.status} ${tokenResp.statusText}${text}`);
throw new Error(`Token fetch failed: ${tokenResp.status}`);
}
const tokenData = await tokenResp.json();
this.wsToken = tokenData.data?.token || null;
if (!this.wsToken) {
console.error('[RoomWs] Token is empty — not logged in?');
throw new Error('No WS token received');
}
} catch (err) {
console.error('[RoomWs] Failed to fetch WS token:', err);
this.setStatus('error');
this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
throw err;
} }
const tokenData = await tokenResp.json();
this.wsToken = tokenData.data?.token || null;
if (!this.wsToken) {
console.error('[RoomWs] Token is empty — not logged in?');
throw new Error('No WS token received');
}
} catch (err) {
console.error('[RoomWs] Failed to fetch WS token:', err);
this.setStatus('error');
this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
throw err;
} }
const wsUrl = this.buildWsUrl(); const wsUrl = this.buildWsUrl();
@ -172,6 +175,13 @@ export class RoomWsClient {
// Guard: if ws is closed before handlers are set, skip // Guard: if ws is closed before handlers are set, skip
if (this.ws.readyState === WebSocket.CLOSED || this.ws.readyState === WebSocket.CLOSING) { if (this.ws.readyState === WebSocket.CLOSED || this.ws.readyState === WebSocket.CLOSING) {
console.warn('[RoomWs] WebSocket closed immediately'); console.warn('[RoomWs] WebSocket closed immediately');
// If we used an existing token and it was immediately rejected, retry with a new token
if (!forceNewToken && this.wsToken) {
console.debug('[RoomWs] Existing token rejected — fetching new token and retrying');
const savedToken = this.wsToken;
this.wsToken = null;
return this.connect(true);
}
return; return;
} }