Files
Webka/README.md
2026-09-06 18:47:42 +05:00

12 KiB
Raw Blame History

Webka

A private camera and screen streaming app for your local network. Nuxt 3 / Vue 3 / TypeScript, Pinia, English/Russian/Spanish, and an Axum/Rust service with SQLite and browser-to-browser WebRTC.

One-command start

Install Node 22.12+ (npm included) and stable Rust/Cargo once. Then, from the repository root:

npm run dev:all

This starts http://127.0.0.1:3000. To listen on all network interfaces at port 3002:

npm run dev:all -- --host 0.0.0.0 --port 3002

The -- after dev:all passes the options to the launcher. Use ordinary double hyphens. npm start -- --host 0.0.0.0 --port 3002 is an alias. --help shows usage.

The launcher installs frontend dependencies automatically on the first run, builds Nuxt, builds Rust, and runs one server serving both the frontend and API on the chosen port. First startup needs internet and can take several minutes. Subsequent runs reuse unchanged dependencies/frontend output; Cargo builds incrementally. Restart the command after code changes to rebuild. This convenience command does not provide hot reload.

The terminal prints the local URL, LAN URLs when available, and the setup-token file location. Open the local URL on the server computer for camera capture; use a printed LAN URL on other devices to watch. The QR code uses the detected LAN address when listening on all interfaces. Local and detected private IPv4 origins are explicitly allowed; arbitrary origins remain blocked. If the selected port is busy, startup stops with an actionable message.

Press Ctrl+C to stop the app and any build subprocesses. Accounts remain in data/. The first login page creates the administrator account using the token from data/setup-token. The administrator cannot be deleted; resetting its password ends its sessions.

For an existing HTTPS proxy, use the homepage setting below. Browser capture on another LAN device still requires a trusted HTTPS address.

For frontend hot reload, the separate development workflow remains available: run cargo run --manifest-path backend/Cargo.toml and npm --prefix frontend run dev in separate terminals, then open http://localhost:3000.

Existing Nginx Proxy Manager on another machine

Set just the address you want to open in a browser (the address served by NPM):

npm run dev:all -- --homepage https://camera.example.com --port 3002

Replace https://camera.example.com with your actual HTTPS domain or IP, including a public port if needed. A bare hostname/IP is treated as HTTPS. A trailing slash is accepted; hosting under a subpath is not supported.

This mode automatically:

  • Listens on 0.0.0.0 so the other machine can reach Webka (unless you explicitly set --host).
  • Serves the frontend and API over HTTP on the chosen internal port; your existing NPM handles HTTPS.
  • Uses the homepage for QR/share links, secure cookies, and allowed request origins.
  • Disables mDNS, even if WEBKA_MDNS=1 was previously set. No Caddy, .local hostname, or certificates on the Webka computer are needed.

In Nginx Proxy Manager → Proxy Hosts, configure:

Setting Value
Domain Names Your homepage hostname, e.g. camera.example.com
Scheme http
Forward Hostname / IP The LAN IP of the Webka computer, e.g. 192.168.1.50
Forward Port 3002 (or your --port)
Websockets Support Enabled
SSL Select your existing trusted certificate; enable Force SSL

Open the homepage HTTPS address on both the host and viewer. Its hostname must resolve to the NPM machine, and NPM must be able to reach the Webka machine's LAN IP/port. The homepage is the browser-facing address, not the internal upstream address. For an HTTPS IP address, the certificate must cover that IP and NPM must route it to Webka. See the official NPM guide and WebSocket proxy support.

To save the address, create webka.config.json in the repository root (or copy webka.config.example.json):

{
  "homepage": "https://camera.example.com",
  "port": 3002
}

Then start with only npm run dev:all. homepage alone is sufficient if the internal port is the default 3000. Optional host and port may also be saved; command-line options override the file. Restart to apply changes. This machine-specific file is ignored by Git. Homepage mode takes priority over legacy WEBKA_ORIGIN, WEBKA_ALLOWED_ORIGINS, and WEBKA_MDNS values.

Legacy manual proxy setup via WEBKA_ORIGIN and WEBKA_ALLOWED_ORIGINS remains supported when no homepage is configured. The launcher does not configure NPM or provision its certificates. The proxy carries page/API/signaling traffic; WebRTC media remains peer-to-peer on the LAN.

Use on your LAN

A webpage cannot launch a Rust server on the device running its browser. Start the Rust service once on a computer on your LAN. A phone, tablet, or computer can then sign in as the administrator and act as the capturing host. This repository does not include a native iOS/Android service wrapper. Each service instance owns its account database.

Browser camera/screen APIs require a secure context: localhost on the service machine, or trusted HTTPS on a LAN device. LAN HTTP IP addresses cannot capture camera/screen. If you already have an HTTPS proxy, use the homepage mode above. If you do not, the included Caddyfile.example is an optional alternative with a trusted local certificate:

  1. Point webka.local at the service computer, through mDNS (WEBKA_MDNS=1), your router, or a hosts entry. .local resolution and multicast support vary by network/device; a local DNS name or IP with a matching trusted certificate also works.

  2. Configure Caddy with the example below, and install/trust Caddy’s local CA on every client device. Keep this CA private.

  3. Start Webka with its exact public origin:

    WEBKA_ORIGIN=https://webka.local WEBKA_MDNS=1 \
    WEBKA_PUBLIC_PORT=443 WEBKA_BIND=127.0.0.1:3001 \
    cargo run --release --manifest-path backend/Cargo.toml
    
  4. Open https://webka.local, create the admin account, add viewer accounts in Accounts, enable preview, and choose Go live. Share the QR code/local address. Each viewer signs in with an account created on this service.

