wip: rework multi account
This commit is contained in:
@@ -9,7 +9,7 @@ use crate::{common::get_all_accounts, Nostr};
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||||||
struct Account {
|
struct Account {
|
||||||
password: String,
|
secret_key: String,
|
||||||
nostr_connect: Option<String>,
|
nostr_connect: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,74 +21,58 @@ pub fn get_accounts() -> Vec<String> {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn watch_account(key: String, state: State<'_, Nostr>) -> Result<String, String> {
|
pub async fn watch_account(id: String, state: State<'_, Nostr>) -> Result<String, String> {
|
||||||
let public_key = PublicKey::from_str(&key).map_err(|e| e.to_string())?;
|
let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?;
|
||||||
let bech32 = public_key.to_bech32().map_err(|e| e.to_string())?;
|
let npub = public_key.to_bech32().map_err(|e| e.to_string())?;
|
||||||
let keyring = Entry::new("Lume Secret Storage", &bech32).map_err(|e| e.to_string())?;
|
let keyring = Entry::new("Lume Safe Storage", &npub).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// Set empty password
|
||||||
keyring.set_password("").map_err(|e| e.to_string())?;
|
keyring.set_password("").map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
// Run sync for this account
|
|
||||||
// run_sync_for(public_key, app_handle);
|
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
state.accounts.lock().unwrap().push(bech32.clone());
|
state.accounts.lock().unwrap().push(npub.clone());
|
||||||
|
|
||||||
Ok(bech32)
|
Ok(npub)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn import_account(
|
pub async fn import_account(
|
||||||
key: String,
|
key: String,
|
||||||
password: String,
|
password: Option<String>,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
let (npub, enc_bech32, signer) = match key.starts_with("ncryptsec") {
|
// Create secret key
|
||||||
true => {
|
let secret_key = if let Some(pw) = password {
|
||||||
let enc = EncryptedSecretKey::from_bech32(key).map_err(|err| err.to_string())?;
|
let enc = EncryptedSecretKey::from_bech32(key).map_err(|err| err.to_string())?;
|
||||||
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
|
enc.to_secret_key(pw).map_err(|err| err.to_string())?
|
||||||
let secret_key = enc.to_secret_key(password).map_err(|err| err.to_string())?;
|
} else {
|
||||||
let keys = Keys::new(secret_key);
|
SecretKey::from_str(&key).map_err(|err| err.to_string())?
|
||||||
let npub = keys
|
|
||||||
.public_key()
|
|
||||||
.to_bech32()
|
|
||||||
.map_err(|err| err.to_string())?;
|
|
||||||
let signer = NostrSigner::Keys(keys);
|
|
||||||
|
|
||||||
(npub, enc_bech32, signer)
|
|
||||||
}
|
|
||||||
false => {
|
|
||||||
let secret_key = SecretKey::from_bech32(key).map_err(|err| err.to_string())?;
|
|
||||||
let keys = Keys::new(secret_key.clone());
|
|
||||||
let npub = keys.public_key().to_bech32().unwrap();
|
|
||||||
let signer = NostrSigner::Keys(keys);
|
|
||||||
let enc = EncryptedSecretKey::new(&secret_key, password, 16, KeySecurity::Medium)
|
|
||||||
.map_err(|err| err.to_string())?;
|
|
||||||
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
|
|
||||||
|
|
||||||
(npub, enc_bech32, signer)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let keyring = Entry::new("Lume Secret Storage", &npub).map_err(|e| e.to_string())?;
|
let hex = secret_key.to_secret_hex();
|
||||||
|
let keys = Keys::new(secret_key);
|
||||||
|
|
||||||
|
let npub = keys
|
||||||
|
.public_key()
|
||||||
|
.to_bech32()
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let signer = NostrSigner::Keys(keys);
|
||||||
|
let keyring = Entry::new("Lume Safe Storage", &npub).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let account = Account {
|
let account = Account {
|
||||||
password: enc_bech32,
|
secret_key: hex,
|
||||||
nostr_connect: None,
|
nostr_connect: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Save secret key to keyring
|
||||||
let pwd = serde_json::to_string(&account).map_err(|e| e.to_string())?;
|
let pwd = serde_json::to_string(&account).map_err(|e| e.to_string())?;
|
||||||
keyring.set_password(&pwd).map_err(|e| e.to_string())?;
|
keyring.set_password(&pwd).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
// Run sync for this account
|
|
||||||
// run_sync_for(public_key, app_handle);
|
|
||||||
|
|
||||||
// Update signer
|
// Update signer
|
||||||
client.set_signer(Some(signer)).await;
|
client.set_signer(Some(signer)).await;
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
state.accounts.lock().unwrap().push(npub.clone());
|
state.accounts.lock().unwrap().push(npub.clone());
|
||||||
|
|
||||||
@@ -121,20 +105,18 @@ pub async fn connect_account(uri: String, state: State<'_, Nostr>) -> Result<Str
|
|||||||
url.query_pairs_mut().clear().extend_pairs(&query);
|
url.query_pairs_mut().clear().extend_pairs(&query);
|
||||||
|
|
||||||
let key = format!("{}_nostrconnect", remote_npub);
|
let key = format!("{}_nostrconnect", remote_npub);
|
||||||
let keyring = Entry::new("Lume Secret Storage", &key).unwrap();
|
let keyring = Entry::new("Lume Safe Storage", &key).unwrap();
|
||||||
let account = Account {
|
let account = Account {
|
||||||
password: app_secret,
|
secret_key: app_secret,
|
||||||
nostr_connect: Some(url.to_string()),
|
nostr_connect: Some(url.to_string()),
|
||||||
};
|
};
|
||||||
let j = serde_json::to_string(&account).map_err(|e| e.to_string())?;
|
|
||||||
let _ = keyring.set_password(&j);
|
|
||||||
|
|
||||||
// Run sync for this account
|
// Save secret key to keyring
|
||||||
// run_sync_for(public_key, app_handle);
|
let pwd = serde_json::to_string(&account).map_err(|e| e.to_string())?;
|
||||||
|
keyring.set_password(&pwd).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
// Update signer
|
// Update signer
|
||||||
let _ = client.set_signer(Some(signer.into())).await;
|
let _ = client.set_signer(Some(signer.into())).await;
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
state.accounts.lock().unwrap().push(remote_npub.clone());
|
state.accounts.lock().unwrap().push(remote_npub.clone());
|
||||||
|
|
||||||
@@ -158,9 +140,9 @@ pub async fn reset_password(key: String, password: String) -> Result<(), String>
|
|||||||
.map_err(|err| err.to_string())?;
|
.map_err(|err| err.to_string())?;
|
||||||
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
|
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
let keyring = Entry::new("Lume Secret Storage", &npub).map_err(|e| e.to_string())?;
|
let keyring = Entry::new("Lume Safe Storage", &npub).map_err(|e| e.to_string())?;
|
||||||
let account = Account {
|
let account = Account {
|
||||||
password: enc_bech32,
|
secret_key: enc_bech32,
|
||||||
nostr_connect: None,
|
nostr_connect: None,
|
||||||
};
|
};
|
||||||
let j = serde_json::to_string(&account).map_err(|e| e.to_string())?;
|
let j = serde_json::to_string(&account).map_err(|e| e.to_string())?;
|
||||||
@@ -172,16 +154,17 @@ pub async fn reset_password(key: String, password: String) -> Result<(), String>
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn get_private_key(id: String) -> Result<String, String> {
|
pub fn get_private_key(id: String) -> Result<String, String> {
|
||||||
let keyring = Entry::new("Lume Secret Storage", &id).map_err(|e| e.to_string())?;
|
let keyring = Entry::new("Lume Safe Storage", &id).map_err(|e| e.to_string())?;
|
||||||
let password = keyring.get_password().map_err(|e| e.to_string())?;
|
let password = keyring.get_password().map_err(|e| e.to_string())?;
|
||||||
|
let account: Account = serde_json::from_str(&password).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
Ok(password)
|
Ok(account.secret_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn delete_account(id: String) -> Result<(), String> {
|
pub fn delete_account(id: String) -> Result<(), String> {
|
||||||
let keyring = Entry::new("Lume Secret Storage", &id).map_err(|e| e.to_string())?;
|
let keyring = Entry::new("Lume Safe Storage", &id).map_err(|e| e.to_string())?;
|
||||||
let _ = keyring.delete_credential();
|
let _ = keyring.delete_credential();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -195,9 +178,6 @@ pub async fn has_signer(id: String, state: State<'_, Nostr>) -> Result<bool, Str
|
|||||||
|
|
||||||
match client.signer().await {
|
match client.signer().await {
|
||||||
Ok(signer) => {
|
Ok(signer) => {
|
||||||
// Emit reload in front-end
|
|
||||||
// handle.emit("signer", ()).unwrap();
|
|
||||||
|
|
||||||
let signer_key = signer.public_key().await.map_err(|e| e.to_string())?;
|
let signer_key = signer.public_key().await.map_err(|e| e.to_string())?;
|
||||||
let is_match = signer_key == public_key;
|
let is_match = signer_key == public_key;
|
||||||
|
|
||||||
@@ -210,13 +190,12 @@ pub async fn has_signer(id: String, state: State<'_, Nostr>) -> Result<bool, Str
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn set_signer(
|
pub async fn set_signer(
|
||||||
account: String,
|
id: String,
|
||||||
password: String,
|
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
handle: tauri::AppHandle,
|
handle: tauri::AppHandle,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let keyring = Entry::new("Lume Secret Storage", &account).map_err(|e| e.to_string())?;
|
let keyring = Entry::new("Lume Safe Storage", &id).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let account = match keyring.get_password() {
|
let account = match keyring.get_password() {
|
||||||
Ok(pw) => {
|
Ok(pw) => {
|
||||||
@@ -228,11 +207,7 @@ pub async fn set_signer(
|
|||||||
|
|
||||||
match account.nostr_connect {
|
match account.nostr_connect {
|
||||||
None => {
|
None => {
|
||||||
let ncryptsec =
|
let secret_key = SecretKey::from_str(&account.secret_key).map_err(|e| e.to_string())?;
|
||||||
EncryptedSecretKey::from_bech32(account.password).map_err(|e| e.to_string())?;
|
|
||||||
let secret_key = ncryptsec
|
|
||||||
.to_secret_key(password)
|
|
||||||
.map_err(|_| "Wrong password.")?;
|
|
||||||
let keys = Keys::new(secret_key);
|
let keys = Keys::new(secret_key);
|
||||||
let signer = NostrSigner::Keys(keys);
|
let signer = NostrSigner::Keys(keys);
|
||||||
|
|
||||||
@@ -245,7 +220,7 @@ pub async fn set_signer(
|
|||||||
}
|
}
|
||||||
Some(bunker) => {
|
Some(bunker) => {
|
||||||
let uri = NostrConnectURI::parse(bunker).map_err(|e| e.to_string())?;
|
let uri = NostrConnectURI::parse(bunker).map_err(|e| e.to_string())?;
|
||||||
let app_keys = Keys::from_str(&account.password).map_err(|e| e.to_string())?;
|
let app_keys = Keys::from_str(&account.secret_key).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
match Nip46Signer::new(uri, app_keys, Duration::from_secs(120), None) {
|
match Nip46Signer::new(uri, app_keys, Duration::from_secs(120), None) {
|
||||||
Ok(signer) => {
|
Ok(signer) => {
|
||||||
|
|||||||
@@ -290,10 +290,10 @@ pub async fn publish(
|
|||||||
warning: Option<String>,
|
warning: Option<String>,
|
||||||
difficulty: Option<u8>,
|
difficulty: Option<u8>,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
) -> Result<String, String> {
|
) -> Result<bool, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
// Create tags from content
|
// Create event tags from content
|
||||||
let mut tags = create_tags(&content);
|
let mut tags = create_tags(&content);
|
||||||
|
|
||||||
// Add client tag
|
// Add client tag
|
||||||
@@ -319,9 +319,14 @@ pub async fn publish(
|
|||||||
.await
|
.await
|
||||||
.map_err(|err| err.to_string())?;
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
// Publish
|
// Save to local database
|
||||||
match client.send_event(event).await {
|
match client.database().save_event(&event).await {
|
||||||
Ok(event_id) => Ok(event_id.to_hex()),
|
Ok(status) => {
|
||||||
|
// Add event to queue to broadcast it later.
|
||||||
|
state.send_queue.lock().unwrap().insert(event);
|
||||||
|
// Return
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,83 +338,81 @@ pub async fn reply(
|
|||||||
to: String,
|
to: String,
|
||||||
root: Option<String>,
|
root: Option<String>,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
) -> Result<String, String> {
|
) -> Result<bool, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let database = client.database();
|
|
||||||
|
|
||||||
let reply_id = EventId::parse(&to).map_err(|err| err.to_string())?;
|
// Create event tags from content
|
||||||
let mut tags = create_tags(&content);
|
let mut tags = create_tags(&content);
|
||||||
|
|
||||||
match database.query(vec![Filter::new().id(reply_id)]).await {
|
// Add client tag
|
||||||
Ok(events) => {
|
// TODO: allow user config this setting
|
||||||
if let Some(event) = events.first() {
|
tags.push(Tag::custom(TagKind::custom("client"), vec!["Lume"]));
|
||||||
let relay_hint = if let Some(relays) = database
|
|
||||||
.event_seen_on_relays(&event.id)
|
// Get reply event
|
||||||
.await
|
let reply_id = EventId::parse(&to).map_err(|err| err.to_string())?;
|
||||||
.map_err(|err| err.to_string())?
|
let reply_to = match client.database().event_by_id(&reply_id).await {
|
||||||
{
|
Ok(event) => {
|
||||||
relays.into_iter().next().map(UncheckedUrl::new)
|
if let Some(event) = event {
|
||||||
} else {
|
event
|
||||||
None
|
|
||||||
};
|
|
||||||
let t = TagStandard::Event {
|
|
||||||
event_id: event.id,
|
|
||||||
relay_url: relay_hint,
|
|
||||||
marker: Some(Marker::Reply),
|
|
||||||
public_key: Some(event.pubkey),
|
|
||||||
};
|
|
||||||
let tag = Tag::from(t);
|
|
||||||
tags.push(tag)
|
|
||||||
} else {
|
} else {
|
||||||
return Err("Reply event is not found.".into());
|
return Err("Event not found in database, cannot reply.".into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => return Err(err.to_string()),
|
Err(e) => return Err(e.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(id) = root {
|
// Get root event if exist
|
||||||
let root_id = match EventId::from_hex(id) {
|
let root = match root {
|
||||||
Ok(val) => val,
|
Some(id) => {
|
||||||
Err(_) => return Err("Event is not valid.".into()),
|
let root_id = EventId::parse(&id).map_err(|err| err.to_string())?;
|
||||||
};
|
(client.database().event_by_id(&root_id).await).unwrap_or_default()
|
||||||
|
|
||||||
if let Ok(events) = database.query(vec![Filter::new().id(root_id)]).await {
|
|
||||||
if let Some(event) = events.first() {
|
|
||||||
let relay_hint = if let Some(relays) = database
|
|
||||||
.event_seen_on_relays(&event.id)
|
|
||||||
.await
|
|
||||||
.map_err(|err| err.to_string())?
|
|
||||||
{
|
|
||||||
relays.into_iter().next().map(UncheckedUrl::new)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let t = TagStandard::Event {
|
|
||||||
event_id: event.id,
|
|
||||||
relay_url: relay_hint,
|
|
||||||
marker: Some(Marker::Root),
|
|
||||||
public_key: Some(event.pubkey),
|
|
||||||
};
|
|
||||||
let tag = Tag::from(t);
|
|
||||||
tags.push(tag)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
match client.publish_text_note(content, tags).await {
|
let builder = EventBuilder::text_note_reply(content, &reply_to, root.as_ref(), None)
|
||||||
Ok(event_id) => Ok(event_id.to_bech32().map_err(|err| err.to_string())?),
|
.add_tags(tags)
|
||||||
|
.pow(DEFAULT_DIFFICULTY);
|
||||||
|
|
||||||
|
// Sign event
|
||||||
|
let event = client
|
||||||
|
.sign_event_builder(builder)
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
// Save to local database
|
||||||
|
match client.database().save_event(&event).await {
|
||||||
|
Ok(status) => {
|
||||||
|
// Add event to queue to broadcast it later.
|
||||||
|
state.send_queue.lock().unwrap().insert(event);
|
||||||
|
// Return
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn repost(raw: String, state: State<'_, Nostr>) -> Result<String, String> {
|
pub async fn repost(raw: String, state: State<'_, Nostr>) -> Result<bool, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let event = Event::from_json(raw).map_err(|err| err.to_string())?;
|
let event = Event::from_json(raw).map_err(|err| err.to_string())?;
|
||||||
|
let builder = EventBuilder::repost(&event, None);
|
||||||
|
|
||||||
match client.repost(&event, None).await {
|
// Sign event
|
||||||
Ok(event_id) => Ok(event_id.to_string()),
|
let event = client
|
||||||
|
.sign_event_builder(builder)
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
// Save to local database
|
||||||
|
match client.database().save_event(&event).await {
|
||||||
|
Ok(status) => {
|
||||||
|
// Add event to queue to broadcast it later.
|
||||||
|
state.send_queue.lock().unwrap().insert(event);
|
||||||
|
// Return
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,50 +32,12 @@ pub struct Mention {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_profile(id: Option<String>, state: State<'_, Nostr>) -> Result<String, String> {
|
pub async fn get_profile(id: String, state: State<'_, Nostr>) -> Result<String, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let public_key: PublicKey = match id {
|
match client.database().profile(public_key).await {
|
||||||
Some(user_id) => PublicKey::parse(&user_id).map_err(|e| e.to_string())?,
|
Ok(profile) => Ok(profile.metadata().as_json()),
|
||||||
None => {
|
|
||||||
let signer = client.signer().await.map_err(|e| e.to_string())?;
|
|
||||||
signer.public_key().await.map_err(|e| e.to_string())?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let filter = Filter::new()
|
|
||||||
.author(public_key)
|
|
||||||
.kind(Kind::Metadata)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
match client.database().query(vec![filter.clone()]).await {
|
|
||||||
Ok(events) => {
|
|
||||||
if let Some(event) = get_latest_event(&events) {
|
|
||||||
if let Ok(metadata) = Metadata::from_json(&event.content) {
|
|
||||||
Ok(metadata.as_json())
|
|
||||||
} else {
|
|
||||||
Err("Parse metadata failed".into())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match client
|
|
||||||
.fetch_events(vec![filter], Some(Duration::from_secs(10)))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(events) => {
|
|
||||||
if let Some(event) = get_latest_event(&events) {
|
|
||||||
if let Ok(metadata) = Metadata::from_json(&event.content) {
|
|
||||||
Ok(metadata.as_json())
|
|
||||||
} else {
|
|
||||||
Err("Metadata is not valid.".into())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err("Not found.".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => Err(e.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => Err(e.to_string()),
|
Err(e) => Err(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,7 +199,7 @@ pub async fn set_group(
|
|||||||
users: Vec<String>,
|
users: Vec<String>,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
handle: tauri::AppHandle,
|
handle: tauri::AppHandle,
|
||||||
) -> Result<String, String> {
|
) -> Result<bool, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let public_keys: Vec<PublicKey> = users
|
let public_keys: Vec<PublicKey> = users
|
||||||
.iter()
|
.iter()
|
||||||
@@ -257,8 +219,19 @@ pub async fn set_group(
|
|||||||
|
|
||||||
let builder = EventBuilder::follow_set(label, public_keys.clone()).add_tags(tags);
|
let builder = EventBuilder::follow_set(label, public_keys.clone()).add_tags(tags);
|
||||||
|
|
||||||
match client.send_event_builder(builder).await {
|
// Sign event
|
||||||
Ok(report) => {
|
let event = client
|
||||||
|
.sign_event_builder(builder)
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
// Save to local database
|
||||||
|
match client.database().save_event(&event).await {
|
||||||
|
Ok(status) => {
|
||||||
|
// Add event to queue to broadcast it later.
|
||||||
|
state.send_queue.lock().unwrap().insert(event);
|
||||||
|
|
||||||
|
// Sync event
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let state = handle.state::<Nostr>();
|
let state = handle.state::<Nostr>();
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
@@ -274,9 +247,10 @@ pub async fn set_group(
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(report.id().to_hex())
|
// Return
|
||||||
|
Ok(status)
|
||||||
}
|
}
|
||||||
Err(e) => Err(e.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,7 +302,7 @@ pub async fn set_interest(
|
|||||||
hashtags: Vec<String>,
|
hashtags: Vec<String>,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
handle: tauri::AppHandle,
|
handle: tauri::AppHandle,
|
||||||
) -> Result<String, String> {
|
) -> Result<bool, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let label = title.to_lowercase().replace(" ", "-");
|
let label = title.to_lowercase().replace(" ", "-");
|
||||||
let mut tags: Vec<Tag> = vec![Tag::title(title)];
|
let mut tags: Vec<Tag> = vec![Tag::title(title)];
|
||||||
@@ -344,8 +318,19 @@ pub async fn set_interest(
|
|||||||
|
|
||||||
let builder = EventBuilder::interest_set(label, hashtags.clone()).add_tags(tags);
|
let builder = EventBuilder::interest_set(label, hashtags.clone()).add_tags(tags);
|
||||||
|
|
||||||
match client.send_event_builder(builder).await {
|
// Sign event
|
||||||
Ok(report) => {
|
let event = client
|
||||||
|
.sign_event_builder(builder)
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
// Save to local database
|
||||||
|
match client.database().save_event(&event).await {
|
||||||
|
Ok(status) => {
|
||||||
|
// Add event to queue to broadcast it later.
|
||||||
|
state.send_queue.lock().unwrap().insert(event);
|
||||||
|
|
||||||
|
// Sync event
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let state = handle.state::<Nostr>();
|
let state = handle.state::<Nostr>();
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
@@ -361,9 +346,10 @@ pub async fn set_interest(
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(report.id().to_hex())
|
// Return
|
||||||
|
Ok(status)
|
||||||
}
|
}
|
||||||
Err(e) => Err(e.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,7 +434,7 @@ pub async fn set_wallet(uri: &str, state: State<'_, Nostr>) -> Result<bool, Stri
|
|||||||
if let Ok(nwc_uri) = NostrWalletConnectURI::from_str(uri) {
|
if let Ok(nwc_uri) = NostrWalletConnectURI::from_str(uri) {
|
||||||
let nwc = NWC::new(nwc_uri);
|
let nwc = NWC::new(nwc_uri);
|
||||||
let keyring =
|
let keyring =
|
||||||
Entry::new("Lume Secret Storage", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
Entry::new("Lume Safe Storage", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
keyring.set_password(uri).map_err(|e| e.to_string())?;
|
keyring.set_password(uri).map_err(|e| e.to_string())?;
|
||||||
client.set_zapper(nwc).await;
|
client.set_zapper(nwc).await;
|
||||||
@@ -466,7 +452,7 @@ pub async fn load_wallet(state: State<'_, Nostr>) -> Result<(), String> {
|
|||||||
|
|
||||||
if client.zapper().await.is_err() {
|
if client.zapper().await.is_err() {
|
||||||
let keyring =
|
let keyring =
|
||||||
Entry::new("Lume Secret Storage", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
Entry::new("Lume Safe Storage", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
match keyring.get_password() {
|
match keyring.get_password() {
|
||||||
Ok(val) => {
|
Ok(val) => {
|
||||||
@@ -486,8 +472,7 @@ pub async fn load_wallet(state: State<'_, Nostr>) -> Result<(), String> {
|
|||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn remove_wallet(state: State<'_, Nostr>) -> Result<(), String> {
|
pub async fn remove_wallet(state: State<'_, Nostr>) -> Result<(), String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let keyring =
|
let keyring = Entry::new("Lume Safe Storage", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
||||||
Entry::new("Lume Secret Storage", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
match keyring.delete_credential() {
|
match keyring.delete_credential() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
|
|
||||||
pub fn get_all_accounts() -> Vec<String> {
|
pub fn get_all_accounts() -> Vec<String> {
|
||||||
let search = Search::new().expect("Unexpected.");
|
let search = Search::new().expect("Unexpected.");
|
||||||
let results = search.by_service("Lume Secret Storage");
|
let results = search.by_service("Lume Safe Storage");
|
||||||
let list = List::list_credentials(&results, Limit::All);
|
let list = List::list_credentials(&results, Limit::All);
|
||||||
let accounts: HashSet<String> = list
|
let accounts: HashSet<String> = list
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
|
|||||||
@@ -5,27 +5,21 @@
|
|||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use border::WebviewWindowExt as BorderWebviewWindowExt;
|
use border::WebviewWindowExt as BorderWebviewWindowExt;
|
||||||
use commands::{
|
use commands::{account::*, event::*, metadata::*, relay::*, sync::NegentropyEvent, window::*};
|
||||||
account::*,
|
|
||||||
event::*,
|
|
||||||
metadata::*,
|
|
||||||
relay::*,
|
|
||||||
sync::{run_fast_sync, NegentropyEvent},
|
|
||||||
window::*,
|
|
||||||
};
|
|
||||||
use common::{get_all_accounts, parse_event};
|
use common::{get_all_accounts, parse_event};
|
||||||
use nostr_sdk::prelude::{Profile as DatabaseProfile, *};
|
use nostr_sdk::prelude::{Profile as DatabaseProfile, *};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
use specta_typescript::Typescript;
|
use specta_typescript::Typescript;
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
fs,
|
fs,
|
||||||
io::{self, BufRead},
|
io::{self, BufRead},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tauri::{path::BaseDirectory, Emitter, EventTarget, Manager};
|
use tauri::{path::BaseDirectory, Emitter, EventTarget, Manager, WindowEvent};
|
||||||
use tauri_plugin_decorum::WebviewWindowExt;
|
use tauri_plugin_decorum::WebviewWindowExt;
|
||||||
use tauri_plugin_notification::{NotificationExt, PermissionState};
|
use tauri_plugin_notification::{NotificationExt, PermissionState};
|
||||||
use tauri_specta::{collect_commands, collect_events, Builder, Event as TauriEvent};
|
use tauri_specta::{collect_commands, collect_events, Builder, Event as TauriEvent};
|
||||||
@@ -37,8 +31,9 @@ pub struct Nostr {
|
|||||||
client: Client,
|
client: Client,
|
||||||
settings: Mutex<Settings>,
|
settings: Mutex<Settings>,
|
||||||
accounts: Mutex<Vec<String>>,
|
accounts: Mutex<Vec<String>>,
|
||||||
subscriptions: Mutex<Vec<SubscriptionId>>,
|
|
||||||
bootstrap_relays: Mutex<Vec<Url>>,
|
bootstrap_relays: Mutex<Vec<Url>>,
|
||||||
|
subscriptions: Mutex<HashSet<SubscriptionId>>,
|
||||||
|
send_queue: Mutex<HashSet<Event>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Type)]
|
#[derive(Clone, Serialize, Deserialize, Type)]
|
||||||
@@ -73,7 +68,7 @@ impl Default for Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Type)]
|
#[derive(Serialize, Deserialize, Type)]
|
||||||
enum SubKind {
|
enum SubscriptionMethod {
|
||||||
Subscribe,
|
Subscribe,
|
||||||
Unsubscribe,
|
Unsubscribe,
|
||||||
}
|
}
|
||||||
@@ -81,13 +76,15 @@ enum SubKind {
|
|||||||
#[derive(Serialize, Deserialize, Type, TauriEvent)]
|
#[derive(Serialize, Deserialize, Type, TauriEvent)]
|
||||||
struct Subscription {
|
struct Subscription {
|
||||||
label: String,
|
label: String,
|
||||||
kind: SubKind,
|
kind: SubscriptionMethod,
|
||||||
event_id: Option<String>,
|
event_id: Option<String>,
|
||||||
contacts: Option<Vec<String>>,
|
contacts: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Type, Clone, TauriEvent)]
|
#[derive(Serialize, Deserialize, Type, TauriEvent)]
|
||||||
struct NewSettings(Settings);
|
struct Sync {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub const DEFAULT_DIFFICULTY: u8 = 21;
|
pub const DEFAULT_DIFFICULTY: u8 = 21;
|
||||||
pub const FETCH_LIMIT: usize = 50;
|
pub const FETCH_LIMIT: usize = 50;
|
||||||
@@ -95,7 +92,6 @@ pub const NOTIFICATION_SUB_ID: &str = "lume_notification";
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let builder = Builder::<tauri::Wry>::new()
|
let builder = Builder::<tauri::Wry>::new()
|
||||||
// Then register them (separated by a comma)
|
|
||||||
.commands(collect_commands![
|
.commands(collect_commands![
|
||||||
get_relays,
|
get_relays,
|
||||||
connect_relay,
|
connect_relay,
|
||||||
@@ -161,7 +157,7 @@ fn main() {
|
|||||||
reopen_lume,
|
reopen_lume,
|
||||||
quit
|
quit
|
||||||
])
|
])
|
||||||
.events(collect_events![Subscription, NewSettings, NegentropyEvent]);
|
.events(collect_events![Subscription, NegentropyEvent]);
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
builder
|
builder
|
||||||
@@ -179,7 +175,6 @@ fn main() {
|
|||||||
let handle = app.handle();
|
let handle = app.handle();
|
||||||
let handle_clone = handle.clone();
|
let handle_clone = handle.clone();
|
||||||
let handle_clone_child = handle_clone.clone();
|
let handle_clone_child = handle_clone.clone();
|
||||||
let handle_clone_sync = handle_clone_child.clone();
|
|
||||||
let main_window = app.get_webview_window("main").unwrap();
|
let main_window = app.get_webview_window("main").unwrap();
|
||||||
|
|
||||||
let config_dir = handle
|
let config_dir = handle
|
||||||
@@ -201,16 +196,6 @@ fn main() {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
main_window.set_traffic_lights_inset(7.0, 10.0).unwrap();
|
main_window.set_traffic_lights_inset(7.0, 10.0).unwrap();
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let win = main_window.clone();
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
main_window.on_window_event(move |event| {
|
|
||||||
if let tauri::WindowEvent::ThemeChanged(_) = event {
|
|
||||||
win.set_traffic_lights_inset(7.0, 10.0).unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let (client, bootstrap_relays) = tauri::async_runtime::block_on(async move {
|
let (client, bootstrap_relays) = tauri::async_runtime::block_on(async move {
|
||||||
// Setup database
|
// Setup database
|
||||||
let database = NostrLMDB::open(config_dir.join("nostr"))
|
let database = NostrLMDB::open(config_dir.join("nostr"))
|
||||||
@@ -260,17 +245,9 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = client.add_discovery_relay("wss://purplepag.es/").await {
|
let _ = client.add_discovery_relay("wss://purplepag.es/").await;
|
||||||
println!("Add discovery relay failed: {}", e)
|
let _ = client.add_discovery_relay("wss://directory.yabu.me/").await;
|
||||||
}
|
let _ = client.add_discovery_relay("wss://user.kindpag.es/").await;
|
||||||
|
|
||||||
if let Err(e) = client.add_discovery_relay("wss://directory.yabu.me/").await {
|
|
||||||
println!("Add discovery relay failed: {}", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = client.add_discovery_relay("wss://user.kindpag.es/").await {
|
|
||||||
println!("Add discovery relay failed: {}", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect
|
// Connect
|
||||||
client.connect_with_timeout(Duration::from_secs(10)).await;
|
client.connect_with_timeout(Duration::from_secs(10)).await;
|
||||||
@@ -283,19 +260,18 @@ fn main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let accounts = get_all_accounts();
|
let accounts = get_all_accounts();
|
||||||
// Run fast sync for all accounts
|
|
||||||
run_fast_sync(accounts.clone(), handle_clone_sync);
|
|
||||||
|
|
||||||
// Create global state
|
// Create global state
|
||||||
app.manage(Nostr {
|
app.manage(Nostr {
|
||||||
client,
|
client,
|
||||||
accounts: Mutex::new(accounts),
|
accounts: Mutex::new(accounts),
|
||||||
settings: Mutex::new(Settings::default()),
|
settings: Mutex::new(Settings::default()),
|
||||||
subscriptions: Mutex::new(Vec::new()),
|
|
||||||
bootstrap_relays: Mutex::new(bootstrap_relays),
|
bootstrap_relays: Mutex::new(bootstrap_relays),
|
||||||
|
subscriptions: Mutex::new(HashSet::new()),
|
||||||
|
send_queue: Mutex::new(HashSet::new()),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle subscription
|
// Handle subscription request
|
||||||
Subscription::listen_any(app, move |event| {
|
Subscription::listen_any(app, move |event| {
|
||||||
let handle = handle_clone_child.to_owned();
|
let handle = handle_clone_child.to_owned();
|
||||||
let payload = event.payload;
|
let payload = event.payload;
|
||||||
@@ -305,7 +281,7 @@ fn main() {
|
|||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
match payload.kind {
|
match payload.kind {
|
||||||
SubKind::Subscribe => {
|
SubscriptionMethod::Subscribe => {
|
||||||
let subscription_id = SubscriptionId::new(payload.label);
|
let subscription_id = SubscriptionId::new(payload.label);
|
||||||
|
|
||||||
if !client
|
if !client
|
||||||
@@ -319,7 +295,7 @@ fn main() {
|
|||||||
.subscriptions
|
.subscriptions
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.push(subscription_id.clone());
|
.insert(subscription_id.clone());
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Total subscriptions: {}",
|
"Total subscriptions: {}",
|
||||||
@@ -371,22 +347,16 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SubKind::Unsubscribe => {
|
SubscriptionMethod::Unsubscribe => {
|
||||||
let subscription_id = SubscriptionId::new(payload.label);
|
let subscription_id = SubscriptionId::new(payload.label);
|
||||||
let mut sub_state = state.subscriptions.lock().unwrap().clone();
|
|
||||||
|
|
||||||
if let Some(pos) = sub_state.iter().position(|x| *x == subscription_id)
|
|
||||||
{
|
|
||||||
sub_state.remove(pos);
|
|
||||||
state.subscriptions.lock().unwrap().clone_from(&sub_state)
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Total subscriptions: {}",
|
"Total subscriptions: {}",
|
||||||
state.subscriptions.lock().unwrap().len()
|
state.subscriptions.lock().unwrap().len()
|
||||||
);
|
);
|
||||||
|
|
||||||
client.unsubscribe(subscription_id).await
|
state.subscriptions.lock().unwrap().remove(&subscription_id);
|
||||||
|
client.unsubscribe(subscription_id).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -570,6 +540,39 @@ fn main() {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
.on_window_event(|window, event| match event {
|
||||||
|
WindowEvent::CloseRequested { api, .. } => {
|
||||||
|
api.prevent_close();
|
||||||
|
// Just hide window not close
|
||||||
|
window.hide().unwrap();
|
||||||
|
|
||||||
|
let state = window.state::<Nostr>();
|
||||||
|
let client = &state.client;
|
||||||
|
let queue: Vec<Event> = state
|
||||||
|
.send_queue
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !queue.is_empty() {
|
||||||
|
tauri::async_runtime::block_on(async {
|
||||||
|
println!("Sending total {} events to relays", queue.len());
|
||||||
|
match client.batch_event(queue, RelaySendOptions::default()).await {
|
||||||
|
Ok(_) => window.destroy().unwrap(),
|
||||||
|
Err(_) => window.emit("batch-event", ()).unwrap(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.destroy().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::Focused(_focused) => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
})
|
||||||
.plugin(prevent_default())
|
.plugin(prevent_default())
|
||||||
.plugin(tauri_plugin_theme::init(ctx.config_mut()))
|
.plugin(tauri_plugin_theme::init(ctx.config_mut()))
|
||||||
.plugin(tauri_plugin_decorum::init())
|
.plugin(tauri_plugin_decorum::init())
|
||||||
|
|||||||
@@ -48,15 +48,15 @@ async saveBootstrapRelays(relays: string) : Promise<Result<null, string>> {
|
|||||||
async getAccounts() : Promise<string[]> {
|
async getAccounts() : Promise<string[]> {
|
||||||
return await TAURI_INVOKE("get_accounts");
|
return await TAURI_INVOKE("get_accounts");
|
||||||
},
|
},
|
||||||
async watchAccount(key: string) : Promise<Result<string, string>> {
|
async watchAccount(id: string) : Promise<Result<string, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("watch_account", { key }) };
|
return { status: "ok", data: await TAURI_INVOKE("watch_account", { id }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async importAccount(key: string, password: string) : Promise<Result<string, string>> {
|
async importAccount(key: string, password: string | null) : Promise<Result<string, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("import_account", { key, password }) };
|
return { status: "ok", data: await TAURI_INVOKE("import_account", { key, password }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -104,15 +104,15 @@ async hasSigner(id: string) : Promise<Result<boolean, string>> {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setSigner(account: string, password: string) : Promise<Result<null, string>> {
|
async setSigner(id: string) : Promise<Result<null, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("set_signer", { account, password }) };
|
return { status: "ok", data: await TAURI_INVOKE("set_signer", { id }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getProfile(id: string | null) : Promise<Result<string, string>> {
|
async getProfile(id: string) : Promise<Result<string, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_profile", { id }) };
|
return { status: "ok", data: await TAURI_INVOKE("get_profile", { id }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -168,7 +168,7 @@ async getAllProfiles() : Promise<Result<Mention[], string>> {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setGroup(title: string, description: string | null, image: string | null, users: string[]) : Promise<Result<string, string>> {
|
async setGroup(title: string, description: string | null, image: string | null, users: string[]) : Promise<Result<boolean, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("set_group", { title, description, image, users }) };
|
return { status: "ok", data: await TAURI_INVOKE("set_group", { title, description, image, users }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -192,7 +192,7 @@ async getAllGroups() : Promise<Result<RichEvent[], string>> {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setInterest(title: string, description: string | null, image: string | null, hashtags: string[]) : Promise<Result<string, string>> {
|
async setInterest(title: string, description: string | null, image: string | null, hashtags: string[]) : Promise<Result<boolean, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("set_interest", { title, description, image, hashtags }) };
|
return { status: "ok", data: await TAURI_INVOKE("set_interest", { title, description, image, hashtags }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -384,7 +384,7 @@ async search(query: string) : Promise<Result<RichEvent[], string>> {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async publish(content: string, warning: string | null, difficulty: number | null) : Promise<Result<string, string>> {
|
async publish(content: string, warning: string | null, difficulty: number | null) : Promise<Result<boolean, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("publish", { content, warning, difficulty }) };
|
return { status: "ok", data: await TAURI_INVOKE("publish", { content, warning, difficulty }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -392,7 +392,7 @@ async publish(content: string, warning: string | null, difficulty: number | null
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async reply(content: string, to: string, root: string | null) : Promise<Result<string, string>> {
|
async reply(content: string, to: string, root: string | null) : Promise<Result<boolean, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("reply", { content, to, root }) };
|
return { status: "ok", data: await TAURI_INVOKE("reply", { content, to, root }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -400,7 +400,7 @@ async reply(content: string, to: string, root: string | null) : Promise<Result<s
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async repost(raw: string) : Promise<Result<string, string>> {
|
async repost(raw: string) : Promise<Result<boolean, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("repost", { raw }) };
|
return { status: "ok", data: await TAURI_INVOKE("repost", { raw }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -501,11 +501,9 @@ async quit() : Promise<void> {
|
|||||||
|
|
||||||
export const events = __makeEvents__<{
|
export const events = __makeEvents__<{
|
||||||
negentropyEvent: NegentropyEvent,
|
negentropyEvent: NegentropyEvent,
|
||||||
newSettings: NewSettings,
|
|
||||||
subscription: Subscription
|
subscription: Subscription
|
||||||
}>({
|
}>({
|
||||||
negentropyEvent: "negentropy-event",
|
negentropyEvent: "negentropy-event",
|
||||||
newSettings: "new-settings",
|
|
||||||
subscription: "subscription"
|
subscription: "subscription"
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -520,13 +518,12 @@ export type Mention = { pubkey: string; avatar: string; display_name: string; na
|
|||||||
export type Meta = { content: string; images: string[]; events: string[]; mentions: string[]; hashtags: string[] }
|
export type Meta = { content: string; images: string[]; events: string[]; mentions: string[]; hashtags: string[] }
|
||||||
export type NegentropyEvent = { kind: NegentropyKind; total_event: number }
|
export type NegentropyEvent = { kind: NegentropyKind; total_event: number }
|
||||||
export type NegentropyKind = "Profile" | "Metadata" | "Events" | "EventIds" | "Global" | "Notification" | "Others"
|
export type NegentropyKind = "Profile" | "Metadata" | "Events" | "EventIds" | "Global" | "Notification" | "Others"
|
||||||
export type NewSettings = Settings
|
|
||||||
export type Profile = { name: string; display_name: string; about: string | null; picture: string; banner: string | null; nip05: string | null; lud16: string | null; website: string | null }
|
export type Profile = { name: string; display_name: string; about: string | null; picture: string; banner: string | null; nip05: string | null; lud16: string | null; website: string | null }
|
||||||
export type Relays = { connected: string[]; read: string[] | null; write: string[] | null; both: string[] | null }
|
export type Relays = { connected: string[]; read: string[] | null; write: string[] | null; both: string[] | null }
|
||||||
export type RichEvent = { raw: string; parsed: Meta | null }
|
export type RichEvent = { raw: string; parsed: Meta | null }
|
||||||
export type Settings = { proxy: string | null; image_resize_service: string | null; use_relay_hint: boolean; content_warning: boolean; trusted_only: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean; transparent: boolean }
|
export type Settings = { proxy: string | null; image_resize_service: string | null; use_relay_hint: boolean; content_warning: boolean; trusted_only: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean; transparent: boolean }
|
||||||
export type SubKind = "Subscribe" | "Unsubscribe"
|
export type Subscription = { label: string; kind: SubscriptionMethod; event_id: string | null; contacts: string[] | null }
|
||||||
export type Subscription = { label: string; kind: SubKind; event_id: string | null; contacts: string[] | null }
|
export type SubscriptionMethod = "Subscribe" | "Unsubscribe"
|
||||||
export type Window = { label: string; title: string; url: string; width: number; height: number; maximizable: boolean; minimizable: boolean; hidden_title: boolean; closable: boolean }
|
export type Window = { label: string; title: string; url: string; width: number; height: number; maximizable: boolean; minimizable: boolean; hidden_title: boolean; closable: boolean }
|
||||||
|
|
||||||
/** tauri-specta globals **/
|
/** tauri-specta globals **/
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
|||||||
function Screen() {
|
function Screen() {
|
||||||
const { queryClient } = Route.useRouteContext();
|
const { queryClient } = Route.useRouteContext();
|
||||||
|
|
||||||
|
/*
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = events.newSettings.listen((data) => {
|
const unlisten = events.newSettings.listen((data) => {
|
||||||
appSettings.setState((state) => {
|
appSettings.setState((state) => {
|
||||||
@@ -31,6 +32,7 @@ function Screen() {
|
|||||||
unlisten.then((f) => f());
|
unlisten.then((f) => f());
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
*/
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = events.negentropyEvent.listen(async (data) => {
|
const unlisten = events.negentropyEvent.listen(async (data) => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Link, Outlet, createLazyFileRoute } from "@tanstack/react-router";
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
||||||
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||||
import { memo, useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/_layout")({
|
export const Route = createLazyFileRoute("/_layout")({
|
||||||
component: Layout,
|
component: Layout,
|
||||||
@@ -80,41 +80,6 @@ function Topbar() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const NegentropyBadge = memo(function NegentropyBadge() {
|
|
||||||
const [process, setProcess] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const unlisten = listen("negentropy", async (data) => {
|
|
||||||
if (data.payload === "Ok") {
|
|
||||||
setProcess(null);
|
|
||||||
} else {
|
|
||||||
setProcess(data.payload);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unlisten.then((f) => f());
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!process) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-7 w-max px-3 inline-flex items-center justify-center text-[9px] font-medium rounded-full bg-black/5 dark:bg-white/5">
|
|
||||||
{process ? (
|
|
||||||
<span>
|
|
||||||
{process.message}
|
|
||||||
{process.total_event > 0 ? ` / ${process.total_event}` : null}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
"Syncing"
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function Account({ pubkey }: { pubkey: string }) {
|
function Account({ pubkey }: { pubkey: string }) {
|
||||||
const navigate = Route.useNavigate();
|
const navigate = Route.useNavigate();
|
||||||
const context = Route.useRouteContext();
|
const context = Route.useRouteContext();
|
||||||
@@ -183,6 +148,16 @@ function Account({ pubkey }: { pubkey: string }) {
|
|||||||
[pubkey],
|
[pubkey],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unlisten = listen("signer-updated", async () => {
|
||||||
|
await context.queryClient.invalidateQueries({ queryKey: ["signer"] });
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten.then((f) => f());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function Screen() {
|
|||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
if (!key.startsWith("nsec1") && !key.startsWith("ncryptsec")) {
|
if (!key.startsWith("nsec") && !key.startsWith("ncryptsec")) {
|
||||||
await message(
|
await message(
|
||||||
"You need to enter a valid private key starts with nsec or ncryptsec",
|
"You need to enter a valid private key starts with nsec or ncryptsec",
|
||||||
{ title: "Login", kind: "info" },
|
{ title: "Login", kind: "info" },
|
||||||
@@ -32,15 +32,18 @@ function Screen() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.startsWith("nsec1") && !password.length) {
|
if (key.startsWith("ncryptsec") && !password.length) {
|
||||||
await message("You must set password to secure your key", {
|
await message("You must enter a password to decrypt your key", {
|
||||||
title: "Login",
|
title: "Login",
|
||||||
kind: "info",
|
kind: "info",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await commands.importAccount(key, password);
|
const res = await commands.importAccount(
|
||||||
|
key,
|
||||||
|
password.length ? password : null,
|
||||||
|
);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
navigate({ to: "/", replace: true });
|
navigate({ to: "/", replace: true });
|
||||||
@@ -94,15 +97,13 @@ function Screen() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{key.length ? (
|
{key.startsWith("ncryptsec") ? (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<label
|
<label
|
||||||
htmlFor="password"
|
htmlFor="password"
|
||||||
className="text-sm font-medium text-neutral-800 dark:text-neutral-200"
|
className="text-sm font-medium text-neutral-800 dark:text-neutral-200"
|
||||||
>
|
>
|
||||||
{!key.startsWith("ncryptsec")
|
Enter password to decrypt your key
|
||||||
? "Set password to secure your key"
|
|
||||||
: "Enter password to decrypt your key"}
|
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
name="password"
|
name="password"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function Screen() {
|
|||||||
if (!key.startsWith("npub") && !key.startsWith("nprofile")) {
|
if (!key.startsWith("npub") && !key.startsWith("nprofile")) {
|
||||||
await message(
|
await message(
|
||||||
"You need to enter a valid public key starts with npub or nprofile",
|
"You need to enter a valid public key starts with npub or nprofile",
|
||||||
{ title: "Login", kind: "info" },
|
{ kind: "info" },
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function Screen() {
|
|||||||
queryKey: ["events", "newsfeed", search.label],
|
queryKey: ["events", "newsfeed", search.label],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||||
const until = pageParam > 0 ? pageParam.toString() : undefined;
|
const until = pageParam > 0 ? pageParam.toString() : null;
|
||||||
const res = await commands.getAllEventsByAuthors(contacts, until);
|
const res = await commands.getAllEventsByAuthors(contacts, until);
|
||||||
|
|
||||||
if (res.status === "error") {
|
if (res.status === "error") {
|
||||||
@@ -76,9 +76,9 @@ export function Screen() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
events.subscription
|
events.subscription
|
||||||
.emit({
|
.emit({
|
||||||
label: search.label,
|
label: search.label as string,
|
||||||
kind: "Subscribe",
|
kind: "Subscribe",
|
||||||
event_id: undefined,
|
event_id: null,
|
||||||
contacts,
|
contacts,
|
||||||
})
|
})
|
||||||
.then(() => console.log("Subscribe: ", search.label));
|
.then(() => console.log("Subscribe: ", search.label));
|
||||||
@@ -86,9 +86,9 @@ export function Screen() {
|
|||||||
return () => {
|
return () => {
|
||||||
events.subscription
|
events.subscription
|
||||||
.emit({
|
.emit({
|
||||||
label: search.label,
|
label: search.label as string,
|
||||||
kind: "Unsubscribe",
|
kind: "Unsubscribe",
|
||||||
event_id: undefined,
|
event_id: null,
|
||||||
contacts,
|
contacts,
|
||||||
})
|
})
|
||||||
.then(() => console.log("Unsubscribe: ", search.label));
|
.then(() => console.log("Unsubscribe: ", search.label));
|
||||||
|
|||||||
Reference in New Issue
Block a user