use crate::app::{dom, templates};
pub(super) fn pair_cancel_pressed() {
dom::swap_outer(
"pair-slot",
r#"<div id="pair-slot" class="pair-slot"><button id="pair-btn" type="button" data-action="add-device" class="ghost">add a device</button></div>"#,
);
dom::swap_inner("pair-msg", "");
}
fn code_key(code: &str) -> [u8; 32] {
crate::wallet::adopt_code_key(code)
}
fn hex_to_bytes(s: &str) -> Option<Vec<u8>> {
crate::encoding::hex_to_bytes(s).ok().filter(|v| !v.is_empty())
}
pub(super) fn run_sync_devices() {
dom::swap_inner(
"pair-msg",
"<span style=\"color:var(--muted)\">discovering devices…</span>",
);
wasm_bindgen_futures::spawn_local(async move {
let msg = match crate::app::teams_sync::sync_my_devices().await {
Ok(0) => {
"no other devices online — open this agent on another device and sync there too"
.to_string()
}
Ok(n) => format!("connected — syncing with {n} device(s)"),
Err(e) => format!("sync failed: {e}"),
};
dom::swap_inner("pair-msg", &dom::msg_span(dom::Msg::Muted, &msg));
});
}
pub(super) fn add_device_pressed() {
let phrase = crate::app::APP
.with(|cell| cell.borrow().wallet.as_ref().map(|w| w.mnemonic.to_string()));
let Some(phrase) = phrase else {
dom::swap_inner("pair-msg", &dom::msg_span(dom::Msg::Error, "no identity on this device"));
return;
};
wasm_bindgen_futures::spawn_local(async move {
let code = generate_pair_code();
let Some(ct) = crate::app::encryption::seal_with_raw_key(&code_key(&code), phrase.as_bytes()).await
else {
dom::swap_inner("pair-msg", &dom::msg_span(dom::Msg::Error, "encrypt failed"));
return;
};
let hex = crate::encoding::bytes_to_hex(&ct);
let url = format!("https://localharness.xyz/?adopt=1#s={hex}");
dom::swap_outer("pair-slot", &templates::adopt_panel(&code, &url).into_string());
dom::swap_inner("pair-msg", "");
});
}
pub(super) fn adopt_device_pressed() {
let code = dom::input_by_id("adopt-code").map(|i| i.value()).unwrap_or_default();
let ct_hex = dom::input_by_id("adopt-ct").map(|i| i.value()).unwrap_or_default();
if code.trim().is_empty() {
return;
}
wasm_bindgen_futures::spawn_local(async move {
let Some(ct) = hex_to_bytes(&ct_hex) else {
dom::swap_inner("adopt-msg", &dom::msg_span(dom::Msg::Error, "bad link — rescan the QR"));
return;
};
match crate::app::encryption::open_with_raw_key(&code_key(&code), &ct).await {
Some(bytes) => {
let phrase = String::from_utf8_lossy(&bytes).into_owned();
match crate::app::wallet_store::import(phrase.trim()).await {
Ok(_) => {
if let Ok(window) = dom::window() {
let _ = window.location().set_href("https://localharness.xyz/");
}
}
Err(err) => {
dom::swap_inner("adopt-msg", &dom::msg_span(dom::Msg::Error, &format!("import failed: {err}")));
}
}
}
None => {
dom::swap_inner("adopt-msg", &dom::msg_span(dom::Msg::Error, "wrong code"));
}
}
});
}
fn generate_pair_code() -> String {
const ALPHABET: &[u8] = b"23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
let mut bytes = [0u8; 8];
let _ = getrandom::getrandom(&mut bytes);
bytes
.iter()
.map(|b| ALPHABET[(*b as usize) % ALPHABET.len()] as char)
.collect()
}