community: the encrypted inbox, friend requests and shares travel sealed to a box key when the other side has published one

This commit is contained in:
KeelrLeek committed 2026-09-02 15:26:30 -05:00
1 parent 13c44f731a
commit 4378c4c0cc
14 files changed
+1387 -145

No files matched your search

+76 -17
View File
@@ -28,6 +28,7 @@ use persona_state::slsk_link::normalize_username;
use serde::{Deserialize, Serialize};
use tauri::{Emitter, Manager, State};
use crate::inbox::{self, Cargo, ShareCargo};
use crate::persona;
use crate::pipeline::LibraryState;
use crate::playlist_formats::{ParsedEntry, ParsedPlaylist};
@@ -59,7 +60,14 @@ pub struct Published {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShareCard {
pub id: String,
/// Empty when it arrived sealed from a persona this device has no
/// Soulseek account for; `from_key` is then the only name.
pub from_username: String,
#[serde(default)]
pub from_key: String,
/// "pm" | "inbox" — how it arrived.
#[serde(default = "carry_pm")]
pub carry: String,
pub owner_key: String,
pub playlist_uuid: String,
pub name: String,
@@ -67,6 +75,10 @@ pub struct ShareCard {
pub received_at: u64,
}
fn carry_pm() -> String {
"pm".into()
}
#[derive(Default, Serialize, Deserialize)]
struct Ledger {
#[serde(default)]
@@ -452,6 +464,24 @@ pub async fn send(app: &tauri::AppHandle, username: &str, playlist_id: i64) -> R
name: rec.name.clone(),
entry_count: rec.entry_count,
};
// Sealed through their inbox when they have one; the Soulseek carry otherwise.
if let Some(link) = crate::slsk_link::known_link(app, username) {
if let Ok(their) = bytes32(&link.persona_key) {
let cargo = Cargo::Share(ShareCargo {
owner,
playlist_uuid: r.playlist_uuid,
name: r.name.clone(),
entry_count: r.entry_count,
});
match inbox::send(app, &their, &cargo).await {
Ok(()) => {
log::info!("shared playlists: sent {:?} to {username} via inbox", rec.name);
return Ok(());
}
Err(e) => log::info!("shared playlists: inbox carry failed ({e}); trying the Soulseek carry"),
}
}
}
use base64::Engine;
let bytes = bincode::serialize(&r).map_err(|e| e.to_string())?;
let text = format!("{SHARE_PREFIX}{}", base64::engine::general_purpose::STANDARD.encode(bytes));
@@ -460,6 +490,38 @@ pub async fn send(app: &tauri::AppHandle, username: &str, playlist_id: i64) -> R
Ok(())
}
fn keep_card(app: &tauri::AppHandle, card: ShareCard) {
let who = if card.from_username.is_empty() { card.from_key.clone() } else { card.from_username.clone() };
let mut ledger = load(app);
ledger.inbox.retain(|c| c.id != card.id);
ledger.inbox.push(card);
if let Err(e) = save(app, &ledger) {
log::warn!("shared playlists: could not keep the share from {who}: {e}");
} else {
log::info!("shared playlists: share received from {who}");
}
}
/// Sealed cargo from inbox.rs: the envelope's signer is the sender.
pub(crate) fn on_inbox_share(app: &tauri::AppHandle, sender: [u8; 32], s: ShareCargo) {
let from_key = b58(&sender);
let from_username = crate::slsk_link::username_for_key(app, &from_key).unwrap_or_default();
keep_card(
app,
ShareCard {
id: format!("{}:{}:{}", from_key, b58(&s.owner), b58(&s.playlist_uuid)),
from_username,
from_key,
carry: "inbox".into(),
owner_key: b58(&s.owner),
playlist_uuid: b58(&s.playlist_uuid),
name: cap(&s.name, pl::MAX_NAME_BYTES),
entry_count: s.entry_count,
received_at: now_secs(),
},
);
}
/// The event pump hands private messages here after the link and friend
/// handshakes. True means it was a share reference, now a card in the Inbox.
pub fn intercept_pm(app: &tauri::AppHandle, from: &str, message: &str) -> bool {
@@ -467,23 +529,20 @@ pub fn intercept_pm(app: &tauri::AppHandle, from: &str, message: &str) -> bool {
use base64::Engine;
let Ok(bytes) = base64::engine::general_purpose::STANDARD.decode(rest.trim()) else { return true };
let Ok(r) = bincode::deserialize::<ShareRef>(&bytes) else { return true };
let card = ShareCard {
id: format!("{}:{}:{}", normalize_username(from), b58(&r.owner), b58(&r.playlist_uuid)),
from_username: from.to_string(),
owner_key: b58(&r.owner),
playlist_uuid: b58(&r.playlist_uuid),
name: cap(&r.name, pl::MAX_NAME_BYTES),
entry_count: r.entry_count,
received_at: now_secs(),
};
let mut ledger = load(app);
ledger.inbox.retain(|c| c.id != card.id);
ledger.inbox.push(card);
if let Err(e) = save(app, &ledger) {
log::warn!("shared playlists: could not keep the share from {from}: {e}");
} else {
log::info!("shared playlists: share received from {from}");
}
keep_card(
app,
ShareCard {
id: format!("{}:{}:{}", normalize_username(from), b58(&r.owner), b58(&r.playlist_uuid)),
from_username: from.to_string(),
from_key: String::new(),
carry: "pm".into(),
owner_key: b58(&r.owner),
playlist_uuid: b58(&r.playlist_uuid),
name: cap(&r.name, pl::MAX_NAME_BYTES),
entry_count: r.entry_count,
received_at: now_secs(),
},
);
true
}