toes/src/cli/http.ts
2026-02-02 15:50:40 -08:00

142 lines
3.8 KiB
TypeScript

import type { Manifest } from '@types'
function getDefaultHost(): string {
if (process.env.NODE_ENV === 'production') {
return `http://toes.local:${process.env.PORT ?? 80}`
}
return `http://localhost:${process.env.PORT ?? 3000}`
}
const normalizeUrl = (url: string) =>
url.startsWith('http://') || url.startsWith('https://') ? url : `http://${url}`
function tryParseError(text: string): string | undefined {
try {
const json = JSON.parse(text)
return json.error
} catch {
return undefined
}
}
export const HOST = process.env.TOES_URL
? normalizeUrl(process.env.TOES_URL)
: getDefaultHost()
export function makeUrl(path: string): string {
return `${HOST}${path}`
}
export function makeAppUrl(port: number): string {
const url = new URL(HOST)
url.port = String(port)
return url.toString().replace(/\/$/, '')
}
export function handleError(error: unknown): void {
if (error instanceof Error && 'code' in error && error.code === 'ConnectionRefused') {
console.error(`🐾 Can't connect to toes server at ${HOST}`)
console.error(` Set TOES_URL to connect to a different host`)
return
}
if (error instanceof Error) {
console.error(`Error: ${error.message}`)
return
}
console.error(error)
}
export async function get<T>(url: string): Promise<T | undefined> {
try {
const res = await fetch(makeUrl(url))
if (!res.ok) {
const text = await res.text()
const msg = tryParseError(text) ?? `${res.status} ${res.statusText}`
throw new Error(msg)
}
return await res.json()
} catch (error) {
handleError(error)
}
}
export async function getManifest(appName: string): Promise<{ exists: boolean, manifest?: Manifest } | null> {
try {
const res = await fetch(makeUrl(`/api/sync/apps/${appName}/manifest`))
if (res.status === 404) return { exists: false }
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
return { exists: true, manifest: await res.json() }
} catch (error) {
handleError(error)
return null
}
}
export async function post<T, B = unknown>(url: string, body?: B): Promise<T | undefined> {
try {
const res = await fetch(makeUrl(url), {
method: 'POST',
headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined,
body: body !== undefined ? JSON.stringify(body) : undefined,
})
if (!res.ok) {
const text = await res.text()
const msg = tryParseError(text) ?? `${res.status} ${res.statusText}`
throw new Error(msg)
}
return await res.json()
} catch (error) {
handleError(error)
}
}
export async function put(url: string, body: Buffer | Uint8Array): Promise<boolean> {
try {
const res = await fetch(makeUrl(url), {
method: 'PUT',
body: body as BodyInit,
})
if (!res.ok) {
const text = await res.text()
const msg = tryParseError(text) ?? `${res.status} ${res.statusText}`
throw new Error(msg)
}
return true
} catch (error) {
handleError(error)
return false
}
}
export async function download(url: string): Promise<Buffer | undefined> {
try {
const fullUrl = makeUrl(url)
const res = await fetch(fullUrl)
if (!res.ok) {
const text = await res.text()
const msg = tryParseError(text) ?? `${res.status} ${res.statusText}`
throw new Error(msg)
}
return Buffer.from(await res.arrayBuffer())
} catch (error) {
handleError(error)
}
}
export async function del(url: string): Promise<boolean> {
try {
const res = await fetch(makeUrl(url), {
method: 'DELETE',
})
if (!res.ok) {
const text = await res.text()
const msg = tryParseError(text) ?? `${res.status} ${res.statusText}`
throw new Error(msg)
}
return true
} catch (error) {
handleError(error)
return false
}
}