use std::path::Path;
use anyhow::{Context, Result};
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine as _;
use parley_core::AgentPubkey;
use parley_mls::{Group, PartyKeys, __test_helpers};
use crate::client::Client;
use crate::state::{
load_channels, load_friends, save_channels, save_friends, ChannelEntry, Channels,
};
pub struct OpenedChannel {
pub group: Group,
pub channel_id: String,
#[allow(dead_code)] pub recipient_pk_b64: String,
}
pub async fn open_or_create_channel(
home: &Path,
party: &PartyKeys,
client: &Client,
recipient: &str,
) -> Result<OpenedChannel> {
let mut friends = load_friends(home).unwrap_or_default();
let recipient_pk_b64 = match friends.resolve(recipient) {
Some(pk) => pk,
None => match client.resolve_handle(recipient).await? {
Some(pk) => {
println!("resolved '{recipient}' via server: {pk}");
friends.by_name.insert(recipient.to_string(), pk.clone());
save_friends(home, &friends)?;
println!("(cached as a local friend; next send is instant)");
pk
}
None => {
anyhow::bail!(
"unknown recipient `{recipient}` — \
not a local alias, not a 43-char pubkey, \
and the server has no handle by that name"
);
}
},
};
let recipient_pk: AgentPubkey = recipient_pk_b64.parse().context("recipient pubkey")?;
let mut channels = load_channels(home).unwrap_or_default();
if let Some(entry) = channels.by_friend.get(&recipient_pk_b64).cloned() {
let gid_bytes = URL_SAFE_NO_PAD.decode(&entry.mls_group_id)?;
let group = Group::load(party, &gid_bytes)
.map_err(|e| anyhow::anyhow!("load group: {e}"))?
.ok_or_else(|| {
anyhow::anyhow!(
"MLS group {} missing from local storage",
entry.mls_group_id
)
})?;
return Ok(OpenedChannel {
group,
channel_id: entry.channel_id,
recipient_pk_b64,
});
}
let claim = client.claim_key_package(&recipient_pk).await?;
let kp_b64 = claim
.get("blob")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("claim response missing blob"))?;
let kp_bytes = URL_SAFE_NO_PAD.decode(kp_b64)?;
let recipient_kp = __test_helpers::parse_key_package(&kp_bytes)
.map_err(|e| anyhow::anyhow!("parse keypackage: {e}"))?;
let create = Group::create_with_members(party, &[recipient_kp])
.map_err(|e| anyhow::anyhow!("create group: {e}"))?;
let welcomes: Vec<(AgentPubkey, Vec<u8>)> = create
.welcomes
.iter()
.map(|w| (AgentPubkey::from_bytes(w.recipient), w.blob.clone()))
.collect();
let resp = client
.create_private_channel(None, &create.group_info, &create.ratchet_tree, welcomes)
.await?;
let chan_id = resp
.get("channel_id")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("create response missing channel_id"))?
.to_owned();
let mls_group_id = URL_SAFE_NO_PAD.encode(create.group.mls_group_id());
channels.by_friend.insert(
recipient_pk_b64.clone(),
ChannelEntry {
channel_id: chan_id.clone(),
mls_group_id,
},
);
save_channels(home, &channels)?;
let _: Channels = channels; Ok(OpenedChannel {
group: create.group,
channel_id: chan_id,
recipient_pk_b64,
})
}