Files
kandev/apps/web/hooks/use-task-sessions.ts
T
2026-08-01 09:45:06 +01:00

141 lines
4.9 KiB
TypeScript

import { useCallback, useEffect, useRef } from "react";
import { useAppStore, useAppStoreApi } from "@/components/state-provider";
import { listTaskSessions } from "@/lib/api";
import type { AppState } from "@/lib/state/store";
import type { TaskSession } from "@/lib/types/http";
import { useForegroundRefresh } from "@/hooks/use-foreground-refresh";
const EMPTY_SESSIONS: TaskSession[] = [];
function storedTaskSessions(getStoreState: () => AppState, taskId: string) {
return getStoreState().taskSessionsByTask.itemsByTaskId[taskId] ?? EMPTY_SESSIONS;
}
async function hydrateTaskSessions({
taskId,
force,
getStoreState,
setTaskSessionsForTask,
}: {
taskId: string;
force: boolean;
getStoreState: () => AppState;
setTaskSessionsForTask: AppState["setTaskSessionsForTask"];
}): Promise<boolean> {
const sessionIdsAtRequestStart = new Set(
storedTaskSessions(getStoreState, taskId).map((session) => session.id),
);
try {
const response = await listTaskSessions(taskId, { cache: "no-store" });
const fetchedSessions = response.sessions ?? [];
const fetchedSessionIds = new Set(fetchedSessions.map((session) => session.id));
const sessionsAddedDuringLoad = storedTaskSessions(getStoreState, taskId).filter(
(session) => !sessionIdsAtRequestStart.has(session.id) && !fetchedSessionIds.has(session.id),
);
setTaskSessionsForTask(taskId, [...fetchedSessions, ...sessionsAddedDuringLoad]);
return sessionsAddedDuringLoad.length > 0;
} catch (error) {
console.error("Failed to load task sessions:", error);
if (!force) setTaskSessionsForTask(taskId, storedTaskSessions(getStoreState, taskId));
return false;
}
}
export function useTaskSessions(taskId: string | null) {
const getStoreState = useAppStoreApi().getState;
const sessions = useAppStore((state) =>
taskId ? (state.taskSessionsByTask.itemsByTaskId[taskId] ?? EMPTY_SESSIONS) : EMPTY_SESSIONS,
);
const isLoading = useAppStore((state) =>
taskId ? (state.taskSessionsByTask.loadingByTaskId[taskId] ?? false) : false,
);
const isLoaded = useAppStore((state) =>
taskId ? (state.taskSessionsByTask.loadedByTaskId[taskId] ?? false) : false,
);
const setTaskSessionsForTask = useAppStore((state) => state.setTaskSessionsForTask);
const setTaskSessionsLoading = useAppStore((state) => state.setTaskSessionsLoading);
const connectionStatus = useAppStore((state) => state.connection.status);
const pendingForcedReloadRef = useRef(false);
const pendingForcedReloadWaitersRef = useRef<Array<() => void>>([]);
const loadSessions = useCallback(
async (force = false) => {
if (!taskId) return;
if (isLoading) {
if (force) {
pendingForcedReloadRef.current = true;
return new Promise<void>((resolve) => {
pendingForcedReloadWaitersRef.current.push(resolve);
});
}
return;
}
if (!force && isLoaded) return;
setTaskSessionsLoading(taskId, true);
try {
const needsFollowUp = await hydrateTaskSessions({
taskId,
force,
getStoreState,
setTaskSessionsForTask,
});
if (needsFollowUp) pendingForcedReloadRef.current = true;
} finally {
setTaskSessionsLoading(taskId, false);
if (force && !pendingForcedReloadRef.current) {
const waiters = pendingForcedReloadWaitersRef.current.splice(0);
waiters.forEach((resolve) => resolve());
}
}
},
[getStoreState, isLoaded, isLoading, setTaskSessionsForTask, setTaskSessionsLoading, taskId],
);
useEffect(() => {
if (!taskId) return;
if (isLoaded || isLoading) return;
loadSessions();
}, [isLoaded, isLoading, loadSessions, taskId]);
useEffect(() => {
pendingForcedReloadRef.current = false;
const waiters = pendingForcedReloadWaitersRef.current.splice(0);
waiters.forEach((resolve) => resolve());
}, [taskId]);
useEffect(() => {
if (!taskId || isLoading) return;
if (!pendingForcedReloadRef.current) return;
pendingForcedReloadRef.current = false;
void loadSessions(true);
}, [isLoading, loadSessions, taskId]);
const previousConnectionStatusRef = useRef(connectionStatus);
useEffect(() => {
const previous = previousConnectionStatusRef.current;
previousConnectionStatusRef.current = connectionStatus;
if (!taskId) return;
if (connectionStatus !== "connected" || previous === "connected") return;
if (!isLoaded) {
if (isLoading) void loadSessions(true);
return;
}
void loadSessions(true);
}, [connectionStatus, isLoaded, isLoading, loadSessions, taskId]);
useForegroundRefresh(
() => {
if (!taskId) return;
if (!isLoaded) {
if (isLoading) void loadSessions(true);
return;
}
void loadSessions(true);
},
Boolean(taskId),
taskId,
);
return { sessions, isLoading, isLoaded, loadSessions };
}