Liking cljdoc? Tell your friends :D

com.blockether.vis.internal.gateway.pairing

Gateway pairing helpers for remote clients.

The QR payload is deliberately tiny and URL-shaped so native apps can scan it without an HTTP round trip:

vis://gateway?url=http%3A%2F%2F100.64.0.10%3A7890&token=...

Tailscale fits naturally: if a 100.64.0.0/10 interface is present we prefer it over LAN addresses, otherwise we fall back to site-local IPv4 addresses.

Gateway pairing helpers for remote clients.

The QR payload is deliberately tiny and URL-shaped so native apps can scan it
without an HTTP round trip:

  vis://gateway?url=http%3A%2F%2F100.64.0.10%3A7890&token=...

Tailscale fits naturally: if a 100.64.0.0/10 interface is present we prefer it
over LAN addresses, otherwise we fall back to site-local IPv4 addresses.
raw docstring

advertised-urlclj

(advertised-url advertise port)

Normalize an operator-supplied address into the base URL clients should dial, or nil when there is nothing to advertise.

The interface scan only sees the addresses this machine HOLDS, and a network can insist on one it does not: a port forward, a proxy, or a policy that drops every address but one. --advertise is that claim, so it takes the link's url= while the scanned addresses ride along as alt=.

A bare host or host:port is completed with http:// and the gateway's own port; anything carrying a scheme is taken verbatim, because only the operator knows the scheme and port on the other side of that route.

Normalize an operator-supplied address into the base URL clients should dial,
or nil when there is nothing to advertise.

The interface scan only sees the addresses this machine HOLDS, and a network
can insist on one it does not: a port forward, a proxy, or a policy that drops
every address but one. `--advertise` is that claim, so it takes the link's
`url=` while the scanned addresses ride along as `alt=`.

A bare host or `host:port` is completed with `http://` and the gateway's own
port; anything carrying a scheme is taken verbatim, because only the operator
knows the scheme and port on the other side of that route.
sourceraw docstring

candidate-hostsclj

(candidate-hosts bind-host)

Reachable hostnames/IPs worth showing in a pairing QR, in preference order.

A concrete bind host is the ONLY candidate: the socket answers there and nowhere else, so offering the machine's other interfaces hands the phone URLs that time out. It also decides WHICH address leads, which is what a network that allows exactly one of them needs — the allowed address has to be url=, not an alt= behind a tailnet address that network drops.

A wildcard bind really does serve every interface, so there Tailscale addresses come first because they keep working off-LAN, then LAN, then the rest, and last the router this machine routes through. Every candidate is a guess the client resolves by trying them in order, so the router costs one entry and answers the case nothing else can: a port forward, where the phone reaches the gateway only through the router's address.

Reachable hostnames/IPs worth showing in a pairing QR, in preference order.

A concrete bind host is the ONLY candidate: the socket answers there and
nowhere else, so offering the machine's other interfaces hands the phone URLs
that time out. It also decides WHICH address leads, which is what a network
that allows exactly one of them needs — the allowed address has to be `url=`,
not an `alt=` behind a tailnet address that network drops.

A wildcard bind really does serve every interface, so there Tailscale
addresses come first because they keep working off-LAN, then LAN, then the
rest, and last the router this machine routes through. Every candidate is a
guess the client resolves by trying them in order, so the router costs one
entry and answers the case nothing else can: a port forward, where the phone
reaches the gateway only through the router's address.
sourceraw docstring

loopback-bind?clj

(loopback-bind? host)

True when host is an address only this machine can reach. A phone can never open such a URL, so pairing against it is meaningless no matter how good the QR is.

True when `host` is an address only this machine can reach. A phone can never
open such a URL, so pairing against it is meaningless no matter how good the
QR is.
sourceraw docstring

pair-bind-hostclj

(pair-bind-host)

Bind address to use when --pair is requested WITHOUT an explicit --host.

Pairing on the loopback default is a contradiction: the QR would encode an address no socket is listening on and the phone would time out minutes later. Asking to pair IS asking for phone access, so it binds every interface.

Binding only the Tailscale IP was the earlier, narrower choice and it broke the pairing link's own promise: pairing-url advertises the LAN addresses as alt= fallbacks, but nothing listened there, so a phone whose Tailscale was off or paused failed on EVERY candidate while sitting on the same Wi-Fi as the gateway. 0.0.0.0 is non-loopback exactly like the Tailscale IP was, so server/start! still forces the bearer token: this widens reach, never auth.

Bind address to use when `--pair` is requested WITHOUT an explicit `--host`.

Pairing on the loopback default is a contradiction: the QR would encode an
address no socket is listening on and the phone would time out minutes later.
Asking to pair IS asking for phone access, so it binds every interface.

