chore: refactor NIP-4E implementation
This commit is contained in:
@@ -1,17 +1,17 @@
|
||||
use std::time::Duration;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::{anyhow, Context as AnyContext, Error};
|
||||
use anyhow::{anyhow, Error};
|
||||
use common::profile::NostrProfile;
|
||||
use global::{
|
||||
constants::{
|
||||
ALL_MESSAGES_SUB_ID, CLIENT_KEYRING, DEVICE_ANNOUNCEMENT_KIND, DEVICE_REQUEST_KIND,
|
||||
DEVICE_RESPONSE_KIND, MASTER_KEYRING, NEW_MESSAGE_SUB_ID,
|
||||
DEVICE_RESPONSE_KIND, DEVICE_SUB_ID, MASTER_KEYRING, NEW_MESSAGE_SUB_ID,
|
||||
},
|
||||
get_app_name, get_client, get_device_name, set_device_keys,
|
||||
get_app_name, get_client, get_device_keys, get_device_name, set_device_keys,
|
||||
};
|
||||
use gpui::{
|
||||
div, px, relative, App, AppContext, AsyncApp, Context, Entity, Global, ParentElement, Styled,
|
||||
Task, Window,
|
||||
div, px, relative, App, AppContext, Context, Entity, Global, ParentElement, Styled, Task,
|
||||
Window,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use smallvec::SmallVec;
|
||||
@@ -81,11 +81,10 @@ pub fn init(window: &mut Window, cx: &App) {
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
// Observe login behavior
|
||||
window
|
||||
.observe(&entity, cx, |this, window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.on_login(window, cx);
|
||||
this.on_device_change(window, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
@@ -137,6 +136,7 @@ impl Device {
|
||||
let metadata = client
|
||||
.fetch_metadata(public_key, Duration::from_secs(2))
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default();
|
||||
|
||||
// Get user's inbox relays
|
||||
@@ -191,7 +191,8 @@ impl Device {
|
||||
})
|
||||
}
|
||||
|
||||
fn on_login(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
/// This function is called whenever the device is changed
|
||||
fn on_device_change(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(profile) = self.profile.as_ref() else {
|
||||
// User not logged in, render the Onboarding View
|
||||
Root::update(window, cx, |this, window, cx| {
|
||||
@@ -208,33 +209,205 @@ impl Device {
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
let pubkey = profile.public_key;
|
||||
let client_keys = self.client_keys.clone();
|
||||
// Get the user's messaging relays
|
||||
// If it is empty, user must setup relays
|
||||
let ready = profile.messaging_relays.is_some();
|
||||
|
||||
// User's messaging relays not found
|
||||
if profile.messaging_relays.is_none() {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
cx.update(|window, cx| {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
cx.update(|window, cx| {
|
||||
if !ready {
|
||||
this.update(cx, |this, cx| {
|
||||
this.render_setup_relays(window, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
this.update(cx, |this, cx| {
|
||||
this.start_subscription(cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Initialize subscription for current user
|
||||
pub fn start_subscription(&self, cx: &Context<Self>) {
|
||||
let Some(profile) = self.profile() else {
|
||||
return;
|
||||
};
|
||||
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
// Initialize subscription for current user
|
||||
_ = Device::subscribe(pubkey, &cx).await;
|
||||
let user = profile.public_key;
|
||||
let client = get_client();
|
||||
|
||||
// Initialize master keys for current user
|
||||
if let Ok(Some(keys)) = Device::fetch_master_keys(pubkey, &cx).await {
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let device_kind = Kind::Custom(DEVICE_ANNOUNCEMENT_KIND);
|
||||
|
||||
// Create a device announcement filter
|
||||
let device = Filter::new().kind(device_kind).author(user).limit(1);
|
||||
|
||||
// Create a contact list filter
|
||||
let contacts = Filter::new().kind(Kind::ContactList).author(user).limit(1);
|
||||
|
||||
// Create a user's data filter
|
||||
let data = Filter::new()
|
||||
.author(user)
|
||||
.since(Timestamp::now())
|
||||
.kinds(vec![
|
||||
Kind::Metadata,
|
||||
Kind::InboxRelays,
|
||||
Kind::RelayList,
|
||||
device_kind,
|
||||
]);
|
||||
|
||||
// Create a filter for getting all gift wrapped events send to current user
|
||||
let msg = Filter::new().kind(Kind::GiftWrap).pubkey(user);
|
||||
|
||||
// Create a filter to continuously receive new messages.
|
||||
let new_msg = Filter::new().kind(Kind::GiftWrap).pubkey(user).limit(0);
|
||||
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
// Only subscribe to the latest device announcement
|
||||
let sub_id = SubscriptionId::new(DEVICE_SUB_ID);
|
||||
client.subscribe_with_id(sub_id, device, Some(opts)).await?;
|
||||
|
||||
// Only subscribe to the latest contact list
|
||||
client.subscribe(contacts, Some(opts)).await?;
|
||||
|
||||
// Continuously receive new user's data since now
|
||||
client.subscribe(data, None).await?;
|
||||
|
||||
let sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
|
||||
client.subscribe_with_id(sub_id, msg, Some(opts)).await?;
|
||||
|
||||
let sub_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID);
|
||||
client.subscribe_with_id(sub_id, new_msg, None).await?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
cx.spawn(|_, _| async move {
|
||||
if let Err(e) = task.await {
|
||||
log::error!("Subscription error: {}", e);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Setup device
|
||||
///
|
||||
/// NIP-4e: <https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md>
|
||||
pub fn setup_device(&mut self, window: &mut Window, cx: &Context<Self>) {
|
||||
let Some(profile) = self.profile() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let client = get_client();
|
||||
let public_key = profile.public_key;
|
||||
let kind = Kind::Custom(DEVICE_ANNOUNCEMENT_KIND);
|
||||
let filter = Filter::new().kind(kind).author(public_key).limit(1);
|
||||
|
||||
// Fetch device announcement events
|
||||
let fetch_announcement = cx.background_spawn(async move {
|
||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
||||
Ok(event)
|
||||
} else {
|
||||
Err(anyhow!("Device Announcement not found."))
|
||||
}
|
||||
});
|
||||
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
if get_device_keys().await.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(event) = fetch_announcement.await {
|
||||
log::info!("Device Announcement: {:?}", event);
|
||||
if let Ok(task) = cx.update(|_, cx| cx.read_credentials(MASTER_KEYRING)) {
|
||||
if let Ok(Some((pubkey, secret))) = task.await {
|
||||
if let Some(n) = event
|
||||
.tags
|
||||
.find(TagKind::custom("n"))
|
||||
.and_then(|t| t.content())
|
||||
.map(|hex| hex.to_owned())
|
||||
{
|
||||
if n == pubkey {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.reinit_master_keys(secret, window, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.request_keys(window, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to read credentials");
|
||||
}
|
||||
|
||||
log::info!("User cancelled keyring.")
|
||||
} else {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.set_master_keys(window, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Create a new master keys
|
||||
///
|
||||
/// NIP-4e: <https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md>
|
||||
pub fn set_master_keys(&self, window: &mut Window, cx: &Context<Self>) {
|
||||
log::info!("Device Announcement isn't found.");
|
||||
log::info!("Appoint this device as master");
|
||||
|
||||
let client = get_client();
|
||||
let app_name = get_app_name();
|
||||
|
||||
let task: Task<Result<Arc<Keys>, Error>> = cx.background_spawn(async move {
|
||||
let keys = Keys::generate();
|
||||
let kind = Kind::Custom(DEVICE_ANNOUNCEMENT_KIND);
|
||||
let client_tag = Tag::client(app_name);
|
||||
let pubkey_tag = Tag::custom(TagKind::custom("n"), vec![keys.public_key().to_hex()]);
|
||||
|
||||
let event = EventBuilder::new(kind, "").tags(vec![client_tag, pubkey_tag]);
|
||||
|
||||
if let Err(e) = client.send_event_builder(event).await {
|
||||
log::error!("Failed to send Device Announcement: {}", e);
|
||||
} else {
|
||||
log::info!("Device Announcement has been sent");
|
||||
}
|
||||
|
||||
Ok(Arc::new(keys))
|
||||
});
|
||||
|
||||
cx.spawn_in(window, |_, mut cx| async move {
|
||||
if get_device_keys().await.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(keys) = task.await {
|
||||
// Update global state
|
||||
set_device_keys(keys.clone()).await;
|
||||
|
||||
// Save keys
|
||||
if let Ok(task) = cx.update(|_, cx| {
|
||||
cx.write_credentials(
|
||||
MASTER_KEYRING,
|
||||
@@ -242,40 +415,166 @@ impl Device {
|
||||
keys.secret_key().as_secret_bytes(),
|
||||
)
|
||||
}) {
|
||||
_ = task.await;
|
||||
}
|
||||
if let Err(e) = task.await {
|
||||
log::error!("Failed to write device keys to keyring: {}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
if let Ok(event) = Device::fetch_request(pubkey, &cx).await {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.handle_request(event, window, cx);
|
||||
})
|
||||
.ok();
|
||||
/// Reinitialize master keys
|
||||
///
|
||||
/// NIP-4e: <https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md>
|
||||
pub fn reinit_master_keys(&self, secret: Vec<u8>, window: &mut Window, cx: &Context<Self>) {
|
||||
let Some(profile) = self.profile() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let client = get_client();
|
||||
let public_key = profile.public_key;
|
||||
|
||||
let task: Task<Result<Arc<Keys>, Error>> = cx.background_spawn(async move {
|
||||
let secret_key = SecretKey::from_slice(&secret)?;
|
||||
let keys = Arc::new(Keys::new(secret_key));
|
||||
|
||||
log::info!("Reappointing this device as master.");
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::Custom(DEVICE_REQUEST_KIND))
|
||||
.author(public_key)
|
||||
.since(Timestamp::now());
|
||||
|
||||
// Subscribe for new device requests
|
||||
_ = client.subscribe(filter, None).await;
|
||||
|
||||
Ok(keys)
|
||||
});
|
||||
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
if let Ok(keys) = task.await {
|
||||
set_device_keys(keys).await;
|
||||
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.fetch_request(window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let client = get_client();
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::Custom(DEVICE_REQUEST_KIND))
|
||||
.author(pubkey)
|
||||
.since(Timestamp::now());
|
||||
|
||||
_ = client.subscribe(filter, None).await;
|
||||
})
|
||||
.await;
|
||||
.ok();
|
||||
} else {
|
||||
// Send request for master keys
|
||||
if Device::request_keys(pubkey, client_keys, &cx).await.is_ok() {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.render_waiting_modal(window, cx);
|
||||
})
|
||||
.ok();
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.request_keys(window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Send a request to ask for device keys from the other Nostr client
|
||||
///
|
||||
/// NIP-4e: <https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md>
|
||||
pub fn request_keys(&self, window: &mut Window, cx: &Context<Self>) {
|
||||
let Some(profile) = self.profile() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let client = get_client();
|
||||
let app_name = get_app_name();
|
||||
|
||||
let public_key = profile.public_key;
|
||||
let client_keys = self.client_keys.clone();
|
||||
|
||||
let kind = Kind::Custom(DEVICE_REQUEST_KIND);
|
||||
let client_tag = Tag::client(app_name);
|
||||
let pubkey_tag = Tag::custom(
|
||||
TagKind::custom("pubkey"),
|
||||
vec![client_keys.public_key().to_hex()],
|
||||
);
|
||||
|
||||
// Create a request event builder
|
||||
let builder = EventBuilder::new(kind, "").tags(vec![client_tag, pubkey_tag]);
|
||||
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
log::info!("Sent a request to ask for device keys from the other Nostr client");
|
||||
|
||||
if let Err(e) = client.send_event_builder(builder).await {
|
||||
log::error!("Failed to send device keys request: {}", e);
|
||||
} else {
|
||||
log::info!("Waiting for response...");
|
||||
}
|
||||
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::Custom(DEVICE_RESPONSE_KIND))
|
||||
.author(public_key);
|
||||
|
||||
// Getting all previous approvals
|
||||
client.subscribe(filter.clone(), Some(opts)).await?;
|
||||
|
||||
// Continously receive the request approval
|
||||
client
|
||||
.subscribe(filter.since(Timestamp::now()), None)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
if task.await.is_ok() {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.render_waiting_modal(window, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Fetch the latest request from the other Nostr client
|
||||
///
|
||||
/// NIP-4e: <https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md>
|
||||
fn fetch_request(&self, window: &mut Window, cx: &Context<Self>) {
|
||||
let Some(profile) = self.profile() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let client = get_client();
|
||||
let public_key = profile.public_key;
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::Custom(DEVICE_REQUEST_KIND))
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
let task: Task<Result<Event, Error>> = cx.background_spawn(async move {
|
||||
let events = client.fetch_events(filter, Duration::from_secs(2)).await?;
|
||||
|
||||
if let Some(event) = events.first_owned() {
|
||||
Ok(event)
|
||||
} else {
|
||||
Err(anyhow!("No request found"))
|
||||
}
|
||||
});
|
||||
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
if let Ok(event) = task.await {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.handle_request(event, window, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@@ -292,10 +591,11 @@ impl Device {
|
||||
.nip44_decrypt(&public_key, &event.content)
|
||||
.await?;
|
||||
|
||||
let keys = Keys::parse(&secret)?;
|
||||
let keys = Arc::new(Keys::parse(&secret)?);
|
||||
|
||||
// Update global state with new device keys
|
||||
set_device_keys(keys).await;
|
||||
|
||||
log::info!("Received device keys from other client");
|
||||
|
||||
Ok(())
|
||||
@@ -510,198 +810,4 @@ impl Device {
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
/// Fetch the latest request from the other Nostr client
|
||||
///
|
||||
/// NIP-4e: <https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md>
|
||||
fn fetch_request(user: PublicKey, cx: &AsyncApp) -> Task<Result<Event, Error>> {
|
||||
let client = get_client();
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::Custom(DEVICE_REQUEST_KIND))
|
||||
.author(user)
|
||||
.limit(1);
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let events = client.fetch_events(filter, Duration::from_secs(2)).await?;
|
||||
|
||||
if let Some(event) = events.first_owned() {
|
||||
Ok(event)
|
||||
} else {
|
||||
Err(anyhow!("No request found"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Send a request to ask for device keys from the other Nostr client
|
||||
///
|
||||
/// NIP-4e: <https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md>
|
||||
fn request_keys(user: PublicKey, client_keys: Keys, cx: &AsyncApp) -> Task<Result<(), Error>> {
|
||||
let client = get_client();
|
||||
let app_name = get_app_name();
|
||||
|
||||
let kind = Kind::Custom(DEVICE_REQUEST_KIND);
|
||||
let client_tag = Tag::client(app_name);
|
||||
let pubkey_tag = Tag::custom(
|
||||
TagKind::custom("pubkey"),
|
||||
vec![client_keys.public_key().to_hex()],
|
||||
);
|
||||
|
||||
// Create a request event builder
|
||||
let builder = EventBuilder::new(kind, "").tags(vec![client_tag, pubkey_tag]);
|
||||
|
||||
cx.background_spawn(async move {
|
||||
log::info!("Sent a request to ask for device keys from the other Nostr client");
|
||||
|
||||
if let Err(e) = client.send_event_builder(builder).await {
|
||||
log::error!("Failed to send device keys request: {}", e);
|
||||
}
|
||||
|
||||
log::info!("Waiting for response...");
|
||||
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::Custom(DEVICE_RESPONSE_KIND))
|
||||
.author(user);
|
||||
|
||||
// Getting all previous approvals
|
||||
client.subscribe(filter.clone(), Some(opts)).await?;
|
||||
|
||||
// Continously receive the request approval
|
||||
client
|
||||
.subscribe(filter.since(Timestamp::now()), None)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the master keys for current user
|
||||
///
|
||||
/// NIP-4e: <https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md>
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn fetch_master_keys(user: PublicKey, cx: &AsyncApp) -> Task<Result<Option<Keys>, Error>> {
|
||||
let client = get_client();
|
||||
|
||||
let kind = Kind::Custom(DEVICE_ANNOUNCEMENT_KIND);
|
||||
let filter = Filter::new().kind(kind).author(user).limit(1);
|
||||
|
||||
// Fetch device announcement events
|
||||
let fetch_announcement = cx.background_spawn(async move {
|
||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
||||
println!("event: {:?}", event);
|
||||
Ok(event)
|
||||
} else {
|
||||
Err(anyhow!("Device Announcement not found."))
|
||||
}
|
||||
});
|
||||
|
||||
cx.spawn(|cx| async move {
|
||||
let Ok(task) = cx.update(|cx| cx.read_credentials(MASTER_KEYRING)) else {
|
||||
return Err(anyhow!("Failed to read credentials"));
|
||||
};
|
||||
|
||||
let secret = task.await;
|
||||
|
||||
if let Ok(event) = fetch_announcement.await {
|
||||
if let Ok(Some((_, secret))) = secret {
|
||||
let secret_key = SecretKey::from_slice(&secret)?;
|
||||
let keys = Keys::new(secret_key);
|
||||
let device_pubkey = keys.public_key();
|
||||
|
||||
log::info!("Device's Public Key: {:?}", device_pubkey);
|
||||
|
||||
let n_tag = event.tags.find(TagKind::custom("n")).context("Not found")?;
|
||||
let content = n_tag.content().context("Not found")?;
|
||||
let target_pubkey = PublicKey::parse(content)?;
|
||||
|
||||
// If device public key matches announcement public key, re-appoint as master
|
||||
if device_pubkey == target_pubkey {
|
||||
log::info!("Re-appointing this device as master");
|
||||
return Ok(Some(keys));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
} else {
|
||||
log::info!("Device announcement is not found, appoint this device as master");
|
||||
|
||||
let app_name = get_app_name();
|
||||
let keys = Keys::generate();
|
||||
let kind = Kind::Custom(DEVICE_ANNOUNCEMENT_KIND);
|
||||
let client_tag = Tag::client(app_name);
|
||||
let pubkey_tag =
|
||||
Tag::custom(TagKind::custom("n"), vec![keys.public_key().to_hex()]);
|
||||
|
||||
let _task: Result<(), Error> = cx
|
||||
.background_spawn(async move {
|
||||
let signer = client.signer().await?;
|
||||
let event = EventBuilder::new(kind, "")
|
||||
.tags(vec![client_tag, pubkey_tag])
|
||||
.sign(&signer)
|
||||
.await?;
|
||||
|
||||
if let Err(e) = client.send_event(&event).await {
|
||||
log::error!("Failed to send device announcement: {}", e);
|
||||
} else {
|
||||
log::info!("Device announcement sent");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(Some(keys))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Initialize subscription for current user
|
||||
fn subscribe(user: PublicKey, cx: &AsyncApp) -> Task<Result<(), Error>> {
|
||||
let client = get_client();
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
let device_kind = Kind::Custom(DEVICE_ANNOUNCEMENT_KIND);
|
||||
|
||||
// Create a device announcement filter
|
||||
let device = Filter::new().kind(device_kind).author(user).limit(1);
|
||||
|
||||
// Create a contact list filter
|
||||
let contacts = Filter::new().kind(Kind::ContactList).author(user).limit(1);
|
||||
|
||||
// Create a user's data filter
|
||||
let data = Filter::new()
|
||||
.author(user)
|
||||
.since(Timestamp::now())
|
||||
.kinds(vec![
|
||||
Kind::Metadata,
|
||||
Kind::InboxRelays,
|
||||
Kind::RelayList,
|
||||
device_kind,
|
||||
]);
|
||||
|
||||
// Create a filter for getting all gift wrapped events send to current user
|
||||
let msg = Filter::new().kind(Kind::GiftWrap).pubkey(user);
|
||||
|
||||
// Create a filter to continuously receive new messages.
|
||||
let new_msg = Filter::new().kind(Kind::GiftWrap).pubkey(user).limit(0);
|
||||
|
||||
cx.background_spawn(async move {
|
||||
// Only subscribe to the latest device announcement
|
||||
client.subscribe(device, Some(opts)).await?;
|
||||
|
||||
// Only subscribe to the latest contact list
|
||||
client.subscribe(contacts, Some(opts)).await?;
|
||||
|
||||
// Continuously receive new user's data since now
|
||||
client.subscribe(data, None).await?;
|
||||
|
||||
let sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
|
||||
client.subscribe_with_id(sub_id, msg, Some(opts)).await?;
|
||||
|
||||
let sub_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID);
|
||||
client.subscribe_with_id(sub_id, new_msg, None).await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use futures::{select, FutureExt};
|
||||
use global::{
|
||||
constants::{
|
||||
ALL_MESSAGES_SUB_ID, APP_ID, APP_NAME, BOOTSTRAP_RELAYS, DEVICE_ANNOUNCEMENT_KIND,
|
||||
DEVICE_REQUEST_KIND, DEVICE_RESPONSE_KIND, NEW_MESSAGE_SUB_ID,
|
||||
DEVICE_REQUEST_KIND, DEVICE_RESPONSE_KIND, DEVICE_SUB_ID, NEW_MESSAGE_SUB_ID,
|
||||
},
|
||||
get_client, get_device_keys, set_device_name,
|
||||
};
|
||||
@@ -41,6 +41,8 @@ enum Signal {
|
||||
RequestMasterKey(Event),
|
||||
/// Receive approve master key event
|
||||
ReceiveMasterKey(Event),
|
||||
/// Receive announcement event
|
||||
ReceiveAnnouncement,
|
||||
/// Receive EOSE
|
||||
Eose,
|
||||
}
|
||||
@@ -117,6 +119,7 @@ fn main() {
|
||||
let rng_keys = Keys::generate();
|
||||
let all_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
|
||||
let new_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID);
|
||||
let device_id = SubscriptionId::new(DEVICE_SUB_ID);
|
||||
let mut notifications = client.notifications();
|
||||
|
||||
while let Ok(notification) = notifications.recv().await {
|
||||
@@ -190,9 +193,9 @@ fn main() {
|
||||
}
|
||||
RelayMessage::EndOfStoredEvents(subscription_id) => {
|
||||
if all_id == *subscription_id {
|
||||
if let Err(e) = event_tx.send(Signal::Eose).await {
|
||||
log::error!("Failed to send eose: {}", e)
|
||||
};
|
||||
_ = event_tx.send(Signal::Eose).await;
|
||||
} else if device_id == *subscription_id {
|
||||
_ = event_tx.send(Signal::ReceiveAnnouncement).await;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -269,6 +272,13 @@ fn main() {
|
||||
chats.update(cx, |this, cx| this.push_message(event, cx))
|
||||
}
|
||||
}
|
||||
Signal::ReceiveAnnouncement => {
|
||||
if let Some(device) = Device::global(cx) {
|
||||
device.update(cx, |this, cx| {
|
||||
this.setup_device(window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
Signal::ReceiveMasterKey(event) => {
|
||||
if let Some(device) = Device::global(cx) {
|
||||
device.update(cx, |this, cx| {
|
||||
|
||||
@@ -4,7 +4,7 @@ use global::{constants::IMAGE_SERVICE, get_client};
|
||||
use gpui::{
|
||||
div, img, prelude::FluentBuilder, AnyElement, App, AppContext, Context, Entity, EventEmitter,
|
||||
Flatten, FocusHandle, Focusable, IntoElement, ParentElement, PathPromptOptions, Render,
|
||||
SharedString, Styled, Window,
|
||||
SharedString, Styled, Task, Window,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use smol::fs;
|
||||
@@ -79,30 +79,18 @@ impl Profile {
|
||||
};
|
||||
|
||||
let client = get_client();
|
||||
let (tx, rx) = oneshot::channel::<Option<Metadata>>();
|
||||
let task: Task<Result<Option<Metadata>, Error>> = cx.background_spawn(async move {
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
let metadata = client
|
||||
.fetch_metadata(public_key, Duration::from_secs(2))
|
||||
.await?;
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let result = async {
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
let metadata = client
|
||||
.fetch_metadata(public_key, Duration::from_secs(2))
|
||||
.await?;
|
||||
|
||||
Ok::<_, anyhow::Error>(metadata)
|
||||
}
|
||||
.await;
|
||||
|
||||
if let Ok(metadata) = result {
|
||||
_ = tx.send(Some(metadata));
|
||||
} else {
|
||||
_ = tx.send(None);
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
Ok(metadata)
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if let Ok(Some(metadata)) = rx.await {
|
||||
if let Ok(Some(metadata)) = task.await {
|
||||
_ = cx.update_window(window_handle, |_, window, cx| {
|
||||
_ = this.update(cx, |this: &mut Profile, cx| {
|
||||
this.avatar_input.update(cx, |this, cx| {
|
||||
|
||||
@@ -222,6 +222,7 @@ impl Compose {
|
||||
let metadata = client
|
||||
.fetch_metadata(public_key, Duration::from_secs(2))
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(NostrProfile::new(public_key, metadata))
|
||||
@@ -237,6 +238,7 @@ impl Compose {
|
||||
let metadata = client
|
||||
.fetch_metadata(public_key, Duration::from_secs(2))
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(NostrProfile::new(public_key, metadata))
|
||||
|
||||
@@ -19,6 +19,7 @@ pub const BOOTSTRAP_RELAYS: [&str; 3] = [
|
||||
/// Subscriptions
|
||||
pub const NEW_MESSAGE_SUB_ID: &str = "listen_new_giftwraps";
|
||||
pub const ALL_MESSAGES_SUB_ID: &str = "listen_all_giftwraps";
|
||||
pub const DEVICE_SUB_ID: &str = "listen_device_announcement";
|
||||
|
||||
/// Image Resizer Service
|
||||
pub const IMAGE_SERVICE: &str = "https://wsrv.nl";
|
||||
|
||||
@@ -63,11 +63,11 @@ pub async fn get_device_keys() -> Option<Arc<dyn NostrSigner>> {
|
||||
}
|
||||
|
||||
/// Set device keys
|
||||
pub async fn set_device_keys<T>(signer: T)
|
||||
pub async fn set_device_keys<T>(signer: Arc<T>)
|
||||
where
|
||||
T: NostrSigner + 'static,
|
||||
{
|
||||
DEVICE_KEYS.lock().await.replace(Arc::new(signer));
|
||||
DEVICE_KEYS.lock().await.replace(signer);
|
||||
|
||||
// Re-subscribe to all messages
|
||||
smol::spawn(async move {
|
||||
|
||||
Reference in New Issue
Block a user