chore: fix crash when failing to parse message (#202)
* clean up * . * fix rich text component * clean up
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -1065,7 +1065,6 @@ dependencies = [
|
|||||||
"gpui_tokio",
|
"gpui_tokio",
|
||||||
"indexset",
|
"indexset",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
"linkify",
|
|
||||||
"log",
|
"log",
|
||||||
"nostr",
|
"nostr",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
@@ -3508,15 +3507,6 @@ dependencies = [
|
|||||||
"rust-ini",
|
"rust-ini",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linkify"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.15"
|
version = "0.4.15"
|
||||||
|
|||||||
@@ -197,7 +197,11 @@ impl AutoUpdater {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("{e}")
|
_ = this.update(cx, |this, cx| {
|
||||||
|
this.set_status(AutoUpdateStatus::Idle, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
log::warn!("{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -371,6 +371,7 @@ impl Room {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Construct a filter for messaging relays
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::InboxRelays)
|
.kind(Kind::InboxRelays)
|
||||||
.author(member)
|
.author(member)
|
||||||
@@ -379,6 +380,7 @@ impl Room {
|
|||||||
// Subscribe to get members messaging relays
|
// Subscribe to get members messaging relays
|
||||||
client.subscribe(filter, Some(opts)).await?;
|
client.subscribe(filter, Some(opts)).await?;
|
||||||
|
|
||||||
|
// Construct a filter for encryption keys announcement
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::Custom(10044))
|
.kind(Kind::Custom(10044))
|
||||||
.author(member)
|
.author(member)
|
||||||
@@ -392,42 +394,6 @@ impl Room {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_connections(&self, cx: &App) -> Task<Result<HashMap<PublicKey, bool>, Error>> {
|
|
||||||
let members = self.members();
|
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
let client = app_state().client();
|
|
||||||
let mut result = HashMap::default();
|
|
||||||
|
|
||||||
for member in members.into_iter() {
|
|
||||||
let filter = Filter::new()
|
|
||||||
.kind(Kind::InboxRelays)
|
|
||||||
.author(member)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if let Some(event) = client.database().query(filter).await?.first() {
|
|
||||||
let urls: Vec<&RelayUrl> = nip17::extract_relay_list(event).collect();
|
|
||||||
|
|
||||||
if urls.is_empty() {
|
|
||||||
result.insert(member, false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for url in urls {
|
|
||||||
client.add_relay(url).await.ok();
|
|
||||||
client.connect_relay(url).await.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
result.insert(member, true);
|
|
||||||
} else {
|
|
||||||
result.insert(member, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all messages belonging to the room
|
/// Get all messages belonging to the room
|
||||||
pub fn get_messages(&self, cx: &App) -> Task<Result<Vec<UnsignedEvent>, Error>> {
|
pub fn get_messages(&self, cx: &App) -> Task<Result<Vec<UnsignedEvent>, Error>> {
|
||||||
let conversation_id = self.id.to_string();
|
let conversation_id = self.id.to_string();
|
||||||
|
|||||||
@@ -30,5 +30,4 @@ serde_json.workspace = true
|
|||||||
indexset = "0.12.3"
|
indexset = "0.12.3"
|
||||||
emojis = "0.6.4"
|
emojis = "0.6.4"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
linkify = "0.10.0"
|
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use person::PersonRegistry;
|
|||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use smol::fs;
|
use smol::fs;
|
||||||
use states::{app_state, SignerKind, QUERY_TIMEOUT};
|
use states::{app_state, SignerKind};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
@@ -45,9 +45,6 @@ mod emoji;
|
|||||||
mod subject;
|
mod subject;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
const NIP17_WARN: &str = "has not set up Messaging Relays, they cannot receive your message.";
|
|
||||||
const EMPTY_WARN: &str = "Something is wrong. Coop cannot display this message";
|
|
||||||
|
|
||||||
pub fn init(room: Entity<Room>, window: &mut Window, cx: &mut App) -> Entity<ChatPanel> {
|
pub fn init(room: Entity<Room>, window: &mut Window, cx: &mut App) -> Entity<ChatPanel> {
|
||||||
cx.new(|cx| ChatPanel::new(room, window, cx))
|
cx.new(|cx| ChatPanel::new(room, window, cx))
|
||||||
}
|
}
|
||||||
@@ -77,7 +74,7 @@ pub struct ChatPanel {
|
|||||||
image_cache: Entity<RetainAllImageCache>,
|
image_cache: Entity<RetainAllImageCache>,
|
||||||
|
|
||||||
_subscriptions: SmallVec<[Subscription; 3]>,
|
_subscriptions: SmallVec<[Subscription; 3]>,
|
||||||
_tasks: SmallVec<[Task<()>; 3]>,
|
_tasks: SmallVec<[Task<()>; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChatPanel {
|
impl ChatPanel {
|
||||||
@@ -99,21 +96,11 @@ impl ChatPanel {
|
|||||||
let list_state = ListState::new(messages.len(), ListAlignment::Bottom, px(1024.));
|
let list_state = ListState::new(messages.len(), ListAlignment::Bottom, px(1024.));
|
||||||
|
|
||||||
let connect = room.read(cx).connect(cx);
|
let connect = room.read(cx).connect(cx);
|
||||||
let verify_connections = room.read(cx).verify_connections(cx);
|
|
||||||
let get_messages = room.read(cx).get_messages(cx);
|
let get_messages = room.read(cx).get_messages(cx);
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
tasks.push(
|
|
||||||
// Get messaging relays and encryption keys announcement for all members
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
if let Err(e) = connect.await {
|
|
||||||
log::error!("Failed to initialize room: {e}");
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
// Load all messages belonging to this room
|
// Load all messages belonging to this room
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
@@ -134,35 +121,11 @@ impl ChatPanel {
|
|||||||
);
|
);
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
// Connect and verify all members messaging relays
|
// Get messaging relays and encryption keys announcement for each member
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.background_spawn(async move {
|
||||||
// Wait for 5 seconds before connecting and verifying
|
if let Err(e) = connect.await {
|
||||||
cx.background_executor()
|
log::error!("Failed to initialize room: {}", e);
|
||||||
.timer(Duration::from_secs(QUERY_TIMEOUT))
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let result = verify_connections.await;
|
|
||||||
|
|
||||||
this.update_in(cx, |this, window, cx| {
|
|
||||||
match result {
|
|
||||||
Ok(data) => {
|
|
||||||
let persons = PersonRegistry::global(cx);
|
|
||||||
|
|
||||||
for (public_key, status) in data.into_iter() {
|
|
||||||
if !status {
|
|
||||||
let profile = persons.read(cx).get_person(&public_key, cx);
|
|
||||||
let name = profile.display_name();
|
|
||||||
|
|
||||||
this.insert_warning(format!("{NIP17_WARN} {name}"), cx);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
window.push_notification(e.to_string(), cx);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -663,6 +626,33 @@ impl ChatPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_message(
|
fn render_message(
|
||||||
|
&mut self,
|
||||||
|
ix: usize,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> AnyElement {
|
||||||
|
if let Some(message) = self.messages.get_index(ix) {
|
||||||
|
match message {
|
||||||
|
Message::User(rendered) => {
|
||||||
|
let text = self
|
||||||
|
.rendered_texts_by_id
|
||||||
|
.entry(rendered.id)
|
||||||
|
.or_insert_with(|| RenderedText::new(&rendered.content, cx))
|
||||||
|
.element(ix.into(), window, cx);
|
||||||
|
|
||||||
|
self.render_text_message(ix, rendered, text, cx)
|
||||||
|
}
|
||||||
|
Message::Warning(content, _timestamp) => {
|
||||||
|
self.render_warning(ix, SharedString::from(content), cx)
|
||||||
|
}
|
||||||
|
Message::System(_timestamp) => self.render_announcement(ix, cx),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.render_warning(ix, SharedString::from("Message not found"), cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_text_message(
|
||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
message: &RenderedMessage,
|
message: &RenderedMessage,
|
||||||
@@ -1358,26 +1348,9 @@ impl Render for ChatPanel {
|
|||||||
.child(
|
.child(
|
||||||
list(
|
list(
|
||||||
self.list_state.clone(),
|
self.list_state.clone(),
|
||||||
cx.processor(move |this, ix: usize, window, cx| {
|
cx.processor(|this, ix, window, cx| {
|
||||||
if let Some(message) = this.messages.get_index(ix) {
|
// Get and render message by index
|
||||||
match message {
|
this.render_message(ix, window, cx)
|
||||||
Message::User(rendered) => {
|
|
||||||
let text = this
|
|
||||||
.rendered_texts_by_id
|
|
||||||
.entry(rendered.id)
|
|
||||||
.or_insert_with(|| RenderedText::new(&rendered.content, cx))
|
|
||||||
.element(ix.into(), window, cx);
|
|
||||||
|
|
||||||
this.render_message(ix, rendered, text, cx)
|
|
||||||
}
|
|
||||||
Message::Warning(content, _timestamp) => {
|
|
||||||
this.render_warning(ix, SharedString::from(content), cx)
|
|
||||||
}
|
|
||||||
Message::System(_timestamp) => this.render_announcement(ix, cx),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.render_warning(ix, SharedString::from(EMPTY_WARN), cx)
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.flex_1(),
|
.flex_1(),
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use common::display::RenderedProfile;
|
use common::display::RenderedProfile;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, AnyView, App, ElementId, HighlightStyle, InteractiveText, IntoElement,
|
AnyElement, App, ElementId, HighlightStyle, InteractiveText, IntoElement, SharedString,
|
||||||
SharedString, StyledText, UnderlineStyle, Window,
|
StyledText, UnderlineStyle, Window,
|
||||||
};
|
};
|
||||||
use linkify::{LinkFinder, LinkKind};
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
@@ -16,7 +15,7 @@ use theme::ActiveTheme;
|
|||||||
use crate::actions::OpenPublicKey;
|
use crate::actions::OpenPublicKey;
|
||||||
|
|
||||||
static URL_REGEX: Lazy<Regex> = Lazy::new(|| {
|
static URL_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||||
Regex::new(r"^(?:[a-zA-Z]+://)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d+)?(/.*)?$").unwrap()
|
Regex::new(r"(?i)(?:^|\s)(?:https?://)?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}(?::\d+)?(?:/[^\s]*)?(?:\s|$)").unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
static NOSTR_URI_REGEX: Lazy<Regex> =
|
static NOSTR_URI_REGEX: Lazy<Regex> =
|
||||||
@@ -24,43 +23,16 @@ static NOSTR_URI_REGEX: Lazy<Regex> =
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Highlight {
|
pub enum Highlight {
|
||||||
Link(HighlightStyle),
|
Link,
|
||||||
Nostr,
|
Nostr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Highlight {
|
|
||||||
fn link() -> Self {
|
|
||||||
Self::Link(HighlightStyle {
|
|
||||||
underline: Some(UnderlineStyle {
|
|
||||||
thickness: 1.0.into(),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn nostr() -> Self {
|
|
||||||
Self::Nostr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<HighlightStyle> for Highlight {
|
|
||||||
fn from(style: HighlightStyle) -> Self {
|
|
||||||
Self::Link(style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type CustomRangeTooltipFn =
|
|
||||||
Option<Arc<dyn Fn(usize, Range<usize>, &mut Window, &mut App) -> Option<AnyView>>>;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct RenderedText {
|
pub struct RenderedText {
|
||||||
pub text: SharedString,
|
pub text: SharedString,
|
||||||
pub highlights: Vec<(Range<usize>, Highlight)>,
|
pub highlights: Vec<(Range<usize>, Highlight)>,
|
||||||
pub link_ranges: Vec<Range<usize>>,
|
pub link_ranges: Vec<Range<usize>>,
|
||||||
pub link_urls: Arc<[String]>,
|
pub link_urls: Arc<[String]>,
|
||||||
pub custom_ranges: Vec<Range<usize>>,
|
|
||||||
custom_ranges_tooltip_fn: CustomRangeTooltipFn,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderedText {
|
impl RenderedText {
|
||||||
@@ -86,19 +58,9 @@ impl RenderedText {
|
|||||||
link_urls: link_urls.into(),
|
link_urls: link_urls.into(),
|
||||||
link_ranges,
|
link_ranges,
|
||||||
highlights,
|
highlights,
|
||||||
custom_ranges: Vec::new(),
|
|
||||||
custom_ranges_tooltip_fn: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn set_tooltip_builder_for_custom_ranges<F>(&mut self, f: F)
|
|
||||||
where
|
|
||||||
F: Fn(usize, Range<usize>, &mut Window, &mut App) -> Option<AnyView> + 'static,
|
|
||||||
{
|
|
||||||
self.custom_ranges_tooltip_fn = Some(Arc::new(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn element(&self, id: ElementId, window: &Window, cx: &App) -> AnyElement {
|
pub fn element(&self, id: ElementId, window: &Window, cx: &App) -> AnyElement {
|
||||||
let link_color = cx.theme().text_accent;
|
let link_color = cx.theme().text_accent;
|
||||||
|
|
||||||
@@ -110,17 +72,11 @@ impl RenderedText {
|
|||||||
(
|
(
|
||||||
range.clone(),
|
range.clone(),
|
||||||
match highlight {
|
match highlight {
|
||||||
Highlight::Link(highlight) => {
|
Highlight::Link => HighlightStyle {
|
||||||
// Check if this is a link highlight by seeing if it has an underline
|
color: Some(link_color),
|
||||||
if highlight.underline.is_some() {
|
underline: Some(UnderlineStyle::default()),
|
||||||
// It's a link, so apply the link color
|
..Default::default()
|
||||||
let mut link_style = *highlight;
|
},
|
||||||
link_style.color = Some(link_color);
|
|
||||||
link_style
|
|
||||||
} else {
|
|
||||||
*highlight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Highlight::Nostr => HighlightStyle {
|
Highlight::Nostr => HighlightStyle {
|
||||||
color: Some(link_color),
|
color: Some(link_color),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -135,49 +91,22 @@ impl RenderedText {
|
|||||||
move |ix, window, cx| {
|
move |ix, window, cx| {
|
||||||
let token = link_urls[ix].as_str();
|
let token = link_urls[ix].as_str();
|
||||||
|
|
||||||
if token.starts_with("nostr:") {
|
if let Some(clean_url) = token.strip_prefix("nostr:") {
|
||||||
let clean_url = token.replace("nostr:", "");
|
if let Ok(public_key) = PublicKey::parse(clean_url) {
|
||||||
let Ok(public_key) = PublicKey::parse(&clean_url) else {
|
|
||||||
log::error!("Failed to parse public key from: {clean_url}");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
window.dispatch_action(Box::new(OpenPublicKey(public_key)), cx);
|
window.dispatch_action(Box::new(OpenPublicKey(public_key)), cx);
|
||||||
} else if is_url(token) {
|
|
||||||
if !token.starts_with("http") {
|
|
||||||
cx.open_url(&format!("https://{token}"));
|
|
||||||
} else {
|
|
||||||
cx.open_url(token);
|
|
||||||
}
|
}
|
||||||
|
} else if is_url(token) {
|
||||||
|
let url = if token.starts_with("http") {
|
||||||
|
token.to_string()
|
||||||
|
} else {
|
||||||
|
format!("https://{token}")
|
||||||
|
};
|
||||||
|
cx.open_url(&url);
|
||||||
} else {
|
} else {
|
||||||
log::warn!("Unrecognized token {token}")
|
log::warn!("Unrecognized token {token}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.tooltip({
|
|
||||||
let link_ranges = self.link_ranges.clone();
|
|
||||||
let link_urls = self.link_urls.clone();
|
|
||||||
let custom_tooltip_ranges = self.custom_ranges.clone();
|
|
||||||
let custom_tooltip_fn = self.custom_ranges_tooltip_fn.clone();
|
|
||||||
move |idx, window, cx| {
|
|
||||||
for (ix, range) in link_ranges.iter().enumerate() {
|
|
||||||
if range.contains(&idx) {
|
|
||||||
let url = &link_urls[ix];
|
|
||||||
if url.starts_with("http") {
|
|
||||||
// return Some(LinkPreview::new(url, cx));
|
|
||||||
}
|
|
||||||
// You can add custom tooltip handling for mentions here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for range in &custom_tooltip_ranges {
|
|
||||||
if range.contains(&idx) {
|
|
||||||
if let Some(f) = &custom_tooltip_fn {
|
|
||||||
return f(idx, range.clone(), window, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,20 +122,15 @@ fn render_plain_text_mut(
|
|||||||
// Copy the content directly
|
// Copy the content directly
|
||||||
text.push_str(content);
|
text.push_str(content);
|
||||||
|
|
||||||
// Initialize the link finder
|
|
||||||
let mut finder = LinkFinder::new();
|
|
||||||
finder.url_must_have_scheme(false);
|
|
||||||
finder.kinds(&[LinkKind::Url]);
|
|
||||||
|
|
||||||
// Collect all URLs
|
// Collect all URLs
|
||||||
let mut url_matches: Vec<(Range<usize>, String)> = Vec::new();
|
let mut url_matches: Vec<(Range<usize>, String)> = Vec::new();
|
||||||
|
|
||||||
for link in finder.links(content) {
|
for link in URL_REGEX.find_iter(content) {
|
||||||
let start = link.start();
|
let range = link.start()..link.end();
|
||||||
let end = link.end();
|
|
||||||
let range = start..end;
|
|
||||||
let url = link.as_str().to_string();
|
let url = link.as_str().to_string();
|
||||||
|
|
||||||
|
log::info!("Found URL: {}", url);
|
||||||
|
|
||||||
url_matches.push((range, url));
|
url_matches.push((range, url));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,9 +138,7 @@ fn render_plain_text_mut(
|
|||||||
let mut nostr_matches: Vec<(Range<usize>, String)> = Vec::new();
|
let mut nostr_matches: Vec<(Range<usize>, String)> = Vec::new();
|
||||||
|
|
||||||
for nostr_match in NOSTR_URI_REGEX.find_iter(content) {
|
for nostr_match in NOSTR_URI_REGEX.find_iter(content) {
|
||||||
let start = nostr_match.start();
|
let range = nostr_match.start()..nostr_match.end();
|
||||||
let end = nostr_match.end();
|
|
||||||
let range = start..end;
|
|
||||||
let nostr_uri = nostr_match.as_str().to_string();
|
let nostr_uri = nostr_match.as_str().to_string();
|
||||||
|
|
||||||
// Check if this nostr URI overlaps with any already processed URL
|
// Check if this nostr URI overlaps with any already processed URL
|
||||||
@@ -240,12 +162,9 @@ fn render_plain_text_mut(
|
|||||||
for (range, entity) in all_matches {
|
for (range, entity) in all_matches {
|
||||||
// Handle URL token
|
// Handle URL token
|
||||||
if is_url(&entity) {
|
if is_url(&entity) {
|
||||||
// Add underline highlight
|
highlights.push((range.clone(), Highlight::Link));
|
||||||
highlights.push((range.clone(), Highlight::link()));
|
|
||||||
// Make it clickable
|
|
||||||
link_ranges.push(range);
|
link_ranges.push(range);
|
||||||
link_urls.push(entity);
|
link_urls.push(entity);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -306,75 +225,6 @@ fn render_plain_text_mut(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_pubkey(
|
|
||||||
public_key: PublicKey,
|
|
||||||
text: &mut String,
|
|
||||||
range: &Range<usize>,
|
|
||||||
highlights: &mut Vec<(Range<usize>, Highlight)>,
|
|
||||||
link_ranges: &mut Vec<Range<usize>>,
|
|
||||||
link_urls: &mut Vec<String>,
|
|
||||||
cx: &App,
|
|
||||||
) {
|
|
||||||
let persons = PersonRegistry::global(cx);
|
|
||||||
let profile = persons.read(cx).get_person(&public_key, cx);
|
|
||||||
let display_name = format!("@{}", profile.display_name());
|
|
||||||
|
|
||||||
// Replace token with display name
|
|
||||||
text.replace_range(range.clone(), &display_name);
|
|
||||||
|
|
||||||
// Adjust ranges
|
|
||||||
let new_length = display_name.len();
|
|
||||||
let length_diff = new_length as isize - (range.end - range.start) as isize;
|
|
||||||
// New range for the replacement
|
|
||||||
let new_range = range.start..(range.start + new_length);
|
|
||||||
|
|
||||||
// Add highlight for the profile name
|
|
||||||
highlights.push((new_range.clone(), Highlight::nostr()));
|
|
||||||
// Make it clickable
|
|
||||||
link_ranges.push(new_range);
|
|
||||||
link_urls.push(format!("nostr:{}", profile.public_key().to_hex()));
|
|
||||||
|
|
||||||
// Adjust subsequent ranges if needed
|
|
||||||
if length_diff != 0 {
|
|
||||||
adjust_ranges(highlights, link_ranges, range.end, length_diff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_bech32(
|
|
||||||
bech32: String,
|
|
||||||
text: &mut String,
|
|
||||||
range: &Range<usize>,
|
|
||||||
highlights: &mut Vec<(Range<usize>, Highlight)>,
|
|
||||||
link_ranges: &mut Vec<Range<usize>>,
|
|
||||||
link_urls: &mut Vec<String>,
|
|
||||||
) {
|
|
||||||
let njump_url = format!("https://njump.me/{bech32}");
|
|
||||||
|
|
||||||
// Create a shortened display format for the URL
|
|
||||||
let shortened_entity = format_shortened_entity(&bech32);
|
|
||||||
let display_text = format!("https://njump.me/{shortened_entity}");
|
|
||||||
|
|
||||||
// Replace the original entity with the shortened display version
|
|
||||||
text.replace_range(range.clone(), &display_text);
|
|
||||||
|
|
||||||
// Adjust the ranges
|
|
||||||
let new_length = display_text.len();
|
|
||||||
let length_diff = new_length as isize - (range.end - range.start) as isize;
|
|
||||||
// New range for the replacement
|
|
||||||
let new_range = range.start..(range.start + new_length);
|
|
||||||
|
|
||||||
// Add underline highlight
|
|
||||||
highlights.push((new_range.clone(), Highlight::link()));
|
|
||||||
// Make it clickable
|
|
||||||
link_ranges.push(new_range);
|
|
||||||
link_urls.push(njump_url);
|
|
||||||
|
|
||||||
// Adjust subsequent ranges if needed
|
|
||||||
if length_diff != 0 {
|
|
||||||
adjust_ranges(highlights, link_ranges, range.end, length_diff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a string is a URL
|
/// Check if a string is a URL
|
||||||
@@ -396,6 +246,61 @@ fn format_shortened_entity(entity: &str) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_pubkey(
|
||||||
|
public_key: PublicKey,
|
||||||
|
text: &mut String,
|
||||||
|
range: &Range<usize>,
|
||||||
|
highlights: &mut Vec<(Range<usize>, Highlight)>,
|
||||||
|
link_ranges: &mut Vec<Range<usize>>,
|
||||||
|
link_urls: &mut Vec<String>,
|
||||||
|
cx: &App,
|
||||||
|
) {
|
||||||
|
let persons = PersonRegistry::global(cx);
|
||||||
|
let profile = persons.read(cx).get_person(&public_key, cx);
|
||||||
|
let display_name = format!("@{}", profile.display_name());
|
||||||
|
|
||||||
|
text.replace_range(range.clone(), &display_name);
|
||||||
|
|
||||||
|
let new_length = display_name.len();
|
||||||
|
let length_diff = new_length as isize - (range.end - range.start) as isize;
|
||||||
|
let new_range = range.start..(range.start + new_length);
|
||||||
|
|
||||||
|
highlights.push((new_range.clone(), Highlight::Nostr));
|
||||||
|
link_ranges.push(new_range);
|
||||||
|
link_urls.push(format!("nostr:{}", profile.public_key().to_hex()));
|
||||||
|
|
||||||
|
if length_diff != 0 {
|
||||||
|
adjust_ranges(highlights, link_ranges, range.end, length_diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_bech32(
|
||||||
|
bech32: String,
|
||||||
|
text: &mut String,
|
||||||
|
range: &Range<usize>,
|
||||||
|
highlights: &mut Vec<(Range<usize>, Highlight)>,
|
||||||
|
link_ranges: &mut Vec<Range<usize>>,
|
||||||
|
link_urls: &mut Vec<String>,
|
||||||
|
) {
|
||||||
|
let njump_url = format!("https://njump.me/{bech32}");
|
||||||
|
let shortened_entity = format_shortened_entity(&bech32);
|
||||||
|
let display_text = format!("https://njump.me/{shortened_entity}");
|
||||||
|
|
||||||
|
text.replace_range(range.clone(), &display_text);
|
||||||
|
|
||||||
|
let new_length = display_text.len();
|
||||||
|
let length_diff = new_length as isize - (range.end - range.start) as isize;
|
||||||
|
let new_range = range.start..(range.start + new_length);
|
||||||
|
|
||||||
|
highlights.push((new_range.clone(), Highlight::Link));
|
||||||
|
link_ranges.push(new_range);
|
||||||
|
link_urls.push(njump_url);
|
||||||
|
|
||||||
|
if length_diff != 0 {
|
||||||
|
adjust_ranges(highlights, link_ranges, range.end, length_diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to adjust ranges when text length changes
|
// Helper function to adjust ranges when text length changes
|
||||||
fn adjust_ranges(
|
fn adjust_ranges(
|
||||||
highlights: &mut [(Range<usize>, Highlight)],
|
highlights: &mut [(Range<usize>, Highlight)],
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use gpui::{Image, ImageFormat, SharedString, SharedUri};
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use qrcode::render::svg;
|
use qrcode::render::svg;
|
||||||
use qrcode::QrCode;
|
use qrcode::QrCode;
|
||||||
use states::IMAGE_RESIZE_SERVICE;
|
|
||||||
|
|
||||||
const NOW: &str = "now";
|
const NOW: &str = "now";
|
||||||
const SECONDS_IN_MINUTE: i64 = 60;
|
const SECONDS_IN_MINUTE: i64 = 60;
|
||||||
@@ -14,6 +13,7 @@ const MINUTES_IN_HOUR: i64 = 60;
|
|||||||
const HOURS_IN_DAY: i64 = 24;
|
const HOURS_IN_DAY: i64 = 24;
|
||||||
const DAYS_IN_MONTH: i64 = 30;
|
const DAYS_IN_MONTH: i64 = 30;
|
||||||
const FALLBACK_IMG: &str = "https://image.nostr.build/c30703b48f511c293a9003be8100cdad37b8798b77a1dc3ec6eb8a20443d5dea.png";
|
const FALLBACK_IMG: &str = "https://image.nostr.build/c30703b48f511c293a9003be8100cdad37b8798b77a1dc3ec6eb8a20443d5dea.png";
|
||||||
|
const IMAGE_RESIZE_SERVICE: &str = "https://wsrv.nl";
|
||||||
|
|
||||||
pub trait RenderedProfile {
|
pub trait RenderedProfile {
|
||||||
fn avatar(&self, proxy: bool) -> SharedUri;
|
fn avatar(&self, proxy: bool) -> SharedUri;
|
||||||
|
|||||||
@@ -1167,11 +1167,11 @@ impl ChatSpace {
|
|||||||
let file_keystore = KeyStore::global(cx).read(cx).is_using_file_keystore();
|
let file_keystore = KeyStore::global(cx).read(cx).is_using_file_keystore();
|
||||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||||
let auth_requests = self.auth_requests.read(cx).len();
|
let auth_requests = self.auth_requests.read(cx).len();
|
||||||
|
let auto_update = AutoUpdater::global(cx);
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.map(
|
.map(|this| match auto_update.read(cx).status.as_ref() {
|
||||||
|this| match AutoUpdater::global(cx).read(cx).status.as_ref() {
|
|
||||||
AutoUpdateStatus::Checking => this.child(
|
AutoUpdateStatus::Checking => this.child(
|
||||||
div()
|
div()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
@@ -1201,8 +1201,7 @@ impl ChatSpace {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
_ => this.child(div()),
|
_ => this.child(div()),
|
||||||
},
|
})
|
||||||
)
|
|
||||||
.when(file_keystore, |this| {
|
.when(file_keystore, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
Button::new("keystore-warning")
|
Button::new("keystore-warning")
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use gpui::{
|
|||||||
TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind,
|
TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind,
|
||||||
WindowOptions,
|
WindowOptions,
|
||||||
};
|
};
|
||||||
use states::{app_state, APP_ID, CLIENT_NAME};
|
use states::{app_state, APP_ID, BOOTSTRAP_RELAYS, CLIENT_NAME, SEARCH_RELAYS};
|
||||||
use ui::Root;
|
use ui::Root;
|
||||||
|
|
||||||
use crate::actions::{load_embedded_fonts, quit, Quit};
|
use crate::actions::{load_embedded_fonts, quit, Quit};
|
||||||
@@ -21,14 +21,34 @@ fn main() {
|
|||||||
// Initialize logging
|
// Initialize logging
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
// Initialize the coop simple storage
|
|
||||||
let _app_state = app_state();
|
|
||||||
|
|
||||||
// Initialize the Application
|
// Initialize the Application
|
||||||
let app = Application::new()
|
let app = Application::new()
|
||||||
.with_assets(Assets)
|
.with_assets(Assets)
|
||||||
.with_http_client(Arc::new(reqwest_client::ReqwestClient::new()));
|
.with_http_client(Arc::new(reqwest_client::ReqwestClient::new()));
|
||||||
|
|
||||||
|
// Initialize app state
|
||||||
|
let app_state = app_state();
|
||||||
|
|
||||||
|
// Connect to relays
|
||||||
|
app.background_executor()
|
||||||
|
.spawn(async move {
|
||||||
|
let client = app_state.client();
|
||||||
|
|
||||||
|
// Get all bootstrapping relays
|
||||||
|
let mut urls = vec![];
|
||||||
|
urls.extend(BOOTSTRAP_RELAYS);
|
||||||
|
urls.extend(SEARCH_RELAYS);
|
||||||
|
|
||||||
|
// Add relay to the relay pool
|
||||||
|
for url in urls.into_iter() {
|
||||||
|
client.add_relay(url).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establish connection to relays
|
||||||
|
client.connect().await;
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
// Run application
|
// Run application
|
||||||
app.run(move |cx| {
|
app.run(move |cx| {
|
||||||
// Load embedded fonts in assets/fonts
|
// Load embedded fonts in assets/fonts
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use anyhow::{anyhow, Error};
|
|||||||
use chat::room::{Room, RoomKind};
|
use chat::room::{Room, RoomKind};
|
||||||
use chat::{ChatEvent, ChatRegistry};
|
use chat::{ChatEvent, ChatRegistry};
|
||||||
use common::debounced_delay::DebouncedDelay;
|
use common::debounced_delay::DebouncedDelay;
|
||||||
use common::display::{RenderedProfile, RenderedTimestamp, TextUtils};
|
use common::display::{RenderedTimestamp, TextUtils};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
deferred, div, relative, uniform_list, AnyElement, App, AppContext, Context, Entity,
|
deferred, div, relative, uniform_list, AnyElement, App, AppContext, Context, Entity,
|
||||||
@@ -628,8 +628,8 @@ impl Sidebar {
|
|||||||
items.push(
|
items.push(
|
||||||
RoomListItem::new(ix)
|
RoomListItem::new(ix)
|
||||||
.room_id(room_id)
|
.room_id(room_id)
|
||||||
.name(member.display_name())
|
.name(this.display_name(cx))
|
||||||
.avatar(member.avatar(proxy))
|
.avatar(this.display_image(proxy, cx))
|
||||||
.public_key(member.public_key())
|
.public_key(member.public_key())
|
||||||
.kind(this.kind)
|
.kind(this.kind)
|
||||||
.created_at(this.created_at.to_ago())
|
.created_at(this.created_at.to_ago())
|
||||||
|
|||||||
@@ -42,9 +42,3 @@ pub const METADATA_BATCH_TIMEOUT: u64 = 300;
|
|||||||
|
|
||||||
/// Default width of the sidebar.
|
/// Default width of the sidebar.
|
||||||
pub const DEFAULT_SIDEBAR_WIDTH: f32 = 240.;
|
pub const DEFAULT_SIDEBAR_WIDTH: f32 = 240.;
|
||||||
|
|
||||||
/// Image Resize Service
|
|
||||||
pub const IMAGE_RESIZE_SERVICE: &str = "https://wsrv.nl";
|
|
||||||
|
|
||||||
/// Default NIP96 Media Server.
|
|
||||||
pub const NIP96_SERVER: &str = "https://nostrmedia.com";
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use nostr_sdk::prelude::*;
|
|||||||
use smol::lock::RwLock;
|
use smol::lock::RwLock;
|
||||||
|
|
||||||
use crate::constants::{
|
use crate::constants::{
|
||||||
BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT, QUERY_TIMEOUT, SEARCH_RELAYS,
|
BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT, QUERY_TIMEOUT,
|
||||||
};
|
};
|
||||||
use crate::paths::config_dir;
|
use crate::paths::config_dir;
|
||||||
use crate::state::ingester::Ingester;
|
use crate::state::ingester::Ingester;
|
||||||
@@ -210,19 +210,6 @@ impl AppState {
|
|||||||
|
|
||||||
/// Handles events from the nostr client
|
/// Handles events from the nostr client
|
||||||
pub async fn handle_notifications(&self) -> Result<(), Error> {
|
pub async fn handle_notifications(&self) -> Result<(), Error> {
|
||||||
// Get all bootstrapping relays
|
|
||||||
let mut urls = vec![];
|
|
||||||
urls.extend(BOOTSTRAP_RELAYS);
|
|
||||||
urls.extend(SEARCH_RELAYS);
|
|
||||||
|
|
||||||
// Add relay to the relay pool
|
|
||||||
for url in urls.into_iter() {
|
|
||||||
self.client.add_relay(url).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Establish connection to relays
|
|
||||||
self.client.connect().await;
|
|
||||||
|
|
||||||
let mut processed_events: HashSet<EventId> = HashSet::new();
|
let mut processed_events: HashSet<EventId> = HashSet::new();
|
||||||
let mut challenges: HashSet<Cow<'_, str>> = HashSet::new();
|
let mut challenges: HashSet<Cow<'_, str>> = HashSet::new();
|
||||||
let mut notifications = self.client.notifications();
|
let mut notifications = self.client.notifications();
|
||||||
@@ -345,9 +332,7 @@ impl AppState {
|
|||||||
self.signal.send(SignalKind::NewProfile(profile)).await;
|
self.signal.send(SignalKind::NewProfile(profile)).await;
|
||||||
}
|
}
|
||||||
Kind::GiftWrap => {
|
Kind::GiftWrap => {
|
||||||
if let Err(e) = self.extract_rumor(&event).await {
|
self.extract_rumor(&event).await.ok();
|
||||||
log::error!("Failed to extract rumor: {e}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -997,6 +982,8 @@ impl AppState {
|
|||||||
.subscribe_with_id_to(&urls, id, filter, None)
|
.subscribe_with_id_to(&urls, id, filter, None)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
log::info!("Subscribed to gift wrap events");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1027,15 +1014,15 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Stores an unwrapped event in local database with reference to original
|
/// Stores an unwrapped event in local database with reference to original
|
||||||
async fn set_rumor(&self, id: EventId, rumor: &UnsignedEvent) -> Result<(), Error> {
|
async fn set_rumor(&self, gift_wrap: EventId, rumor: &UnsignedEvent) -> Result<(), Error> {
|
||||||
let rumor_id = rumor.id.context("Rumor is missing an event id")?;
|
let rumor_id = rumor.id.context("Rumor is missing an event id")?;
|
||||||
let author = rumor.pubkey;
|
let author = rumor.pubkey;
|
||||||
let conversation = self.conversation_id(rumor).to_string();
|
let conversation = self.conversation_id(rumor);
|
||||||
|
|
||||||
let mut tags = rumor.tags.clone().to_vec();
|
let mut tags = rumor.tags.clone().to_vec();
|
||||||
|
|
||||||
// Add a unique identifier
|
// Add a unique identifier
|
||||||
tags.push(Tag::identifier(id));
|
tags.push(Tag::identifier(gift_wrap));
|
||||||
|
|
||||||
// Add a reference to the rumor's author
|
// Add a reference to the rumor's author
|
||||||
tags.push(Tag::custom(
|
tags.push(Tag::custom(
|
||||||
@@ -1046,7 +1033,7 @@ impl AppState {
|
|||||||
// Add a conversation id
|
// Add a conversation id
|
||||||
tags.push(Tag::custom(
|
tags.push(Tag::custom(
|
||||||
TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::C)),
|
TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::C)),
|
||||||
[conversation],
|
[conversation.to_string()],
|
||||||
));
|
));
|
||||||
|
|
||||||
// Add a reference to the rumor's id
|
// Add a reference to the rumor's id
|
||||||
@@ -1063,21 +1050,23 @@ impl AppState {
|
|||||||
// Convert rumor to json
|
// Convert rumor to json
|
||||||
let content = rumor.as_json();
|
let content = rumor.as_json();
|
||||||
|
|
||||||
|
// Construct the event
|
||||||
let event = EventBuilder::new(Kind::ApplicationSpecificData, content)
|
let event = EventBuilder::new(Kind::ApplicationSpecificData, content)
|
||||||
.tags(tags)
|
.tags(tags)
|
||||||
.sign(&Keys::generate())
|
.sign(&Keys::generate())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
// Save the event to the database
|
||||||
self.client.database().save_event(&event).await?;
|
self.client.database().save_event(&event).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a previously unwrapped event from local database
|
/// Retrieves a previously unwrapped event from local database
|
||||||
async fn get_rumor(&self, id: EventId) -> Result<UnsignedEvent, Error> {
|
async fn get_rumor(&self, gift_wrap: EventId) -> Result<UnsignedEvent, Error> {
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::ApplicationSpecificData)
|
.kind(Kind::ApplicationSpecificData)
|
||||||
.identifier(id)
|
.identifier(gift_wrap)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if let Some(event) = self.client.database().query(filter).await?.first_owned() {
|
if let Some(event) = self.client.database().query(filter).await?.first_owned() {
|
||||||
@@ -1118,20 +1107,15 @@ impl AppState {
|
|||||||
|
|
||||||
// Helper method to try unwrapping with different signers
|
// Helper method to try unwrapping with different signers
|
||||||
async fn try_unwrap_gift_wrap(&self, gift_wrap: &Event) -> Result<UnwrappedGift, Error> {
|
async fn try_unwrap_gift_wrap(&self, gift_wrap: &Event) -> Result<UnwrappedGift, Error> {
|
||||||
// Try to unwrap with the encryption key first
|
// Try to unwrap with the encryption key if available
|
||||||
// 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
|
||||||
if let Some(signer) = self.device.read().await.encryption.as_ref() {
|
if let Some(signer) = self.device.read().await.encryption.as_ref() {
|
||||||
match UnwrappedGift::from_gift_wrap(signer, gift_wrap).await {
|
if let Ok(unwrapped) = UnwrappedGift::from_gift_wrap(signer, gift_wrap).await {
|
||||||
Ok(unwrapped) => {
|
|
||||||
return Ok(unwrapped);
|
return Ok(unwrapped);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
|
||||||
log::warn!("Failed to unwrap with the encryption key: {e}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to unwrap with the user's signer
|
// Fallback to unwrap with the user's signer
|
||||||
let signer = self.client.signer().await?;
|
let signer = self.client.signer().await?;
|
||||||
let unwrapped = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await?;
|
let unwrapped = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user