Binding only the Tailscale IP was the earlier, narrower choice and it broke
the pairing link's own promise: `pairing-url` advertises the LAN addresses as
`alt=` fallbacks, but nothing listened there, so a phone whose Tailscale was
off or paused failed on EVERY candidate while sitting on the same Wi-Fi as
the gateway. `0.0.0.0` is non-loopback exactly like the Tailscale IP was, so
`server/start!` still forces the bearer token: this widens reach, never auth.
sourceraw docstring

pairing-jsonclj

(pairing-json {:keys [host port token require-token? advertise]})
source

pairing-urlclj

(pairing-url {:keys [host port token advertise]})

The vis://gateway deep link. url= is the best guess (Tailscale first), and alt= carries the remaining routable hosts so a phone that cannot reach the first one (no Tailscale, different LAN) falls back instead of failing. A concrete bind has a single candidate, so its link carries no alt= at all. IPv4 link-local (169.254/16) is dropped from the alternates: no phone can route it, and every extra host makes the QR denser.

:advertise pins url= to an address the scan cannot know and demotes every scanned host to alt=.

The `vis://gateway` deep link. `url=` is the best guess (Tailscale first), and
`alt=` carries the remaining routable hosts so a phone that cannot reach the
first one (no Tailscale, different LAN) falls back instead of failing. A
concrete bind has a single candidate, so its link carries no `alt=` at all.
IPv4 link-local (169.254/16) is dropped from the alternates: no phone can
route it, and every extra host makes the QR denser.

`:advertise` pins `url=` to an address the scan cannot know and demotes every
scanned host to `alt=`.
sourceraw docstring

(print-pairing!
  {:keys [require-token? emit host port advertise] :or {emit println} :as opts})

Emit the companion pairing block (title, reachable hosts, vis:// URL, and a terminal QR). Each line goes through emit (default println); CLI callers whose stdout is redirected to the log file pass a real-terminal writer so the QR is actually visible. Returns the pairing URL payload.

Bound to loopback there is nothing to pair with: the interface scan still finds Tailscale/LAN addresses, but the listener is not on them, so a QR built from those would encode a URL that times out — the failure landing on the phone, minutes later, looking like a broken app. Refuse and print the restart command instead; returns nil. An :advertise address overrides that refusal: the operator has named a route Vis cannot see, such as a proxy in front of the loopback port.

Emit the companion pairing block (title, reachable hosts, `vis://` URL, and a
terminal QR). Each line goes through `emit` (default `println`); CLI callers
whose stdout is redirected to the log file pass a real-terminal writer so the
QR is actually visible. Returns the pairing URL payload.

Bound to loopback there is nothing to pair with: the interface scan still
finds Tailscale/LAN addresses, but the listener is not on them, so a QR built
from those would encode a URL that times out — the failure landing on the
phone, minutes later, looking like a broken app. Refuse and print the restart
command instead; returns nil. An `:advertise` address overrides that refusal:
the operator has named a route Vis cannot see, such as a proxy in front of
the loopback port.
sourceraw docstring

tailscale-hostsclj

(tailscale-hosts)

Tailscale (100.64/10) IPv4 addresses currently bound to a live interface, in discovery order. Empty when Tailscale is not up, so callers can fall back to LAN / 0.0.0.0 guidance.

Tailscale (100.64/10) IPv4 addresses currently bound to a live interface, in
discovery order. Empty when Tailscale is not up, so callers can fall back to
LAN / `0.0.0.0` guidance.
sourceraw docstring

terminal-qrclj

(terminal-qr text)

Render text as a terminal QR code using Unicode half-blocks. Returns a string so tests and CLI callers can decide where to print it.

Two properties matter for a phone camera to actually decode this:

  • a full 4-module quiet zone (the spec minimum; a 1-module margin scans only on a perfect white background), padded to an even module height so the bottom quiet zone survives the half-block row packing;
  • the block glyph paints the light modules, like qrencode -t UTF8, so the code reads correctly on the dark terminal themes everyone runs. Painting dark modules instead produces a photo-negative that most scanners reject.
Render `text` as a terminal QR code using Unicode half-blocks. Returns a string
so tests and CLI callers can decide where to print it.

Two properties matter for a phone camera to actually decode this:

- a full 4-module quiet zone (the spec minimum; a 1-module margin scans only
  on a perfect white background), padded to an even module height so the
  bottom quiet zone survives the half-block row packing;
- the block glyph paints the *light* modules, like `qrencode -t UTF8`, so the
  code reads correctly on the dark terminal themes everyone runs. Painting
  dark modules instead produces a photo-negative that most scanners reject.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close