Skip to content

fix: make LeLab usable over plain-HTTP LAN access - #153

Open
openpilotcc wants to merge 4 commits into
huggingface:mainfrom
openpilotcc:fix/plain-http-lan-access
Open

openpilotcc wants to merge 4 commits into
huggingface:mainfrom
openpilotcc:fix/plain-http-lan-access

Conversation

@openpilotcc

@openpilotcc openpilotcc commented Nov 5, 2026 •

Copy link
Copy Markdown

Summary

Accessing LeLab from another machine over plain HTTP on a LAN IP (e.g. http://<server-ip>:8000/) rendered a completely blank page: correct <title> and favicon, empty #root, no visible errors, while the backend log showed every request succeeding. Beyond the frontend, the launcher always bound to 127.0.0.1 on port 8000, so the server could not even be reached from other machines, or started at all if :8000 was taken. This PR fixes the frontend issues and adds --host / --port flags so the network-access story works end to end.

Root causes

  1. crypto.randomUUID is undefined in insecure contexts. SingleTabGuard calls it unconditionally in its mount effect, but the API is only exposed in secure contexts (HTTPS or localhost). On http://<lan-ip>:8000 the effect throws a TypeError, React unmounts the whole tree, and the page goes blank. localhost access works, which is why it went unnoticed.
  2. The fallback API base URL is hardcoded to http://localhost:8000. Even with (1) fixed, a browser on another machine resolved every API call against its own localhost, where nothing listens.
  3. The launcher binds to 127.0.0.1:8000 unconditionally. There was no way to expose the server to the network or move it off a taken port.

Changes

  • SingleTabGuard.tsx: feature-detect crypto.randomUUID and fall back to a crypto.getRandomValues-based v4 UUID. The id is only used for tab election, so it does not need crypto-grade randomness, and getRandomValues is available in insecure contexts.

  • ApiContext.tsx: default the base URL to window.location.origin in production, since lelab serves the UI and the API on the same origin (also covers access via hostname or other NICs). Two cases deliberately keep the localhost:8000 default:

    • --dev (Vite on :8080, API on :8000);
    • the HF Space, which serves only the UI and is driven via ?api= (detected with the existing isHostedSpace() helper).

    The ?api= query param and the stored override keep their current precedence in all cases.

  • lelab/scripts/lelab.py: new --host and --port options, defaults 127.0.0.1 / 8000 (behavior unchanged unless opted in). lelab --host 0.0.0.0 --port 9000 binds the backend to the requested interface and port; in --dev, --host also applies to the Vite server and --port selects the backend port that the dev frontend's ?api= URL points to. lelab --stop --port N frees a matching custom backend port. Readiness probes and the auto-opened browser stay on loopback, which a wildcard-bound socket accepts anyway.

  • README.md: a "Custom host and port" section in Quick Start documenting both flags.

Testing

  • pytest tests/test_scripts_lelab.py: 23 pass (6 new: parser defaults, --host / --port parsing, custom host and port threaded through both prod and dev launchers, dev browser URL pointing at the custom API port, --stop port matching).
  • ruff check and ruff format --check: clean.
  • npm run test: 17 files / 58 tests pass; npx tsc -b and eslint output is identical to main (remaining findings are pre-existing); npm run build succeeds.
  • End-to-end: built this branch, served it from a LAN host, and opened it in a browser with empty localStorage. The full UI renders, API calls hit the serving origin, and the joint-data WebSocket connects. The same build still works via localhost:8000.

crypto.randomUUID is only exposed in secure contexts (HTTPS or
localhost). Opening LeLab over plain HTTP on a LAN IP
(http://<host-ip>:8000) made the SingleTabGuard effect throw a
TypeError, which unmounted the whole React tree and left the page
completely blank behind a correct <title> and favicon. Fall back to
a crypto.getRandomValues-based v4 UUID there; the id is only used
for tab election, so it does not need crypto-grade randomness.
The fallback API base was hardcoded to http://localhost:8000, so a
browser on another machine opening http://<host-ip>:8000 sent every
API call to its own localhost and rendered an empty UI. Production
`lelab` serves the UI and the API on the same origin, so default to
window.location.origin there. Dev (Vite on :8080, API on :8000) and
the HF Space (UI-only, driven via ?api=) keep the localhost default.
@openpilotcc
openpilotcc force-pushed the fix/plain-http-lan-access branch from 3c05eb8 to 4ed284a Compare November 5, 2026 01:28
`lelab` hardcoded 127.0.0.1, so the server could not be reached from
another machine at all, even with a browser that resolved everything
correctly. `--host 0.0.0.0` now binds the backend (and, in --dev, the
Vite server) to the requested interface; the default stays 127.0.0.1,
so nothing changes unless the flag is passed. Readiness probes and the
auto-opened browser keep using loopback, which a wildcard-bound socket
accepts either way.
@openpilotcc openpilotcc changed the title fix(frontend): render correctly when accessed over plain-HTTP LAN fix: make LeLab usable over plain-HTTP LAN access Nov 5, 2026
--host alone still left the backend pinned to :8000, which may be
taken or firewalled on the host. --port now selects the backend port
in both prod and dev modes (the dev frontend's ?api= URL follows it),
and `lelab --stop --port N` frees a matching custom backend port.
The default stays 8000, so existing invocations behave as before.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant