use crate::wallet;
const EPH_KEY: &str = "lh_seed_eph";
const GUARD: &str = "lh_seed_pull_tried";
const APEX: &str = "https://localharness.xyz";
fn session() -> Option<web_sys::Storage> {
web_sys::window().and_then(|w| w.session_storage().ok().flatten())
}
fn hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push_str(&format!("{b:02x}"));
}
s
}
fn unhex(s: &str) -> Option<Vec<u8>> {
let t = s.trim().trim_start_matches("0x").trim_start_matches("0X");
if t.len() % 2 != 0 {
return None;
}
let b = t.as_bytes();
let mut out = Vec::with_capacity(t.len() / 2);
let nib = |c: u8| -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
};
let mut i = 0;
while i < b.len() {
out.push((nib(b[i])? << 4) | nib(b[i + 1])?);
i += 2;
}
Some(out)
}
pub(crate) async fn kick_export(name: &str) -> bool {
let Some(storage) = session() else { return false };
let (eph_pub, eph_signer) = wallet::ephemeral_keypair();
let eph_priv_hex = hex(&eph_signer.to_bytes());
if storage.set_item(EPH_KEY, &eph_priv_hex).is_err() {
return false;
}
let _ = storage.set_item(GUARD, "1");
let url = format!("{APEX}/?seed_export=1&to={name}#epk={}", hex(&eph_pub));
if let Some(window) = web_sys::window() {
return window.location().set_href(&url).is_ok();
}
false
}
pub(crate) async fn maybe_auto_kick(name: &str) -> bool {
if super::wallet_store::load().await.is_some() {
return false; }
let Some(storage) = session() else { return false };
if storage.get_item(GUARD).ok().flatten().is_some() {
return false; }
kick_export(name).await
}
pub(crate) async fn handle_apex_export() {
let to = super::read_query_param("to")
.map(|s| super::decode_uri_component(&s))
.unwrap_or_default();
let to_ok = !to.is_empty()
&& to.len() <= 63
&& to.chars().all(|c| c.is_ascii_alphanumeric() || c == '-');
let epk_hex = super::read_fragment_param("epk").unwrap_or_default();
if !to_ok || epk_hex.is_empty() {
super::paint_apex(super::tenant::Host::Apex).await;
return;
}
let sealed_hex = seal_seed_for(&to, &epk_hex).await;
let url = match sealed_hex {
Some(ct_hex) => format!("https://{to}.localharness.xyz/?seed_import=1#s={ct_hex}"),
None => format!("https://{to}.localharness.xyz/?seed_import=none"),
};
if let Some(window) = web_sys::window() {
let _ = window.location().set_href(&url);
}
}
async fn seal_seed_for(to: &str, epk_hex: &str) -> Option<String> {
let epk = unhex(epk_hex)?;
let wallet = super::wallet_store::load().await?;
let owner = super::registry::owner_of_name(to).await.ok().flatten()?;
if !owner.eq_ignore_ascii_case(&wallet.address_hex()) {
return None;
}
let mnemonic = wallet.mnemonic.to_string();
let ct = super::encryption::ecies_seal(&epk, mnemonic.as_bytes()).await?;
Some(hex(&ct))
}
pub(crate) async fn handle_tenant_import() -> bool {
let mode = super::read_query_param("seed_import").unwrap_or_default();
let imported = if mode == "1" { try_import().await } else { false };
if let Some(storage) = session() {
let _ = storage.remove_item(EPH_KEY);
}
scrub_url();
imported
}
async fn try_import() -> bool {
let Some(ct_hex) = super::read_fragment_param("s") else { return false };
let Some(ct) = unhex(&ct_hex) else { return false };
let Some(storage) = session() else { return false };
let Some(eph_hex) = storage.get_item(EPH_KEY).ok().flatten() else { return false };
let Ok(eph_signer) = wallet::from_private_key_hex(eph_hex.trim()) else { return false };
let Some(pt) = super::encryption::ecies_open(&eph_signer, &ct).await else { return false };
let Ok(phrase) = String::from_utf8(pt) else { return false };
super::wallet_store::import(phrase.trim()).await.is_ok()
}
fn scrub_url() {
let Some(window) = web_sys::window() else { return };
let Ok(history) = window.history() else { return };
let path = window.location().pathname().unwrap_or_else(|_| "/".to_string());
let _ = history.replace_state_with_url(&wasm_bindgen::JsValue::NULL, "", Some(&path));
}