Files
session-viewer/src/client/hooks/useSession.ts
teernisse a51c134da7 Harden API layer: encode session IDs and validate export payload
Session fetch (useSession.ts):
- Wrap the session ID in encodeURIComponent before interpolating
  into the fetch URL. Session IDs can contain characters like '+'
  or '/' that would corrupt the path without encoding.

Export route (export.ts):
- Add validation that redactedMessageUuids, when present, is an
  array. Previously only visibleMessageUuids was checked, so a
  malformed redactedMessageUuids value (e.g. a string or object)
  would silently pass validation and potentially cause downstream
  type errors in the HTML exporter.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 09:34:29 -05:00

73 lines
2.1 KiB
TypeScript

import { useState, useEffect, useCallback } from "react";
import type { SessionEntry, SessionDetailResponse } from "../lib/types";
interface SessionState {
sessions: SessionEntry[];
sessionsLoading: boolean;
sessionsError: string | null;
currentSession: SessionDetailResponse | null;
sessionLoading: boolean;
sessionError: string | null;
loadSessions: () => Promise<void>;
loadSession: (id: string) => Promise<void>;
}
export function useSession(): SessionState {
const [sessions, setSessions] = useState<SessionEntry[]>([]);
const [sessionsLoading, setSessionsLoading] = useState(true);
const [sessionsError, setSessionsError] = useState<string | null>(null);
const [currentSession, setCurrentSession] =
useState<SessionDetailResponse | null>(null);
const [sessionLoading, setSessionLoading] = useState(false);
const [sessionError, setSessionError] = useState<string | null>(null);
const loadSessions = useCallback(async () => {
setSessionsLoading(true);
setSessionsError(null);
try {
const res = await fetch("/api/sessions");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
setSessions(data.sessions);
} catch (err) {
setSessionsError(
err instanceof Error ? err.message : "Failed to load sessions"
);
} finally {
setSessionsLoading(false);
}
}, []);
const loadSession = useCallback(async (id: string) => {
setSessionLoading(true);
setSessionError(null);
try {
const res = await fetch(`/api/sessions/${encodeURIComponent(id)}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
setCurrentSession(data);
} catch (err) {
setSessionError(
err instanceof Error ? err.message : "Failed to load session"
);
} finally {
setSessionLoading(false);
}
}, []);
useEffect(() => {
loadSessions();
}, [loadSessions]);
return {
sessions,
sessionsLoading,
sessionsError,
currentSession,
sessionLoading,
sessionError,
loadSessions,
loadSession,
};
}