hashiverse_client_wasm/
wasm_bootstrap_provider.rs1use gloo_net::http::Request;
2use hashiverse_lib::tools::{config, tools};
3use hashiverse_lib::transport::bootstrap_provider::bootstrap_provider::BootstrapProvider;
4use log::warn;
5use send_wrapper::SendWrapper;
6use std::collections::HashSet;
7
8const DOH_PROVIDERS: &[&str] = &[
11 "https://cloudflare-dns.com/dns-query",
12 "https://dns.google/resolve",
13 ];
15
16#[derive(serde::Deserialize)]
17struct DoHResponse {
18 #[serde(rename = "Answer")]
19 answer: Option<Vec<DoHRecord>>,
20}
21
22#[derive(serde::Deserialize)]
23struct DoHRecord {
24 #[serde(rename = "type")]
25 record_type: u16,
26 data: String,
27}
28
29pub struct WasmBootstrapProvider {}
34
35impl WasmBootstrapProvider {
36 pub fn new() -> Self {
37 Self {}
38 }
39
40 async fn query_doh(doh_provider: &str, domain: &str) -> Vec<String> {
41 let url = format!("{}?name={}&type=A", doh_provider, domain);
42 let fetch_future = async move {
43 let response = Request::get(&url)
44 .header("Accept", "application/dns-json")
45 .send()
46 .await
47 .map_err(|e| anyhow::anyhow!("DoH fetch error: {}", e))?;
48
49 let text = response
50 .text()
51 .await
52 .map_err(|e| anyhow::anyhow!("DoH read error: {}", e))?;
53
54 let doh_response: DoHResponse = serde_json::from_str(&text)
55 .map_err(|e| anyhow::anyhow!("DoH parse error: {}", e))?;
56
57 Ok::<DoHResponse, anyhow::Error>(doh_response)
58 };
59
60 match SendWrapper::new(fetch_future).await {
61 Ok(doh_response) => doh_response
62 .answer
63 .unwrap_or_default()
64 .into_iter()
65 .filter(|record| record.record_type == 1) .map(|record| format!("{}:443", record.data))
67 .collect(),
68 Err(e) => {
69 warn!("DoH query failed for {} via {}: {}", domain, doh_provider, e);
70 vec![]
71 }
72 }
73 }
74}
75
76#[async_trait::async_trait]
77impl BootstrapProvider for WasmBootstrapProvider {
78 async fn get_bootstrap_addresses(&self) -> Vec<String> {
79 let bootstrap_config = SendWrapper::new(crate::wasm_local_settings::local_settings_get("bootstrap"))
80 .await
81 .unwrap_or(None)
82 .unwrap_or_else(|| "production".to_string());
83
84 match bootstrap_config.as_str() {
85 "production" => {
86 let mut addresses = HashSet::new();
87 for domain in config::BOOTSTRAP_DOMAINS {
88 for doh_provider in DOH_PROVIDERS {
89 for address in Self::query_doh(doh_provider, domain).await {
90 addresses.insert(address);
91 }
92 }
93 }
94 let mut addresses: Vec<String> = addresses.into_iter().collect();
95 tools::shuffle(&mut addresses);
96 addresses
97 }
98 "local" => vec!["127.0.0.1:443".to_string()],
99 manual => {
100 let mut addresses: Vec<String> = manual.split(',').map(|s| s.trim().to_string()).collect();
101 tools::shuffle(&mut addresses);
102 addresses
103 }
104 }
105 }
106}