"use client"; import { useQueries, useQuery } from "@tanstack/react-query"; import type { SubsDetail } from "../lib/subs"; import type { ChannelSubsManifest, SubsManifest } from "../lib/manifest"; import { PromiseMap } from "../lib/archive/reader"; import { channelRef, readerFor } from "../lib/archive/readers"; import { makeId, splitId } from "./originId"; const resolved = new Map(); const inFlight = new Map>(); // The reader has a per-channel subs-manifest cache too, but its answer for an // unreachable manifest is `null` (the right answer for a scanner probing thirty // channels, the wrong one for a UI that wants react-query to retry). So the // memo with the THROWING policy stays here, over the reader's raw read. const channelManifests = new PromiseMap(); // A per-channel subs reference: channel slug + the origin it lives on // ("" = same-origin). Used by the multi-origin manifest hook. export type SubsManifestRef = { channelSlug: string; origin?: string }; // `id` is an OriginId (see originId.ts): bare slug same-origin, origin-prefixed // cross-origin. Maps are keyed by the full id so origins don't collide. export function fetchSubs(id: string): Promise { const hit = resolved.get(id); if (hit) return Promise.resolve(hit); const flying = inFlight.get(id); if (flying) return flying; const p = load(id).then((detail) => { resolved.set(id, detail); inFlight.delete(id); return detail; }); p.catch(() => inFlight.delete(id)); inFlight.set(id, p); return p; } function fetchChannelSubsManifest( channelSlug: string, origin = "", ): Promise { return channelManifests.take(makeId(origin, channelSlug), () => readerFor(origin).readChannelSubsManifest(channelSlug), ); } async function load(id: string): Promise { const { origin, slug } = splitId(id); const slashIdx = slug.indexOf("/"); if (slashIdx < 0) throw new Error(`Malformed subs slug: ${slug}`); const channelSlug = slug.slice(0, slashIdx); const videoId = slug.slice(slashIdx + 1); const manifest = await fetchChannelSubsManifest(channelSlug, origin); const pageIndex = manifest.slugToPage[videoId]; if (pageIndex === undefined) throw new Error(`Unknown subs slug: ${slug}`); const page = await readerFor(origin).subsPage( channelRef(channelSlug, origin), pageIndex, ); let found: SubsDetail | undefined; for (const entry of page) { const entryId = makeId(origin, entry.slug); if (entry.slug === slug) found = entry; resolved.set(entryId, entry); } if (!found) throw new Error(`Subs ${slug} missing from page ${pageIndex}`); return found; } export function useSubsManifest(origin = "") { return useQuery({ queryKey: ["subs-manifest", origin], queryFn: () => readerFor(origin).readSubsSiteManifest(), }); } // Loads each per-channel subs manifest so we can enumerate the full set of // sub-having slugs without fetching cue pages eagerly. Returns one entry per // ref. Accepts either bare channel slugs (same-origin) or {channelSlug, origin} // refs (federated). Errors are absorbed per channel so a missing manifest // doesn't break the whole UI. export function useChannelSubsManifests(refs: Array) { return useQueries({ queries: refs.map((ref) => { const { channelSlug, origin = "" } = typeof ref === "string" ? { channelSlug: ref, origin: "" } : ref; return { queryKey: ["channel-subs-manifest", origin, channelSlug], queryFn: () => fetchChannelSubsManifest(channelSlug, origin), }; }), }); }