krz/moonrise

Night-sky companion for iPhone, Apple Watch and Vision Pro: what's up tonight, the Moon, planets, launches and missions, and a Library of the solar system. SwiftUI app with a Cloudflare Workers backend. astronomy cloudflare-workers ios night-sky space swiftui visionos watchos

server/src/ll2.ts

04b2b1d914788057bcd20945b876847386eb8f65
moonrise/server/src/ll2.ts history · blame · raw

74 lines · 2604 bytes

6 symbols in this file
 1// Launch Library 2 (The Space Devs). Anonymous limit: 15 requests per hour.
 2// Images are not used: their licences are often "Unknown".
 3
 4export const UPCOMING_URL = "https://ll.thespacedevs.com/2.3.0/launches/upcoming/?limit=20&mode=normal";
 5
 6export interface Launch {
 7  id: string;
 8  name: string;
 9  net: string;
10  windowStart: string | null;
11  windowEnd: string | null;
12  status: string;
13  statusName: string;
14  provider: string;
15  rocket: string;
16  mission: string | null;
17  missionType: string | null;
18  missionDescription: string | null;
19  orbit: string | null;
20  pad: string;
21  location: string;
22  padLatitude: number | null;
23  padLongitude: number | null;
24  lastUpdated: string;
25}
26
27interface Raw {
28  id: string;
29  name: string;
30  net: string;
31  window_start?: string | null;
32  window_end?: string | null;
33  last_updated: string;
34  status: { abbrev: string; name: string };
35  launch_service_provider: { name: string };
36  rocket: { configuration: { full_name?: string; name: string } };
37  mission?: { name?: string; type?: string; description?: string; orbit?: { abbrev?: string } | null } | null;
38  pad: { name: string; latitude?: string | number | null; longitude?: string | number | null; location: { name: string } };
39}
40
41const num = (v: string | number | null | undefined) => (v == null || v === "" ? null : Number(v));
42
43export function normalize(r: Raw): Launch {
44  return {
45    id: r.id,
46    name: r.name,
47    net: r.net,
48    windowStart: r.window_start ?? null,
49    windowEnd: r.window_end ?? null,
50    status: r.status.abbrev,
51    statusName: r.status.name,
52    provider: r.launch_service_provider.name,
53    rocket: r.rocket.configuration.full_name ?? r.rocket.configuration.name,
54    mission: r.mission?.name ?? null,
55    missionType: r.mission?.type ?? null,
56    missionDescription: r.mission?.description?.trim() ?? null,
57    orbit: r.mission?.orbit?.abbrev ?? null,
58    pad: r.pad.name,
59    location: r.pad.location.name,
60    padLatitude: num(r.pad.latitude),
61    padLongitude: num(r.pad.longitude),
62    lastUpdated: r.last_updated,
63  };
64}
65
66/** `apiKey` lifts the anonymous per-IP limit, which Cloudflare's shared egress IPs often exhaust (HTTP 429). */
67export async function fetchUpcoming(apiKey?: string): Promise<Launch[]> {
68  const headers: Record<string, string> = { "user-agent": "moonrise-server (sh.krz.moonrise)" };
69  if (apiKey) headers.authorization = `Token ${apiKey}`;
70  const res = await fetch(UPCOMING_URL, { headers });
71  if (!res.ok) throw new Error(`LL2 ${res.status}`);
72  const body = (await res.json()) as { results: Raw[] };
73  return body.results.map(normalize);
74}