spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1'use client';2/** Browser-side fetches: same origin `/api/v1/*` (Next rewrite → FastAPI). Never import server `api.ts` in client components. */3import type { Envelope, LivePosition, Paginated, PositionsSnapshot, SatelliteRow, SearchPayload, SearchResult, Track } from './types';45async function get<T>(path: string, signal?: AbortSignal): Promise<T> {6 const res = await fetch(`/api/v1${path}`, { headers: { accept: 'application/json' }, signal });7 if (!res.ok) throw new Error(`API ${res.status} on ${path}`);8 return (await res.json()) as T;9}1011export const clientApi = {12 positions: (signal?: AbortSignal) => get<Envelope<PositionsSnapshot>>('/orbit/positions', signal),13 live: (ident: string, signal?: AbortSignal) => get<Envelope<LivePosition & { orbit_class: string | null }>>(`/satellites/${encodeURIComponent(ident)}/live`, signal),14 position: (ident: string, signal?: AbortSignal) => get<Envelope<LivePosition & { satellite: { slug: string; name: string; norad_id: number | null } }>>(`/satellites/${encodeURIComponent(ident)}/position`, signal),15 track: (ident: string, signal?: AbortSignal) => get<Envelope<Track>>(`/satellites/${encodeURIComponent(ident)}/track`, signal),16 satellite: (ident: string, signal?: AbortSignal) => get<Envelope<SatelliteRow>>(`/satellites/${encodeURIComponent(ident)}`, signal),17 satellites: (qs: string, signal?: AbortSignal) => get<Paginated<SatelliteRow>>(`/satellites?${qs}`, signal),18 search: (q: string, limit = 12, signal?: AbortSignal) => get<Envelope<SearchPayload>>(`/search?q=${encodeURIComponent(q)}&limit=${limit}`, signal),19 suggest: (q: string, signal?: AbortSignal) => get<Envelope<SearchResult[]>>(`/search/suggest?q=${encodeURIComponent(q)}`, signal),20 view: (entity_type: string, entity_id: string) =>21 fetch('/api/v1/views', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ entity_type, entity_id }), keepalive: true }).catch(() => undefined),22};23