import type { Manifest } from '@types' const DEFAULT_HOST = process.env.DEV ? 'http://localhost:3000' : 'http://toes.local' 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) : DEFAULT_HOST 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(url: string): Promise { 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(url: string, body?: B): Promise { 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 { 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 { 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 { 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 } }