feat: follow-up to d93cecb

This commit is contained in:
2025-03-10 08:34:41 +07:00
parent d93cecbea3
commit 0822b46596
3 changed files with 187 additions and 116 deletions

View File

@@ -29,6 +29,76 @@ struct GlobalDevice(Entity<Device>);
impl Global for GlobalDevice {} impl Global for GlobalDevice {}
#[derive(Debug, Default)]
pub enum DeviceState {
Master,
Minion,
#[default]
None,
}
impl DeviceState {
pub fn subscribe(&self, window: &mut Window, cx: &mut Context<Self>) {
match self {
Self::Master => {
let client = get_client();
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let filter = Filter::new()
.kind(Kind::Custom(DEVICE_REQUEST_KIND))
.author(public_key)
.since(Timestamp::now());
// Subscribe for new device requests
_ = client.subscribe(filter, None).await;
Ok(())
});
cx.spawn_in(window, |_, _cx| async move {
if let Err(err) = task.await {
log::error!("Failed to subscribe for device requests: {}", err);
}
})
.detach();
}
Self::Minion => {
let client = get_client();
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let opts =
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let filter = Filter::new()
.kind(Kind::Custom(DEVICE_RESPONSE_KIND))
.author(public_key);
// Getting all previous approvals
client.subscribe(filter.clone(), Some(opts)).await?;
// Continously receive the request approval
client
.subscribe(filter.since(Timestamp::now()), None)
.await?;
Ok(())
});
cx.spawn_in(window, |_, _cx| async move {
if let Err(err) = task.await {
log::error!("Failed to subscribe for device approval: {}", err);
}
})
.detach();
}
_ => {}
}
}
}
/// Current Device (Client) /// Current Device (Client)
/// ///
/// 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>
@@ -37,7 +107,9 @@ pub struct Device {
/// Profile (Metadata) of current user /// Profile (Metadata) of current user
profile: Option<NostrProfile>, profile: Option<NostrProfile>,
/// Client Keys /// Client Keys
client_keys: Keys, client_keys: Arc<Keys>,
/// Device State
state: Entity<DeviceState>,
} }
pub fn init(window: &mut Window, cx: &App) { pub fn init(window: &mut Window, cx: &App) {
@@ -49,7 +121,7 @@ pub fn init(window: &mut Window, cx: &App) {
let client_keys = if let Ok(Some((_, secret))) = read_keys.await { let client_keys = if let Ok(Some((_, secret))) = read_keys.await {
let secret_key = SecretKey::from_slice(&secret).unwrap(); let secret_key = SecretKey::from_slice(&secret).unwrap();
Keys::new(secret_key) Arc::new(Keys::new(secret_key))
} else { } else {
// Generate new keys and save them to keyring // Generate new keys and save them to keyring
let keys = Keys::generate(); let keys = Keys::generate();
@@ -64,12 +136,33 @@ pub fn init(window: &mut Window, cx: &App) {
_ = write_keys.await; _ = write_keys.await;
}; };
keys Arc::new(keys)
}; };
cx.update(|cx| { cx.update(|cx| {
let state = cx.new(|_| DeviceState::None);
window_handle
.update(cx, |_, window, cx| {
// Open the onboarding view
Root::update(window, cx, |this, window, cx| {
this.replace_view(onboarding::init(window, cx).into());
cx.notify();
});
window
.observe(&state, cx, |this, window, cx| {
this.update(cx, |this, cx| {
this.subscribe(window, cx);
});
})
.detach();
})
.ok();
let entity = cx.new(|_| Device { let entity = cx.new(|_| Device {
profile: None, profile: None,
state,
client_keys, client_keys,
}); });
@@ -116,6 +209,13 @@ impl Device {
cx.notify(); cx.notify();
} }
pub fn set_state(&mut self, state: DeviceState, cx: &mut Context<Self>) {
self.state.update(cx, |this, cx| {
*this = state;
cx.notify();
});
}
/// Login and set user signer /// Login and set user signer
pub fn login<T>(&self, signer: T, cx: &mut Context<Self>) -> Task<Result<(), Error>> pub fn login<T>(&self, signer: T, cx: &mut Context<Self>) -> Task<Result<(), Error>>
where where
@@ -318,66 +418,86 @@ impl Device {
}); });
cx.spawn_in(window, |this, mut cx| async move { cx.spawn_in(window, |this, mut cx| async move {
// Device Keys has been set, no need to retrieve device announcement again
if get_device_keys().await.is_some() { if get_device_keys().await.is_some() {
return; return;
} }
if let Ok(event) = fetch_announcement.await { match fetch_announcement.await {
log::info!("Device Announcement: {:?}", event); Ok(event) => {
if let Ok(task) = cx.update(|_, cx| cx.read_credentials(MASTER_KEYRING)) { log::info!("Found a device announcement: {:?}", event);
if let Ok(Some((pubkey, secret))) = task.await {
if let Some(n) = event let n_tag = event
.tags .tags
.find(TagKind::custom("n")) .find(TagKind::custom("n"))
.and_then(|t| t.content()) .and_then(|t| t.content())
.map(|hex| hex.to_owned()) .map(|hex| hex.to_owned());
{
if n == pubkey { let credentials_task =
cx.update(|window, cx| { match cx.update(|_, cx| cx.read_credentials(MASTER_KEYRING)) {
this.update(cx, |this, cx| { Ok(task) => task,
this.reinit_master_keys(secret, window, cx); Err(err) => {
}) log::error!("Failed to read credentials: {:?}", err);
.ok(); log::info!("Trying to request keys from Master Device...");
})
.ok();
} else {
cx.update(|window, cx| { cx.update(|window, cx| {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.request_keys(window, cx); this.request_keys(window, cx);
this.set_state(DeviceState::Master, cx);
}) })
.ok();
}) })
.ok(); .ok();
}
return; return;
}
};
match credentials_task.await {
Ok(Some((pubkey, secret))) if n_tag.as_deref() == Some(&pubkey) => {
cx.update(|window, cx| {
this.update(cx, |this, cx| {
this.set_master_keys(secret, window, cx);
this.set_state(DeviceState::Master, cx);
})
})
.ok();
}
_ => {
log::info!("This device is not the Master Device.");
log::info!("Trying to request keys from Master Device...");
cx.update(|window, cx| {
this.update(cx, |this, cx| {
this.request_keys(window, cx);
this.set_state(DeviceState::Minion, cx);
})
})
.ok();
} }
} }
} else {
log::error!("Failed to read credentials");
} }
Err(_) => {
log::info!("Device Announcement not found.");
log::info!("Appoint this device as master.");
log::info!("User cancelled keyring.") cx.update(|window, cx| {
} else { this.update(cx, |this, cx| {
cx.update(|window, cx| { this.set_new_master_keys(window, cx);
this.update(cx, |this, cx| { this.set_state(DeviceState::Master, cx);
this.set_master_keys(window, cx); })
.ok();
}) })
.ok(); .ok();
}) }
.ok();
} }
}) })
.detach(); .detach();
} }
/// Create a new master keys /// Create a new Master Keys, appointing this device as Master Device.
/// ///
/// 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>
pub fn set_master_keys(&self, window: &mut Window, cx: &Context<Self>) { pub fn set_new_master_keys(&self, window: &mut Window, cx: &Context<Self>) {
log::info!("Device Announcement isn't found.");
log::info!("Appoint this device as master");
let client = get_client(); let client = get_client();
let app_name = get_app_name(); let app_name = get_app_name();
@@ -424,54 +544,27 @@ impl Device {
.detach(); .detach();
} }
/// Reinitialize master keys /// Device already has Master Keys, re-appointing this device as Master Device.
/// ///
/// 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>
pub fn reinit_master_keys(&self, secret: Vec<u8>, window: &mut Window, cx: &Context<Self>) { pub fn set_master_keys(&self, secret: Vec<u8>, window: &mut Window, cx: &Context<Self>) {
let Some(profile) = self.profile() else { let Ok(secret_key) = SecretKey::from_slice(&secret) else {
log::error!("Failed to parse secret key");
return; return;
}; };
let keys = Arc::new(Keys::new(secret_key));
let client = get_client();
let public_key = profile.public_key;
let task: Task<Result<Arc<Keys>, Error>> = cx.background_spawn(async move {
let secret_key = SecretKey::from_slice(&secret)?;
let keys = Arc::new(Keys::new(secret_key));
log::info!("Reappointing this device as master.");
let filter = Filter::new()
.kind(Kind::Custom(DEVICE_REQUEST_KIND))
.author(public_key)
.since(Timestamp::now());
// Subscribe for new device requests
_ = client.subscribe(filter, None).await;
Ok(keys)
});
cx.spawn_in(window, |this, mut cx| async move { cx.spawn_in(window, |this, mut cx| async move {
if let Ok(keys) = task.await { log::info!("Re-appointing this device as Master Device.");
set_device_keys(keys).await; set_device_keys(keys).await;
cx.update(|window, cx| { cx.update(|window, cx| {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.fetch_request(window, cx); this.fetch_request(window, cx);
})
.ok();
}) })
.ok(); .ok();
} else { })
cx.update(|window, cx| { .ok();
this.update(cx, |this, cx| {
this.request_keys(window, cx);
})
.ok();
})
.ok();
}
}) })
.detach(); .detach();
} }
@@ -480,14 +573,8 @@ impl Device {
/// ///
/// 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>
pub fn request_keys(&self, window: &mut Window, cx: &Context<Self>) { pub fn request_keys(&self, window: &mut Window, cx: &Context<Self>) {
let Some(profile) = self.profile() else {
return;
};
let client = get_client(); let client = get_client();
let app_name = get_app_name(); let app_name = get_app_name();
let public_key = profile.public_key;
let client_keys = self.client_keys.clone(); let client_keys = self.client_keys.clone();
let kind = Kind::Custom(DEVICE_REQUEST_KIND); let kind = Kind::Custom(DEVICE_REQUEST_KIND);
@@ -509,20 +596,6 @@ impl Device {
log::info!("Waiting for response..."); log::info!("Waiting for response...");
} }
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let filter = Filter::new()
.kind(Kind::Custom(DEVICE_RESPONSE_KIND))
.author(public_key);
// Getting all previous approvals
client.subscribe(filter.clone(), Some(opts)).await?;
// Continously receive the request approval
client
.subscribe(filter.since(Timestamp::now()), None)
.await?;
Ok(()) Ok(())
}); });
@@ -543,7 +616,7 @@ impl Device {
/// Fetch the latest request from the other Nostr client /// Fetch the latest request from the other Nostr client
/// ///
/// 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>
fn fetch_request(&self, window: &mut Window, cx: &Context<Self>) { pub fn fetch_request(&self, window: &mut Window, cx: &Context<Self>) {
let Some(profile) = self.profile() else { let Some(profile) = self.profile() else {
return; return;
}; };
@@ -580,10 +653,11 @@ impl Device {
.detach(); .detach();
} }
/// Receive device keys approval from other Nostr client, /// Received Device Keys approval from Master Device,
/// then process and update device keys. ///
pub fn handle_response(&self, event: Event, window: &mut Window, cx: &Context<Self>) { /// NIP-4e: <https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md>
let local_signer = self.client_keys.clone().into_nostr_signer(); pub fn handle_approval(&self, event: Event, window: &mut Window, cx: &Context<Self>) {
let local_signer = self.client_keys.clone();
let task = cx.background_spawn(async move { let task = cx.background_spawn(async move {
if let Some(public_key) = event.tags.public_keys().copied().last() { if let Some(public_key) = event.tags.public_keys().copied().last() {
@@ -592,15 +666,12 @@ impl Device {
.await?; .await?;
let keys = Arc::new(Keys::parse(&secret)?); let keys = Arc::new(Keys::parse(&secret)?);
// Update global state with new device keys // Update global state with new device keys
set_device_keys(keys).await; set_device_keys(keys).await;
log::info!("Received device keys from other client");
Ok(()) Ok(())
} else { } else {
Err(anyhow!("Failed to retrieve device key")) Err(anyhow!("Failed to decrypt the Master Keys"))
} }
}); });
@@ -612,6 +683,7 @@ impl Device {
.ok(); .ok();
} else { } else {
cx.update(|window, cx| { cx.update(|window, cx| {
window.close_modal(cx);
window.push_notification( window.push_notification(
Notification::success("Device Keys request has been approved"), Notification::success("Device Keys request has been approved"),
cx, cx,
@@ -623,8 +695,9 @@ impl Device {
.detach(); .detach();
} }
/// Received device keys request from other Nostr client, /// Received Master Keys request from other Nostr client
/// then process the request and send approval response. ///
/// NIP-4e: <https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md>
pub fn handle_request(&self, event: Event, window: &mut Window, cx: &mut Context<Self>) { pub fn handle_request(&self, event: Event, window: &mut Window, cx: &mut Context<Self>) {
let Some(public_key) = event let Some(public_key) = event
.tags .tags
@@ -632,12 +705,13 @@ impl Device {
.and_then(|tag| tag.content()) .and_then(|tag| tag.content())
.and_then(|content| PublicKey::parse(content).ok()) .and_then(|content| PublicKey::parse(content).ok())
else { else {
log::error!("Invalid public key");
return; return;
}; };
let client = get_client(); let client = get_client();
let read_keys = cx.read_credentials(MASTER_KEYRING); let read_keys = cx.read_credentials(MASTER_KEYRING);
let local_signer = self.client_keys.clone().into_nostr_signer(); let local_signer = self.client_keys.clone();
let device_name = event let device_name = event
.tags .tags

View File

@@ -282,7 +282,7 @@ fn main() {
Signal::ReceiveMasterKey(event) => { Signal::ReceiveMasterKey(event) => {
if let Some(device) = Device::global(cx) { if let Some(device) = Device::global(cx) {
device.update(cx, |this, cx| { device.update(cx, |this, cx| {
this.handle_response(event, window, cx); this.handle_approval(event, window, cx);
}); });
} }
} }

View File

@@ -293,10 +293,7 @@ impl ColorScaleSet {
} }
} }
pub fn darken(&self, cx: &App) -> Hsla { pub fn darken(&self, _cx: &App) -> Hsla {
match cx.theme().appearance { self.light.step_12()
Appearance::Light => self.light.step_12(),
Appearance::Dark => self.dark.step_1(),
}
} }
} }