Skip to main content

hashiverse_client_wasm/
wasm_bootstrap_provider.rs

1use 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
8// Only providers that return Access-Control-Allow-Origin: * are usable from the browser.
9// Tested 2026-03-31: Cloudflare and Google pass; Quad9, NextDNS, AdGuard do not.
10const DOH_PROVIDERS: &[&str] = &[
11    "https://cloudflare-dns.com/dns-query",
12    "https://dns.google/resolve",
13    // "https://dns.quad9.net/dns-query", // Currently does not support CORS (queries from browser - let's check in occasionally)
14];
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
29/// Bootstrap provider that performs DNSSEC-validated DNS resolution using the
30/// browser's Fetch API to query DNS-over-HTTPS (DoH) providers directly.
31/// This is the WASM equivalent of `DnssecBootstrapProvider` which uses
32/// `hickory-resolver` for native targets.
33pub 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) // A records only
66                .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}