uvlink
v0.2.0
Published
A browser bridge for AWS Lambda MicroVMs — reach a MicroVM endpoint from a browser without hand-rolling a proxy. Injects the X-aws-proxy-auth token for HTTP and the lambda-microvms subprotocols for WebSockets, and auto-refreshes the token. Personal hobby
Maintainers
Readme
uvlink
Reach an AWS Lambda MicroVM from a browser — without hand-rolling a proxy.
A MicroVM endpoint requires a credential a browser can't attach: HTTP needs the
X-aws-proxy-auth header, and WebSockets carry the token in a custom
lambda-microvms.* subprotocol. So every interactive app (notebooks, web IDEs,
dashboards, terminals) ends up writing the same little auth-injecting proxy.
uvlink is that proxy, done once, properly:
- ✅ Injects the auth token for HTTP (
X-aws-proxy-auth+X-aws-proxy-port) - ✅ Injects the WebSocket subprotocols, preserving your app's own subprotocol
- ✅ Auto-refreshes the token before its 60-minute expiry (long sessions just work)
- ✅ Zero runtime dependencies — Node built-ins only
- ✅ Use it as a CLI (
npx) or a library
⚠️ Disclaimer
This is a personal hobby project. It is NOT an official product of, affiliated
with, supported by, or endorsed by AWS, AWS Lambda, or Amazon in any way. It is
maintained on a best-effort, as-is basis by an individual in their personal
capacity. The names "AWS", "Lambda", and "MicroVMs" are used only descriptively to
indicate what the tool interoperates with; all trademarks belong to their
respective owners. Provided under the MIT license with no warranty (see
LICENSE). Use at your own risk.
Install
npm install uvlink # as a library
# or just run it, no install:
npx uvlink --helpQuick start (CLI)
Point it at a running MicroVM; it looks up the endpoint, mints + refreshes the token, and serves a browser-friendly local URL:
npx uvlink --microvm-id microvm-0123… --region us-east-1 --port 3000
# ✓ open http://localhost:3000 in your browserOr supply the endpoint and token yourself (no AWS calls made by the tool):
npx uvlink --endpoint xxxx.lambda-microvm.us-east-1.on.aws --token <jwe>Quick start (library)
import { createProxy } from 'uvlink';
const proxy = createProxy({
endpoint: 'xxxx.lambda-microvm.us-east-1.on.aws',
microvmId: 'microvm-0123…', // self-mints + refreshes the token via the aws CLI
region: 'us-east-1',
port: 3000,
});
const { url } = await proxy.listen();
console.log(`open ${url}`); // browser -> http://localhost:3000
// ... later
await proxy.close();Supplying tokens — three ways
Pick whichever fits; the first two keep the library pure transport (no AWS dependency):
// 1. static token (you manage refresh)
createProxy({ endpoint, token: '<jwe>' });
// 2. bring your own minter (SDK, your control plane, anything)
createProxy({ endpoint, getToken: async () => myMint() });
// 3. self-mint + auto-refresh via the aws CLI
createProxy({ endpoint, microvmId: 'microvm-…', region: 'us-east-1' });One MicroVM, many apps
A single MicroVM often runs several services on different in-VM ports (JupyterLab on 8888, TensorBoard on 6006, an API on 8080; or several agents). uvlink can front all of them from one process, two ways:
Mode A — listener-per-port (--map)
Each app gets its own local port. Nothing rewrites paths, redirects, or cookies, so even picky SPAs just work. The robust default.
npx uvlink --microvm-id microvm-… --region us-east-1 \
--map 3000:8888 \ # Jupyter -> http://localhost:3000
--map 3001:6006 \ # TensorBoard -> http://localhost:3001
--map 3002:8080 # API -> http://localhost:3002createProxy({
endpoint, microvmId: 'microvm-…', region: 'us-east-1',
routes: [
{ listen: 3000, backendPort: 8888 },
{ listen: 3001, backendPort: 6006 },
],
});Mode B — path-prefix on one origin (--route)
One local port; a path prefix selects the app. Great for fronting several
agents/services behind a single URL with no CORS between them (a control UI can
talk to every backend same-origin). The prefix is stripped before forwarding and
Location redirects are rewritten so apps mounted under a prefix still work.
npx uvlink --microvm-id microvm-… --region us-east-1 --port 3000 \
--route /agent-a=7001 \ # -> http://localhost:3000/agent-a
--route /agent-b=7002 \ # -> http://localhost:3000/agent-b
--route /console=7000 # -> http://localhost:3000/consolecreateProxy({
endpoint, microvmId: 'microvm-…', region: 'us-east-1', port: 3000,
routes: [
{ prefix: '/agent-a', backendPort: 7001, stripPrefix: true },
{ prefix: '/agent-b', backendPort: 7002, stripPrefix: true },
],
});Pass --no-strip-prefix (or stripPrefix: false) when the app is already mounted
under the prefix. Longest matching prefix wins; an unmatched path returns 404.
When self-minting, the token is automatically scoped to just the ports you
route to (least privilege) — pass --allowed-ports all (or allowedPorts: 'all')
to opt out, or a custom list to override. See examples/multi-app.mjs
(Mode A), examples/multi-agent.mjs (Mode B), and
docs/routing-spec.html for the design.
API
createProxy(options)
| option | type | notes |
|---|---|---|
| endpoint | string | required — MicroVM endpoint host (no scheme) |
| port | number | local port (default 3000) |
| host | string | bind address (default 127.0.0.1) |
| backendPort | string | in-VM port to route to (default 8080) |
| token | string | static token, or… |
| getToken | ()=>string\|Promise<string> | custom minter, or… |
| microvmId | string | self-mint via aws CLI (+ region, awsCli, ttlMinutes) |
| onLog | (level,msg)=>void | optional logger |
Returns { server, listen(), close() }.
createPool(options) — optional fan-out
Round-robin one browser-facing port across multiple MicroVM endpoints (each
with its own getToken). For real autoscaling/pooling/recycling, use a dedicated
load balancer; this is a convenience for simple fan-out.
import { createPool } from 'uvlink';
const pool = createPool({ targets: [
{ endpoint: 'a…on.aws', getToken: () => tokA },
{ endpoint: 'b…on.aws', getToken: () => tokB },
]});
await pool.listen(3000);How it works
browser ──HTTP/WS──▶ uvlink ──HTTPS──▶ MicroVM endpoint
· HTTP: add X-aws-proxy-auth + X-aws-proxy-port
· WS: prepend lambda-microvms.* subprotocols,
keep the app's own subprotocol
· token cached + refreshed before the 60-min TTLExamples
See examples/: JupyterLab, a static site, and a WebSocket app.
Caveats / honest scope
- This runs a local (or sidecar) proxy. It does not change the platform; it's the client-side bridge that works today. (A native browser-auth path would make it unnecessary — until then, this.)
- Self-minting shells out to the
awsCLI; for SDK-based or custom auth usegetToken. - For production multi-VM serving (pooling, scaling, health, recycling) you want a
real load balancer/autoscaler, not just
createPool.
Development
node test/run.mjs # runs against a local mock endpoint — no AWS neededLicense
MIT
