docs/desktop-app.mdx
/api/tags first, then OpenAI-compatible /v1/models) and populates a dropdown. Embedding models are filtered out. If discovery fails, a manual text input appears as fallbacklocalStorage change event. The main window listens for this event and hot-reloads all secrets without requiring an app restartsecrets-vault) rather than individual entries per key. This reduces macOS Keychain authorization prompts from 20+ to exactly 1 on each app launch. A one-time migration reads any existing individual entries, consolidates them, and cleans up the old formatAll five variants run on three platforms that work together:
┌─────────────────────────────────────┐
│ Vercel (Edge) │
│ 60+ edge functions · static SPA │
│ Proto gateway (24 typed services) │
│ CORS allowlist · Redis cache │
│ AI pipeline · market analytics │
│ CDN caching (s-maxage) · PWA host │
└──────────┬─────────────┬────────────┘
│ │ fallback
│ ▼
│ ┌───────────────────────────────────┐
│ │ Tauri Desktop (Rust + Node) │
│ │ OS keychain · Token-auth sidecar │
│ │ 60+ local API handlers · br/gzip │
│ │ Cloud fallback · Traffic logging │
│ └───────────────────────────────────┘
│
│ https:// (server-side)
│ wss:// (client-side)
▼
┌──────────────────────────────────────────┐
│ Railway (Relay Server) │
│ AIS WebSocket · OpenSky OAuth2 │
│ Telegram MTProto (26 OSINT channels) │
│ OREF rocket alerts (residential proxy) │
│ Polymarket proxy (queue backpressure) │
│ ICAO NOTAM · RSS proxy · gzip all resp │
└──────────────────────────────────────────┘
Why two platforms? Several upstream APIs (OpenSky Network, CNN RSS, UN News, CISA, IAEA) actively block requests from Vercel's IP ranges, and some require persistent connections or protocols that edge functions cannot support. The Railway relay server acts as an alternate origin, handling:
curl through a residential proxy (Akamai WAF blocks datacenter TLS fingerprints)The Vercel edge functions connect to Railway via WS_RELAY_URL (server-side, HTTPS) while browser clients connect via VITE_WS_RELAY_URL (client-side, WSS). This separation keeps the relay URL configurable per deployment without leaking server-side configuration to the browser.
All Railway relay responses are gzip-compressed (zlib gzipSync) when the client accepts it and the payload exceeds 1KB, reducing egress by ~80% for JSON and XML responses. The desktop local sidecar now prefers Brotli (br) and falls back to gzip for payloads larger than 1KB, setting Content-Encoding and Vary: Accept-Encoding automatically.
The Tauri desktop app wraps the dashboard in a native window (macOS, Windows, Linux) with a local Node.js sidecar that runs all API handlers without cloud dependency:
┌─────────────────────────────────────────────────┐
│ Tauri (Rust) │
│ Window management · Consolidated keychain vault│
│ Token generation · Log management · Menu bar │
│ Polymarket native TLS bridge │
└─────────────────────┬───────────────────────────┘
│ spawn + env vars
▼
┌─────────────────────────────────────────────────┐
│ Node.js Sidecar (dynamic port) │
│ 60+ API handlers · Local RSS proxy │
│ Brotli/Gzip compression · Cloud fallback │
│ Traffic logging · Verbose debug mode │
└─────────────────────┬───────────────────────────┘
│ fetch (on local failure)
▼
┌─────────────────────────────────────────────────┐
│ Cloud (worldmonitor.app) │
│ Transparent fallback when local handlers fail │
└─────────────────────────────────────────────────┘
API keys are stored in the operating system's credential manager (macOS Keychain, Windows Credential Manager) — never in plaintext config files. All secrets are consolidated into a single JSON vault entry in the keychain, so app startup requires exactly one OS authorization prompt regardless of how many keys are configured.
At sidecar launch, the vault is read, parsed, and injected as environment variables. Empty or whitespace-only values are skipped. Secrets can also be updated at runtime without restarting the sidecar: saving a key in the Settings window triggers a POST /api/local-env-update call that hot-patches process.env so handlers pick up the new value immediately.
Verification pipeline — when you enter a credential in Settings, the app validates it against the actual provider API (Groq → /openai/v1/models, Ollama → /api/tags, FRED → GDP test query, NASA FIRMS → fire data fetch, etc.). Network errors (timeouts, DNS failures, unreachable hosts) are treated as soft passes — the key is saved with a "could not verify" notice rather than blocking. Only explicit 401/403 responses from the provider mark a key as invalid. This prevents transient network issues from locking users out of their own credentials.
Smart re-verification — when saving settings, the verification pipeline skips keys that haven't been modified since their last successful verification. This prevents unnecessary round-trips to provider APIs when a user changes one key but has 15 others already configured and validated. Only newly entered or modified keys trigger verification requests.
Desktop-specific requirements — some features require fewer credentials on desktop than on the web. For example, AIS vessel tracking on the web requires both a relay URL and an API key, but the desktop sidecar handles relay connections internally, so only the API key is needed. The settings panel adapts its required-fields display based on the detected platform.
World Monitor desktop uses a runtime configuration schema with per-feature toggles and secret-backed credentials.
The desktop vault schema (Rust SUPPORTED_SECRET_KEYS) supports the following 25 keys:
GROQ_API_KEYOPENROUTER_API_KEYFRED_API_KEYEIA_API_KEYFINNHUB_API_KEYCLOUDFLARE_API_TOKENACLED_ACCESS_TOKENURLHAUS_AUTH_KEYOTX_API_KEYABUSEIPDB_API_KEYNASA_FIRMS_API_KEYWINGBITS_API_KEYWS_RELAY_URLVITE_WS_RELAY_URLVITE_OPENSKY_RELAY_URLOPENSKY_CLIENT_IDOPENSKY_CLIENT_SECRETAISSTREAM_API_KEYOLLAMA_API_URLOLLAMA_MODELWORLDMONITOR_API_KEY — gates cloud fallback access (min 16 chars)WTO_API_KEYAVIATIONSTACK_APIICAO_API_KEYUCDP_ACCESS_TOKENEach feature includes:
id: stable feature identifier.requiredSecrets: list of keys that must be present and valid.enabled: user-toggle state from runtime settings panel.available: computed (enabled && requiredSecrets valid).fallback: user-facing degraded behavior description.Desktop builds persist secrets in OS credential storage through Tauri command bindings backed by Rust keyring entries (world-monitor service namespace).
Secrets are not stored in plaintext files by the frontend.
If required secrets are missing/disabled:
A unique 32-character hex token is generated per app launch using randomized hash state (RandomState from Rust's standard library). The token is:
LOCAL_API_TOKENget_local_api_token Tauri command (lazy-loaded on first API request)Authorization: Bearer <token> to every local requestThe /api/service-status health check endpoint is exempt from token validation to support monitoring tools.
The sidecar defaults to port 46123 but handles EADDRINUSE gracefully — if the port is occupied (another World Monitor instance, or any other process), the sidecar binds to port 0 and lets the OS assign an available ephemeral port. The actual bound port is written to a port file (sidecar.port in the logs directory) that the Rust host polls on startup (100ms intervals, 5-second timeout). The frontend discovers the port at runtime via the get_local_api_port IPC command, and getApiBaseUrl() in runtime.ts is the canonical accessor — hardcoding port 46123 in frontend code is prohibited. The CSP connect-src directive uses http://127.0.0.1:* to accommodate any port.
The sidecar includes a built-in RSS proxy handler that fetches news feeds directly from source domains, bypassing the cloud RSS proxy entirely. This means the desktop app can load all 435+ RSS feeds without any cloud dependency — the same domain allowlist used by the Vercel edge proxy is enforced locally. Combined with the local API handlers, this enables the desktop app to operate as a fully self-contained intelligence aggregation platform.
The sidecar employs multiple resilience patterns to maintain data availability when upstream APIs degrade:
Promise.all. This transforms 10 concurrent requests that would trigger HTTP 429 into a staggered sequence that stays under rate limitsWhen a local API handler is missing, throws an error, or returns a 5xx status, the sidecar transparently proxies the request to the cloud deployment. Endpoints that fail are marked as cloudPreferred — subsequent requests skip the local handler and go directly to the cloud until the sidecar is restarted. Origin and Referer headers are stripped before proxying to maintain server-to-server parity.
GET /api/local-traffic-logPOST /api/local-debug-toggle, persists across sidecar restarts in verbose-mode.jsondesktop.log captures Rust-side events (startup, secret injection counts, menu actions), while local-api.log captures Node.js stdout/stderrglobalThis.fetch to force IPv4 for all outbound requests. Government APIs (NASA FIRMS, EIA, FRED) publish AAAA DNS records but their IPv6 endpoints frequently timeout. The patch uses node:https with family: 4 to bypass Happy Eyeballs and avoid cascading ETIMEDOUT failuresCmd+Alt+I toggles the embedded web inspectorThe desktop app checks for new versions by polling worldmonitor.app/api/version — once at startup (after a 5-second delay) and then every 6 hours. When a newer version is detected (semver comparison), a non-intrusive update badge appears with a direct link to the GitHub Release page.
Update prompts are dismissable per-version — dismissing v2.5.0 won't suppress v2.6.0 notifications. The updater is variant-aware: a Tech Monitor desktop build links to the Tech Monitor release asset, not the full variant.
The /api/version endpoint reads the latest GitHub Release tag and caches the result for 1 hour, so version checks don't hit the GitHub API on every request.