Files
roboco/panel/src/hooks/use-channels.ts
T

111 lines
3.9 KiB
TypeScript
Raw Normal View History

2026-04-20 15:10:54 +02:00
"use client";
import { useQuery } from "@tanstack/react-query";
2026-06-03 06:35:03 +02:00
import { isAxiosError } from "axios";
2026-04-20 15:10:54 +02:00
import { channelsApi, type ChannelFilters } from "@/lib/api/channels";
import { sessionsApi } from "@/lib/api/sessions";
import { messagesApi } from "@/lib/api/messages";
2026-06-03 06:35:03 +02:00
// A 404 on a session/message read is terminal: the session has closed and been
// reaped server-side, so it will never come back. Retrying (or continuing to
// poll) a 404'd session is exactly what produces the "404 storm" as dead
// session-ids accumulate. Treat 404 as final — fail fast, never retry.
function isNotFound(error: unknown): boolean {
return isAxiosError(error) && error.response?.status === 404;
}
function retryUnlessNotFound(failureCount: number, error: unknown): boolean {
if (isNotFound(error)) return false;
return failureCount < 1;
}
2026-04-20 15:10:54 +02:00
export const channelKeys = {
all: ["channels"] as const,
2026-06-29 05:38:21 +02:00
list: (filters?: ChannelFilters) =>
[...channelKeys.all, "list", filters] as const,
2026-04-20 15:10:54 +02:00
detail: (id: string) => [...channelKeys.all, "detail", id] as const,
2026-06-29 05:38:21 +02:00
groups: (channelId: string) =>
[...channelKeys.all, "groups", channelId] as const,
2026-04-20 15:10:54 +02:00
};
export const sessionKeys = {
all: ["sessions"] as const,
list: (groupId: string) => [...sessionKeys.all, "list", groupId] as const,
detail: (id: string) => [...sessionKeys.all, "detail", id] as const,
};
export const messageKeys = {
all: ["messages"] as const,
list: (sessionId: string) => [...messageKeys.all, "list", sessionId] as const,
};
// Fetch channel list once - manual refresh available
export function useChannels(filters?: ChannelFilters) {
return useQuery({
queryKey: channelKeys.list(filters),
queryFn: () => channelsApi.list(filters),
staleTime: Infinity,
});
}
export function useChannel(channelId: string | null) {
return useQuery({
queryKey: channelKeys.detail(channelId || ""),
queryFn: () => channelsApi.get(channelId!),
enabled: !!channelId,
staleTime: Infinity,
});
}
// Fetch groups for a channel
export function useChannelGroups(channelId: string | null) {
return useQuery({
queryKey: channelKeys.groups(channelId || ""),
queryFn: () => channelsApi.getGroups(channelId!),
enabled: !!channelId,
staleTime: Infinity,
});
}
// Fetch sessions for a group
export function useGroupSessions(groupId: string | null) {
return useQuery({
queryKey: sessionKeys.list(groupId || ""),
queryFn: () => sessionsApi.listByGroup(groupId!),
enabled: !!groupId,
staleTime: Infinity,
});
}
// Fetch a single session by ID. GET /sessions/{id} returns task_links (with
// titles) in one shot, so no separate per-link task fetch is needed.
2026-04-20 15:10:54 +02:00
export function useSession(sessionId: string | null) {
return useQuery({
queryKey: sessionKeys.detail(sessionId || ""),
queryFn: () => sessionsApi.get(sessionId!),
2026-04-20 15:10:54 +02:00
enabled: !!sessionId,
staleTime: 1000 * 60 * 5, // 5 minutes
2026-06-03 06:35:03 +02:00
// A reaped (404) session must not be retried — that is the storm source.
retry: retryUnlessNotFound,
2026-04-20 15:10:54 +02:00
});
}
2026-06-03 06:35:03 +02:00
// Fetch messages for a session - WebSocket handles new messages.
//
// The transcript is read once and held (staleTime Infinity, no focus/reconnect
// refetch in the global defaults), so an open session is fetched a single time
// and a closed session's immutable transcript stays loaded for review. A 404
// means the session was reaped server-side; that is terminal, so we never retry
// it — retrying reaped sessions is what produced the growing 404 storm as dead
// session-ids accumulated. When the consumer unmounts, React Query deactivates
// the query (no background refetch loop survives) and GCs it after gcTime.
2026-04-20 15:10:54 +02:00
export function useSessionMessages(sessionId: string | null) {
return useQuery({
queryKey: messageKeys.list(sessionId || ""),
queryFn: () => messagesApi.listBySession(sessionId!),
enabled: !!sessionId,
staleTime: Infinity,
2026-06-03 06:35:03 +02:00
retry: retryUnlessNotFound,
2026-04-20 15:10:54 +02:00
});
}