Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1390,7 +1390,11 @@ pub async fn publish_nostr_profile(
lud16: Option<String>,
banner: Option<String>,
) -> Result<(), String> {
let (keys, client) = get_keys_and_client(&app).await?;
// Use the signer-trait path so both local nsec users and NIP-46
// remote signers can update kind:0 — `get_keys_and_client`
// would have errored out for remote signers because the signer
// backend isn't `Keys`.
let (signer, client) = get_signer_and_client(&app).await?;
let profile = discovery::NostrProfile {
picture,
banner,
Expand All @@ -1401,25 +1405,35 @@ pub async fn publish_nostr_profile(
nip05,
lud16,
};
discovery::publish_profile(&keys, &client, &profile).await
discovery::publish_profile(&signer, &client, &profile).await
}

/// Create a NIP-98 HTTP Auth header (kind 27235) for authenticated uploads.
/// Create a NIP-98 HTTP Auth header (kind 27235) for authenticated
/// uploads. Goes through the `NostrSigner` trait so remote-signer
/// users can also authenticate uploads — image hosts like
/// blossom.primal.net use NIP-98 to verify the uploader, and
/// hardcoding to local keys would break that flow for NIP-46 users.
#[tauri::command]
pub async fn create_nip98_auth(
app: tauri::AppHandle,
url: String,
method: String,
) -> Result<String, String> {
let (keys, _client) = get_keys_and_client(&app).await?;
let (signer, _client) = get_signer_and_client(&app).await?;
use nostr_sdk::prelude::*;
let event = EventBuilder::new(Kind::Custom(27235), "")
let author = signer
.get_public_key()
.await
.map_err(|e| format!("get_public_key failed: {e}"))?;
let unsigned = EventBuilder::new(Kind::Custom(27235), "")
.tags(vec![
Tag::parse(vec!["u".to_string(), url]).map_err(|e| format!("tag error: {e}"))?,
Tag::parse(vec!["method".to_string(), method.to_uppercase()])
.map_err(|e| format!("tag error: {e}"))?,
])
.sign(&keys)
.build(author);
let event = signer
.sign_event(unsigned)
.await
.map_err(|e| format!("sign error: {e}"))?;
let json = event.as_json();
Expand Down
22 changes: 16 additions & 6 deletions src-tauri/src/discovery.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use nostr_sdk::{
Client, Event, EventBuilder, Filter, Keys, Kind, PublicKey, SecretKey, Tag, TagKind,
Client, Event, EventBuilder, Filter, Keys, Kind, NostrSigner, PublicKey, SecretKey, Tag,
TagKind,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
Expand Down Expand Up @@ -403,9 +404,11 @@ pub async fn fetch_profile(
}

/// Publish a kind 0 metadata event with profile fields.
/// Fetches the existing kind 0 first and merges to avoid clobbering unset fields.
/// Fetches the existing kind 0 first and merges to avoid clobbering
/// unset fields. Signs through the `NostrSigner` trait so both local
/// keys and NIP-46 remote signers work without branching.
pub async fn publish_profile(
keys: &Keys,
signer: &std::sync::Arc<dyn NostrSigner>,
client: &Client,
profile: &NostrProfile,
) -> Result<(), String> {
Expand All @@ -414,11 +417,16 @@ pub async fn publish_profile(
.as_deref()
.ok_or_else(|| "profile name is required".to_string())?;

let author_pubkey = signer
.get_public_key()
.await
.map_err(|e| format!("get_public_key failed: {e}"))?;

// Fetch existing kind 0 to merge with
let mut meta = {
let filter = Filter::new()
.kind(Kind::Metadata)
.author(keys.public_key())
.author(author_pubkey)
.limit(1);
let events = client
.fetch_events(vec![filter], Duration::from_secs(5))
Expand Down Expand Up @@ -466,9 +474,11 @@ pub async fn publish_profile(
}

let content = meta.to_string();
let event = EventBuilder::new(Kind::Metadata, content)
let unsigned = EventBuilder::new(Kind::Metadata, content)
.tags(vec![deadcat_sdk::client_tag()])
.sign(keys)
.build(author_pubkey);
let event = signer
.sign_event(unsigned)
.await
.map_err(|e| format!("failed to sign profile event: {e}"))?;
client
Expand Down
Loading