commit d3da4eb009ff7266edd67764f1aeeffe3513360c
parent 19637d612eda6f9fb3eb04b74a78971429ae0abc
Author: Greg Heartsfield <scsibug@imap.cc>
Date: Mon, 3 Jan 2022 18:42:24 -0500
feat: implementation of proposed NIP-11 (server metadata)
Diffstat:
7 files changed, 118 insertions(+), 5 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -1097,6 +1097,7 @@ version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
dependencies = [
+ "indexmap",
"itoa",
"ryu",
"serde 1.0.131",
diff --git a/Cargo.toml b/Cargo.toml
@@ -17,7 +17,7 @@ config = { version = "0.11", features = ["toml"] }
bitcoin_hashes = { version = "^0.9", features = ["serde"] }
secp256k1 = {git = "https://github.com/rust-bitcoin/rust-secp256k1.git", rev = "50034ccb18fdd84904ab3aa6c84a12fcced33209", features = ["rand", "rand-std", "serde", "bitcoin_hashes"] }
serde = { version = "^1.0", features = ["derive"] }
-serde_json = "^1.0"
+serde_json = {version = "^1.0", features = ["preserve_order"]}
hex = "^0.4"
rusqlite = "^0.26"
lazy_static = "^1.4"
diff --git a/config.toml b/config.toml
@@ -1,4 +1,16 @@
# Nostr-rs-relay configuration
+
+[info]
+# Relay information for clients. Put your unique server name here.
+name = "nostr-rs-relay"
+# Description
+description = "A newly created nostr-rs-relay.\n\nCustomize this with your own info."
+# Administrative contact pubkey
+#pubkey = "0c2d168a4ae8ca58c9f1ab237b5df682599c6c7ab74307ea8b05684b60405d41"
+
+# Administrative contact email
+#email = "contact@example.com"
+
[database]
# Directory for SQLite files. Defaults to the current directory. Can
# also be specified (and overriden) with the "--db dirname" command
diff --git a/src/config.rs b/src/config.rs
@@ -10,6 +10,16 @@ lazy_static! {
#[derive(Debug, Serialize, Deserialize)]
#[allow(unused)]
+pub struct Info {
+ pub name: Option<String>,
+ #[serde(rename = "description")]
+ pub descr: Option<String>,
+ pub pubkey: Option<String>,
+ pub email: Option<String>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[allow(unused)]
pub struct Database {
pub data_directory: String,
}
@@ -52,6 +62,7 @@ pub struct Limits {
#[derive(Debug, Serialize, Deserialize)]
#[allow(unused)]
pub struct Settings {
+ pub info: Info,
pub database: Database,
pub network: Network,
pub limits: Limits,
@@ -89,6 +100,12 @@ impl Settings {
impl Default for Settings {
fn default() -> Self {
Settings {
+ info: Info {
+ name: Some("Unnamed nostr-rs-relay".to_owned()),
+ descr: None,
+ pubkey: None,
+ email: None,
+ },
database: Database {
data_directory: ".".to_owned(),
},
diff --git a/src/info.rs b/src/info.rs
@@ -0,0 +1,61 @@
+use crate::config;
+/// Relay Info
+use serde::{Deserialize, Serialize};
+use serde_json::value::Value;
+
+const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
+
+#[derive(Debug, Serialize, Deserialize)]
+#[allow(unused)]
+pub struct RelayInfo {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub name: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub descr: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub pubkey: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub email: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub supported_nips: Option<Vec<String>>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub software: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub version: Option<String>,
+}
+
+impl Default for RelayInfo {
+ fn default() -> Self {
+ RelayInfo {
+ name: None,
+ descr: None,
+ pubkey: None,
+ email: None,
+ supported_nips: Some(vec!["NIP-01".to_owned()]),
+ software: Some("https://git.sr.ht/~gheartsfield/nostr-rs-relay".to_owned()),
+ version: CARGO_PKG_VERSION.map(|x| x.to_owned()),
+ }
+ }
+}
+
+/// Convert an Info struct into Relay Info json string
+pub fn relay_info_json(info: &config::Info) -> String {
+ // get a default RelayInfo
+ let mut r = RelayInfo::default();
+ // update fields from Info, if present
+ r.name = info.name.clone();
+ r.descr = info.descr.clone();
+ r.pubkey = info.pubkey.clone();
+ r.email = info.email.clone();
+ r.to_json()
+}
+
+impl RelayInfo {
+ pub fn to_json(self) -> String {
+ // create the info ARRAY
+ let mut info_arr: Vec<Value> = vec![];
+ info_arr.push(Value::String("NOSTR_SERVER_INFO".to_owned()));
+ info_arr.push(serde_json::to_value(&self).unwrap());
+ serde_json::to_string_pretty(&info_arr).unwrap()
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
@@ -4,5 +4,6 @@ pub mod conn;
pub mod db;
pub mod error;
pub mod event;
+pub mod info;
pub mod protostream;
pub mod subscription;
diff --git a/src/main.rs b/src/main.rs
@@ -1,6 +1,7 @@
//! Server process
use futures::SinkExt;
use futures::StreamExt;
+use hyper::header::ACCEPT;
use hyper::service::{make_service_fn, service_fn};
use hyper::upgrade::Upgraded;
use hyper::{
@@ -13,6 +14,7 @@ use nostr_rs_relay::conn;
use nostr_rs_relay::db;
use nostr_rs_relay::error::{Error, Result};
use nostr_rs_relay::event::Event;
+use nostr_rs_relay::info::relay_info_json;
use nostr_rs_relay::protostream;
use nostr_rs_relay::protostream::NostrMessage::*;
use nostr_rs_relay::protostream::NostrResponse::*;
@@ -47,7 +49,7 @@ async fn handle_web_request(
request.uri().path(),
request.headers().contains_key(header::UPGRADE),
) {
- //if the request is ws_echo and the request headers contains an Upgrade key
+ // Request for / as websocket
("/", true) => {
debug!("websocket with upgrade request");
//assume request is a handshake, so create the handshake response
@@ -96,11 +98,30 @@ async fn handle_web_request(
};
Ok::<_, Infallible>(response)
}
+ // Request for Relay info
("/", false) => {
// handle request at root with no upgrade header
- Ok(Response::new(Body::from(
- "This is a Nostr relay.\n".to_string(),
- )))
+ // Check if this is a nostr server info request
+ let accept_header = &request.headers().get(ACCEPT);
+ // check if application/nostr+json is included
+ if let Some(media_types) = accept_header {
+ if let Ok(mt_str) = media_types.to_str() {
+ if mt_str.contains("application/nostr+json") {
+ let config = config::SETTINGS.read().unwrap();
+ // build a relay info response
+ debug!("Responding to server info request");
+ let b = Body::from(relay_info_json(&config.info));
+ return Ok(Response::builder()
+ .status(200)
+ .header("Content-Type", "application/nostr+json")
+ .body(b)
+ .unwrap());
+ }
+ }
+ }
+ return Ok(Response::new(Body::from(
+ "Please use a Nostr client to connect.",
+ )));
}
(_, _) => {
//handle any other url