2 Commits

Author SHA1 Message Date
f075a83320 refactor device 2026-03-16 14:00:11 +07:00
1f9c0444d5 update deps 2026-03-16 08:18:52 +07:00
4 changed files with 300 additions and 289 deletions

88
Cargo.lock generated
View File

@@ -117,9 +117,9 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.13"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
@@ -220,9 +220,9 @@ dependencies = [
[[package]]
name = "ashpd"
version = "0.13.5"
version = "0.13.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b09507a218cf6eb4ab0659883e54880cea3984e3dbaa4989b6cda3f8f8a97a5"
checksum = "313dc617cf7b7e5d58021f999756898e60bdddd64eab2bc2f67909659e3ce5f9"
dependencies = [
"enumflags2",
"futures-channel",
@@ -914,9 +914,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.56"
version = "1.2.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -1200,7 +1200,7 @@ dependencies = [
[[package]]
name = "collections"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"indexmap",
"rustc-hash 2.1.1",
@@ -1214,9 +1214,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "colorchoice"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "combine"
@@ -1659,7 +1659,7 @@ dependencies = [
[[package]]
name = "derive_refineable"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"proc-macro2",
"quote",
@@ -1857,9 +1857,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "embed-resource"
version = "3.0.6"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e"
checksum = "47ec73ddcf6b7f23173d5c3c5a32b5507dc0a734de7730aa14abc5d5e296bb5f"
dependencies = [
"cc",
"memchr",
@@ -2650,7 +2650,7 @@ dependencies = [
[[package]]
name = "gpui"
version = "0.2.2"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"async-channel 2.5.0",
@@ -2729,7 +2729,7 @@ dependencies = [
[[package]]
name = "gpui_linux"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"as-raw-xcb-connection",
@@ -2777,7 +2777,7 @@ dependencies = [
[[package]]
name = "gpui_macos"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"async-task",
@@ -2819,7 +2819,7 @@ dependencies = [
[[package]]
name = "gpui_macros"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -2830,7 +2830,7 @@ dependencies = [
[[package]]
name = "gpui_platform"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"console_error_panic_hook",
"gpui",
@@ -2843,7 +2843,7 @@ dependencies = [
[[package]]
name = "gpui_tokio"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"gpui",
@@ -2854,7 +2854,7 @@ dependencies = [
[[package]]
name = "gpui_util"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"log",
@@ -2863,7 +2863,7 @@ dependencies = [
[[package]]
name = "gpui_web"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"console_error_panic_hook",
@@ -2887,7 +2887,7 @@ dependencies = [
[[package]]
name = "gpui_wgpu"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"bytemuck",
@@ -2915,7 +2915,7 @@ dependencies = [
[[package]]
name = "gpui_windows"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"collections",
@@ -3158,7 +3158,7 @@ dependencies = [
[[package]]
name = "http_client"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"async-compression",
@@ -3183,7 +3183,7 @@ dependencies = [
[[package]]
name = "http_client_tls"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"rustls",
"rustls-platform-verifier",
@@ -4023,7 +4023,7 @@ dependencies = [
[[package]]
name = "media"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"bindgen",
@@ -4814,7 +4814,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "perf"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"collections",
"serde",
@@ -5025,9 +5025,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "portable-atomic-util"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5"
checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3"
dependencies = [
"portable-atomic",
]
@@ -5524,7 +5524,7 @@ dependencies = [
[[package]]
name = "refineable"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"derive_refineable",
]
@@ -5623,7 +5623,7 @@ dependencies = [
[[package]]
name = "reqwest_client"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"bytes",
@@ -5678,7 +5678,7 @@ dependencies = [
[[package]]
name = "rope"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"arrayvec",
"log",
@@ -5964,7 +5964,7 @@ dependencies = [
[[package]]
name = "scheduler"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"async-task",
"backtrace",
@@ -6588,7 +6588,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "sum_tree"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"arrayvec",
"log",
@@ -6973,9 +6973,9 @@ dependencies = [
[[package]]
name = "tinyvec"
version = "1.10.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
@@ -7284,9 +7284,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.22"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
dependencies = [
"matchers",
"nu-ansi-term",
@@ -7348,9 +7348,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "uds_windows"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b70b87d15e91f553711b40df3048faf27a7a04e01e0ddc0cf9309f0af7c2ca"
checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
dependencies = [
"memoffset",
"tempfile",
@@ -7550,7 +7550,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "util"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"async-fs",
@@ -7589,7 +7589,7 @@ dependencies = [
[[package]]
name = "util_macros"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"perf",
"quote",
@@ -9391,7 +9391,7 @@ dependencies = [
[[package]]
name = "zlog"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"anyhow",
"chrono",
@@ -9408,7 +9408,7 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[[package]]
name = "ztracing"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
dependencies = [
"tracing",
"tracing-subscriber",
@@ -9419,7 +9419,7 @@ dependencies = [
[[package]]
name = "ztracing_macro"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#7b9afc8c454607222eaf751bbc38159ececc1f7a"
source = "git+https://github.com/zed-industries/zed#cbc39669b414c2601f86ece9faffe164a33b5ad7"
[[package]]
name = "zune-core"

View File

@@ -37,6 +37,7 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Workspace> {
cx.new(|cx| Workspace::new(window, cx))
}
struct DeviceNotifcation;
struct SignerNotifcation;
struct RelayNotifcation;
@@ -171,9 +172,32 @@ impl Workspace {
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) => {
window.push_notification(Notification::error(error).autohide(false), cx);
}
_ => {
// TODO
}
};
}),
);
@@ -424,35 +448,13 @@ impl Workspace {
.child(SharedString::from(ENC_WARN)),
),
)
.on_ok(move |_ev, window, cx| {
.on_ok(move |_ev, _window, cx| {
let device = DeviceRegistry::global(cx);
let task = device.read(cx).create_encryption(cx);
window
.spawn(cx, async move |cx| {
let result = task.await;
cx.update(|window, cx| match result {
Ok(keys) => {
device.update(cx, |this, cx| {
this.set_signer(keys, cx);
this.listen_request(cx);
this.set_announcement(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
// true to close modal
true
})
});
}
@@ -702,10 +704,12 @@ impl Workspace {
.ghost()
.dropdown_menu(move |this, _window, 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.))
.item(PopupMenuItem::element(move |_window, cx| {
.when(requesting, |this| {
this.item(PopupMenuItem::element(move |_window, cx| {
h_flex()
.px_1()
.w_full()
@@ -715,12 +719,31 @@ impl Workspace {
div()
.size_1p5()
.rounded_full()
.when(state.set(), |this| this.bg(gpui::green()))
.when(state.requesting(), |this| {
this.bg(cx.theme().icon_accent)
}),
.bg(cx.theme().icon_accent),
)
.child(SharedString::from(state.to_string()))
.child(SharedString::from("Waiting for approval..."))
}))
})
.item(PopupMenuItem::element(move |_window, cx| {
h_flex()
.px_1()
.w_full()
.gap_2()
.text_sm()
.child(div().size_1p5().rounded_full().map(|this| {
if subscribing {
this.bg(cx.theme().icon_accent)
} else {
this.bg(cx.theme().icon_muted)
}
}))
.map(|this| {
if subscribing {
this.child(SharedString::from("Getting messages..."))
} else {
this.child(SharedString::from("Not getting messages"))
}
})
}))
.separator()
.menu_with_icon(

View File

@@ -10,9 +10,7 @@ use gpui::{
};
use nostr_sdk::prelude::*;
use person::PersonRegistry;
use state::{
Announcement, DEVICE_GIFTWRAP, DeviceState, NostrRegistry, StateEvent, TIMEOUT, app_name,
};
use state::{Announcement, DEVICE_GIFTWRAP, NostrRegistry, StateEvent, TIMEOUT, app_name};
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
@@ -36,17 +34,53 @@ impl Global for GlobalDeviceRegistry {}
pub enum DeviceEvent {
/// A new encryption signer has been 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
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
///
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
#[derive(Debug)]
pub struct DeviceRegistry {
/// Device state
state: DeviceState,
/// Whether the registry is currently subscribing to gift wrap events
pub subscribing: bool,
/// Whether the registry is waiting for encryption key approval from other devices
pub requesting: bool,
/// Async tasks
tasks: Vec<Task<Result<(), Error>>>,
@@ -71,30 +105,30 @@ impl DeviceRegistry {
/// Create a new device registry instance
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx);
let state = DeviceState::default();
let subscription = Some(cx.subscribe_in(
&nostr,
window,
|this, _state, event, _window, cx| match event {
// Get announcement when signer is set
let subscription = cx.subscribe_in(&nostr, window, |this, _e, event, _window, cx| {
match event {
StateEvent::SignerSet => {
this.reset(cx);
this.set_subscribing(false, cx);
this.set_requesting(false, cx);
}
StateEvent::RelayConnected => {
this.get_announcement(cx);
}
_ => {}
},
));
};
});
cx.defer_in(window, |this, window, cx| {
this.handle_notifications(window, cx);
});
Self {
state,
subscribing: false,
requesting: false,
tasks: vec![],
_subscription: subscription,
_subscription: Some(subscription),
}
}
@@ -140,13 +174,13 @@ impl DeviceRegistry {
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
while let Ok(event) = rx.recv_async().await {
match event.kind {
// New request event
// New request event from other device
Kind::Custom(4454) => {
this.update_in(cx, |this, window, cx| {
this.ask_for_approval(event, window, cx);
})?;
}
// New response event
// New response event from the master device
Kind::Custom(4455) => {
this.update(cx, |this, cx| {
this.extract_encryption(event, cx);
@@ -155,24 +189,24 @@ impl DeviceRegistry {
_ => {}
}
}
Ok(())
}));
}
/// Get the device state
pub fn state(&self) -> DeviceState {
self.state.clone()
/// Set whether the registry is currently subscribing to gift wrap events
fn set_subscribing(&mut self, subscribing: bool, cx: &mut Context<Self>) {
self.subscribing = subscribing;
cx.notify();
}
/// Set the device state
fn set_state(&mut self, state: DeviceState, cx: &mut Context<Self>) {
self.state = state;
/// Set whether the registry is waiting for encryption key approval from other devices
fn set_requesting(&mut self, requesting: bool, cx: &mut Context<Self>) {
self.requesting = requesting;
cx.notify();
}
/// 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
S: NostrSigner + 'static,
{
@@ -184,7 +218,7 @@ impl DeviceRegistry {
// Update state
this.update(cx, |this, cx| {
this.set_state(DeviceState::Set, cx);
cx.emit(DeviceEvent::Set);
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
fn get_messages(&mut self, cx: &mut Context<Self>) {
let task = self.subscribe_to_giftwrap_events(cx);
@@ -205,59 +233,50 @@ impl DeviceRegistry {
self.tasks.push(cx.spawn(async move |this, cx| {
if let Err(e) = task.await {
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(())
}));
}
/// 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
fn subscribe_to_giftwrap_events(&self, cx: &App) -> Task<Result<(), Error>> {
let persons = PersonRegistry::global(cx);
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
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 {
let urls = urls.await?;
let encryption = signer.get_encryption_signer().await.context("not found")?;
let public_key = encryption.get_public_key().await?;
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
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
let target: HashMap<RelayUrl, Filter> = urls
let target: HashMap<RelayUrl, Filter> = relays
.into_iter()
.map(|relay| (relay, filter.clone()))
.collect();
@@ -302,13 +321,15 @@ impl DeviceRegistry {
self.tasks.push(cx.spawn(async move |this, cx| {
match task.await {
Ok(event) => {
// Set encryption key from the announcement event
this.update(cx, |this, cx| {
this.new_signer(&event, cx);
this.set_encryption(&event, cx);
})?;
}
Err(_) => {
// User has no announcement, create a new one
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
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 client = nostr.read(cx).client();
@@ -328,12 +371,13 @@ impl DeviceRegistry {
cx.background_spawn(async move {
// Construct an announcement event
let event = client
.sign_event_builder(EventBuilder::new(Kind::Custom(10044), "").tags(vec![
let builder = EventBuilder::new(Kind::Custom(10044), "").tags(vec![
Tag::custom(TagKind::custom("n"), vec![n]),
Tag::client(app_name()),
]))
.await?;
]);
// Sign the event with user's signer
let event = client.sign_event_builder(builder).await?;
// Publish announcement
client.send_event(&event).to_nip65().await?;
@@ -345,39 +389,23 @@ impl DeviceRegistry {
})
}
/// Create a new device signer and announce it
fn announce(&mut self, 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>) {
/// Set encryption key from the announcement event
fn set_encryption(&mut self, event: &Event, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let announcement = Announcement::from(event);
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 {
if let Ok(keys) = get_keys(&client).await {
if keys.public_key() != device_pubkey {
return Err(anyhow!("Key mismatch"));
return Err(anyhow!("Encryption Key doesn't match the announcement"));
};
Ok(keys)
} 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) => {
this.update(cx, |this, cx| {
this.set_signer(keys, cx);
this.listen_request(cx);
this.wait_for_request(cx);
})?;
}
Err(e) => {
log::warn!("Failed to initialize device signer: {e}");
this.update(cx, |this, cx| {
this.request(cx);
this.listen_approval(cx);
this.update(cx, |_this, cx| {
cx.emit(DeviceEvent::not_set(e.to_string()));
})?;
}
};
Ok(())
}));
}
/// Listen for device key requests on user's write relays
pub fn listen_request(&mut self, cx: &mut Context<Self>) {
/// Wait for encryption key requests from now on
fn wait_for_request(&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 {
let public_key = signer.get_public_key().await?;
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
// Construct a filter for device key requests
let filter = Filter::new()
// Construct a filter for encryption key requests
let now = Filter::new()
.kind(Kind::Custom(4454))
.author(public_key)
.since(Timestamp::now());
// Subscribe to the device key requests on user's write relays
client.subscribe(filter).await?;
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))
// Construct a filter for the last encryption key request
let last = Filter::new()
.kind(Kind::Custom(4454))
.author(public_key)
.since(Timestamp::now());
.limit(1);
// Subscribe to the device key requests on user's write relays
client.subscribe(filter).await?;
client.subscribe(vec![now, last]).await?;
Ok(())
}));
}
/// 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 client = nostr.read(cx).client();
let signer = nostr.read(cx).signer();
@@ -461,9 +464,10 @@ impl DeviceRegistry {
let app_keys = nostr.read(cx).keys();
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?;
// Construct a filter to get the latest approval event
let filter = Filter::new()
.kind(Kind::Custom(4455))
.author(public_key)
@@ -471,30 +475,18 @@ impl DeviceRegistry {
.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))
}
// Found an approval event
Some(event) => Ok(Some(event)),
// No approval event found, construct a request event
None => {
// Construct an event for device key request
let event = client
.sign_event_builder(EventBuilder::new(Kind::Custom(4454), "").tags(vec![
Tag::client(app_name()),
let builder = EventBuilder::new(Kind::Custom(4454), "").tags(vec![
Tag::custom(TagKind::custom("P"), vec![app_pubkey]),
]))
.await?;
Tag::client(app_name()),
]);
// Sign the event with user's signer
let event = client.sign_event_builder(builder).await?;
// Send the event to write relays
client.send_event(&event).to_nip65().await?;
@@ -506,32 +498,56 @@ impl DeviceRegistry {
self.tasks.push(cx.spawn(async move |this, cx| {
match task.await {
Ok(Some(keys)) => {
Ok(Some(event)) => {
this.update(cx, |this, cx| {
this.set_signer(keys, cx);
this.extract_encryption(event, cx);
})?;
}
Ok(None) => {
this.update(cx, |this, cx| {
this.set_state(DeviceState::Requesting, cx);
this.set_requesting(true, cx);
this.wait_for_approval(cx);
})?;
}
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(())
}));
}
/// 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>) {
let nostr = NostrRegistry::global(cx);
let app_keys = nostr.read(cx).keys();
let task: Task<Result<Keys, Error>> = cx.background_spawn(async move {
let root_device = event
let master = event
.tags
.find(TagKind::custom("P"))
.and_then(|tag| tag.content())
@@ -539,7 +555,7 @@ impl DeviceRegistry {
.context("Invalid event's tags")?;
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 keys = Keys::new(secret);
@@ -548,13 +564,19 @@ impl DeviceRegistry {
});
self.tasks.push(cx.spawn(async move |this, cx| {
let keys = task.await?;
// Update signer
match task.await {
Ok(keys) => {
this.update(cx, |this, 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(())
}));
}

View File

@@ -1,40 +1,6 @@
use std::fmt::Display;
use gpui::SharedString;
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
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Announcement {