Real-time LoRa mesh packet analyzer. Desktop-first, dark-mode-primary, dense information display for radio hobbyists.
Built with React 19, TypeScript, Tailwind CSS 4, TanStack Query, and TanStack Virtual.
Beacon Web ships as a container image, ghcr.io/meshcore-beacon/beacon-web, and is deployed
together with beacon-server. The compose files and walkthroughs are in
beacon-docs: the
all-in-one stack
or the split deployment.
The VITE_* variables the container reads are listed in
Configuration.
Image tags and versioning are in
Releases and versioning.
npm install
cp .env.example .env.local
npm run dev # Vite dev server at http://localhost:5173For live updates against a local beacon-server, uncomment these three lines in .env.local
before starting Vite. They route /api and /ws through the dev server so the WebSocket
arrives with the server's own origin, which beacon-server's same-origin check requires:
VITE_DEV_PROXY=http://localhost:8080
VITE_API_BASE=/api/v1
VITE_WS_URL=ws://localhost:5173/ws
Without them the page talks to localhost:8080 directly and the WebSocket is refused. Running
the server itself is covered in the
shared contributor guide.
| Command | Description |
|---|---|
npm run dev |
Start dev server in the foreground |
npm run start / stop / restart |
Run the dev server in the background (scripts/devserver.sh) |
npm run dev:status / dev:stop-all |
Show or stop background dev servers |
npm run build |
Type-check and build for production. This is the only real typecheck |
npm run preview |
Preview production build locally |
npm run lint |
Run ESLint |
npm test |
Run tests |
The UI is translatable; see docs/translations.md to add a language.
.build/
Dockerfile # multi-stage build (Node + Caddy)
Caddyfile # internal Caddy config (static file serving)
docker-entrypoint.sh # runtime env var injection
src/
api/
client.ts # typed REST client (fetch wrapper)
ws-manager.ts # WebSocket connection, reconnect, subscription management
components/ # shared UI components
features/ # feature modules (packets, nodes, observers, channels, map, routes, traces, stats)
hooks/ # React hooks (region, theme, WebSocket)
i18n/ # i18next setup and locale catalogs
lib/ # constants, formatters, theme utilities
types/ # TypeScript types and enums
App.tsx # providers + routing + WS init
main.tsx # entry point
index.css # Tailwind setup, theme tokens, animations
- Region-driven: All data queries and WS subscriptions are scoped to an IATA region code. Changing region resets the cache and resubscribes.
- Live + historical merge: WebSocket pushes live packets into a
LivePacketStorebuffer (capped at 500). Historical data comes from cursor-paginated REST viauseInfiniteQuery(max 20 pages). Both are merged and deduped at render time. - Packet filtering: Payload-type, route-type and scope filters are sent to the server and added to the query key, so paging walks the full filtered history. The observer filter and hash/path search stay client-side; one
useMemopredicate runs over the merged list, so live packets are filtered too. - Reconnect with jitter: Exponential backoff with +/-25% random jitter prevents thundering herd on server bounce.
- API contract: every REST, packet and WebSocket shape this app consumes
- High level design
- Translations: adding a language
- Contributing, and the shared workflow for branches, commits and releases
See CONTRIBUTING.md. All contributors are welcome; please also read the Code of Conduct. To report a security issue, see SECURITY.md.
Licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See LICENSE for the full text and CONTRIBUTORS.md for acknowledgements.