set default data for newly created identity
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m31s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m21s

This commit is contained in:
2026-02-09 10:12:21 +07:00
parent 031883c278
commit 6cce0d8bea
14 changed files with 158 additions and 154 deletions

View File

@@ -4,7 +4,6 @@ use std::sync::Arc;
use std::time::Duration;
use anyhow::{anyhow, Context as AnyhowContext, Error};
use common::BOOTSTRAP_RELAYS;
use gpui::http_client::{AsyncBody, HttpClient};
use gpui::{
App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, Global, Subscription, Task,

View File

@@ -440,7 +440,6 @@ impl Room {
// Construct a direct message event
//
// WARNING: never sign and send this event to relays
// TODO
let mut event = EventBuilder::new(Kind::PrivateDirectMessage, content)
.tags(tags)
.build(Keys::generate().public_key());

View File

@@ -19,5 +19,4 @@ log.workspace = true
dirs = "5.0"
qrcode = "0.14.1"
whoami = "1.6.1"
nostr = { git = "https://github.com/rust-nostr/nostr" }

View File

@@ -1,28 +0,0 @@
pub const CLIENT_NAME: &str = "Coop";
pub const APP_ID: &str = "su.reya.coop";
/// Bootstrap Relays.
pub const BOOTSTRAP_RELAYS: [&str; 4] = [
"wss://relay.damus.io",
"wss://relay.primal.net",
"wss://relay.nos.social",
"wss://user.kindpag.es",
];
/// Search Relays.
pub const SEARCH_RELAYS: [&str; 2] = ["wss://search.nos.today", "wss://relay.noswhere.com"];
/// Default relay for Nostr Connect
pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
/// Default retry count for fetching NIP-17 relays
pub const RELAY_RETRY: u64 = 2;
/// Default retry count for sending messages
pub const SEND_RETRY: u64 = 10;
/// Default timeout (in seconds) for Nostr Connect
pub const NOSTR_CONNECT_TIMEOUT: u64 = 200;
/// Default timeout (in seconds) for Nostr Connect (Bunker)
pub const BUNKER_TIMEOUT: u64 = 30;

View File

@@ -1,66 +1,11 @@
use std::sync::OnceLock;
pub use constants::*;
pub use debounced_delay::*;
pub use display::*;
pub use event::*;
pub use nip96::*;
use nostr_sdk::prelude::*;
pub use paths::*;
mod constants;
mod debounced_delay;
mod display;
mod event;
mod nip96;
mod paths;
static APP_NAME: OnceLock<String> = OnceLock::new();
static NIP65_RELAYS: OnceLock<Vec<(RelayUrl, Option<RelayMetadata>)>> = OnceLock::new();
static NIP17_RELAYS: OnceLock<Vec<RelayUrl>> = OnceLock::new();
/// Get the app name
pub fn app_name() -> &'static String {
APP_NAME.get_or_init(|| {
let devicename = whoami::devicename();
let platform = whoami::platform();
format!("{CLIENT_NAME} on {platform} ({devicename})")
})
}
/// Default NIP-65 Relays. Used for new account
pub fn default_nip65_relays() -> &'static Vec<(RelayUrl, Option<RelayMetadata>)> {
NIP65_RELAYS.get_or_init(|| {
vec![
(
RelayUrl::parse("wss://nostr.mom").unwrap(),
Some(RelayMetadata::Read),
),
(
RelayUrl::parse("wss://nostr.bitcoiner.social").unwrap(),
Some(RelayMetadata::Read),
),
(
RelayUrl::parse("wss://nos.lol").unwrap(),
Some(RelayMetadata::Write),
),
(
RelayUrl::parse("wss://relay.snort.social").unwrap(),
Some(RelayMetadata::Write),
),
(RelayUrl::parse("wss://relay.primal.net").unwrap(), None),
(RelayUrl::parse("wss://relay.damus.io").unwrap(), None),
]
})
}
/// Default NIP-17 Relays. Used for new account
pub fn default_nip17_relays() -> &'static Vec<RelayUrl> {
NIP17_RELAYS.get_or_init(|| {
vec![
RelayUrl::parse("wss://nip17.com").unwrap(),
RelayUrl::parse("wss://auth.nostr1.com").unwrap(),
]
})
}

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::time::Duration;
use anyhow::{Context as AnyhowContext, Error};
use common::{shorten_pubkey, RenderedTimestamp, BOOTSTRAP_RELAYS};
use common::{shorten_pubkey, RenderedTimestamp};
use gpui::prelude::FluentBuilder;
use gpui::{
div, px, relative, rems, uniform_list, App, AppContext, Context, Div, Entity,
@@ -11,7 +11,7 @@ use gpui::{
use nostr_sdk::prelude::*;
use person::{Person, PersonRegistry};
use smallvec::{smallvec, SmallVec};
use state::{NostrAddress, NostrRegistry, TIMEOUT};
use state::{NostrAddress, NostrRegistry, BOOTSTRAP_RELAYS, TIMEOUT};
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};

View File

@@ -1,12 +1,12 @@
use std::sync::{Arc, Mutex};
use assets::Assets;
use common::{APP_ID, CLIENT_NAME};
use gpui::{
point, px, size, App, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem,
SharedString, Size, TitlebarOptions, WindowBackgroundAppearance, WindowBounds,
WindowDecorations, WindowKind, WindowOptions,
};
use state::{APP_ID, CLIENT_NAME};
use ui::Root;
use crate::actions::Quit;

View File

@@ -2,7 +2,6 @@ use std::collections::HashSet;
use std::time::Duration;
use anyhow::{anyhow, Context as AnyhowContext, Error};
use common::BOOTSTRAP_RELAYS;
use dock::panel::{Panel, PanelEvent};
use gpui::prelude::FluentBuilder;
use gpui::{
@@ -12,7 +11,7 @@ use gpui::{
};
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use state::NostrRegistry;
use state::{NostrRegistry, BOOTSTRAP_RELAYS};
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::input::{InputEvent, InputState, TextInput};

View File

@@ -3,15 +3,15 @@ use std::sync::Arc;
use std::time::Duration;
use anyhow::{anyhow, Context as AnyhowContext, Error};
use common::app_name;
pub use device::*;
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use state::{NostrRegistry, RelayState, DEVICE_GIFTWRAP, TIMEOUT, USER_GIFTWRAP};
use state::{app_name, NostrRegistry, RelayState, DEVICE_GIFTWRAP, TIMEOUT, USER_GIFTWRAP};
mod device;
pub use device::*;
const IDENTIFIER: &str = "coop:device";
pub fn init(cx: &mut App) {

View File

@@ -4,12 +4,12 @@ use std::rc::Rc;
use std::time::Duration;
use anyhow::{anyhow, Error};
use common::{EventUtils, BOOTSTRAP_RELAYS};
use common::EventUtils;
use gpui::{App, AppContext, Context, Entity, Global, Task};
use nostr_sdk::prelude::*;
pub use person::*;
use smallvec::{smallvec, SmallVec};
use state::{Announcement, NostrRegistry, TIMEOUT};
use state::{Announcement, NostrRegistry, BOOTSTRAP_RELAYS, TIMEOUT};
mod person;

View File

@@ -25,3 +25,4 @@ serde_json.workspace = true
rustls = "0.23"
petname = "2.0.2"
whoami = "1.6.1"

View File

@@ -0,0 +1,60 @@
use std::sync::OnceLock;
/// Client name (Application name)
pub const CLIENT_NAME: &str = "Coop";
/// COOP's public key
pub const COOP_PUBKEY: &str = "npub126kl5fruqan90py77gf6pvfvygefl2mu2ukew6xdx5pc5uqscwgsnkgarv";
/// App ID
pub const APP_ID: &str = "su.reya.coop";
/// Keyring name
pub const KEYRING: &str = "Coop Secret Storage";
/// Default timeout for subscription
pub const TIMEOUT: u64 = 3;
/// Default delay for searching
pub const FIND_DELAY: u64 = 600;
/// Default limit for searching
pub const FIND_LIMIT: usize = 20;
/// Default timeout for Nostr Connect
pub const NOSTR_CONNECT_TIMEOUT: u64 = 200;
/// Default Nostr Connect relay
pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
/// Default subscription id for device gift wrap events
pub const DEVICE_GIFTWRAP: &str = "device-gift-wraps";
/// Default subscription id for user gift wrap events
pub const USER_GIFTWRAP: &str = "user-gift-wraps";
/// Default vertex relays
pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"];
/// Default search relays
pub const SEARCH_RELAYS: [&str; 2] = ["wss://antiprimal.net", "wss://search.nos.today"];
/// Default bootstrap relays
pub const BOOTSTRAP_RELAYS: [&str; 4] = [
"wss://relay.damus.io",
"wss://relay.primal.net",
"wss://relay.nos.social",
"wss://user.kindpag.es",
];
static APP_NAME: OnceLock<String> = OnceLock::new();
/// Get the app name
pub fn app_name() -> &'static String {
APP_NAME.get_or_init(|| {
let devicename = whoami::devicename();
let platform = whoami::platform();
format!("{CLIENT_NAME} on {platform} ({devicename})")
})
}

View File

@@ -4,53 +4,25 @@ use std::sync::Arc;
use std::time::Duration;
use anyhow::{anyhow, Context as AnyhowContext, Error};
use common::{config_dir, CLIENT_NAME};
use common::config_dir;
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
use nostr_connect::prelude::*;
use nostr_gossip_memory::prelude::*;
use nostr_lmdb::NostrLmdb;
use nostr_sdk::prelude::*;
mod constants;
mod device;
mod event;
mod gossip;
mod nip05;
mod signer;
pub use constants::*;
pub use device::*;
pub use event::*;
pub use gossip::*;
pub use nip05::*;
pub use signer::*;
/// Default timeout for subscription
pub const TIMEOUT: u64 = 3;
/// Default delay for searching
pub const FIND_DELAY: u64 = 600;
/// Default limit for searching
pub const FIND_LIMIT: usize = 20;
/// Default timeout for Nostr Connect
pub const NOSTR_CONNECT_TIMEOUT: u64 = 200;
/// Default Nostr Connect relay
pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
/// Default subscription id for device gift wrap events
pub const DEVICE_GIFTWRAP: &str = "device-gift-wraps";
/// Default subscription id for user gift wrap events
pub const USER_GIFTWRAP: &str = "user-gift-wraps";
/// Default avatar for new users
pub const DEFAULT_AVATAR: &str = "https://image.nostr.build/93bb6084457a42620849b6827f3f34f111ae5a4ac728638a989d4ed4b4bb3ac8.png";
/// Default vertex relays
pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"];
/// Default search relays
pub const SEARCH_RELAYS: [&str; 2] = ["wss://search.nos.today", "wss://relay.noswhere.com"];
/// Default bootstrap relays
pub const BOOTSTRAP_RELAYS: [&str; 4] = [
"wss://relay.damus.io",
"wss://relay.primal.net",
"wss://relay.nos.social",
"wss://user.kindpag.es",
];
pub fn init(cx: &mut App) {
// rustls uses the `aws_lc_rs` provider by default
// This only errors if the default provider has already
@@ -626,8 +598,8 @@ impl NostrRegistry {
let builder = EventBuilder::metadata(&metadata);
let event = client.sign_event_builder(builder).await?;
// Send event to user's write relayss
client.send_event(&event).to_nip65().await?;
// Send event to user's relays
client.send_event(&event).await?;
Ok(())
})
@@ -635,7 +607,7 @@ impl NostrRegistry {
/// Get local stored identity
fn get_identity(&mut self, cx: &mut Context<Self>) {
let read_credential = cx.read_credentials(CLIENT_NAME);
let read_credential = cx.read_credentials(KEYRING);
self.tasks.push(cx.spawn(async move |this, cx| {
match read_credential.await {
@@ -662,42 +634,72 @@ impl NostrRegistry {
/// Create a new identity
fn create_identity(&mut self, cx: &mut Context<Self>) {
let client = self.client();
let keys = Keys::generate();
let async_keys = keys.clone();
// Get write credential task
let write_credential = cx.write_credentials(
CLIENT_NAME,
KEYRING,
&keys.public_key().to_hex(),
&keys.secret_key().to_secret_bytes(),
);
// Update the signer
self.set_signer(keys, false, cx);
// Run async tasks in background
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
// Build and sign the relay list event
let relay_list = default_relay_list();
let event = EventBuilder::relay_list(relay_list)
.sign(&async_keys)
.await?;
// Generate a unique name and avatar for the identity
let name = petname::petname(2, "-").unwrap_or("Cooper".to_string());
let avatar = Url::parse(DEFAULT_AVATAR).unwrap();
// Publish relay list event
client.send_event(&event).await?;
// Construct metadata for the identity
let metadata = Metadata::new()
.display_name(&name)
.name(&name)
.picture(avatar);
// Build and sign the metadata event
let name = petname::petname(2, "-").unwrap_or("Cooper".to_string());
let avatar = Url::parse(&format!("https://avatar.vercel.sh/{name}")).unwrap();
let metadata = Metadata::new().display_name(&name).picture(avatar);
let event = EventBuilder::metadata(&metadata).sign(&async_keys).await?;
// Update user's metadata
let task = self.set_metadata(&metadata, cx);
// Publish metadata event
client.send_event(&event).await?;
// Spawn a task to write the credentials
cx.background_spawn(async move {
if let Err(e) = task.await {
log::error!("Failed to update metadata: {}", e);
}
// Build and sign the contact list event
let contacts = vec![Contact::new(PublicKey::parse(COOP_PUBKEY).unwrap())];
let event = EventBuilder::contact_list(contacts)
.sign(&async_keys)
.await?;
if let Err(e) = write_credential.await {
log::error!("Failed to write credentials: {}", e);
}
})
.detach();
// Publish contact list event
client.send_event(&event).await?;
// Build and sign the messaging relay list event
let relays = default_messaging_relays();
let event = EventBuilder::nip17_relay_list(relays)
.sign(&async_keys)
.await?;
// Publish messaging relay list event
client.send_event(&event).await?;
// Write user's credentials to the system keyring
write_credential.await?;
Ok(())
});
self.tasks.push(cx.spawn(async move |this, cx| {
// Wait for the task to complete
task.await?;
// Update the signer
this.update(cx, |this, cx| {
this.set_signer(keys, false, cx);
})?;
Ok(())
}));
}
/// Get local stored bunker connection
@@ -922,6 +924,34 @@ impl NostrRegistry {
}
}
fn default_relay_list() -> Vec<(RelayUrl, Option<RelayMetadata>)> {
vec![
(
RelayUrl::parse("wss://relay.gulugulu.moe").unwrap(),
Some(RelayMetadata::Write),
),
(
RelayUrl::parse("wss://relay.primal.net/").unwrap(),
Some(RelayMetadata::Write),
),
(
RelayUrl::parse("wss://relay.primal.net/").unwrap(),
Some(RelayMetadata::Read),
),
(
RelayUrl::parse("wss://nos.lol/").unwrap(),
Some(RelayMetadata::Read),
),
]
}
fn default_messaging_relays() -> Vec<RelayUrl> {
vec![
RelayUrl::parse("wss://auth.nostr1.com/").unwrap(),
RelayUrl::parse("wss://nip17.com/").unwrap(),
]
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RelayState {
#[default]