update encryption panel
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m58s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m39s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (macos-latest, stable) (pull_request) Has been cancelled
Rust / build (windows-latest, stable) (pull_request) Has been cancelled
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m58s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m39s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (macos-latest, stable) (pull_request) Has been cancelled
Rust / build (windows-latest, stable) (pull_request) Has been cancelled
This commit is contained in:
3
assets/icons/reset.svg
Normal file
3
assets/icons/reset.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M13 17.25C16.4518 17.25 19.25 14.4518 19.25 11V10.5C19.25 6.77208 16.2279 3.75 12.5 3.75C8.77208 3.75 5.75 6.77208 5.75 10.5V20" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 16.5L5.75 20.25L9.5 16.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 435 B |
@@ -13,38 +13,6 @@ const MINUTES_IN_HOUR: i64 = 60;
|
|||||||
const HOURS_IN_DAY: i64 = 24;
|
const HOURS_IN_DAY: i64 = 24;
|
||||||
const DAYS_IN_MONTH: i64 = 30;
|
const DAYS_IN_MONTH: i64 = 30;
|
||||||
|
|
||||||
pub trait RenderedProfile {
|
|
||||||
fn avatar(&self) -> SharedString;
|
|
||||||
fn display_name(&self) -> SharedString;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderedProfile for Profile {
|
|
||||||
fn avatar(&self) -> SharedString {
|
|
||||||
self.metadata()
|
|
||||||
.picture
|
|
||||||
.as_ref()
|
|
||||||
.filter(|picture| !picture.is_empty())
|
|
||||||
.map(|picture| picture.into())
|
|
||||||
.unwrap_or_else(|| "brand/avatar.png".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_name(&self) -> SharedString {
|
|
||||||
if let Some(display_name) = self.metadata().display_name.as_ref() {
|
|
||||||
if !display_name.is_empty() {
|
|
||||||
return SharedString::from(display_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(name) = self.metadata().name.as_ref() {
|
|
||||||
if !name.is_empty() {
|
|
||||||
return SharedString::from(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedString::from(shorten_pubkey(self.public_key(), 4))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait RenderedTimestamp {
|
pub trait RenderedTimestamp {
|
||||||
fn to_human_time(&self) -> SharedString;
|
fn to_human_time(&self) -> SharedString;
|
||||||
fn to_ago(&self) -> SharedString;
|
fn to_ago(&self) -> SharedString;
|
||||||
@@ -126,13 +94,3 @@ impl<T: AsRef<str>> TextUtils for T {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shorten_pubkey(public_key: PublicKey, len: usize) -> String {
|
|
||||||
let Ok(pubkey) = public_key.to_bech32();
|
|
||||||
|
|
||||||
format!(
|
|
||||||
"{}:{}",
|
|
||||||
&pubkey[0..(len + 1)],
|
|
||||||
&pubkey[pubkey.len() - len..]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ use std::collections::HashMap;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{Context as AnyhowContext, Error};
|
use anyhow::{Context as AnyhowContext, Error};
|
||||||
use common::{shorten_pubkey, RenderedTimestamp};
|
use common::RenderedTimestamp;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, relative, rems, uniform_list, App, AppContext, Context, Div, Entity,
|
div, px, relative, rems, uniform_list, App, AppContext, Context, Div, Entity,
|
||||||
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window,
|
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window,
|
||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::{Person, PersonRegistry};
|
use person::{shorten_pubkey, Person, PersonRegistry};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use state::{NostrAddress, NostrRegistry, BOOTSTRAP_RELAYS, TIMEOUT};
|
use state::{NostrAddress, NostrRegistry, BOOTSTRAP_RELAYS, TIMEOUT};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
|||||||
@@ -1,27 +1,151 @@
|
|||||||
|
use anyhow::Error;
|
||||||
|
use device::DeviceRegistry;
|
||||||
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
div, px, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
IntoElement, Render, SharedString, Styled, Window,
|
IntoElement, ParentElement, Render, SharedString, Styled, Task, Window,
|
||||||
};
|
};
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use person::{shorten_pubkey, PersonRegistry};
|
||||||
|
use state::Announcement;
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock_area::panel::{Panel, PanelEvent};
|
use ui::dock_area::panel::{Panel, PanelEvent};
|
||||||
use ui::v_flex;
|
use ui::notification::Notification;
|
||||||
|
use ui::{divider, h_flex, v_flex, Disableable, IconName, Sizable, StyledExt, WindowExtension};
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<EncryptionPanel> {
|
const MSG: &str =
|
||||||
cx.new(|cx| EncryptionPanel::new(window, cx))
|
"Encryption Key is a special key that used to encrypt and decrypt your messages. \
|
||||||
|
Your identity is completely decoupled from all encryption processes to protect your privacy.";
|
||||||
|
|
||||||
|
const NOTICE: &str = "By resetting your encryption key, you will lose access to \
|
||||||
|
all your encrypted messages before. This action cannot be undone.";
|
||||||
|
|
||||||
|
pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<EncryptionPanel> {
|
||||||
|
cx.new(|cx| EncryptionPanel::new(public_key, window, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EncryptionPanel {
|
pub struct EncryptionPanel {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
|
|
||||||
|
/// User's public key
|
||||||
|
public_key: PublicKey,
|
||||||
|
|
||||||
|
/// Whether the panel is loading
|
||||||
|
loading: bool,
|
||||||
|
|
||||||
|
/// Tasks
|
||||||
|
tasks: Vec<Task<Result<(), Error>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EncryptionPanel {
|
impl EncryptionPanel {
|
||||||
fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn new(public_key: PublicKey, _window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: "Encryption".into(),
|
name: "Encryption".into(),
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
|
public_key,
|
||||||
|
loading: false,
|
||||||
|
tasks: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
||||||
|
self.loading = status;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn approve(&mut self, event: &Event, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let device = DeviceRegistry::global(cx);
|
||||||
|
let task = device.read(cx).approve(event, cx);
|
||||||
|
let id = event.id;
|
||||||
|
|
||||||
|
// Update loading status
|
||||||
|
self.set_loading(true, cx);
|
||||||
|
|
||||||
|
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||||
|
match task.await {
|
||||||
|
Ok(_) => {
|
||||||
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
// Reset loading status
|
||||||
|
this.set_loading(false, cx);
|
||||||
|
|
||||||
|
// Remove request
|
||||||
|
device.update(cx, |this, cx| {
|
||||||
|
this.remove_request(&id, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.push_notification("Approved", cx);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
this.set_loading(false, cx);
|
||||||
|
window.push_notification(Notification::error(e.to_string()), cx);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_requests(&mut self, cx: &mut Context<Self>) -> Vec<impl IntoElement> {
|
||||||
|
const TITLE: &str = "You've requested for the Encryption Key from:";
|
||||||
|
|
||||||
|
let device = DeviceRegistry::global(cx);
|
||||||
|
let requests = device.read(cx).requests.clone();
|
||||||
|
let mut items = Vec::new();
|
||||||
|
|
||||||
|
for event in requests.into_iter() {
|
||||||
|
let request = Announcement::from(&event);
|
||||||
|
let client_name = request.client_name();
|
||||||
|
let target = request.public_key();
|
||||||
|
|
||||||
|
items.push(
|
||||||
|
v_flex()
|
||||||
|
.gap_2()
|
||||||
|
.text_sm()
|
||||||
|
.child(SharedString::from(TITLE))
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.h_12()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.px_2()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.bg(cx.theme().warning_background)
|
||||||
|
.text_color(cx.theme().warning_foreground)
|
||||||
|
.child(client_name.clone()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.h_7()
|
||||||
|
.w_full()
|
||||||
|
.px_2()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.bg(cx.theme().elevated_surface_background)
|
||||||
|
.child(SharedString::from(target.to_hex())),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex().justify_end().gap_2().child(
|
||||||
|
Button::new("approve")
|
||||||
|
.label("Approve")
|
||||||
|
.ghost()
|
||||||
|
.small()
|
||||||
|
.disabled(self.loading)
|
||||||
|
.loading(self.loading)
|
||||||
|
.on_click(cx.listener(move |this, _ev, window, cx| {
|
||||||
|
this.approve(&event, window, cx);
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panel for EncryptionPanel {
|
impl Panel for EncryptionPanel {
|
||||||
@@ -43,12 +167,128 @@ impl Focusable for EncryptionPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for EncryptionPanel {
|
impl Render for EncryptionPanel {
|
||||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let device = DeviceRegistry::global(cx);
|
||||||
|
let state = device.read(cx).state();
|
||||||
|
let has_requests = device.read(cx).has_requests();
|
||||||
|
|
||||||
|
let persons = PersonRegistry::global(cx);
|
||||||
|
let profile = persons.read(cx).get(&self.public_key, cx);
|
||||||
|
|
||||||
|
let Some(announcement) = profile.announcement() else {
|
||||||
|
return div();
|
||||||
|
};
|
||||||
|
|
||||||
|
let pubkey = SharedString::from(shorten_pubkey(announcement.public_key(), 16));
|
||||||
|
let client_name = announcement.client_name();
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.p_3()
|
||||||
|
.gap_3()
|
||||||
|
.w_full()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(SharedString::from(MSG)),
|
||||||
|
)
|
||||||
|
.child(divider(cx))
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_3()
|
||||||
|
.text_sm()
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(SharedString::from("Device Name:")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.h_12()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.p_2()
|
.rounded(cx.theme().radius)
|
||||||
.gap_10()
|
.bg(cx.theme().elevated_surface_background)
|
||||||
|
.child(client_name.clone()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(SharedString::from("Encryption Public Key:")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.h_7()
|
||||||
|
.w_full()
|
||||||
|
.px_2()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.bg(cx.theme().elevated_surface_background)
|
||||||
|
.child(pubkey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.when(has_requests, |this| {
|
||||||
|
this.child(divider(cx)).child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.w_full()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(SharedString::from("Requests:")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_2()
|
||||||
|
.flex_1()
|
||||||
|
.w_full()
|
||||||
|
.children(self.render_requests(cx)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(divider(cx))
|
||||||
|
.when(state.requesting(), |this| {
|
||||||
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.h_8()
|
||||||
|
.justify_center()
|
||||||
|
.text_xs()
|
||||||
|
.text_center()
|
||||||
|
.text_color(cx.theme().text_accent)
|
||||||
|
.bg(cx.theme().elevated_surface_background)
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.child(SharedString::from(
|
||||||
|
"Please open other device and approve the request",
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when(state.set(), |this| {
|
||||||
|
this.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Button::new("reset")
|
||||||
|
.icon(IconName::Reset)
|
||||||
|
.label("Reset")
|
||||||
|
.warning()
|
||||||
|
.small()
|
||||||
|
.font_semibold(),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.italic()
|
||||||
|
.text_size(px(10.))
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(SharedString::from(NOTICE)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ impl MessagingRelayPanel {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
this.set_updating(false, cx);
|
||||||
this.set_error(e.to_string(), window, cx);
|
this.set_error(e.to_string(), window, cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use std::str::FromStr;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use common::shorten_pubkey;
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, rems, AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter,
|
div, rems, AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter,
|
||||||
FocusHandle, Focusable, IntoElement, ParentElement, PathPromptOptions, Render, SharedString,
|
FocusHandle, Focusable, IntoElement, ParentElement, PathPromptOptions, Render, SharedString,
|
||||||
@@ -10,7 +9,7 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use gpui_tokio::Tokio;
|
use gpui_tokio::Tokio;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::{Person, PersonRegistry};
|
use person::{shorten_pubkey, Person, PersonRegistry};
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smol::fs;
|
use smol::fs;
|
||||||
use state::{nostr_upload, NostrRegistry};
|
use state::{nostr_upload, NostrRegistry};
|
||||||
|
|||||||
@@ -234,6 +234,7 @@ impl RelayListPanel {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
this.set_updating(false, cx);
|
||||||
this.set_error(e.to_string(), window, cx);
|
this.set_error(e.to_string(), window, cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,15 +182,20 @@ impl Workspace {
|
|||||||
fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context<Self>) {
|
fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
match command {
|
match command {
|
||||||
Command::OpenEncryptionPanel => {
|
Command::OpenEncryptionPanel => {
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
let signer = nostr.read(cx).signer();
|
||||||
|
|
||||||
|
if let Some(public_key) = signer.public_key() {
|
||||||
self.dock.update(cx, |this, cx| {
|
self.dock.update(cx, |this, cx| {
|
||||||
this.add_panel(
|
this.add_panel(
|
||||||
Arc::new(encryption_key::init(window, cx)),
|
Arc::new(encryption_key::init(public_key, window, cx)),
|
||||||
DockPlacement::Right,
|
DockPlacement::Right,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Command::OpenInboxPanel => {
|
Command::OpenInboxPanel => {
|
||||||
self.dock.update(cx, |this, cx| {
|
self.dock.update(cx, |this, cx| {
|
||||||
this.add_panel(
|
this.add_panel(
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ impl Global for GlobalDeviceRegistry {}
|
|||||||
/// 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 {
|
||||||
|
/// Request for encryption key from other devices
|
||||||
|
pub requests: Vec<Event>,
|
||||||
|
|
||||||
/// Device state
|
/// Device state
|
||||||
state: DeviceState,
|
state: DeviceState,
|
||||||
|
|
||||||
/// Device requests
|
|
||||||
requests: Entity<HashSet<Event>>,
|
|
||||||
|
|
||||||
/// Async tasks
|
/// Async tasks
|
||||||
tasks: Vec<Task<Result<(), Error>>>,
|
tasks: Vec<Task<Result<(), Error>>>,
|
||||||
|
|
||||||
@@ -52,8 +52,6 @@ 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 requests = cx.new(|_| HashSet::default());
|
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
@@ -77,7 +75,7 @@ impl DeviceRegistry {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
requests,
|
requests: vec![],
|
||||||
state: DeviceState::default(),
|
state: DeviceState::default(),
|
||||||
tasks: vec![],
|
tasks: vec![],
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
@@ -89,7 +87,7 @@ impl DeviceRegistry {
|
|||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let (tx, rx) = flume::bounded::<Event>(100);
|
let (tx, rx) = flume::bounded::<Event>(100);
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
self.tasks.push(cx.background_spawn(async move {
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
let mut processed_events = HashSet::new();
|
let mut processed_events = HashSet::new();
|
||||||
|
|
||||||
@@ -107,20 +105,21 @@ impl DeviceRegistry {
|
|||||||
match event.kind {
|
match event.kind {
|
||||||
Kind::Custom(4454) => {
|
Kind::Custom(4454) => {
|
||||||
if verify_author(&client, event.as_ref()).await {
|
if verify_author(&client, event.as_ref()).await {
|
||||||
tx.send_async(event.into_owned()).await.ok();
|
tx.send_async(event.into_owned()).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Kind::Custom(4455) => {
|
Kind::Custom(4455) => {
|
||||||
if verify_author(&client, event.as_ref()).await {
|
if verify_author(&client, event.as_ref()).await {
|
||||||
tx.send_async(event.into_owned()).await.ok();
|
tx.send_async(event.into_owned()).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.detach();
|
Ok(())
|
||||||
|
}));
|
||||||
|
|
||||||
self.tasks.push(
|
self.tasks.push(
|
||||||
// Update GPUI states
|
// Update GPUI states
|
||||||
@@ -147,8 +146,8 @@ impl DeviceRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the device state
|
/// Get the device state
|
||||||
pub fn state(&self) -> &DeviceState {
|
pub fn state(&self) -> DeviceState {
|
||||||
&self.state
|
self.state.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the device state
|
/// Set the device state
|
||||||
@@ -181,19 +180,25 @@ impl DeviceRegistry {
|
|||||||
/// Reset the device state
|
/// Reset the device state
|
||||||
fn reset(&mut self, cx: &mut Context<Self>) {
|
fn reset(&mut self, cx: &mut Context<Self>) {
|
||||||
self.state = DeviceState::Idle;
|
self.state = DeviceState::Idle;
|
||||||
self.requests.update(cx, |this, cx| {
|
self.requests.clear();
|
||||||
this.clear();
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a request for device keys
|
/// Add a request for device keys
|
||||||
fn add_request(&mut self, request: Event, cx: &mut Context<Self>) {
|
fn add_request(&mut self, request: Event, cx: &mut Context<Self>) {
|
||||||
self.requests.update(cx, |this, cx| {
|
self.requests.push(request);
|
||||||
this.insert(request);
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
/// Remove a request for device keys
|
||||||
|
pub fn remove_request(&mut self, id: &EventId, cx: &mut Context<Self>) {
|
||||||
|
self.requests.retain(|r| r.id != *id);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if there are any pending requests
|
||||||
|
pub fn has_requests(&self) -> bool {
|
||||||
|
!self.requests.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all messages for encryption keys
|
/// Get all messages for encryption keys
|
||||||
@@ -290,12 +295,12 @@ impl DeviceRegistry {
|
|||||||
match task.await {
|
match task.await {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.init_device_signer(&event, cx);
|
this.new_signer(&event, cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.announce_device(cx);
|
this.announce(cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,13 +310,15 @@ impl DeviceRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new device signer and announce it
|
/// Create a new device signer and announce it
|
||||||
fn announce_device(&mut self, cx: &mut Context<Self>) {
|
fn announce(&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();
|
||||||
|
|
||||||
|
// Get current user
|
||||||
let signer = nostr.read(cx).signer();
|
let signer = nostr.read(cx).signer();
|
||||||
let public_key = signer.public_key().unwrap();
|
let public_key = signer.public_key().unwrap();
|
||||||
|
|
||||||
|
// Get user's write relays
|
||||||
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
|
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
|
||||||
|
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
@@ -338,20 +345,20 @@ impl DeviceRegistry {
|
|||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
if task.await.is_ok() {
|
if task.await.is_ok() {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_signer(keys, cx);
|
this.set_signer(keys, cx);
|
||||||
this.listen_device_request(cx);
|
this.listen_request(cx);
|
||||||
})
|
})?;
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.detach();
|
Ok(())
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize device signer (decoupled encryption key) for the current user
|
/// Initialize device signer (decoupled encryption key) for the current user
|
||||||
fn init_device_signer(&mut self, event: &Event, cx: &mut Context<Self>) {
|
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();
|
||||||
|
|
||||||
@@ -375,14 +382,14 @@ 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_device_request(cx);
|
this.listen_request(cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.request_device_keys(cx);
|
this.request(cx);
|
||||||
this.listen_device_approval(cx);
|
this.listen_approval(cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@@ -394,7 +401,7 @@ impl DeviceRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Listen for device key requests on user's write relays
|
/// Listen for device key requests on user's write relays
|
||||||
fn listen_device_request(&mut self, cx: &mut Context<Self>) {
|
fn listen_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();
|
||||||
|
|
||||||
@@ -426,7 +433,7 @@ impl DeviceRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Listen for device key approvals on user's write relays
|
/// Listen for device key approvals on user's write relays
|
||||||
fn listen_device_approval(&mut self, cx: &mut Context<Self>) {
|
fn listen_approval(&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();
|
||||||
|
|
||||||
@@ -435,7 +442,7 @@ impl DeviceRegistry {
|
|||||||
|
|
||||||
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
|
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
|
||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
self.tasks.push(cx.background_spawn(async move {
|
||||||
let urls = write_relays.await;
|
let urls = write_relays.await;
|
||||||
|
|
||||||
// Construct a filter for device key requests
|
// Construct a filter for device key requests
|
||||||
@@ -452,13 +459,11 @@ impl DeviceRegistry {
|
|||||||
client.subscribe(target).await?;
|
client.subscribe(target).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
}));
|
||||||
|
|
||||||
task.detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request encryption keys from other device
|
/// Request encryption keys from other device
|
||||||
fn request_device_keys(&mut self, cx: &mut Context<Self>) {
|
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();
|
||||||
|
|
||||||
@@ -559,34 +564,32 @@ impl DeviceRegistry {
|
|||||||
Ok(keys)
|
Ok(keys)
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
match task.await {
|
let keys = 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);
|
||||||
})
|
})?;
|
||||||
.ok();
|
|
||||||
}
|
Ok(())
|
||||||
Err(e) => {
|
}));
|
||||||
log::error!("Error: {e}")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Approve requests for device keys from other devices
|
/// Approve requests for device keys from other devices
|
||||||
#[allow(dead_code)]
|
pub fn approve(&self, event: &Event, cx: &App) -> Task<Result<(), Error>> {
|
||||||
fn approve(&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();
|
||||||
|
|
||||||
|
// Get current user
|
||||||
let signer = nostr.read(cx).signer();
|
let signer = nostr.read(cx).signer();
|
||||||
let public_key = signer.public_key().unwrap();
|
let public_key = signer.public_key().unwrap();
|
||||||
|
|
||||||
|
// Get user's write relays
|
||||||
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
|
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
|
||||||
|
let event = event.clone();
|
||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let urls = write_relays.await;
|
let urls = write_relays.await;
|
||||||
|
|
||||||
// Get device keys
|
// Get device keys
|
||||||
@@ -619,9 +622,7 @@ impl DeviceRegistry {
|
|||||||
client.send_event(&event).to(urls).await?;
|
client.send_event(&event).to(urls).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
})
|
||||||
|
|
||||||
task.detach();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::sync::OnceLock;
|
|||||||
pub const CLIENT_NAME: &str = "Coop";
|
pub const CLIENT_NAME: &str = "Coop";
|
||||||
|
|
||||||
/// COOP's public key
|
/// COOP's public key
|
||||||
pub const COOP_PUBKEY: &str = "npub126kl5fruqan90py77gf6pvfvygefl2mu2ukew6xdx5pc5uqscwgsnkgarv";
|
pub const COOP_PUBKEY: &str = "npub1j3rz3ndl902lya6ywxvy5c983lxs8mpukqnx4pa4lt5wrykwl5ys7wpw3x";
|
||||||
|
|
||||||
/// App ID
|
/// App ID
|
||||||
pub const APP_ID: &str = "su.reya.coop";
|
pub const APP_ID: &str = "su.reya.coop";
|
||||||
|
|||||||
@@ -9,6 +9,20 @@ pub enum DeviceState {
|
|||||||
Set,
|
Set,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ pub enum IconName {
|
|||||||
Plus,
|
Plus,
|
||||||
PlusCircle,
|
PlusCircle,
|
||||||
Profile,
|
Profile,
|
||||||
|
Reset,
|
||||||
Relay,
|
Relay,
|
||||||
Reply,
|
Reply,
|
||||||
Refresh,
|
Refresh,
|
||||||
@@ -112,6 +113,7 @@ impl IconNamed for IconName {
|
|||||||
Self::Plus => "icons/plus.svg",
|
Self::Plus => "icons/plus.svg",
|
||||||
Self::PlusCircle => "icons/plus-circle.svg",
|
Self::PlusCircle => "icons/plus-circle.svg",
|
||||||
Self::Profile => "icons/profile.svg",
|
Self::Profile => "icons/profile.svg",
|
||||||
|
Self::Reset => "icons/reset.svg",
|
||||||
Self::Relay => "icons/relay.svg",
|
Self::Relay => "icons/relay.svg",
|
||||||
Self::Reply => "icons/reply.svg",
|
Self::Reply => "icons/reply.svg",
|
||||||
Self::Refresh => "icons/refresh.svg",
|
Self::Refresh => "icons/refresh.svg",
|
||||||
|
|||||||
Reference in New Issue
Block a user