chore: rewrite the backend (not tested) (#203)

* wip: refactor

* refactor

* clean up

* .

* rename

* add relay auth

* .

* .

* optimize

* .

* clean up

* add encryption crate

* .

* .

* .

* .

* .

* add encryption crate

* .

* refactor nip4e

* .

* fix endless loop

* fix metadata fetching
This commit is contained in:
reya
2025-11-11 09:09:33 +07:00
committed by GitHub
parent a1a0a7ecd4
commit 512834b640
68 changed files with 3503 additions and 3194 deletions

View File

@@ -5,14 +5,18 @@ edition.workspace = true
publish.workspace = true
[dependencies]
states = { path = "../states" }
state = { path = "../state" }
settings = { path = "../settings" }
common = { path = "../common" }
theme = { path = "../theme" }
ui = { path = "../ui" }
gpui.workspace = true
nostr.workspace = true
nostr-sdk.workspace = true
anyhow.workspace = true
smallvec.workspace = true
smol.workspace = true
log.workspace = true
serde.workspace = true
serde_json.workspace = true

View File

@@ -1,9 +1,15 @@
use std::sync::Arc;
use std::time::Duration;
use anyhow::Error;
use common::BOOTSTRAP_RELAYS;
use gpui::{App, AppContext, Context, Entity, Global, Task};
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use state::NostrRegistry;
pub fn init(public_key: PublicKey, cx: &mut App) {
Account::set_global(cx.new(|cx| Account::new(public_key, cx)), cx);
pub fn init(cx: &mut App) {
Account::set_global(cx.new(Account::new), cx);
}
struct GlobalAccount(Entity<Account>);
@@ -12,10 +18,24 @@ impl Global for GlobalAccount {}
pub struct Account {
/// The public key of the account
public_key: PublicKey,
public_key: Option<PublicKey>,
/// Status of the current user NIP-65 relays
pub nip65_status: RelayStatus,
/// Status of the current user NIP-17 relays
pub nip17_status: RelayStatus,
/// Tasks for asynchronous operations
_tasks: SmallVec<[Task<()>; 1]>,
_tasks: SmallVec<[Task<()>; 2]>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RelayStatus {
#[default]
Initial,
NotSet,
Set,
}
impl Account {
@@ -35,20 +55,152 @@ impl Account {
}
/// Set the global account instance
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) {
fn set_global(state: Entity<Self>, cx: &mut App) {
cx.set_global(GlobalAccount(state));
}
/// Create a new account instance
pub(crate) fn new(public_key: PublicKey, _cx: &mut Context<Self>) -> Self {
fn new(cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let mut tasks = smallvec![];
tasks.push(
// Observe the nostr signer and set the public key when it sets
cx.spawn({
let client = Arc::clone(&client);
async move |this, cx| {
let result = cx
.background_spawn(async move { Self::observe_signer(&client).await })
.await;
if let Some(public_key) = result {
this.update(cx, |this, cx| {
this.set_account(public_key, cx);
})
.expect("Entity has been released")
}
}
}),
);
Self {
public_key,
_tasks: smallvec![],
public_key: None,
nip65_status: RelayStatus::default(),
nip17_status: RelayStatus::default(),
_tasks: tasks,
}
}
/// Observe the signer and return the public key when it sets
async fn observe_signer(client: &Client) -> Option<PublicKey> {
let loop_duration = Duration::from_millis(800);
loop {
if let Ok(signer) = client.signer().await {
if let Ok(public_key) = signer.get_public_key().await {
// Get current user's gossip relays
Self::get_gossip_relays(client, public_key).await.ok()?;
return Some(public_key);
}
}
smol::Timer::after(loop_duration).await;
}
}
/// Get gossip relays for a given public key
async fn get_gossip_relays(client: &Client, public_key: PublicKey) -> Result<(), Error> {
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let filter = Filter::new()
.kind(Kind::RelayList)
.author(public_key)
.limit(1);
// Subscribe to events from the bootstrapping relays
client
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
.await?;
log::info!("Getting user's gossip relays...");
Ok(())
}
/// Ensure the user has NIP-65 relays
async fn ensure_nip65_relays(client: &Client, public_key: PublicKey) -> Result<bool, Error> {
let filter = Filter::new()
.kind(Kind::RelayList)
.author(public_key)
.limit(1);
// Count the number of nip65 relays event in the database
let total = client.database().count(filter).await.unwrap_or(0);
Ok(total > 0)
}
/// Ensure the user has NIP-17 relays
async fn ensure_nip17_relays(client: &Client, public_key: PublicKey) -> Result<bool, Error> {
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
// Count the number of nip17 relays event in the database
let total = client.database().count(filter).await.unwrap_or(0);
Ok(total > 0)
}
/// Set the public key of the account
pub fn set_account(&mut self, public_key: PublicKey, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
// Update account's public key
self.public_key = Some(public_key);
// Add background task
self._tasks.push(
// Verify user's nip65 and nip17 relays
cx.spawn(async move |this, cx| {
cx.background_executor()
.timer(Duration::from_secs(10))
.await;
let ensure_nip65 = Self::ensure_nip65_relays(&client, public_key).await;
let ensure_nip17 = Self::ensure_nip17_relays(&client, public_key).await;
this.update(cx, |this, cx| {
this.nip65_status = match ensure_nip65 {
Ok(true) => RelayStatus::Set,
_ => RelayStatus::NotSet,
};
this.nip17_status = match ensure_nip17 {
Ok(true) => RelayStatus::Set,
_ => RelayStatus::NotSet,
};
cx.notify();
})
.expect("Entity has been released")
}),
);
cx.notify();
}
/// Check if the account entity has a public key
pub fn has_account(&self) -> bool {
self.public_key.is_some()
}
/// Get the public key of the account
pub fn public_key(&self) -> PublicKey {
self.public_key
// This method is only called when user is logged in, so unwrap safely
self.public_key.unwrap()
}
}