wip: dekey
This commit is contained in:
@@ -39,7 +39,6 @@ use crate::text::RenderedText;
|
|||||||
|
|
||||||
mod actions;
|
mod actions;
|
||||||
mod emoji;
|
mod emoji;
|
||||||
mod subject;
|
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
pub fn init(room: WeakEntity<Room>, window: &mut Window, cx: &mut App) -> Entity<ChatPanel> {
|
pub fn init(room: WeakEntity<Room>, window: &mut Window, cx: &mut App) -> Entity<ChatPanel> {
|
||||||
@@ -601,7 +600,6 @@ impl ChatPanel {
|
|||||||
text: AnyElement,
|
text: AnyElement,
|
||||||
cx: &Context<Self>,
|
cx: &Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
|
||||||
let hide_avatar = AppSettings::get_hide_user_avatars(cx);
|
let hide_avatar = AppSettings::get_hide_user_avatars(cx);
|
||||||
|
|
||||||
let id = message.id;
|
let id = message.id;
|
||||||
@@ -1132,7 +1130,6 @@ impl Panel for ChatPanel {
|
|||||||
fn title(&self, cx: &App) -> AnyElement {
|
fn title(&self, cx: &App) -> AnyElement {
|
||||||
self.room
|
self.room
|
||||||
.read_with(cx, |this, cx| {
|
.read_with(cx, |this, cx| {
|
||||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
|
||||||
let label = this.display_name(cx);
|
let label = this.display_name(cx);
|
||||||
let url = this.display_image(cx);
|
let url = this.display_image(cx);
|
||||||
|
|
||||||
|
|||||||
@@ -541,7 +541,7 @@ impl ChatSpace {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when_some(identity.read(cx).option_public_key(), |this, public_key| {
|
.when_some(identity.read(cx).public_key, |this, public_key| {
|
||||||
let persons = PersonRegistry::global(cx);
|
let persons = PersonRegistry::global(cx);
|
||||||
let profile = persons.read(cx).get(&public_key, cx);
|
let profile = persons.read(cx).get(&public_key, cx);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use gpui::{App, AppContext, Context, Entity, Global, Task};
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
pub use person::*;
|
pub use person::*;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use state::{NostrRegistry, TIMEOUT};
|
use state::{Announcement, NostrRegistry, TIMEOUT};
|
||||||
|
|
||||||
mod person;
|
mod person;
|
||||||
|
|
||||||
@@ -21,6 +21,12 @@ struct GlobalPersonRegistry(Entity<PersonRegistry>);
|
|||||||
|
|
||||||
impl Global for GlobalPersonRegistry {}
|
impl Global for GlobalPersonRegistry {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Dispatch {
|
||||||
|
Person(Box<Person>),
|
||||||
|
Announcement(Box<Event>),
|
||||||
|
}
|
||||||
|
|
||||||
/// Person Registry
|
/// Person Registry
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PersonRegistry {
|
pub struct PersonRegistry {
|
||||||
@@ -54,7 +60,7 @@ impl PersonRegistry {
|
|||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
// Channel for communication between nostr and gpui
|
// Channel for communication between nostr and gpui
|
||||||
let (tx, rx) = flume::bounded::<Person>(100);
|
let (tx, rx) = flume::bounded::<Dispatch>(100);
|
||||||
let (mta_tx, mta_rx) = flume::bounded::<PublicKey>(100);
|
let (mta_tx, mta_rx) = flume::bounded::<PublicKey>(100);
|
||||||
|
|
||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
@@ -84,9 +90,16 @@ impl PersonRegistry {
|
|||||||
tasks.push(
|
tasks.push(
|
||||||
// Update GPUI state
|
// Update GPUI state
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
while let Ok(person) = rx.recv_async().await {
|
while let Ok(event) = rx.recv_async().await {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.insert(person, cx);
|
match event {
|
||||||
|
Dispatch::Person(person) => {
|
||||||
|
this.insert(*person, cx);
|
||||||
|
}
|
||||||
|
Dispatch::Announcement(event) => {
|
||||||
|
this.set_announcement(&event, cx);
|
||||||
|
}
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -124,7 +137,7 @@ impl PersonRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handle nostr notifications
|
/// Handle nostr notifications
|
||||||
async fn handle_notifications(client: &Client, tx: &flume::Sender<Person>) {
|
async fn handle_notifications(client: &Client, tx: &flume::Sender<Dispatch>) {
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
let mut processed_events = HashSet::new();
|
let mut processed_events = HashSet::new();
|
||||||
|
|
||||||
@@ -144,12 +157,21 @@ impl PersonRegistry {
|
|||||||
Kind::Metadata => {
|
Kind::Metadata => {
|
||||||
let metadata = Metadata::from_json(&event.content).unwrap_or_default();
|
let metadata = Metadata::from_json(&event.content).unwrap_or_default();
|
||||||
let person = Person::new(event.pubkey, metadata);
|
let person = Person::new(event.pubkey, metadata);
|
||||||
|
let val = Box::new(person);
|
||||||
|
|
||||||
tx.send_async(person).await.ok();
|
// Send
|
||||||
|
tx.send_async(Dispatch::Person(val)).await.ok();
|
||||||
|
}
|
||||||
|
Kind::Custom(10044) => {
|
||||||
|
let val = Box::new(event.into_owned());
|
||||||
|
|
||||||
|
// Send
|
||||||
|
tx.send_async(Dispatch::Announcement(val)).await.ok();
|
||||||
}
|
}
|
||||||
Kind::ContactList => {
|
Kind::ContactList => {
|
||||||
let public_keys = event.extract_public_keys();
|
let public_keys = event.extract_public_keys();
|
||||||
|
|
||||||
|
// Get metadata for all public keys
|
||||||
Self::get_metadata(client, public_keys).await.ok();
|
Self::get_metadata(client, public_keys).await.ok();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -232,6 +254,18 @@ impl PersonRegistry {
|
|||||||
Ok(persons)
|
Ok(persons)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set profile encryption keys announcement
|
||||||
|
fn set_announcement(&mut self, event: &Event, cx: &mut App) {
|
||||||
|
if let Some(person) = self.persons.get(&event.pubkey) {
|
||||||
|
let announcement = Announcement::from(event);
|
||||||
|
|
||||||
|
person.update(cx, |person, cx| {
|
||||||
|
person.set_announcement(announcement);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Insert batch of persons
|
/// Insert batch of persons
|
||||||
fn bulk_inserts(&mut self, persons: Vec<Person>, cx: &mut Context<Self>) {
|
fn bulk_inserts(&mut self, persons: Vec<Person>, cx: &mut Context<Self>) {
|
||||||
for person in persons.into_iter() {
|
for person in persons.into_iter() {
|
||||||
|
|||||||
@@ -8,8 +8,13 @@ use state::Announcement;
|
|||||||
/// Person
|
/// Person
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Person {
|
pub struct Person {
|
||||||
|
/// Public Key
|
||||||
public_key: PublicKey,
|
public_key: PublicKey,
|
||||||
|
|
||||||
|
/// Metadata (profile)
|
||||||
metadata: Metadata,
|
metadata: Metadata,
|
||||||
|
|
||||||
|
/// Dekey (NIP-4e) announcement
|
||||||
announcement: Option<Announcement>,
|
announcement: Option<Announcement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +74,12 @@ impl Person {
|
|||||||
self.announcement.clone()
|
self.announcement.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set profile encryption keys announcement
|
||||||
|
pub fn set_announcement(&mut self, announcement: Announcement) {
|
||||||
|
self.announcement = Some(announcement);
|
||||||
|
log::info!("Updated announcement for: {}", self.public_key());
|
||||||
|
}
|
||||||
|
|
||||||
/// Get profile avatar
|
/// Get profile avatar
|
||||||
pub fn avatar(&self) -> SharedString {
|
pub fn avatar(&self) -> SharedString {
|
||||||
self.metadata()
|
self.metadata()
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
use gpui::SharedString;
|
use gpui::SharedString;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
|
pub enum DeviceState {
|
||||||
|
#[default]
|
||||||
|
Initial,
|
||||||
|
Requesting,
|
||||||
|
Set,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Announcement
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Announcement {
|
pub struct Announcement {
|
||||||
id: EventId,
|
/// The public key of the device that created this announcement.
|
||||||
public_key: PublicKey,
|
public_key: PublicKey,
|
||||||
|
|
||||||
|
/// The name of the device that created this announcement.
|
||||||
client_name: Option<String>,
|
client_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,27 +35,24 @@ impl From<&Event> for Announcement {
|
|||||||
.and_then(|tag| tag.content())
|
.and_then(|tag| tag.content())
|
||||||
.map(|c| c.to_string());
|
.map(|c| c.to_string());
|
||||||
|
|
||||||
Self::new(val.id, client_name, public_key)
|
Self::new(public_key, client_name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Announcement {
|
impl Announcement {
|
||||||
pub fn new(id: EventId, client_name: Option<String>, public_key: PublicKey) -> Self {
|
pub fn new(public_key: PublicKey, client_name: Option<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
|
||||||
client_name,
|
|
||||||
public_key,
|
public_key,
|
||||||
|
client_name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> EventId {
|
/// Returns the public key of the device that created this announcement.
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn public_key(&self) -> PublicKey {
|
pub fn public_key(&self) -> PublicKey {
|
||||||
self.public_key
|
self.public_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the client name of the device that created this announcement.
|
||||||
pub fn client_name(&self) -> SharedString {
|
pub fn client_name(&self) -> SharedString {
|
||||||
self.client_name
|
self.client_name
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use nostr_sdk::prelude::*;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::Announcement;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
pub enum RelayState {
|
pub enum RelayState {
|
||||||
@@ -16,13 +16,15 @@ impl RelayState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Identity {
|
pub struct Identity {
|
||||||
/// The public key of the account
|
/// The public key of the account
|
||||||
public_key: Option<PublicKey>,
|
pub public_key: Option<PublicKey>,
|
||||||
|
|
||||||
/// Encryption key announcement
|
/// Decoupled encryption key
|
||||||
announcement: Option<Announcement>,
|
///
|
||||||
|
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
|
||||||
|
dekey: Option<Arc<dyn NostrSigner>>,
|
||||||
|
|
||||||
/// Status of the current user NIP-65 relays
|
/// Status of the current user NIP-65 relays
|
||||||
relay_list: RelayState,
|
relay_list: RelayState,
|
||||||
@@ -41,7 +43,7 @@ impl Identity {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
public_key: None,
|
public_key: None,
|
||||||
announcement: None,
|
dekey: None,
|
||||||
relay_list: RelayState::default(),
|
relay_list: RelayState::default(),
|
||||||
messaging_relays: RelayState::default(),
|
messaging_relays: RelayState::default(),
|
||||||
}
|
}
|
||||||
@@ -57,6 +59,7 @@ impl Identity {
|
|||||||
self.relay_list
|
self.relay_list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the state of the NIP-17 relays.
|
||||||
pub fn set_messaging_relays_state(&mut self, state: RelayState) {
|
pub fn set_messaging_relays_state(&mut self, state: RelayState) {
|
||||||
self.messaging_relays = state;
|
self.messaging_relays = state;
|
||||||
}
|
}
|
||||||
@@ -66,6 +69,26 @@ impl Identity {
|
|||||||
self.messaging_relays
|
self.messaging_relays
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the decoupled encryption key.
|
||||||
|
pub fn dekey(&self) -> Option<Arc<dyn NostrSigner>> {
|
||||||
|
self.dekey.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the decoupled encryption key.
|
||||||
|
pub fn set_dekey<S>(&mut self, dekey: S)
|
||||||
|
where
|
||||||
|
S: NostrSigner + 'static,
|
||||||
|
{
|
||||||
|
self.dekey = Some(Arc::new(dekey));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Force getting the public key of the identity.
|
||||||
|
///
|
||||||
|
/// Panics if the public key is not set.
|
||||||
|
pub fn public_key(&self) -> PublicKey {
|
||||||
|
self.public_key.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the identity has a public key.
|
/// Returns true if the identity has a public key.
|
||||||
pub fn has_public_key(&self) -> bool {
|
pub fn has_public_key(&self) -> bool {
|
||||||
self.public_key.is_some()
|
self.public_key.is_some()
|
||||||
@@ -80,15 +103,4 @@ impl Identity {
|
|||||||
pub fn unset_public_key(&mut self) {
|
pub fn unset_public_key(&mut self) {
|
||||||
self.public_key = None;
|
self.public_key = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the public key of the identity.
|
|
||||||
pub fn option_public_key(&self) -> Option<PublicKey> {
|
|
||||||
self.public_key
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the public key of the identity.
|
|
||||||
pub fn public_key(&self) -> PublicKey {
|
|
||||||
// This method is safe to unwrap because the public key is always called when the identity is created.
|
|
||||||
self.public_key.unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::{anyhow, Context as AnyhowContext, Error};
|
||||||
use common::{config_dir, BOOTSTRAP_RELAYS, SEARCH_RELAYS};
|
use common::{app_name, config_dir, BOOTSTRAP_RELAYS, SEARCH_RELAYS};
|
||||||
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
|
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
|
||||||
use nostr_lmdb::NostrLmdb;
|
use nostr_lmdb::NostrLmdb;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
@@ -52,6 +52,11 @@ pub struct NostrRegistry {
|
|||||||
/// Gossip implementation
|
/// Gossip implementation
|
||||||
gossip: Entity<Gossip>,
|
gossip: Entity<Gossip>,
|
||||||
|
|
||||||
|
/// Device state
|
||||||
|
///
|
||||||
|
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
|
||||||
|
device_state: Entity<DeviceState>,
|
||||||
|
|
||||||
/// Tasks for asynchronous operations
|
/// Tasks for asynchronous operations
|
||||||
tasks: Vec<Task<Result<(), Error>>>,
|
tasks: Vec<Task<Result<(), Error>>>,
|
||||||
|
|
||||||
@@ -108,6 +113,7 @@ impl NostrRegistry {
|
|||||||
|
|
||||||
// Construct the identity entity
|
// Construct the identity entity
|
||||||
let identity = cx.new(|_| Identity::default());
|
let identity = cx.new(|_| Identity::default());
|
||||||
|
let device_state = cx.new(|_| DeviceState::default());
|
||||||
|
|
||||||
// Channel for communication between nostr and gpui
|
// Channel for communication between nostr and gpui
|
||||||
let (tx, rx) = flume::bounded::<Event>(2048);
|
let (tx, rx) = flume::bounded::<Event>(2048);
|
||||||
@@ -123,16 +129,18 @@ impl NostrRegistry {
|
|||||||
RelayState::Initial => {
|
RelayState::Initial => {
|
||||||
this.get_relay_list(cx);
|
this.get_relay_list(cx);
|
||||||
}
|
}
|
||||||
RelayState::Set => match state.read(cx).messaging_relays_state() {
|
RelayState::Set => {
|
||||||
RelayState::Initial => {
|
match state.read(cx).messaging_relays_state() {
|
||||||
this.get_profile(cx);
|
RelayState::Initial => {
|
||||||
this.get_messaging_relays(cx);
|
this.get_profile(cx);
|
||||||
}
|
this.get_messaging_relays(cx);
|
||||||
RelayState::Set => {
|
}
|
||||||
this.get_messages(cx);
|
RelayState::Set => {
|
||||||
}
|
this.get_messages(state.read(cx).dekey(), cx);
|
||||||
_ => {}
|
}
|
||||||
},
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +158,7 @@ impl NostrRegistry {
|
|||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
// Update GPUI states
|
// Update GPUI states
|
||||||
cx.spawn(async move |_this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
while let Ok(event) = rx.recv_async().await {
|
while let Ok(event) = rx.recv_async().await {
|
||||||
match event.kind {
|
match event.kind {
|
||||||
Kind::RelayList => {
|
Kind::RelayList => {
|
||||||
@@ -165,6 +173,11 @@ impl NostrRegistry {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
Kind::Custom(10044) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.init_dekey(&event, cx);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,6 +189,7 @@ impl NostrRegistry {
|
|||||||
Self {
|
Self {
|
||||||
client,
|
client,
|
||||||
identity,
|
identity,
|
||||||
|
device_state,
|
||||||
gossip,
|
gossip,
|
||||||
app_keys,
|
app_keys,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
@@ -183,7 +197,7 @@ impl NostrRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle nostr notifications
|
/// Handle nostr notifications
|
||||||
async fn handle_notifications(client: &Client, tx: &flume::Sender<Event>) -> Result<(), Error> {
|
async fn handle_notifications(client: &Client, tx: &flume::Sender<Event>) -> Result<(), Error> {
|
||||||
// Add bootstrap relay to the relay pool
|
// Add bootstrap relay to the relay pool
|
||||||
for url in BOOTSTRAP_RELAYS.into_iter() {
|
for url in BOOTSTRAP_RELAYS.into_iter() {
|
||||||
@@ -198,6 +212,7 @@ impl NostrRegistry {
|
|||||||
// Connect to all added relays
|
// Connect to all added relays
|
||||||
client.connect().await;
|
client.connect().await;
|
||||||
|
|
||||||
|
// Handle nostr notifications
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
let mut processed_events = HashSet::new();
|
let mut processed_events = HashSet::new();
|
||||||
|
|
||||||
@@ -217,7 +232,7 @@ impl NostrRegistry {
|
|||||||
Kind::RelayList => {
|
Kind::RelayList => {
|
||||||
// Automatically get messaging relays for each member when the user opens a room
|
// Automatically get messaging relays for each member when the user opens a room
|
||||||
if subscription_id.as_str().starts_with("room-") {
|
if subscription_id.as_str().starts_with("room-") {
|
||||||
Self::get_messaging_relays_by(client, event.as_ref()).await?;
|
Self::get_adv_events_by(client, event.as_ref()).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.send_async(event.into_owned()).await?;
|
tx.send_async(event.into_owned()).await?;
|
||||||
@@ -225,6 +240,16 @@ impl NostrRegistry {
|
|||||||
Kind::InboxRelays => {
|
Kind::InboxRelays => {
|
||||||
tx.send_async(event.into_owned()).await?;
|
tx.send_async(event.into_owned()).await?;
|
||||||
}
|
}
|
||||||
|
Kind::Custom(10044) => {
|
||||||
|
if let Ok(signer) = client.signer().await {
|
||||||
|
if let Ok(public_key) = signer.get_public_key().await {
|
||||||
|
// Only send if the event is from the current user
|
||||||
|
if public_key == event.pubkey {
|
||||||
|
tx.send_async(event.into_owned()).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,8 +276,8 @@ impl NostrRegistry {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Automatically get messaging relays from a received relay list
|
/// Automatically get messaging relays and encryption announcement from a received relay list
|
||||||
async fn get_messaging_relays_by(client: &Client, event: &Event) -> Result<(), Error> {
|
async fn get_adv_events_by(client: &Client, event: &Event) -> Result<(), Error> {
|
||||||
// Subscription options
|
// Subscription options
|
||||||
let opts = SubscribeAutoCloseOptions::default()
|
let opts = SubscribeAutoCloseOptions::default()
|
||||||
.timeout(Some(Duration::from_secs(TIMEOUT)))
|
.timeout(Some(Duration::from_secs(TIMEOUT)))
|
||||||
@@ -276,16 +301,20 @@ impl NostrRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Construct filter for inbox relays
|
// Construct filter for inbox relays
|
||||||
let filter = Filter::new()
|
let inbox = Filter::new()
|
||||||
.kind(Kind::InboxRelays)
|
.kind(Kind::InboxRelays)
|
||||||
.author(event.pubkey)
|
.author(event.pubkey)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
client
|
// Construct filter for encryption announcement
|
||||||
.subscribe_to(write_relays, vec![filter], Some(opts))
|
let announcement = Filter::new()
|
||||||
.await?;
|
.kind(Kind::Custom(10044))
|
||||||
|
.author(event.pubkey)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
log::info!("Getting inbox relays for: {}", event.pubkey);
|
client
|
||||||
|
.subscribe_to(write_relays, vec![inbox, announcement], Some(opts))
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -528,18 +557,8 @@ impl NostrRegistry {
|
|||||||
.limit(1)
|
.limit(1)
|
||||||
.author(public_key);
|
.author(public_key);
|
||||||
|
|
||||||
// Filter for encryption keys announcement
|
|
||||||
let encryption_keys = Filter::new()
|
|
||||||
.kind(Kind::Custom(10044))
|
|
||||||
.limit(1)
|
|
||||||
.author(public_key);
|
|
||||||
|
|
||||||
client
|
client
|
||||||
.subscribe_to(
|
.subscribe_to(urls, vec![metadata, contact_list], Some(opts))
|
||||||
urls,
|
|
||||||
vec![metadata, contact_list, encryption_keys],
|
|
||||||
Some(opts),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -604,7 +623,10 @@ impl NostrRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Continuously get gift wrap events for the current user in their messaging relays
|
/// Continuously get gift wrap events for the current user in their messaging relays
|
||||||
fn get_messages(&mut self, cx: &mut Context<Self>) {
|
fn get_messages<T>(&mut self, dekey: Option<T>, cx: &mut Context<Self>)
|
||||||
|
where
|
||||||
|
T: NostrSigner + 'static,
|
||||||
|
{
|
||||||
let client = self.client();
|
let client = self.client();
|
||||||
let public_key = self.identity().read(cx).public_key();
|
let public_key = self.identity().read(cx).public_key();
|
||||||
let messaging_relays = self.messaging_relays(&public_key, cx);
|
let messaging_relays = self.messaging_relays(&public_key, cx);
|
||||||
@@ -612,43 +634,176 @@ impl NostrRegistry {
|
|||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let urls = messaging_relays.await;
|
let urls = messaging_relays.await;
|
||||||
let id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION);
|
let id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION);
|
||||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
let mut filters = vec![];
|
||||||
|
|
||||||
if let Err(e) = client
|
// Construct a filter to get user messages
|
||||||
.subscribe_with_id_to(urls, id, vec![filter], None)
|
filters.push(Filter::new().kind(Kind::GiftWrap).pubkey(public_key));
|
||||||
.await
|
|
||||||
{
|
// Construct a filter to get dekey messages if available
|
||||||
|
if let Some(signer) = dekey {
|
||||||
|
if let Ok(pubkey) = signer.get_public_key().await {
|
||||||
|
filters.push(Filter::new().kind(Kind::GiftWrap).pubkey(pubkey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = client.subscribe_with_id_to(urls, id, filters, None).await {
|
||||||
log::error!("Failed to subscribe to gift wrap events: {e}");
|
log::error!("Failed to subscribe to gift wrap events: {e}");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Subscribe to event kinds to author's write relays
|
/// Set the decoupled encryption key for the current user
|
||||||
pub fn subscribe<I>(&self, kinds: I, author: PublicKey, cx: &App)
|
fn set_dekey<T>(&mut self, dekey: T, cx: &mut Context<Self>)
|
||||||
where
|
where
|
||||||
I: Into<Vec<Kind>>,
|
T: NostrSigner + 'static,
|
||||||
{
|
{
|
||||||
|
self.identity.update(cx, |this, cx| {
|
||||||
|
this.set_dekey(dekey);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
self.device_state.update(cx, |this, cx| {
|
||||||
|
*this = DeviceState::Set;
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize dekey (decoupled encryption key) for the current user
|
||||||
|
fn init_dekey(&mut self, event: &Event, cx: &mut Context<Self>) {
|
||||||
let client = self.client();
|
let client = self.client();
|
||||||
let write_relays = self.write_relays(&author, cx);
|
let announcement = Announcement::from(event);
|
||||||
|
let dekey = announcement.public_key();
|
||||||
|
|
||||||
// Construct filters based on event kinds
|
let task: Task<Result<Keys, Error>> = cx.background_spawn(async move {
|
||||||
let filters: Vec<Filter> = kinds
|
let signer = client.signer().await?;
|
||||||
.into()
|
let public_key = signer.get_public_key().await?;
|
||||||
.into_iter()
|
|
||||||
.map(|kind| Filter::new().kind(kind).author(author).limit(1))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
let filter = Filter::new()
|
||||||
let urls = write_relays.await;
|
.identifier("coop:device")
|
||||||
|
.kind(Kind::ApplicationSpecificData)
|
||||||
|
.author(public_key)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
// Construct subscription options
|
if let Some(event) = client.database().query(filter).await?.first() {
|
||||||
let opts = SubscribeAutoCloseOptions::default()
|
let content = signer.nip44_decrypt(&public_key, &event.content).await?;
|
||||||
.timeout(Some(Duration::from_secs(TIMEOUT)))
|
let secret = SecretKey::parse(&content)?;
|
||||||
.exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let keys = Keys::new(secret);
|
||||||
|
|
||||||
if let Err(e) = client.subscribe_to(urls, filters, Some(opts)).await {
|
if keys.public_key() == dekey {
|
||||||
log::error!("Failed to create a subscription: {e}");
|
Ok(keys)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Key mismatch"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Key not found"))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
match task.await {
|
||||||
|
Ok(keys) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_dekey(keys, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Failed to initialize dekey: {e}");
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.request_dekey(cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request dekey from other device
|
||||||
|
fn request_dekey(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let client = self.client();
|
||||||
|
let device_state = self.device_state.downgrade();
|
||||||
|
let public_key = self.identity().read(cx).public_key();
|
||||||
|
let write_relays = self.write_relays(&public_key, cx);
|
||||||
|
|
||||||
|
let app_keys = self.app_keys().clone();
|
||||||
|
let app_pubkey = app_keys.public_key();
|
||||||
|
|
||||||
|
let task: Task<Result<Option<Keys>, Error>> = cx.background_spawn(async move {
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kind(Kind::Custom(4455))
|
||||||
|
.author(public_key)
|
||||||
|
.pubkey(app_pubkey)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
match client.database().query(filter).await?.first_owned() {
|
||||||
|
Some(event) => {
|
||||||
|
let root_device = event
|
||||||
|
.tags
|
||||||
|
.find(TagKind::custom("P"))
|
||||||
|
.and_then(|tag| tag.content())
|
||||||
|
.and_then(|content| PublicKey::parse(content).ok())
|
||||||
|
.context("Invalid event's tags")?;
|
||||||
|
|
||||||
|
let payload = event.content.as_str();
|
||||||
|
let decrypted = app_keys.nip44_decrypt(&root_device, payload).await?;
|
||||||
|
|
||||||
|
let secret = SecretKey::from_hex(&decrypted)?;
|
||||||
|
let keys = Keys::new(secret);
|
||||||
|
|
||||||
|
Ok(Some(keys))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let urls = write_relays.await;
|
||||||
|
|
||||||
|
// Construct an event for device key request
|
||||||
|
let event = EventBuilder::new(Kind::Custom(4454), "")
|
||||||
|
.tags(vec![
|
||||||
|
Tag::client(app_name()),
|
||||||
|
Tag::custom(TagKind::custom("P"), vec![app_pubkey]),
|
||||||
|
])
|
||||||
|
.sign(&signer)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Send the event to write relays
|
||||||
|
client.send_event_to(&urls, &event).await?;
|
||||||
|
|
||||||
|
// Construct a filter to get the approval response event
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kind(Kind::Custom(4455))
|
||||||
|
.author(public_key)
|
||||||
|
.since(Timestamp::now());
|
||||||
|
|
||||||
|
// Subscribe to the approval response event
|
||||||
|
client.subscribe_to(&urls, vec![filter], None).await?;
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
match task.await {
|
||||||
|
Ok(Some(keys)) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_dekey(keys, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
device_state
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
*this = DeviceState::Requesting;
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to request the encryption key: {e}");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|||||||
Reference in New Issue
Block a user