Skip to content

Feature: friction-free Omadia UI ↔ host pairing (LAN mDNS + public-internet discovery) #293

Description

@iret77

Summary

Pairing the Omadia UI desktop app to an Omadia host is currently a manual, brittle step: the user has to hand-type the full canvas transport URL (wss://<host>/omadia-ui/canvas), including scheme and path suffix. That URL is not the URL a human knows — in a split deployment (Next.js operator front + middleware behind, different hosts) the transport URL differs from the browser/operator URL, and there is no in-product way to discover it. This request proposes friction-free pairing for the two deployment shapes an open-source, self-hostable product must serve: host on the same LAN and host on the public internet.

Motivation

Discovered during a real pairing attempt against a test instance (odoo-bot-harness.fly.dev): the operator/web-ui front gates everything behind a Next.js login and does not route the canvas WebSocket; the canvas WS lives on the separate middleware service. Entering the operator URL loops at the connect step, and the working transport URL is only recoverable from a colleague's saved settings.json or via Fly CLI. For self-hosters this is a non-starter.

Design principle: the server owns the mapping "human-facing URL → transport URL", not the user. The app should need either nothing (LAN) or only the one URL the user already knows (remote).

Scenario A — Omadia host on the same LAN (zero-config, Bonjour/mDNS)

The common case for self-hosting (cf. Home Assistant, Plex, Syncthing, Jellyfin).

  • Host side (middleware): advertise an mDNS service _omadia._tcp with TXT records carrying:
    • path (canvas WS path, e.g. /omadia-ui/canvas)
    • proto (canvas protocol version, e.g. 1.0)
    • auth (auth mode: none | password | oidc)
    • name (human instance label)
  • Client side (Omadia UI): browse _omadia._tcp in the Electron main process (e.g. bonjour-service), present a "Discovered hosts" picker — click to connect, zero typing.

Scenario B — Omadia host on the public internet (URL discovery)

  • User enters the one URL they already know (the operator/browser URL, https://…) — no scheme juggling, no /omadia-ui/canvas suffix.
  • Client GETs an un-gated discovery endpoint on that origin (e.g. /.well-known/omadia-ui or a public /omadia-ui/info) which returns:
    • wsUrlabsolute canvas WS URL incl. host (may be the same origin or a separate middleware host)
    • auth — providers + login-start URL
    • protocolVersion
  • Client authenticates via the discovered provider → session cookie → connects to the discovered wsUrl.

Manual fallback

Full wss://…/omadia-ui/canvas paste — last resort for debugging / exotic setups only.

Unifying abstraction

All three paths produce the same descriptor and the connect code stays source-agnostic:

{ wsUrl, protocolVersion, auth }

mDNS discovery and HTTP discovery return the same shape; the picker, the URL-discovery flow, and manual entry all feed it.

Concrete work

Server-side (this repo, byte5ai/omadia):

  • Expose the discovery endpoint before the login gate (public) and have it return absolute URLs (host included), not relative paths.
  • Either have web-ui proxy the canvas WS, or have discovery return the middleware's public host — one of the two must exist.
  • Align the auth path: the desktop app calls /api/v1/auth/*, the deployment exposes /bot-api/v1/auth/*.
  • Add the mDNS advertiser to the middleware (_omadia._tcp + TXT).

Client-side (byte5ai/omadia-ui):

  • Accept https:// input and derive wss:// automatically.
  • mDNS browser + "Discovered hosts" picker UI.
  • Consume the discovery descriptor; keep manual paste as fallback.

Existing half

/omadia-ui/info already returns { transport: 'websocket', websocket: '/omadia-ui/canvas', dispatchService, protocolVersions, … } — but it is currently gated (302 → /login) and returns a relative path. Making it public + absolute covers most of Scenario B.


Filed from the Omadia UI side after a pairing session; client work tracked in byte5ai/omadia-ui.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions