From 519b745ad160dc073ab60860caadba72c25adf8e Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Mon, 2 Mar 2026 21:25:14 -0800 Subject: [PATCH] Add README with usage docs --- README.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7181995..0cb4cc8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,73 @@ -# 👟 sneaker +# sneaker -don't ask +An HTTP tunnel server and client. Expose a local server to the internet through a WebSocket-based tunnel with auto-assigned subdomains. +## Install + +```sh +bun add @because/sneaker +``` + +## Server + +Run the tunnel server: + +```sh +bun run src/server.tsx +``` + +The server exposes: + +- `GET /health` — returns the current git SHA +- `GET /tunnels` — lists active tunnel connections +- `GET /tunnel/:app` — redirects to the app's subdomain +- `GET /tunnel?app=NAME` — WebSocket endpoint for clients to establish a tunnel (optional `subdomain` query param to request a specific subdomain) + +Incoming HTTP requests to `SUBDOMAIN.your-server.com` are forwarded through the WebSocket to the connected client and proxied to the local target. + +The server adds an `x-sneaker` header with the server's hostname to all proxied requests. + +## Client + +```ts +import { connect } from "@because/sneaker" + +const tunnel = connect({ + server: "ws://your-server.com", + app: "my-app", + target: "http://localhost:3000", + subdomain: "my-app", // optional: request a specific subdomain + reconnect: true, // optional: auto-reconnect (default: true) + onOpen(subdomain) { + console.log(`Tunnel open: ${subdomain}.your-server.com`) + }, + onClose() { + console.log("Tunnel closed") + }, + onRequest(req) { + console.log(`${req.method} ${req.path}`) + }, + onError(err) { + console.error(err) + }, +}) + +// Later: +tunnel.close() +``` + +### Client options + +| Option | Type | Description | +|---|---|---| +| `server` | `string` | WebSocket URL of the tunnel server | +| `app` | `string` | App name identifier | +| `target` | `string` | Local URL to proxy requests to | +| `subdomain` | `string?` | Request a specific subdomain | +| `reconnect` | `boolean?` | Auto-reconnect on disconnect (default: `true`) | +| `onOpen` | `(subdomain: string) => void` | Called when tunnel is established | +| `onClose` | `() => void` | Called when tunnel disconnects | +| `onRequest` | `(req) => void` | Called for each proxied request | +| `onError` | `(error: Error) => void` | Called on errors | + +Binary responses (images, fonts, etc.) are automatically base64-encoded over the tunnel and decoded by the server.