| 1 | import { useEffect, useMemo, useState } from "react"; |
| 2 | import { useList, type BaseServer } from "@/hooks/useList"; |
| 3 | import { apiClient } from "@/lib/apiClient"; |
| 4 | import { BROWSER_API_PATHS } from "@/lib/apiPaths"; |
| 5 | import { |
| 6 | parseLeaseMetadata, |
| 7 | resolveLeasePayment, |
| 8 | resolveLeaseThumbnail, |
| 9 | } from "@/lib/metadata"; |
| 10 | import type { Lease, PublicStateResponse } from "@/types/api"; |
| 11 | |
| 12 | type PublicState = { |
| 13 | leases: Lease[]; |
| 14 | landingPageEnabled: boolean; |
| 15 | }; |
| 16 | |
| 17 | function convertPublicLeasesToServers(leases: Lease[]): BaseServer[] { |
| 18 | return leases.map((row) => { |
| 19 | const metadata = parseLeaseMetadata(row.metadata); |
| 20 | const payment = resolveLeasePayment(metadata); |
| 21 | const hostname = row.hostname || ""; |
| 22 | const serviceName = row.name || ""; |
| 23 | |
| 24 | return { |
| 25 | id: hostname, |
| 26 | name: serviceName || hostname || "(unnamed)", |
| 27 | description: metadata.description || "", |
| 28 | tags: metadata.tags, |
| 29 | thumbnail: resolveLeaseThumbnail(metadata, hostname), |
| 30 | owner: metadata.owner || "", |
| 31 | online: (row.ready || 0) > 0, |
| 32 | dns: hostname, |
| 33 | link: hostname ? `https://${hostname}/` : "", |
| 34 | lastUpdated: row.last_seen_at || undefined, |
| 35 | firstSeen: row.first_seen_at || undefined, |
| 36 | paymentEnabled: payment.enabled, |
| 37 | paymentLabel: payment.label, |
| 38 | }; |
| 39 | }); |
| 40 | } |
| 41 | |
| 42 | export function useServerList() { |
| 43 | const [publicState, setPublicState] = useState<PublicState>({ |
| 44 | leases: [], |
| 45 | landingPageEnabled: false, |
| 46 | }); |
| 47 | |
| 48 | useEffect(() => { |
| 49 | let cancelled = false; |
| 50 | |
| 51 | void (async () => { |
| 52 | try { |
| 53 | const data = await apiClient.get<PublicStateResponse>( |
| 54 | BROWSER_API_PATHS.public.state |
| 55 | ); |
| 56 | if (cancelled) { |
| 57 | return; |
| 58 | } |
| 59 | setPublicState({ |
| 60 | leases: Array.isArray(data?.leases) ? data.leases : [], |
| 61 | landingPageEnabled: data?.landing_page_enabled ?? false, |
| 62 | }); |
| 63 | } catch (error) { |
| 64 | console.error("Failed to load public relay state", error); |
| 65 | if (!cancelled) { |
| 66 | setPublicState({ leases: [], landingPageEnabled: false }); |
| 67 | } |
| 68 | } |
| 69 | })(); |
| 70 | |
| 71 | return () => { |
| 72 | cancelled = true; |
| 73 | }; |
| 74 | }, []); |
| 75 | |
| 76 | const servers: BaseServer[] = useMemo( |
| 77 | () => convertPublicLeasesToServers(publicState.leases), |
| 78 | [publicState.leases] |
| 79 | ); |
| 80 | |
| 81 | const list = useList({ |
| 82 | servers, |
| 83 | storageKey: "serverFavorites", |
| 84 | }); |
| 85 | |
| 86 | return { |
| 87 | ...list, |
| 88 | landingPageEnabled: publicState.landingPageEnabled, |
| 89 | }; |
| 90 | } |