From 6f03954850a2cc187ac3c4ee20edb2bb60ccba45 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath <2+defunkt@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:50:40 -0800 Subject: [PATCH] don't hardcode `localhost` --- README.md | 7 ++-- src/cli/commands/manage.ts | 20 +++------- src/cli/http.ts | 58 ++++++++++++++++++++++++----- src/client/components/AppDetail.tsx | 4 +- src/client/tool-iframes.ts | 16 +++++--- src/server/index.tsx | 6 ++- 6 files changed, 74 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index eff1f27..78877ea 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,9 @@ Plug it in, turn it on, and forget about the cloud. by default, the CLI connects to `localhost:3000` in dev and `toes.local:80` in production. ```bash -toes config # show current host -TOES_URL=http://192.168.1.50:3000 toes list # full URL -TOES_HOST=mypi.local toes list # hostname (port 80) -TOES_HOST=mypi.local PORT=3000 toes list # hostname + port +toes config # show current host +TOES_URL=http://192.168.1.50:3000 toes list # connect to IP +TOES_URL=http://mypi.local toes list # connect to hostname ``` set `NODE_ENV=production` to default to `toes.local:80`. diff --git a/src/cli/commands/manage.ts b/src/cli/commands/manage.ts index d2fab62..ff41fb6 100644 --- a/src/cli/commands/manage.ts +++ b/src/cli/commands/manage.ts @@ -3,7 +3,7 @@ import { generateTemplates, type TemplateType } from '%templates' import color from 'kleur' import { existsSync, mkdirSync, writeFileSync } from 'fs' import { basename, join } from 'path' -import { del, get, getManifest, HOST, post } from '../http' +import { del, get, getManifest, HOST, makeAppUrl, post } from '../http' import { confirm, prompt } from '../prompts' import { resolveAppName } from '../name' import { pushApp } from './sync' @@ -20,23 +20,15 @@ export async function configShow() { const source = process.env.TOES_URL ? 'TOES_URL' - : process.env.TOES_HOST - ? 'TOES_HOST' + (process.env.PORT ? ' + PORT' : '') - : process.env.NODE_ENV === 'production' - ? 'default (production)' - : 'default (development)' + : process.env.NODE_ENV === 'production' + ? '(production)' + : '(development)' console.log(`Source: ${color.gray(source)}`) if (process.env.TOES_URL) { console.log(` TOES_URL=${process.env.TOES_URL}`) } - if (process.env.TOES_HOST) { - console.log(` TOES_HOST=${process.env.TOES_HOST}`) - } - if (process.env.PORT) { - console.log(` PORT=${process.env.PORT}`) - } if (process.env.NODE_ENV) { console.log(` NODE_ENV=${process.env.NODE_ENV}`) } @@ -57,7 +49,7 @@ export async function infoApp(arg?: string) { console.log(` State: ${app.state}`) if (app.port) { console.log(` Port: ${app.port}`) - console.log(` URL: http://localhost:${app.port}`) + console.log(` URL: ${makeAppUrl(app.port)}`) } if (app.started) { const uptime = Date.now() - app.started @@ -185,7 +177,7 @@ export async function openApp(arg?: string) { console.error(`App is not running: ${name}`) return } - const url = `http://localhost:${app.port}` + const url = makeAppUrl(app.port!) console.log(`Opening ${url}`) Bun.spawn(['open', url]) } diff --git a/src/cli/http.ts b/src/cli/http.ts index fd83fcc..c71f83a 100644 --- a/src/cli/http.ts +++ b/src/cli/http.ts @@ -7,20 +7,40 @@ function getDefaultHost(): string { return `http://localhost:${process.env.PORT ?? 3000}` } -const defaultPort = process.env.NODE_ENV === 'production' ? 80 : 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 - ?? (process.env.TOES_HOST ? `http://${process.env.TOES_HOST}:${process.env.PORT ?? defaultPort}` : undefined) - ?? getDefaultHost() + ? 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 or TOES_HOST to connect to a different 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) @@ -29,7 +49,11 @@ export function handleError(error: unknown): void { export async function get(url: string): Promise { try { const res = await fetch(makeUrl(url)) - if (!res.ok) throw new Error(`${res.status} ${res.statusText}`) + 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) @@ -55,7 +79,11 @@ export async function post(url: string, body?: B): Promise { try { const fullUrl = makeUrl(url) const res = await fetch(fullUrl) - if (!res.ok) throw new Error(`${res.status} ${res.statusText}`) + 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) @@ -92,7 +128,11 @@ export async function del(url: string): Promise { const res = await fetch(makeUrl(url), { method: 'DELETE', }) - if (!res.ok) throw new Error(`${res.status} ${res.statusText}`) + 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) diff --git a/src/client/components/AppDetail.tsx b/src/client/components/AppDetail.tsx index 9017e02..d8d7a84 100644 --- a/src/client/components/AppDetail.tsx +++ b/src/client/components/AppDetail.tsx @@ -74,8 +74,8 @@ export function AppDetail({ app, render }: { app: App, render: () => void }) { URL - - http://localhost:{app.port} + + {location.protocol}//{location.hostname}:{app.port} diff --git a/src/client/tool-iframes.ts b/src/client/tool-iframes.ts index 38422e6..3b45684 100644 --- a/src/client/tool-iframes.ts +++ b/src/client/tool-iframes.ts @@ -47,15 +47,17 @@ export function initToolIframes() { if (iframes.size === 0) { const existingIframes = container.querySelectorAll('iframe') existingIframes.forEach(iframe => { - const match = iframe.src.match(/localhost:(\d+)/) - if (match && match[1]) { - const port = parseInt(match[1], 10) + try { + const url = new URL(iframe.src) + const port = parseInt(url.port, 10) const toolName = iframe.dataset.toolName const appName = iframe.dataset.appName - if (toolName) { + if (port && toolName) { const cacheKey = appName ? `${toolName}:${appName}` : toolName iframes.set(cacheKey, { iframe, port }) } + } catch { + // Invalid URL, skip } }) } @@ -68,11 +70,13 @@ export function initToolIframes() { ` } -// Build URL with params +// Build URL with params using TOES_URL base function buildToolUrl(port: number, params: Record): string { + const base = new URL(window.location.origin) + base.port = String(port) const searchParams = new URLSearchParams(params) const query = searchParams.toString() - return query ? `http://localhost:${port}?${query}` : `http://localhost:${port}` + return query ? `${base.origin}?${query}` : base.origin } // Build cache key from tool name and params diff --git a/src/server/index.tsx b/src/server/index.tsx index 0af94db..2bfde77 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -1,4 +1,4 @@ -import { allApps, initApps } from '$apps' +import { allApps, initApps, TOES_URL } from '$apps' import appsRouter from './api/apps' import syncRouter from './api/sync' import { Hype } from '@because/hype' @@ -16,7 +16,9 @@ app.get('/tool/:tool', c => { return c.text(`Tool "${toolName}" not found or not running`, 404) } const params = new URLSearchParams(c.req.query()).toString() - const url = params ? `http://localhost:${tool.port}?${params}` : `http://localhost:${tool.port}` + const base = new URL(TOES_URL) + base.port = String(tool.port) + const url = params ? `${base.origin}?${params}` : base.origin return c.redirect(url) })