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:
parent
c8eba28e7a
commit
a26551343c
@ -171,7 +171,7 @@ export const DiscordMemberList = memo(function DiscordMemberList({
|
|||||||
icon={<Bot className="h-3 w-3" />}
|
icon={<Bot className="h-3 w-3" />}
|
||||||
>
|
>
|
||||||
{aiConfigs.map((ai) => {
|
{aiConfigs.map((ai) => {
|
||||||
const label = ai.modelName ?? ai.model;
|
const label = ai.modelName || 'Unknown AI';
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={ai.model}
|
key={ai.model}
|
||||||
|
|||||||
@ -195,6 +195,8 @@ export function RoomProvider({
|
|||||||
const [wsClient, setWsClient] = useState<RoomWsClient | null>(null);
|
const [wsClient, setWsClient] = useState<RoomWsClient | null>(null);
|
||||||
const wsClientRef = useRef<RoomWsClient | null>(null);
|
const wsClientRef = useRef<RoomWsClient | null>(null);
|
||||||
const activeRoomIdRef = useRef<string | null>(activeRoomId);
|
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 [wsStatus, setWsStatus] = useState<RoomWsStatus>('idle');
|
||||||
const [wsError, setWsError] = useState<string | null>(null);
|
const [wsError, setWsError] = useState<string | null>(null);
|
||||||
const [wsToken, setWsToken] = 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;
|
if (payload.room_id !== activeRoomIdRef.current) return;
|
||||||
setPins((prev) => prev.filter((p) => p.message !== payload.message_id));
|
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) => {
|
onUserPresence: (payload) => {
|
||||||
if (payload.room_id !== activeRoomIdRef.current) return;
|
if (payload.room_id !== activeRoomIdRef.current) return;
|
||||||
setPresence((prev) => ({ ...prev, [payload.user_id]: payload.status }));
|
setPresence((prev) => ({ ...prev, [payload.user_id]: payload.status }));
|
||||||
@ -1303,6 +1313,9 @@ export function RoomProvider({
|
|||||||
}
|
}
|
||||||
}, [activeRoomId]);
|
}, [activeRoomId]);
|
||||||
|
|
||||||
|
useEffect(() => { fetchRoomAiConfigsRef.current = fetchRoomAiConfigs; }, [fetchRoomAiConfigs]);
|
||||||
|
useEffect(() => { fetchProjectReposRef.current = fetchProjectRepos; }, [fetchProjectRepos]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchProjectRepos();
|
fetchProjectRepos();
|
||||||
}, [fetchProjectRepos]);
|
}, [fetchProjectRepos]);
|
||||||
|
|||||||
@ -81,6 +81,8 @@ export interface RoomWsCallbacks {
|
|||||||
onUserPresence?: (payload: UserPresencePayload) => void;
|
onUserPresence?: (payload: UserPresencePayload) => void;
|
||||||
onTypingStart?: (payload: import('./ws-protocol').TypingStartPayload) => void;
|
onTypingStart?: (payload: import('./ws-protocol').TypingStartPayload) => void;
|
||||||
onTypingStop?: (payload: import('./ws-protocol').TypingStopPayload) => void;
|
onTypingStop?: (payload: import('./ws-protocol').TypingStopPayload) => void;
|
||||||
|
onRoomAiUpdated?: (payload: ProjectEventPayload) => void;
|
||||||
|
onRepoChanged?: (payload: ProjectEventPayload) => void;
|
||||||
onStatusChange?: (status: RoomWsStatus) => void;
|
onStatusChange?: (status: RoomWsStatus) => void;
|
||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
/** Called each time the client sends a heartbeat ping */
|
/** 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
|
// Safety timeout: if not open within 10s, give up
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
if (this.status === 'connecting') {
|
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.ws?.close();
|
||||||
this.setStatus('error');
|
this.setStatus('error');
|
||||||
reject(new Error('Connection timeout'));
|
reject(new Error('Connection timeout'));
|
||||||
@ -246,6 +249,10 @@ export class RoomWsClient {
|
|||||||
req.reject(new Error(`WebSocket closed: ${ev.reason || 'unknown'}`));
|
req.reject(new Error(`WebSocket closed: ${ev.reason || 'unknown'}`));
|
||||||
}
|
}
|
||||||
this.pendingRequests.clear();
|
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) {
|
if (this.shouldReconnect) {
|
||||||
this.scheduleReconnect();
|
this.scheduleReconnect();
|
||||||
}
|
}
|
||||||
@ -1130,6 +1137,14 @@ export class RoomWsClient {
|
|||||||
room_id: event.room_id ?? '',
|
room_id: event.room_id ?? '',
|
||||||
} as import('./ws-protocol').MessageUnpinnedPayload);
|
} as import('./ws-protocol').MessageUnpinnedPayload);
|
||||||
break;
|
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:
|
default:
|
||||||
// Other project events (member_joined, room_created, etc.)
|
// Other project events (member_joined, room_created, etc.)
|
||||||
this.callbacks.onProjectEvent?.(event);
|
this.callbacks.onProjectEvent?.(event);
|
||||||
@ -1178,8 +1193,15 @@ export class RoomWsClient {
|
|||||||
const delay = Math.floor(jitter);
|
const delay = Math.floor(jitter);
|
||||||
this.reconnectAttempt++;
|
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 = setTimeout(() => {
|
||||||
this.reconnectTimer = null;
|
this.reconnectTimer = null;
|
||||||
|
if (forceNew) {
|
||||||
|
this.wsToken = null;
|
||||||
|
console.debug('[RoomWs] Clearing token after 3 reconnect failures, will fetch fresh');
|
||||||
|
}
|
||||||
this.connect().catch(() => {});
|
this.connect().catch(() => {});
|
||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user