diff --git a/crates/nip4e/src/lib.rs b/crates/nip4e/src/lib.rs index 17bb0b91..85d318fd 100644 --- a/crates/nip4e/src/lib.rs +++ b/crates/nip4e/src/lib.rs @@ -121,7 +121,7 @@ impl DeviceRegistry { // Subscribe to nostr state events cx.observe(&settings, move |this, settings, cx| { if settings.read(cx).is_nip4e_enabled(cx) { - this.get_announcement(cx); + this.init_encryption(cx); }; }), ); @@ -135,6 +135,11 @@ impl DeviceRegistry { this.resync.store(false, Ordering::Relaxed); // The in-flight request and client key belong to the old account this.app_keys = None; + // The encryption signer belongs to the old account + this.signer.update(cx, |signer, cx| { + *signer = None; + cx.notify(); + }); if this.pending_request.take().is_some() { // The old approval must not target the new account's signer @@ -148,7 +153,7 @@ impl DeviceRegistry { } this.handle_notifications(cx); - this.get_announcement(cx); + this.init_encryption(cx); } }), ); @@ -372,6 +377,33 @@ impl DeviceRegistry { } } + /// Initialize encryption from the local database before reconciling with relays + fn init_encryption(&mut self, cx: &mut Context) { + let nostr = NostrRegistry::global(cx); + let client = nostr.read(cx).client(); + let signer = nostr.read(cx).signer(); + + self.tasks.push(cx.spawn(async move |this, cx| { + let Ok(keys) = get_keys(&client, &signer).await else { + // No local keys; the announcement reconciliation will guide setup + this.update(cx, |this, cx| this.get_announcement(cx))?; + return Ok(()); + }; + + this.update(cx, |this, cx| { + // Relay reconciliation may have set the signer already + if this.signer.read(cx).is_none() { + this.set_signer(keys, cx); + this.wait_for_request(cx); + } + + this.get_announcement(cx); + })?; + + Ok(()) + })); + } + /// Get device announcement for current user pub fn get_announcement(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); @@ -409,8 +441,11 @@ impl DeviceRegistry { } if !announcement_existed.load(Ordering::Acquire) { - this.update(cx, |_this, cx| { - cx.emit(DeviceEvent::NotSet); + this.update(cx, |this, cx| { + // The device already initialized encryption from the local database + if this.signer.read(cx).is_none() { + cx.emit(DeviceEvent::NotSet); + } })?; } @@ -581,8 +616,11 @@ impl DeviceRegistry { self.tasks.push(cx.spawn(async move |this, cx| { if let Ok(keys) = task.await { this.update(cx, |this, cx| { - this.set_signer(keys, cx); - this.wait_for_request(cx); + // The local database may have initialized this device already + if this.signer.read(cx).is_none() { + this.set_signer(keys, cx); + this.wait_for_request(cx); + } this.rotate_if_due(cx); })?; } else { @@ -761,6 +799,18 @@ impl DeviceRegistry { let identity = signer.get_public_key_async().await?; let keys = decrypt_approval(&client, &app_keys, identity, &event).await?; + // Persist the transferred key so restarts initialize from the local database + if let Err(e) = set_keys( + &client, + &signer, + &keys.secret_key().to_secret_hex(), + Timestamp::now(), + ) + .await + { + log::error!("Failed to persist the transferred encryption key: {e}"); + } + Ok(keys) });