import { ReactNode, useCallback, useState } from 'react'; import { MatrixClient, Room } from 'matrix-js-sdk'; import { useQuery } from '@tanstack/react-query'; import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces'; import { useMatrixClient } from '../hooks/useMatrixClient'; import { LocalRoomSummary, useLocalRoomSummary } from '../hooks/useLocalRoomSummary'; import { AsyncState, AsyncStatus } from '../hooks/useAsyncCallback'; // MSC3266 returns canonical_alias at runtime, but the SDK's RoomSummary type // Omit<>s it — add it back so callers can use it. export type IRoomSummary = Awaited> & { canonical_alias?: string; }; export type RoomSummaryState = { loading: boolean; error: unknown }; type RoomSummaryLoaderProps = { roomIdOrAlias: string; /** * Resident servers to route the summary request through. REQUIRED for a room * the local homeserver isn't already in (the exact case a preview exists for) — * without it the summary comes back sparse or 404s. */ via?: string[]; children: (roomSummary: IRoomSummary | undefined, state: RoomSummaryState) => ReactNode; }; export function RoomSummaryLoader({ roomIdOrAlias, via, children }: RoomSummaryLoaderProps) { const mx = useMatrixClient(); const fetchSummary = useCallback( () => mx.getRoomSummary(roomIdOrAlias, via), [mx, roomIdOrAlias, via], ); const { data, isLoading, error } = useQuery({ // `via` is part of the key so a no-via failure isn't reused once we have servers. queryKey: [roomIdOrAlias, 'summary', via ?? []], queryFn: fetchSummary, retry: 1, staleTime: 5 * 60 * 1000, }); return children(data, { loading: isLoading, error }); } export function LocalRoomSummaryLoader({ room, children, }: { room: Room; children: (roomSummary: LocalRoomSummary) => ReactNode; }) { const summary = useLocalRoomSummary(room); return children(summary); } export function HierarchyRoomSummaryLoader({ roomId, children, }: { roomId: string; children: (state: AsyncState) => ReactNode; }) { const mx = useMatrixClient(); const fetchSummary = useCallback(() => mx.getRoomHierarchy(roomId, 1, 1), [mx, roomId]); const [errorMemo, setError] = useState(); const { data, error } = useQuery({ queryKey: [roomId, `hierarchy`], queryFn: fetchSummary, retryOnMount: false, refetchOnWindowFocus: false, retry: (failureCount, err) => { setError(err); if (failureCount > 3) return false; return true; }, }); let state: AsyncState = { status: AsyncStatus.Loading, }; if (error) { state = { status: AsyncStatus.Error, error, }; } if (errorMemo) { state = { status: AsyncStatus.Error, error: errorMemo, }; } const summary = data?.rooms[0] ?? undefined; if (summary) { state = { status: AsyncStatus.Success, data: summary, }; } return children(state); }