//! Shared iroh endpoint construction. //! //! Two planes, two identities: //! //! * The **video** plane (host/viewer sessions) binds with an *ephemeral* //! keypair — a fresh `EndpointId` per run. Each session is a throwaway tunnel, //! and keeping its id ephemeral means a screen-share leaks no stable //! fingerprint. //! * The **control** plane (the always-on friends presence service) binds with //! the machine's *persistent* identity (see [`identity`]), so peers can find //! and recognise each other across launches. //! //! They must use different identities because both can be live at once on the //! same machine (the GUI's control endpoint while a host session runs), and //! iroh routes by `EndpointId` — two live endpoints sharing one id would make //! relay delivery ambiguous. use std::str::FromStr; use anyhow::{Context, Result}; use iroh::endpoint::presets; use iroh::{Endpoint, RelayMap, RelayMode, RelayUrl}; use super::alpn::ALPN; /// Environment variable consulted when `--relay` isn't passed. Lets the GUI's /// child processes and scripted runs inherit a relay choice without a flag. pub const RELAY_ENV: &str = "PIXELPASS_RELAY"; /// Resolve the relay override: explicit `--relay` wins, else `PIXELPASS_RELAY`, /// else `None` (use the bundled defaults). pub fn relay_override(flag: Option<&str>) -> Option { flag.map(str::to_owned).or_else(|| { std::env::var(RELAY_ENV) .ok() .filter(|s| !s.trim().is_empty()) }) } /// Bind a **video-plane** endpoint (host/viewer) with an ephemeral identity. /// /// With no `relay` override we use [`presets::N0`] — n0 DNS discovery, the /// library's default relays, and the chosen crypto provider. With an override /// we keep all of that but swap in a single custom relay via /// [`RelayMode::Custom`]; this is how a user gets off the rc's bundled /// (canary-grade) relays or points at a self-hosted one. Discovery is /// unchanged, so peers still resolve each other by endpoint id. pub async fn bind(relay: Option<&str>) -> Result { // No `secret_key` set → iroh mints a fresh ephemeral keypair for this run. bind_with(relay, None, ALPN).await } /// Bind the **control-plane** endpoint with the machine's persistent identity /// (see [`super::identity`]) and the friends [`super::alpn::CONTROL_ALPN`]. Its /// `EndpointId` is the stable id friends know you by. #[cfg(feature = "gui")] pub async fn bind_control(relay: Option<&str>) -> Result { let secret_key = super::identity::load_or_create()?; bind_with(relay, Some(secret_key), super::alpn::CONTROL_ALPN).await } /// Shared builder: optional persistent key (None → ephemeral) + the plane's ALPN. async fn bind_with( relay: Option<&str>, key: Option, alpn: &[u8], ) -> Result { let mut builder = Endpoint::builder(presets::N0).alpns(vec![alpn.to_vec()]); if let Some(key) = key { builder = builder.secret_key(key); } if let Some(url) = relay { let url = RelayUrl::from_str(url).with_context(|| { format!("invalid relay URL {url:?} (expected e.g. https://relay.example/)") })?; builder = builder.relay_mode(RelayMode::Custom(RelayMap::from(url))); } builder .bind() .await .context("failed to bind the iroh endpoint") }