Caddy terminates HTTPS and proxies to the loopback Rust server. Cookies gain Secure automatically when WEBKA_ORIGIN starts with https://. WebSocket handshakes and all mutations validate an explicitly configured Origin. No CORS wildcard or trusted forwarded-IP headers are used. The login throttle sees the reverse proxy as one IP, so its ten-attempts-per-minute limit is shared behind that proxy.

Use a private LAN and firewall; do not expose the service through public port forwarding. The app uses no cloud accounts, public STUN/TURN, telemetry, CDNs, or external fonts. WebRTC connects peers directly with local ICE candidates. Client isolation, VLANs, and firewalls that block peer UDP can prevent streams; no cloud relay fallback is provided. The one active host sends a separate peer stream to each viewer (32 signaling connections total, including the host). Capacity depends on the host device and Wi-Fi.

Configuration

Variable Default Purpose
WEBKA_BIND 127.0.0.1:3001 Rust listener
WEBKA_ORIGIN http://localhost:3000 Exact public origin; used for CSRF checks, QR and secure cookies
WEBKA_ALLOWED_ORIGINS none Additional trusted exact origins; launcher configures local/LAN aliases
WEBKA_DATA data Local database and persistent secrets
WEBKA_ASSETS frontend/.output/public Generated frontend; paths are relative to the current directory
WEBKA_MDNS off Set 1 to advertise _webka._tcp.local. and webka.local
WEBKA_PUBLIC_PORT 443 for HTTPS, otherwise 3000 Advertised public port

Back up WEBKA_DATA securely. On Unix the data directory is owner-only and secret files are created with mode 0600. On Windows configure equivalent directory ACLs. Session tokens are random, HMAC-signed, stored only as SHA-256 digests in SQLite, and sent in HttpOnly/SameSite=Strict cookies. Argon2id stores salted passwords. Sessions expire after 30 minutes without activity and always after 12 hours. Visible authenticated pages send a heartbeat; hidden pages do not. Revocation closes signaling and tells the host to close the affected peer. A recipient may still retain media already received.

Features and implementation

  • /login: protected first-run setup and local sign-in.
  • /host: camera preview, front/back/screen selection, start/stop, uptime, bitrate presets, QR link and viewer disconnection.
  • /watch: live video, connection/resolution indicator, fullscreen, logout and automatic reconnect with backoff.
  • /settings/accounts: viewer creation/deletion, password reset, last login and active session revocation.
  • /settings/general: light/dark glass, accent, locale and security information.
  • Shared GlassCard, GlassButton, GlassNavBar, GlassModal and LanguageSwitcher with layered rim highlights, pointer-responsive specular light, rounded geometry, reduced-motion support, native keyboard focus and dialog focus containment.
  • Content panels stay opaque. Glass is used for navigation, source/share controls and dialogs. CSS edge layers approximate refraction; they do not implement physically accurate optical lensing.

Quality presets control the host’s per-peer send bitrate; the viewer shows actual received resolution. Capture is video-only. Closing/navigating away from the host tab stops capture; a browser may also suspend hosting when the device locks or the tab is backgrounded. Screen share support varies, particularly on mobile. Back-camera discovery becomes reliable after granting camera permission. Physical-device tests are documented in docs/MANUAL_TEST_PLAN.md.

Enable Settings → Host screen → Do not turn off the host to keep the capturing device's screen awake while hosting. The preference defaults to off and is saved in this browser. The same toggle is on the host screen so you can change it without leaving a live stream. An indicator shows whether the request is active, unavailable, or unsupported; a failed request never stops the stream.

This uses the browser's Screen Wake Lock API. Use HTTPS (or localhost) and leave Webka visible. The lock is released on stop, navigation, logout, or when the page becomes hidden, and is requested again on return if hosting is still active. Power-saving settings can deny or release it; a retry button is provided. It cannot override manual locking, closing the lid, or background suspension, and does not keep the separate server computer awake. Keeping the screen on uses more battery. No scrolling or simulated input is needed.

Nuxt 3 is retained to match the requested stack. Its upstream documentation now marks it end-of-life; plan a Nuxt 4 upgrade before broader distribution. Reference: https://nuxt.com/docs/3.x/getting-started/installation.

Validation

npm run test:install
npm run test:launcher
npm run test:wake-lock
cargo fmt --manifest-path backend/Cargo.toml --check
cargo test --manifest-path backend/Cargo.toml
cargo clippy --manifest-path backend/Cargo.toml --all-targets -- -D warnings
npm --prefix frontend run typecheck
npm --prefix frontend run build
node frontend/scripts/check-locales.mjs
cargo build --manifest-path backend/Cargo.toml
python3 scripts/smoke.py
# Optional POSIX integration test: reserves port 3002, tests startup/restart/stop.
python3 scripts/test-launcher.py
# Optional POSIX HTTPS proxy integration test (requires OpenSSL).
python3 scripts/test-proxy.py

test:install checks the frontend lockfile from a temporary folder without node_modules. When updating dependencies, run it with both npm 10 (bundled with Node 22) and npm 11; root npm install only installs the launcher package and does not repair the frontend lockfile.

See docs/API.md for REST/signaling contracts and docs/MANUAL_TEST_PLAN.md for real-device checks. Design references: Apple’s introduction and Meet Liquid Glass. No Apple assets are included. Nothing is published by the build commands.