1use std::{str::FromStr, time::Duration};
24
25use anyhow::{Context, Result, anyhow};
26use iroh::{Endpoint, RelayMap, RelayMode, RelayUrl, SecretKey, endpoint::presets::Preset};
27
28use crate::{
29 ClientBuilder,
30 api_secret::{API_SECRET_ENV_VAR_NAME, ApiSecret},
31 caps::{Cap, Caps, DEFAULT_CAP_EXPIRY},
32};
33
34#[derive(Debug, Clone)]
38pub struct IrohServicesPreset {
39 secret_key: SecretKey,
40 relays: RelayMap,
41 api_secret: ApiSecret,
43}
44
45impl IrohServicesPreset {
46 pub fn builder() -> PresetBuilder {
49 preset()
50 }
51
52 pub fn api_secret(&self) -> &ApiSecret {
56 &self.api_secret
57 }
58
59 pub fn client_builder(&self, endpoint: &Endpoint) -> ClientBuilder {
60 ClientBuilder::new(endpoint)
61 .api_secret(self.api_secret.clone())
62 .unwrap()
63 }
64}
65
66impl Preset for IrohServicesPreset {
67 fn apply(self, builder: iroh::endpoint::Builder) -> iroh::endpoint::Builder {
68 let mut builder = iroh::endpoint::presets::N0.apply(builder);
71 builder = builder.relay_mode(RelayMode::Custom(self.relays));
72 builder = builder.secret_key(self.secret_key);
73 builder
74 }
75}
76
77#[derive(Debug, Clone)]
80pub struct PresetBuilder {
81 cap_expiry: Duration,
82 secret_key: Option<SecretKey>,
83 relays: RelayMap,
84 api_secret: Option<ApiSecret>,
85}
86
87pub fn preset() -> PresetBuilder {
91 PresetBuilder {
92 cap_expiry: DEFAULT_CAP_EXPIRY,
93 secret_key: None,
94 relays: iroh::endpoint::default_relay_mode().relay_map(),
95 api_secret: None,
96 }
97}
98
99impl PresetBuilder {
100 pub fn secret_key(mut self, secret_key: SecretKey) -> Self {
103 self.secret_key = Some(secret_key);
104 self
105 }
106
107 pub fn relays<I, S>(mut self, relays: I) -> Result<Self>
108 where
109 I: IntoIterator<Item = S>,
110 S: AsRef<str>,
111 {
112 let parsed = relays
113 .into_iter()
114 .map(|s| {
115 let s = s.as_ref();
116 s.parse::<RelayUrl>()
117 .with_context(|| format!("invalid relay url {s:?}"))
118 })
119 .collect::<anyhow::Result<Vec<_>>>()?;
120
121 self.relays = RelayMap::from_iter(parsed);
122 Ok(self)
123 }
124
125 pub fn relay_mode(mut self, mode: RelayMode) -> Self {
128 self.relays = mode.relay_map();
129 self
130 }
131
132 pub fn relay_map(mut self, map: RelayMap) -> Self {
134 self.relays = map;
135 self
136 }
137
138 pub fn api_secret_from_env(self) -> Result<Self> {
140 let ticket = ApiSecret::from_env_var(API_SECRET_ENV_VAR_NAME)?;
141 Ok(self.api_secret(ticket))
142 }
143
144 pub fn api_secret_from_str(self, secret_key: &str) -> Result<Self> {
146 let key = ApiSecret::from_str(secret_key).context("invalid iroh services api secret")?;
147 Ok(self.api_secret(key))
148 }
149
150 pub fn api_secret(mut self, api_secret: ApiSecret) -> Self {
153 self.api_secret = Some(api_secret);
154 self
155 }
156
157 pub fn build(self) -> Result<IrohServicesPreset> {
159 let secret_key = self.secret_key.unwrap_or_else(SecretKey::generate);
160
161 let Some(api_secret) = self.api_secret else {
162 return Err(anyhow!(
163 "api secret is required to use iroh_services relay preset"
164 ));
165 };
166
167 let rcan = crate::caps::create_api_token_from_secret_key(
169 api_secret.secret.clone(),
170 secret_key.public(),
171 self.cap_expiry,
172 Caps::new([Cap::Relay(crate::caps::RelayCap::Use)]),
173 )?;
174
175 let mut token = data_encoding::BASE32_NOPAD.encode(&rcan.encode());
176 token.make_ascii_lowercase();
177
178 let relays = self.relays.with_auth_token(token);
179
180 Ok(IrohServicesPreset {
181 secret_key,
182 relays,
183 api_secret,
184 })
185 }
186}