refactor device
This commit is contained in:
@@ -37,6 +37,7 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Workspace> {
|
|||||||
cx.new(|cx| Workspace::new(window, cx))
|
cx.new(|cx| Workspace::new(window, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DeviceNotifcation;
|
||||||
struct SignerNotifcation;
|
struct SignerNotifcation;
|
||||||
struct RelayNotifcation;
|
struct RelayNotifcation;
|
||||||
|
|
||||||
@@ -171,9 +172,32 @@ impl Workspace {
|
|||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
DeviceEvent::NotSet { reason } => {
|
||||||
|
let note = Notification::new()
|
||||||
|
.id::<DeviceNotifcation>()
|
||||||
|
.title("Cannot setup the encryption key")
|
||||||
|
.message(reason)
|
||||||
|
.autohide(false)
|
||||||
|
.with_kind(NotificationKind::Error);
|
||||||
|
|
||||||
|
window.push_notification(note, cx);
|
||||||
|
}
|
||||||
|
DeviceEvent::NotSubscribe { reason } => {
|
||||||
|
let note = Notification::new()
|
||||||
|
.id::<DeviceNotifcation>()
|
||||||
|
.title("Cannot getting messages")
|
||||||
|
.message(reason)
|
||||||
|
.autohide(false)
|
||||||
|
.with_kind(NotificationKind::Error);
|
||||||
|
|
||||||
|
window.push_notification(note, cx);
|
||||||
|
}
|
||||||
DeviceEvent::Error(error) => {
|
DeviceEvent::Error(error) => {
|
||||||
window.push_notification(Notification::error(error).autohide(false), cx);
|
window.push_notification(Notification::error(error).autohide(false), cx);
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -424,35 +448,13 @@ impl Workspace {
|
|||||||
.child(SharedString::from(ENC_WARN)),
|
.child(SharedString::from(ENC_WARN)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.on_ok(move |_ev, window, cx| {
|
.on_ok(move |_ev, _window, cx| {
|
||||||
let device = DeviceRegistry::global(cx);
|
let device = DeviceRegistry::global(cx);
|
||||||
let task = device.read(cx).create_encryption(cx);
|
device.update(cx, |this, cx| {
|
||||||
|
this.set_announcement(cx);
|
||||||
window
|
});
|
||||||
.spawn(cx, async move |cx| {
|
// true to close modal
|
||||||
let result = task.await;
|
true
|
||||||
|
|
||||||
cx.update(|window, cx| match result {
|
|
||||||
Ok(keys) => {
|
|
||||||
device.update(cx, |this, cx| {
|
|
||||||
this.set_signer(keys, cx);
|
|
||||||
this.listen_request(cx);
|
|
||||||
});
|
|
||||||
window.close_modal(cx);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
window.push_notification(
|
|
||||||
Notification::error(e.to_string()).autohide(false),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
// false to keep modal open
|
|
||||||
false
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -702,25 +704,46 @@ impl Workspace {
|
|||||||
.ghost()
|
.ghost()
|
||||||
.dropdown_menu(move |this, _window, cx| {
|
.dropdown_menu(move |this, _window, cx| {
|
||||||
let device = DeviceRegistry::global(cx);
|
let device = DeviceRegistry::global(cx);
|
||||||
let state = device.read(cx).state();
|
let subscribing = device.read(cx).subscribing;
|
||||||
|
let requesting = device.read(cx).requesting;
|
||||||
|
|
||||||
this.min_w(px(260.))
|
this.min_w(px(260.))
|
||||||
|
.when(requesting, |this| {
|
||||||
|
this.item(PopupMenuItem::element(move |_window, cx| {
|
||||||
|
h_flex()
|
||||||
|
.px_1()
|
||||||
|
.w_full()
|
||||||
|
.gap_2()
|
||||||
|
.text_sm()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.size_1p5()
|
||||||
|
.rounded_full()
|
||||||
|
.bg(cx.theme().icon_accent),
|
||||||
|
)
|
||||||
|
.child(SharedString::from("Waiting for approval..."))
|
||||||
|
}))
|
||||||
|
})
|
||||||
.item(PopupMenuItem::element(move |_window, cx| {
|
.item(PopupMenuItem::element(move |_window, cx| {
|
||||||
h_flex()
|
h_flex()
|
||||||
.px_1()
|
.px_1()
|
||||||
.w_full()
|
.w_full()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.child(
|
.child(div().size_1p5().rounded_full().map(|this| {
|
||||||
div()
|
if subscribing {
|
||||||
.size_1p5()
|
this.bg(cx.theme().icon_accent)
|
||||||
.rounded_full()
|
} else {
|
||||||
.when(state.set(), |this| this.bg(gpui::green()))
|
this.bg(cx.theme().icon_muted)
|
||||||
.when(state.requesting(), |this| {
|
}
|
||||||
this.bg(cx.theme().icon_accent)
|
}))
|
||||||
}),
|
.map(|this| {
|
||||||
)
|
if subscribing {
|
||||||
.child(SharedString::from(state.to_string()))
|
this.child(SharedString::from("Getting messages..."))
|
||||||
|
} else {
|
||||||
|
this.child(SharedString::from("Not getting messages"))
|
||||||
|
}
|
||||||
|
})
|
||||||
}))
|
}))
|
||||||
.separator()
|
.separator()
|
||||||
.menu_with_icon(
|
.menu_with_icon(
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
use state::{
|
use state::{Announcement, DEVICE_GIFTWRAP, NostrRegistry, StateEvent, TIMEOUT, app_name};
|
||||||
Announcement, DEVICE_GIFTWRAP, DeviceState, NostrRegistry, StateEvent, TIMEOUT, app_name,
|
|
||||||
};
|
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
@@ -36,17 +34,53 @@ impl Global for GlobalDeviceRegistry {}
|
|||||||
pub enum DeviceEvent {
|
pub enum DeviceEvent {
|
||||||
/// A new encryption signer has been set
|
/// A new encryption signer has been set
|
||||||
Set,
|
Set,
|
||||||
|
/// The encryption key has been reset
|
||||||
|
Reset,
|
||||||
|
/// Encryption key is not set
|
||||||
|
NotSet { reason: SharedString },
|
||||||
|
/// An event to notify that Coop isn't subscribed to gift wrap events
|
||||||
|
NotSubscribe { reason: SharedString },
|
||||||
/// An error occurred
|
/// An error occurred
|
||||||
Error(SharedString),
|
Error(SharedString),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DeviceEvent {
|
||||||
|
pub fn error<T>(error: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<SharedString>,
|
||||||
|
{
|
||||||
|
Self::Error(error.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn not_subscribe<T>(reason: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<SharedString>,
|
||||||
|
{
|
||||||
|
Self::NotSubscribe {
|
||||||
|
reason: reason.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn not_set<T>(reason: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<SharedString>,
|
||||||
|
{
|
||||||
|
Self::NotSet {
|
||||||
|
reason: reason.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Device Registry
|
/// Device Registry
|
||||||
///
|
///
|
||||||
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
|
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DeviceRegistry {
|
pub struct DeviceRegistry {
|
||||||
/// Device state
|
/// Whether the registry is currently subscribing to gift wrap events
|
||||||
state: DeviceState,
|
pub subscribing: bool,
|
||||||
|
|
||||||
|
/// Whether the registry is waiting for encryption key approval from other devices
|
||||||
|
pub requesting: bool,
|
||||||
|
|
||||||
/// Async tasks
|
/// Async tasks
|
||||||
tasks: Vec<Task<Result<(), Error>>>,
|
tasks: Vec<Task<Result<(), Error>>>,
|
||||||
@@ -71,30 +105,30 @@ impl DeviceRegistry {
|
|||||||
/// Create a new device registry instance
|
/// Create a new device registry instance
|
||||||
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let state = DeviceState::default();
|
|
||||||
|
|
||||||
let subscription = Some(cx.subscribe_in(
|
// Get announcement when signer is set
|
||||||
&nostr,
|
let subscription = cx.subscribe_in(&nostr, window, |this, _e, event, _window, cx| {
|
||||||
window,
|
match event {
|
||||||
|this, _state, event, _window, cx| match event {
|
|
||||||
StateEvent::SignerSet => {
|
StateEvent::SignerSet => {
|
||||||
this.reset(cx);
|
this.set_subscribing(false, cx);
|
||||||
|
this.set_requesting(false, cx);
|
||||||
}
|
}
|
||||||
StateEvent::RelayConnected => {
|
StateEvent::RelayConnected => {
|
||||||
this.get_announcement(cx);
|
this.get_announcement(cx);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
};
|
||||||
));
|
});
|
||||||
|
|
||||||
cx.defer_in(window, |this, window, cx| {
|
cx.defer_in(window, |this, window, cx| {
|
||||||
this.handle_notifications(window, cx);
|
this.handle_notifications(window, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
state,
|
subscribing: false,
|
||||||
|
requesting: false,
|
||||||
tasks: vec![],
|
tasks: vec![],
|
||||||
_subscription: subscription,
|
_subscription: Some(subscription),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,13 +174,13 @@ impl DeviceRegistry {
|
|||||||
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
self.tasks.push(cx.spawn_in(window, 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 {
|
||||||
// New request event
|
// New request event from other device
|
||||||
Kind::Custom(4454) => {
|
Kind::Custom(4454) => {
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.ask_for_approval(event, window, cx);
|
this.ask_for_approval(event, window, cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
// New response event
|
// New response event from the master device
|
||||||
Kind::Custom(4455) => {
|
Kind::Custom(4455) => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.extract_encryption(event, cx);
|
this.extract_encryption(event, cx);
|
||||||
@@ -155,24 +189,24 @@ impl DeviceRegistry {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the device state
|
/// Set whether the registry is currently subscribing to gift wrap events
|
||||||
pub fn state(&self) -> DeviceState {
|
fn set_subscribing(&mut self, subscribing: bool, cx: &mut Context<Self>) {
|
||||||
self.state.clone()
|
self.subscribing = subscribing;
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the device state
|
/// Set whether the registry is waiting for encryption key approval from other devices
|
||||||
fn set_state(&mut self, state: DeviceState, cx: &mut Context<Self>) {
|
fn set_requesting(&mut self, requesting: bool, cx: &mut Context<Self>) {
|
||||||
self.state = state;
|
self.requesting = requesting;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the decoupled encryption key for the current user
|
/// Set the decoupled encryption key for the current user
|
||||||
pub fn set_signer<S>(&mut self, new: S, cx: &mut Context<Self>)
|
fn set_signer<S>(&mut self, new: S, cx: &mut Context<Self>)
|
||||||
where
|
where
|
||||||
S: NostrSigner + 'static,
|
S: NostrSigner + 'static,
|
||||||
{
|
{
|
||||||
@@ -184,7 +218,7 @@ impl DeviceRegistry {
|
|||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_state(DeviceState::Set, cx);
|
cx.emit(DeviceEvent::Set);
|
||||||
this.get_messages(cx);
|
this.get_messages(cx);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -192,12 +226,6 @@ impl DeviceRegistry {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset the device state
|
|
||||||
fn reset(&mut self, cx: &mut Context<Self>) {
|
|
||||||
self.state = DeviceState::Idle;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all messages for encryption keys
|
/// Get all messages for encryption keys
|
||||||
fn get_messages(&mut self, cx: &mut Context<Self>) {
|
fn get_messages(&mut self, cx: &mut Context<Self>) {
|
||||||
let task = self.subscribe_to_giftwrap_events(cx);
|
let task = self.subscribe_to_giftwrap_events(cx);
|
||||||
@@ -205,59 +233,50 @@ impl DeviceRegistry {
|
|||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
if let Err(e) = task.await {
|
if let Err(e) = task.await {
|
||||||
this.update(cx, |_this, cx| {
|
this.update(cx, |_this, cx| {
|
||||||
cx.emit(DeviceEvent::Error(SharedString::from(e.to_string())));
|
cx.emit(DeviceEvent::not_subscribe(e.to_string()));
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_subscribing(true, cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the messaging relays for the current user
|
|
||||||
fn get_user_messaging_relays(&self, cx: &App) -> Task<Result<Vec<RelayUrl>, Error>> {
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
let client = nostr.read(cx).client();
|
|
||||||
let signer = nostr.read(cx).signer();
|
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
let public_key = signer.get_public_key().await?;
|
|
||||||
let filter = Filter::new()
|
|
||||||
.kind(Kind::InboxRelays)
|
|
||||||
.author(public_key)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
|
||||||
// Extract relay URLs from the event
|
|
||||||
let urls: Vec<RelayUrl> = nip17::extract_owned_relay_list(event).collect();
|
|
||||||
|
|
||||||
// Ensure all relays are connected
|
|
||||||
for url in urls.iter() {
|
|
||||||
client.add_relay(url).and_connect().await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(urls)
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Relays not found"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 subscribe_to_giftwrap_events(&self, cx: &App) -> Task<Result<(), Error>> {
|
fn subscribe_to_giftwrap_events(&self, cx: &App) -> Task<Result<(), Error>> {
|
||||||
|
let persons = PersonRegistry::global(cx);
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let signer = nostr.read(cx).signer();
|
let signer = nostr.read(cx).signer();
|
||||||
let urls = self.get_user_messaging_relays(cx);
|
|
||||||
|
let Some(user) = signer.public_key() else {
|
||||||
|
return Task::ready(Err(anyhow!("User not found")));
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile = persons.read(cx).get(&user, cx);
|
||||||
|
let relays = profile.messaging_relays().clone();
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let urls = urls.await?;
|
|
||||||
let encryption = signer.get_encryption_signer().await.context("not found")?;
|
let encryption = signer.get_encryption_signer().await.context("not found")?;
|
||||||
let public_key = encryption.get_public_key().await?;
|
let public_key = encryption.get_public_key().await?;
|
||||||
|
|
||||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||||
let id = SubscriptionId::new(DEVICE_GIFTWRAP);
|
let id = SubscriptionId::new(DEVICE_GIFTWRAP);
|
||||||
|
|
||||||
|
// Ensure user has relays configured
|
||||||
|
if relays.is_empty() {
|
||||||
|
return Err(anyhow!("No messaging relays found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure relays are connected
|
||||||
|
for url in relays.iter() {
|
||||||
|
client.add_relay(url).and_connect().await?;
|
||||||
|
}
|
||||||
|
|
||||||
// Construct target for subscription
|
// Construct target for subscription
|
||||||
let target: HashMap<RelayUrl, Filter> = urls
|
let target: HashMap<RelayUrl, Filter> = relays
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|relay| (relay, filter.clone()))
|
.map(|relay| (relay, filter.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
@@ -302,13 +321,15 @@ impl DeviceRegistry {
|
|||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
match task.await {
|
match task.await {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
|
// Set encryption key from the announcement event
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.new_signer(&event, cx);
|
this.set_encryption(&event, cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
// User has no announcement, create a new one
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.announce(cx);
|
this.set_announcement(cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,8 +338,30 @@ impl DeviceRegistry {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new device signer and announce it to user's relay list
|
||||||
|
pub fn set_announcement(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let task = self.new_encryption(cx);
|
||||||
|
|
||||||
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
|
match task.await {
|
||||||
|
Ok(keys) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_signer(keys, cx);
|
||||||
|
this.wait_for_request(cx);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
this.update(cx, |_this, cx| {
|
||||||
|
cx.emit(DeviceEvent::error(e.to_string()));
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/// Create new encryption keys
|
/// Create new encryption keys
|
||||||
pub fn create_encryption(&self, cx: &App) -> Task<Result<Keys, Error>> {
|
fn new_encryption(&self, cx: &App) -> Task<Result<Keys, Error>> {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
@@ -328,12 +371,13 @@ impl DeviceRegistry {
|
|||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
// Construct an announcement event
|
// Construct an announcement event
|
||||||
let event = client
|
let builder = EventBuilder::new(Kind::Custom(10044), "").tags(vec![
|
||||||
.sign_event_builder(EventBuilder::new(Kind::Custom(10044), "").tags(vec![
|
Tag::custom(TagKind::custom("n"), vec![n]),
|
||||||
Tag::custom(TagKind::custom("n"), vec![n]),
|
Tag::client(app_name()),
|
||||||
Tag::client(app_name()),
|
]);
|
||||||
]))
|
|
||||||
.await?;
|
// Sign the event with user's signer
|
||||||
|
let event = client.sign_event_builder(builder).await?;
|
||||||
|
|
||||||
// Publish announcement
|
// Publish announcement
|
||||||
client.send_event(&event).to_nip65().await?;
|
client.send_event(&event).to_nip65().await?;
|
||||||
@@ -345,39 +389,23 @@ impl DeviceRegistry {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new device signer and announce it
|
/// Set encryption key from the announcement event
|
||||||
fn announce(&mut self, cx: &mut Context<Self>) {
|
fn set_encryption(&mut self, event: &Event, cx: &mut Context<Self>) {
|
||||||
let task = self.create_encryption(cx);
|
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
|
||||||
let keys = task.await?;
|
|
||||||
|
|
||||||
// Update signer
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.set_signer(keys, cx);
|
|
||||||
this.listen_request(cx);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initialize device signer (decoupled encryption key) for the current user
|
|
||||||
pub fn new_signer(&mut self, event: &Event, cx: &mut Context<Self>) {
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
let announcement = Announcement::from(event);
|
let announcement = Announcement::from(event);
|
||||||
let device_pubkey = announcement.public_key();
|
let device_pubkey = announcement.public_key();
|
||||||
|
|
||||||
|
// Get encryption key from the database and compare with the announcement
|
||||||
let task: Task<Result<Keys, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Keys, Error>> = cx.background_spawn(async move {
|
||||||
if let Ok(keys) = get_keys(&client).await {
|
if let Ok(keys) = get_keys(&client).await {
|
||||||
if keys.public_key() != device_pubkey {
|
if keys.public_key() != device_pubkey {
|
||||||
return Err(anyhow!("Key mismatch"));
|
return Err(anyhow!("Encryption Key doesn't match the announcement"));
|
||||||
};
|
};
|
||||||
Ok(keys)
|
Ok(keys)
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Key not found"))
|
Err(anyhow!("Encryption Key not found. Please create a new key"))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -386,74 +414,49 @@ impl DeviceRegistry {
|
|||||||
Ok(keys) => {
|
Ok(keys) => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_signer(keys, cx);
|
this.set_signer(keys, cx);
|
||||||
this.listen_request(cx);
|
this.wait_for_request(cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("Failed to initialize device signer: {e}");
|
this.update(cx, |_this, cx| {
|
||||||
this.update(cx, |this, cx| {
|
cx.emit(DeviceEvent::not_set(e.to_string()));
|
||||||
this.request(cx);
|
|
||||||
this.listen_approval(cx);
|
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Listen for device key requests on user's write relays
|
/// Wait for encryption key requests from now on
|
||||||
pub fn listen_request(&mut self, cx: &mut Context<Self>) {
|
fn wait_for_request(&mut self, cx: &mut Context<Self>) {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let signer = nostr.read(cx).signer();
|
let signer = nostr.read(cx).signer();
|
||||||
|
|
||||||
let Some(public_key) = signer.public_key() else {
|
self.tasks.push(cx.background_spawn(async move {
|
||||||
return;
|
let public_key = signer.get_public_key().await?;
|
||||||
};
|
|
||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
// Construct a filter for encryption key requests
|
||||||
// Construct a filter for device key requests
|
let now = Filter::new()
|
||||||
let filter = Filter::new()
|
|
||||||
.kind(Kind::Custom(4454))
|
.kind(Kind::Custom(4454))
|
||||||
.author(public_key)
|
.author(public_key)
|
||||||
.since(Timestamp::now());
|
.since(Timestamp::now());
|
||||||
|
|
||||||
// Subscribe to the device key requests on user's write relays
|
// Construct a filter for the last encryption key request
|
||||||
client.subscribe(filter).await?;
|
let last = Filter::new()
|
||||||
|
.kind(Kind::Custom(4454))
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
task.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Listen for device key approvals on user's write relays
|
|
||||||
fn listen_approval(&mut self, cx: &mut Context<Self>) {
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
let client = nostr.read(cx).client();
|
|
||||||
let signer = nostr.read(cx).signer();
|
|
||||||
|
|
||||||
let Some(public_key) = signer.public_key() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.tasks.push(cx.background_spawn(async move {
|
|
||||||
// Construct a filter for device key requests
|
|
||||||
let filter = Filter::new()
|
|
||||||
.kind(Kind::Custom(4455))
|
|
||||||
.author(public_key)
|
.author(public_key)
|
||||||
.since(Timestamp::now());
|
.limit(1);
|
||||||
|
|
||||||
// Subscribe to the device key requests on user's write relays
|
// Subscribe to the device key requests on user's write relays
|
||||||
client.subscribe(filter).await?;
|
client.subscribe(vec![now, last]).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request encryption keys from other device
|
/// Request encryption keys from other device
|
||||||
fn request(&mut self, cx: &mut Context<Self>) {
|
pub fn request(&mut self, cx: &mut Context<Self>) {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let signer = nostr.read(cx).signer();
|
let signer = nostr.read(cx).signer();
|
||||||
@@ -461,9 +464,10 @@ impl DeviceRegistry {
|
|||||||
let app_keys = nostr.read(cx).keys();
|
let app_keys = nostr.read(cx).keys();
|
||||||
let app_pubkey = app_keys.public_key();
|
let app_pubkey = app_keys.public_key();
|
||||||
|
|
||||||
let task: Task<Result<Option<Keys>, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Option<Event>, Error>> = cx.background_spawn(async move {
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
|
// Construct a filter to get the latest approval event
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::Custom(4455))
|
.kind(Kind::Custom(4455))
|
||||||
.author(public_key)
|
.author(public_key)
|
||||||
@@ -471,30 +475,18 @@ impl DeviceRegistry {
|
|||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
match client.database().query(filter).await?.first_owned() {
|
match client.database().query(filter).await?.first_owned() {
|
||||||
Some(event) => {
|
// Found an approval event
|
||||||
let root_device = event
|
Some(event) => Ok(Some(event)),
|
||||||
.tags
|
// No approval event found, construct a request event
|
||||||
.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 => {
|
None => {
|
||||||
// Construct an event for device key request
|
// Construct an event for device key request
|
||||||
let event = client
|
let builder = EventBuilder::new(Kind::Custom(4454), "").tags(vec![
|
||||||
.sign_event_builder(EventBuilder::new(Kind::Custom(4454), "").tags(vec![
|
Tag::custom(TagKind::custom("P"), vec![app_pubkey]),
|
||||||
Tag::client(app_name()),
|
Tag::client(app_name()),
|
||||||
Tag::custom(TagKind::custom("P"), vec![app_pubkey]),
|
]);
|
||||||
]))
|
|
||||||
.await?;
|
// Sign the event with user's signer
|
||||||
|
let event = client.sign_event_builder(builder).await?;
|
||||||
|
|
||||||
// Send the event to write relays
|
// Send the event to write relays
|
||||||
client.send_event(&event).to_nip65().await?;
|
client.send_event(&event).to_nip65().await?;
|
||||||
@@ -506,32 +498,56 @@ impl DeviceRegistry {
|
|||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
match task.await {
|
match task.await {
|
||||||
Ok(Some(keys)) => {
|
Ok(Some(event)) => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_signer(keys, cx);
|
this.extract_encryption(event, cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_state(DeviceState::Requesting, cx);
|
this.set_requesting(true, cx);
|
||||||
|
this.wait_for_approval(cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to request the encryption key: {e}");
|
this.update(cx, |_this, cx| {
|
||||||
|
cx.emit(DeviceEvent::error(e.to_string()));
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Ok(())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for encryption key approvals
|
||||||
|
fn wait_for_approval(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
let client = nostr.read(cx).client();
|
||||||
|
let signer = nostr.read(cx).signer();
|
||||||
|
|
||||||
|
self.tasks.push(cx.background_spawn(async move {
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
|
// Construct a filter for device key requests
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kind(Kind::Custom(4455))
|
||||||
|
.author(public_key)
|
||||||
|
.since(Timestamp::now());
|
||||||
|
|
||||||
|
// Subscribe to the device key requests on user's write relays
|
||||||
|
client.subscribe(filter).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the response event for device keys from other devices
|
/// Parse the approval event to get encryption key then set it
|
||||||
fn extract_encryption(&mut self, event: Event, cx: &mut Context<Self>) {
|
fn extract_encryption(&mut self, event: Event, cx: &mut Context<Self>) {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let app_keys = nostr.read(cx).keys();
|
let app_keys = nostr.read(cx).keys();
|
||||||
|
|
||||||
let task: Task<Result<Keys, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Keys, Error>> = cx.background_spawn(async move {
|
||||||
let root_device = event
|
let master = event
|
||||||
.tags
|
.tags
|
||||||
.find(TagKind::custom("P"))
|
.find(TagKind::custom("P"))
|
||||||
.and_then(|tag| tag.content())
|
.and_then(|tag| tag.content())
|
||||||
@@ -539,7 +555,7 @@ impl DeviceRegistry {
|
|||||||
.context("Invalid event's tags")?;
|
.context("Invalid event's tags")?;
|
||||||
|
|
||||||
let payload = event.content.as_str();
|
let payload = event.content.as_str();
|
||||||
let decrypted = app_keys.nip44_decrypt(&root_device, payload).await?;
|
let decrypted = app_keys.nip44_decrypt(&master, payload).await?;
|
||||||
|
|
||||||
let secret = SecretKey::from_hex(&decrypted)?;
|
let secret = SecretKey::from_hex(&decrypted)?;
|
||||||
let keys = Keys::new(secret);
|
let keys = Keys::new(secret);
|
||||||
@@ -548,13 +564,19 @@ impl DeviceRegistry {
|
|||||||
});
|
});
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
let keys = task.await?;
|
match task.await {
|
||||||
|
Ok(keys) => {
|
||||||
// Update signer
|
this.update(cx, |this, cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.set_signer(keys, cx);
|
||||||
this.set_signer(keys, cx);
|
this.set_requesting(false, cx);
|
||||||
})?;
|
})?;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
this.update(cx, |_this, cx| {
|
||||||
|
cx.emit(DeviceEvent::not_set(e.to_string()));
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,6 @@
|
|||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
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]
|
|
||||||
Idle,
|
|
||||||
Requesting,
|
|
||||||
Set,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for DeviceState {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
DeviceState::Idle => write!(f, "Idle"),
|
|
||||||
DeviceState::Requesting => write!(f, "Wait for approval"),
|
|
||||||
DeviceState::Set => write!(f, "Encryption Key is ready"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeviceState {
|
|
||||||
pub fn idle(&self) -> bool {
|
|
||||||
matches!(self, DeviceState::Idle)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn requesting(&self) -> bool {
|
|
||||||
matches!(self, DeviceState::Requesting)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&self) -> bool {
|
|
||||||
matches!(self, DeviceState::Set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Announcement
|
/// Announcement
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Announcement {
|
pub struct Announcement {
|
||||||
|
|||||||
Reference in New Issue
Block a user