fix(frontend): refresh WS token after connection failures and handle AI/repo events

Clear wsToken on auth-related close codes (3000-4999), connection
timeout, and after 3 consecutive reconnect failures so the next connect
attempt fetches a fresh token. Add onRoomAiUpdated and onRepoChanged
callbacks that re-fetch AI configs and repo list when pushed via WS.
Fix AI member list to never display raw UUID.
This commit is contained in:
ZhenYi 2026-04-26 23:59:07 +08:00
parent c8eba28e7a
commit a26551343c
3 changed files with 37 additions and 2 deletions

View File

@ -171,7 +171,7 @@ export const DiscordMemberList = memo(function DiscordMemberList({
icon={<Bot className="h-3 w-3" />}
>
{aiConfigs.map((ai) => {
const label = ai.modelName ?? ai.model;
const label = ai.modelName || 'Unknown AI';
return (
<button
key={ai.model}

View File

@ -195,6 +195,8 @@ export function RoomProvider({
const [wsClient, setWsClient] = useState<RoomWsClient | null>(null);
const wsClientRef = useRef<RoomWsClient | null>(null);
const activeRoomIdRef = useRef<string | null>(activeRoomId);
const fetchRoomAiConfigsRef = useRef<() => Promise<void>>(async () => {});
const fetchProjectReposRef = useRef<() => Promise<void>>(async () => {});
const [wsStatus, setWsStatus] = useState<RoomWsStatus>('idle');
const [wsError, setWsError] = useState<string | null>(null);
const [wsToken, setWsToken] = useState<string | null>(null);
@ -723,6 +725,14 @@ export function RoomProvider({
if (payload.room_id !== activeRoomIdRef.current) return;
setPins((prev) => prev.filter((p) => p.message !== payload.message_id));
},
onRoomAiUpdated: (payload) => {
if (payload.room_id && payload.room_id === activeRoomIdRef.current) {
fetchRoomAiConfigsRef.current();
}
},
onRepoChanged: () => {
fetchProjectReposRef.current();
},
onUserPresence: (payload) => {
if (payload.room_id !== activeRoomIdRef.current) return;
setPresence((prev) => ({ ...prev, [payload.user_id]: payload.status }));
@ -1303,6 +1313,9 @@ export function RoomProvider({
}
}, [activeRoomId]);
useEffect(() => { fetchRoomAiConfigsRef.current = fetchRoomAiConfigs; }, [fetchRoomAiConfigs]);
useEffect(() => { fetchProjectReposRef.current = fetchProjectRepos; }, [fetchProjectRepos]);
useEffect(() => {
fetchProjectRepos();
}, [fetchProjectRepos]);

View File

@ -81,6 +81,8 @@ export interface RoomWsCallbacks {
onUserPresence?: (payload: UserPresencePayload) => void;
onTypingStart?: (payload: import('./ws-protocol').TypingStartPayload) => void;
onTypingStop?: (payload: import('./ws-protocol').TypingStopPayload) => void;
onRoomAiUpdated?: (payload: ProjectEventPayload) => void;
onRepoChanged?: (payload: ProjectEventPayload) => void;
onStatusChange?: (status: RoomWsStatus) => void;
onError?: (error: Error) => void;
/** Called each time the client sends a heartbeat ping */
@ -212,7 +214,8 @@ export class RoomWsClient {
// Safety timeout: if not open within 10s, give up
const timeoutId = setTimeout(() => {
if (this.status === 'connecting') {
console.error(`[RoomWs] Connection timeout after 10s — closing`);
console.error(`[RoomWs] Connection timeout after 10s — clearing token and closing`);
this.wsToken = null;
this.ws?.close();
this.setStatus('error');
reject(new Error('Connection timeout'));
@ -246,6 +249,10 @@ export class RoomWsClient {
req.reject(new Error(`WebSocket closed: ${ev.reason || 'unknown'}`));
}
this.pendingRequests.clear();
// Auth-related close codes (3000-4999) — clear token so reconnect fetches a fresh one
if (ev.code >= 3000 && ev.code < 5000) {
this.wsToken = null;
}
if (this.shouldReconnect) {
this.scheduleReconnect();
}
@ -1130,6 +1137,14 @@ export class RoomWsClient {
room_id: event.room_id ?? '',
} as import('./ws-protocol').MessageUnpinnedPayload);
break;
case 'room_ai_updated':
this.callbacks.onRoomAiUpdated?.(event);
break;
case 'repo_created':
case 'repo_updated':
case 'repo_deleted':
this.callbacks.onRepoChanged?.(event);
break;
default:
// Other project events (member_joined, room_created, etc.)
this.callbacks.onProjectEvent?.(event);
@ -1178,8 +1193,15 @@ export class RoomWsClient {
const delay = Math.floor(jitter);
this.reconnectAttempt++;
// After 3 consecutive failures, clear token so next connect fetches a fresh one
const forceNew = this.reconnectAttempt >= 3;
this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = null;
if (forceNew) {
this.wsToken = null;
console.debug('[RoomWs] Clearing token after 3 reconnect failures, will fetch fresh');
}
this.connect().catch(() => {});
}, delay);
}