Skip to content

UI auto-reinserts wss://relay.primal.net, making it impossible to replace the default relay #45

Description

@sroertgen

Summary

In frontend/components/ui/relay-input.tsx, a useEffect hook auto-inserts wss://relay.primal.net whenever the relay list is empty. This makes it impossible to replace the single pre-populated primal relay with a different one through the UI — deleting it just triggers the effect and re-adds it.

Offending code

https://github.com/FROSTR-ORG/igloo-server/blob/main/frontend/components/ui/relay-input.tsx#L87-L92

// Add a default relay if the list is empty
useEffect(() => {
  if (relays.length === 0) {
    const defaultRelay = "wss://relay.primal.net";
    onChangeRef.current([defaultRelay]);
  }
}, [relays.length]);

Reproduce

  1. Fresh onboarding in Database mode. The Configure tab shows one relay: wss://relay.primal.net.
  2. Click the trash icon next to it.
  3. The relay list momentarily empties, then the effect fires and re-inserts wss://relay.primal.net.
  4. Alternative workflow: add your own relay first so the list has two entries, then delete primal. This also fails under certain persistence paths — once the list is saved and reloaded, primal reappears at the top on next visit (likely because the config round-trip goes through a path that empties the list transiently and the effect refills it).

Net effect: you can never end up with a relay list that does not contain wss://relay.primal.net.

Impact

  • Users who run their own relay (e.g., for a self-hosted test deployment, privacy, or network isolation) cannot point the signer exclusively at their relay via the UI. The signer ends up publishing/subscribing on primal regardless.
  • In a FROSTR test setup using a dedicated private relay, the signer connects to primal, NIP-07 extensions (e.g., Frost2x) broadcast signing requests on the intended relay, and signing silently fails because signer ↔ client have no relay in common.

Workaround (users)

Set RELAYS in the env — it seeds the list with your own relay and is not overwritten by the effect:

RELAYS=[\"wss://my-own-relay.example.org\"]

This works around the symptom but the UI remains broken: users can still not edit the relay list via Configure without primal reappearing.

Suggested fix

Either:

  1. Drop the effect entirely. An empty relay list is a valid state — the backend/UI can show a warning ("no relays configured") rather than silently re-injecting a third-party default. This is the least surprising behavior and respects user intent.
  2. Only seed when the component mounts with no relays AND nothing is saved server-side (e.g., first-time onboarding, before the user has touched Configure). After the user has explicitly cleared the list, do not re-add. The cleanest way is to seed at the API / initial-load layer, not in a render-time effect in the input component.
  3. At minimum, don't hardcode a third-party relay. If a default is desired, make it configurable via env (e.g., DEFAULT_RELAY) with a sane fallback, so operators of self-hosted/private deployments aren't forced onto relay.primal.net.

Happy to open a PR if a direction is preferred.

Environment

  • Version: ghcr.io/frostr-org/igloo-server:1.1.0
  • Mode: Database (UI onboarding)
  • Discovered while standing up a private FROSTR test relay and wiring an always-on signer at it via an Ansible role.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions