Add appUrl() helper and x-app-url header

This commit is contained in:
Chris Wanstrath 2026-04-04 14:40:07 -07:00
parent db46287695
commit eb2e5a4436
5 changed files with 26 additions and 3 deletions

View File

@ -681,11 +681,25 @@ toes metrics my-app # Single app
```bash ```bash
toes share my-app toes share my-app
# ↗ Sharing my-app... https://abc123.trycloudflare.com # ↗ Sharing my-app... https://myapp.toes.space
``` ```
**`toes unshare [name]`** — Stop sharing an app. **`toes unshare [name]`** — Stop sharing an app.
When an app is shared, every proxied request includes an `x-app-url` header set to the app's public tunnel URL (e.g., `https://myapp.toes.space`). When not shared, it's the local URL (e.g., `http://myapp.toes.local`).
Use `appUrl()` from `@because/toes` to read this header:
```tsx
import { appUrl } from '@because/toes'
app.get('/callback', c => {
const url = appUrl(c.req.raw)
// url = "https://myapp.toes.space" if shared, "http://myapp.toes.local" otherwise
return c.redirect(`${url}/done`)
})
```
--- ---
## Environment Variables ## Environment Variables
@ -697,8 +711,9 @@ Toes injects these variables into every app process automatically:
| `PORT` | Assigned port (3001-3100). Your app must listen on this port. | | `PORT` | Assigned port (3001-3100). Your app must listen on this port. |
| `APPS_DIR` | Path to the apps directory on the server. | | `APPS_DIR` | Path to the apps directory on the server. |
| `DATA_DIR` | Per-app data directory for persistent storage. | | `DATA_DIR` | Per-app data directory for persistent storage. |
| `TOES_URL` | Base URL of the Toes server (e.g., `http://toes.local:3000`). | | `TOES_URL` | Base URL of the Toes server (e.g., `http://toes.local`). |
| `TOES_DIR` | Path to the Toes config directory. | | `TOES_DIR` | Path to the Toes config directory. |
| `APP_URL` | The app's local URL (e.g., `http://myapp.toes.local`). For the public URL that accounts for sharing, use `appUrl(req)` from `@because/toes` (see [Sharing](#sharing)). |
You can set custom variables per-app or globally. Global variables are inherited by all apps. Per-app variables override globals. You can set custom variables per-app or globally. Global variables are inherited by all apps. Per-app variables override globals.

View File

@ -19,7 +19,9 @@ export const TOES_DIR = process.env.TOES_DIR ?? join(process.env.DATA_DIR ?? '.'
const dataRoot = process.env.DATA_DIR ?? '.' const dataRoot = process.env.DATA_DIR ?? '.'
const defaultHost = process.env.NODE_ENV === 'production' ? LOCAL_HOST : 'localhost' const defaultHost = process.env.NODE_ENV === 'production' ? LOCAL_HOST : 'localhost'
export const TOES_URL = process.env.TOES_URL ?? `http://${defaultHost}:${process.env.PORT || 3000}` export const TOES_URL = process.env.TOES_URL ?? (process.env.NODE_ENV === 'production'
? `http://${defaultHost}`
: `http://${defaultHost}:${process.env.PORT || 3000}`)
const HEALTH_CHECK_FAILURES_BEFORE_RESTART = 3 const HEALTH_CHECK_FAILURES_BEFORE_RESTART = 3
const HEALTH_CHECK_INTERVAL = 30000 const HEALTH_CHECK_INTERVAL = 30000

View File

@ -49,6 +49,9 @@ export async function proxySubdomain(subdomain: string, req: Request): Promise<R
const headers = new Headers(req.headers) const headers = new Headers(req.headers)
headers.set('host', `localhost:${app.port}`) headers.set('host', `localhost:${app.port}`)
if (!headers.has('x-app-url')) {
headers.set('x-app-url', app.tunnelUrl ?? `${url.protocol}//${subdomain}.${url.hostname}`)
}
headers.delete('connection') headers.delete('connection')
headers.delete('content-length') headers.delete('content-length')
headers.delete('keep-alive') headers.delete('keep-alive')

View File

@ -4,3 +4,4 @@ export { loadAppEnv } from './env'
export type { ToesEvent, ToesEventType } from './events' export type { ToesEvent, ToesEventType } from './events'
export { on } from './events' export { on } from './events'
export { baseStyles, ToolScript } from './scripts.tsx' export { baseStyles, ToolScript } from './scripts.tsx'
export { appUrl } from './url'

2
src/tools/url.ts Normal file
View File

@ -0,0 +1,2 @@
export const appUrl = (req: Request): string =>
req.headers.get('x-app-url') ?? process.env.APP_URL!