feat: resend failed messages (#147)
* . * . * fix * fix * update * fix * . * .
This commit is contained in:
@@ -64,18 +64,22 @@ pub fn new_account(window: &mut Window, cx: &mut App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChatSpace {
|
pub struct ChatSpace {
|
||||||
// Workspace
|
// App's Title Bar
|
||||||
title_bar: Entity<TitleBar>,
|
title_bar: Entity<TitleBar>,
|
||||||
|
|
||||||
|
// App's Dock Area
|
||||||
dock: Entity<DockArea>,
|
dock: Entity<DockArea>,
|
||||||
|
|
||||||
// Temporarily store all authentication requests
|
// All authentication requests
|
||||||
auth_requests: HashMap<AuthRequest, bool>,
|
auth_requests: HashMap<RelayUrl, AuthRequest>,
|
||||||
|
|
||||||
// Local state to determine if the user has set up NIP-17 relays
|
// Local state to determine if the user has set up NIP-17 relays
|
||||||
has_nip17_relays: bool,
|
nip17_relays: bool,
|
||||||
|
|
||||||
// System
|
// All subscriptions for observing the app state
|
||||||
_subscriptions: SmallVec<[Subscription; 3]>,
|
_subscriptions: SmallVec<[Subscription; 3]>,
|
||||||
|
|
||||||
|
// All long running tasks
|
||||||
_tasks: SmallVec<[Task<()>; 5]>,
|
_tasks: SmallVec<[Task<()>; 5]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +186,7 @@ impl ChatSpace {
|
|||||||
dock,
|
dock,
|
||||||
title_bar,
|
title_bar,
|
||||||
auth_requests: HashMap::new(),
|
auth_requests: HashMap::new(),
|
||||||
has_nip17_relays: true,
|
nip17_relays: true,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
_tasks: tasks,
|
_tasks: tasks,
|
||||||
}
|
}
|
||||||
@@ -573,7 +577,7 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
Signal::DmRelayNotFound => {
|
Signal::DmRelayNotFound => {
|
||||||
view.update(cx, |this, cx| {
|
view.update(cx, |this, cx| {
|
||||||
this.set_no_nip17_relays(cx);
|
this.set_required_relays(cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -728,10 +732,8 @@ impl ChatSpace {
|
|||||||
match event.created_at >= css.init_at {
|
match event.created_at >= css.init_at {
|
||||||
// New message: send a signal to notify the UI
|
// New message: send a signal to notify the UI
|
||||||
true => {
|
true => {
|
||||||
// Prevent notification if the event was sent by Coop
|
smol::Timer::after(Duration::from_millis(200)).await;
|
||||||
if !css.sent_ids.read().await.contains(&target.id) {
|
ingester.send(Signal::Message((target.id, event))).await;
|
||||||
ingester.send(Signal::Message((target.id, event))).await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Old message: Coop is probably processing the user's messages during initial load
|
// Old message: Coop is probably processing the user's messages during initial load
|
||||||
false => {
|
false => {
|
||||||
@@ -942,20 +944,20 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn reopen_auth_request(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn reopen_auth_request(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
for req in self.auth_requests.clone().into_iter() {
|
for (_, request) in self.auth_requests.clone().into_iter() {
|
||||||
self.open_auth_request(req.0, window, cx);
|
self.open_auth_request(request, window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_auth_request(&mut self, req: &AuthRequest, cx: &mut Context<Self>) {
|
fn push_auth_request(&mut self, req: &AuthRequest, cx: &mut Context<Self>) {
|
||||||
self.auth_requests.insert(req.to_owned(), false);
|
self.auth_requests.insert(req.url.clone(), req.to_owned());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sending_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
|
fn sending_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
|
||||||
for (req, status) in self.auth_requests.iter_mut() {
|
for (_, req) in self.auth_requests.iter_mut() {
|
||||||
if req.challenge == challenge {
|
if req.challenge == challenge {
|
||||||
*status = true;
|
req.sending = true;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -965,16 +967,16 @@ impl ChatSpace {
|
|||||||
if let Some(req) = self
|
if let Some(req) = self
|
||||||
.auth_requests
|
.auth_requests
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(req, _)| req.challenge == challenge)
|
.find(|(_, req)| req.challenge == challenge)
|
||||||
{
|
{
|
||||||
req.1.to_owned()
|
req.1.sending
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
|
fn remove_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
|
||||||
self.auth_requests.retain(|r, _| r.challenge != challenge);
|
self.auth_requests.retain(|_, r| r.challenge != challenge);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1026,8 +1028,8 @@ impl ChatSpace {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_no_nip17_relays(&mut self, cx: &mut Context<Self>) {
|
fn set_required_relays(&mut self, cx: &mut Context<Self>) {
|
||||||
self.has_nip17_relays = false;
|
self.nip17_relays = false;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1054,19 +1056,13 @@ impl ChatSpace {
|
|||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
if let Ok((secret, profile)) = task.await {
|
if let Ok((secret, profile)) = task.await {
|
||||||
cx.update(|window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.set_account_layout(secret, profile, window, cx);
|
||||||
this.set_account_layout(secret, profile, window, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
} else {
|
} else {
|
||||||
cx.update(|window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.set_onboarding_layout(window, cx);
|
||||||
this.set_onboarding_layout(window, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -1283,7 +1279,6 @@ impl ChatSpace {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||||
let is_auto_auth = AppSettings::read_global(cx).is_auto_auth();
|
|
||||||
let updating = AutoUpdater::read_global(cx).status.is_updating();
|
let updating = AutoUpdater::read_global(cx).status.is_updating();
|
||||||
let updated = AutoUpdater::read_global(cx).status.is_updated();
|
let updated = AutoUpdater::read_global(cx).status.is_updated();
|
||||||
let auth_requests = self.auth_requests.len();
|
let auth_requests = self.auth_requests.len();
|
||||||
@@ -1322,7 +1317,7 @@ impl ChatSpace {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(auth_requests > 0 && !is_auto_auth, |this| {
|
.when(auth_requests > 0, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("requests")
|
.id("requests")
|
||||||
@@ -1342,7 +1337,7 @@ impl ChatSpace {
|
|||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(!self.has_nip17_relays, |this| {
|
.when(!self.nip17_relays, |this| {
|
||||||
this.child(setup_nip17_relay(t!("relays.button")))
|
this.child(setup_nip17_relay(t!("relays.button")))
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use smallvec::{smallvec, SmallVec};
|
|||||||
use smol::fs;
|
use smol::fs;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonRounded, ButtonVariants};
|
||||||
use ui::dock_area::panel::{Panel, PanelEvent};
|
use ui::dock_area::panel::{Panel, PanelEvent};
|
||||||
use ui::emoji_picker::EmojiPicker;
|
use ui::emoji_picker::EmojiPicker;
|
||||||
use ui::input::{InputEvent, InputState, TextInput};
|
use ui::input::{InputEvent, InputState, TextInput};
|
||||||
@@ -307,6 +307,38 @@ impl Chat {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resend_message(&mut self, id: &EventId, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if let Some(reports) = self.reports_by_id.get(id).cloned() {
|
||||||
|
if let Some(message) = self.message(id) {
|
||||||
|
let backup = AppSettings::get_backup_messages(cx);
|
||||||
|
let id_clone = id.to_owned();
|
||||||
|
let message = message.content.to_owned();
|
||||||
|
let task = self.room.read(cx).resend(reports, message, backup, cx);
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
match task.await {
|
||||||
|
Ok(reports) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.reports_by_id.entry(id_clone).and_modify(|this| {
|
||||||
|
*this = reports;
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
cx.update(|window, cx| {
|
||||||
|
window.push_notification(e.to_string(), cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if a message failed to send by its ID
|
/// Check if a message failed to send by its ID
|
||||||
fn is_sent_failed(&self, id: &EventId) -> bool {
|
fn is_sent_failed(&self, id: &EventId) -> bool {
|
||||||
self.reports_by_id
|
self.reports_by_id
|
||||||
@@ -609,7 +641,23 @@ impl Chat {
|
|||||||
})
|
})
|
||||||
.child(text)
|
.child(text)
|
||||||
.when(is_sent_failed, |this| {
|
.when(is_sent_failed, |this| {
|
||||||
this.child(self.render_message_reports(&id, cx))
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(self.render_message_reports(&id, cx))
|
||||||
|
.child(
|
||||||
|
Button::new(SharedString::from(id.to_hex()))
|
||||||
|
.label(t!("common.resend"))
|
||||||
|
.danger()
|
||||||
|
.xsmall()
|
||||||
|
.rounded(ButtonRounded::Full)
|
||||||
|
.on_click(cx.listener(
|
||||||
|
move |this, _, window, cx| {
|
||||||
|
this.resend_message(&id, window, cx);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -677,15 +725,17 @@ impl Chat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_message_sent(&self, id: &EventId, _cx: &Context<Self>) -> impl IntoElement {
|
fn render_message_sent(&self, id: &EventId, _cx: &Context<Self>) -> impl IntoElement {
|
||||||
div().id("").child(shared_t!("chat.sent")).when_some(
|
div()
|
||||||
self.sent_reports(id).cloned(),
|
.id(SharedString::from(id.to_hex()))
|
||||||
|this, reports| {
|
.child(shared_t!("chat.sent"))
|
||||||
|
.when_some(self.sent_reports(id).cloned(), |this, reports| {
|
||||||
this.on_click(move |_e, window, cx| {
|
this.on_click(move |_e, window, cx| {
|
||||||
let reports = reports.clone();
|
let reports = reports.clone();
|
||||||
|
|
||||||
window.open_modal(cx, move |this, _window, cx| {
|
window.open_modal(cx, move |this, _window, cx| {
|
||||||
this.title(shared_t!("chat.reports")).child(
|
this.show_close(true)
|
||||||
v_flex().pb_4().gap_4().children({
|
.title(shared_t!("chat.reports"))
|
||||||
|
.child(v_flex().pb_4().gap_4().children({
|
||||||
let mut items = Vec::with_capacity(reports.len());
|
let mut items = Vec::with_capacity(reports.len());
|
||||||
|
|
||||||
for report in reports.iter() {
|
for report in reports.iter() {
|
||||||
@@ -693,30 +743,29 @@ impl Chat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
items
|
items
|
||||||
}),
|
}))
|
||||||
)
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_message_reports(&self, id: &EventId, cx: &Context<Self>) -> impl IntoElement {
|
fn render_message_reports(&self, id: &EventId, cx: &Context<Self>) -> impl IntoElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("")
|
.id(SharedString::from(id.to_hex()))
|
||||||
.gap_1()
|
.gap_0p5()
|
||||||
.text_color(cx.theme().danger_foreground)
|
.text_color(cx.theme().danger_foreground)
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.italic()
|
.italic()
|
||||||
.child(Icon::new(IconName::Info).small())
|
.child(Icon::new(IconName::Info).xsmall())
|
||||||
.child(shared_t!("chat.sent_failed"))
|
.child(shared_t!("chat.sent_failed"))
|
||||||
.when_some(self.sent_reports(id).cloned(), |this, reports| {
|
.when_some(self.sent_reports(id).cloned(), |this, reports| {
|
||||||
this.on_click(move |_e, window, cx| {
|
this.on_click(move |_e, window, cx| {
|
||||||
let reports = reports.clone();
|
let reports = reports.clone();
|
||||||
|
|
||||||
window.open_modal(cx, move |this, _window, cx| {
|
window.open_modal(cx, move |this, _window, cx| {
|
||||||
this.title(shared_t!("chat.reports")).child(
|
this.show_close(true)
|
||||||
v_flex().pb_4().gap_4().children({
|
.title(shared_t!("chat.reports"))
|
||||||
|
.child(v_flex().gap_4().pb_4().w_full().children({
|
||||||
let mut items = Vec::with_capacity(reports.len());
|
let mut items = Vec::with_capacity(reports.len());
|
||||||
|
|
||||||
for report in reports.iter() {
|
for report in reports.iter() {
|
||||||
@@ -724,8 +773,7 @@ impl Chat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
items
|
items
|
||||||
}),
|
}))
|
||||||
)
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -739,6 +787,7 @@ impl Chat {
|
|||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
.w_full()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
@@ -752,7 +801,7 @@ impl Chat {
|
|||||||
.child(name.clone()),
|
.child(name.clone()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.when(report.nip17_relays_not_found, |this| {
|
.when(report.relays_not_found, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_wrap()
|
.flex_wrap()
|
||||||
@@ -773,7 +822,7 @@ impl Chat {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when_some(report.local_error.clone(), |this, error| {
|
.when_some(report.error.clone(), |this, error| {
|
||||||
this.child(
|
this.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_wrap()
|
.flex_wrap()
|
||||||
@@ -788,38 +837,36 @@ impl Chat {
|
|||||||
.child(div().flex_1().w_full().text_center().child(error)),
|
.child(div().flex_1().w_full().text_center().child(error)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when_some(report.output.clone(), |this, output| {
|
.when_some(report.status.clone(), |this, output| {
|
||||||
this.child(
|
this.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.text_xs()
|
.w_full()
|
||||||
.children({
|
.children({
|
||||||
let mut items = Vec::with_capacity(output.failed.len());
|
let mut items = Vec::with_capacity(output.failed.len());
|
||||||
|
|
||||||
for (url, msg) in output.failed.into_iter() {
|
for (url, msg) in output.failed.into_iter() {
|
||||||
items.push(
|
items.push(
|
||||||
h_flex()
|
v_flex()
|
||||||
.gap_1()
|
.gap_0p5()
|
||||||
.justify_between()
|
.py_1()
|
||||||
.text_sm()
|
.px_2()
|
||||||
|
.w_full()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.bg(cx.theme().elevated_surface_background)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.text_xs()
|
||||||
.py_0p5()
|
.font_semibold()
|
||||||
.px_2()
|
.line_height(relative(1.25))
|
||||||
.bg(cx.theme().elevated_surface_background)
|
.child(SharedString::from(url.to_string())),
|
||||||
.rounded_sm()
|
|
||||||
.child(url.to_string()),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.text_sm()
|
||||||
.py_0p5()
|
|
||||||
.px_2()
|
|
||||||
.bg(cx.theme().danger_background)
|
|
||||||
.text_color(cx.theme().danger_foreground)
|
.text_color(cx.theme().danger_foreground)
|
||||||
.rounded_sm()
|
.line_height(relative(1.25))
|
||||||
.child(msg.to_string()),
|
.child(SharedString::from(msg.to_string())),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -831,27 +878,25 @@ impl Chat {
|
|||||||
|
|
||||||
for url in output.success.into_iter() {
|
for url in output.success.into_iter() {
|
||||||
items.push(
|
items.push(
|
||||||
h_flex()
|
v_flex()
|
||||||
.gap_1()
|
.gap_0p5()
|
||||||
.justify_between()
|
.py_1()
|
||||||
.text_sm()
|
.px_2()
|
||||||
|
.w_full()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.bg(cx.theme().elevated_surface_background)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.text_xs()
|
||||||
.py_0p5()
|
.font_semibold()
|
||||||
.px_2()
|
.line_height(relative(1.25))
|
||||||
.bg(cx.theme().elevated_surface_background)
|
.child(SharedString::from(url.to_string())),
|
||||||
.rounded_sm()
|
|
||||||
.child(url.to_string()),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.text_sm()
|
||||||
.py_0p5()
|
|
||||||
.px_2()
|
|
||||||
.bg(cx.theme().secondary_background)
|
|
||||||
.text_color(cx.theme().secondary_foreground)
|
.text_color(cx.theme().secondary_foreground)
|
||||||
.rounded_sm()
|
.line_height(relative(1.25))
|
||||||
.child(shared_t!("chat.sent_success")),
|
.child(shared_t!("chat.sent_success")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -921,7 +966,7 @@ impl Chat {
|
|||||||
let path: SharedString = url.to_string().into();
|
let path: SharedString = url.to_string().into();
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id("")
|
.id(SharedString::from(url.to_string()))
|
||||||
.relative()
|
.relative()
|
||||||
.w_16()
|
.w_16()
|
||||||
.child(
|
.child(
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
|
|||||||
pub const RELAY_RETRY: u64 = 2;
|
pub const RELAY_RETRY: u64 = 2;
|
||||||
|
|
||||||
/// Default retry count for sending messages
|
/// Default retry count for sending messages
|
||||||
pub const SEND_RETRY: u64 = 5;
|
pub const SEND_RETRY: u64 = 10;
|
||||||
|
|
||||||
/// Default timeout (in seconds) for Nostr Connect
|
/// Default timeout (in seconds) for Nostr Connect
|
||||||
pub const NOSTR_CONNECT_TIMEOUT: u64 = 200;
|
pub const NOSTR_CONNECT_TIMEOUT: u64 = 200;
|
||||||
|
|||||||
@@ -13,16 +13,18 @@ use crate::paths::support_dir;
|
|||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod paths;
|
pub mod paths;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct AuthRequest {
|
pub struct AuthRequest {
|
||||||
pub challenge: String,
|
|
||||||
pub url: RelayUrl,
|
pub url: RelayUrl,
|
||||||
|
pub challenge: String,
|
||||||
|
pub sending: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthRequest {
|
impl AuthRequest {
|
||||||
pub fn new(challenge: impl Into<String>, url: RelayUrl) -> Self {
|
pub fn new(challenge: impl Into<String>, url: RelayUrl) -> Self {
|
||||||
Self {
|
Self {
|
||||||
challenge: challenge.into(),
|
challenge: challenge.into(),
|
||||||
|
sending: false,
|
||||||
url,
|
url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -16,45 +17,51 @@ use crate::Registry;
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SendReport {
|
pub struct SendReport {
|
||||||
pub receiver: PublicKey,
|
pub receiver: PublicKey,
|
||||||
pub output: Option<Output<EventId>>,
|
pub tags: Option<Vec<Tag>>,
|
||||||
pub local_error: Option<SharedString>,
|
pub status: Option<Output<EventId>>,
|
||||||
pub nip17_relays_not_found: bool,
|
pub error: Option<SharedString>,
|
||||||
|
pub relays_not_found: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SendReport {
|
impl SendReport {
|
||||||
pub fn output(receiver: PublicKey, output: Output<EventId>) -> Self {
|
pub fn new(receiver: PublicKey) -> Self {
|
||||||
Self {
|
Self {
|
||||||
receiver,
|
receiver,
|
||||||
output: Some(output),
|
status: None,
|
||||||
local_error: None,
|
error: None,
|
||||||
nip17_relays_not_found: false,
|
tags: None,
|
||||||
|
relays_not_found: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn error(receiver: PublicKey, error: impl Into<SharedString>) -> Self {
|
pub fn not_found(mut self) -> Self {
|
||||||
Self {
|
self.relays_not_found = true;
|
||||||
receiver,
|
self
|
||||||
output: None,
|
|
||||||
local_error: Some(error.into()),
|
|
||||||
nip17_relays_not_found: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nip17_relays_not_found(receiver: PublicKey) -> Self {
|
pub fn error(mut self, error: impl Into<SharedString>) -> Self {
|
||||||
Self {
|
self.error = Some(error.into());
|
||||||
receiver,
|
self.relays_not_found = false;
|
||||||
output: None,
|
self
|
||||||
local_error: None,
|
}
|
||||||
nip17_relays_not_found: true,
|
|
||||||
}
|
pub fn status(mut self, output: Output<EventId>) -> Self {
|
||||||
|
self.status = Some(output);
|
||||||
|
self.relays_not_found = false;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tags(mut self, tags: &Vec<Tag>) -> Self {
|
||||||
|
self.tags = Some(tags.to_owned());
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_relay_error(&self) -> bool {
|
pub fn is_relay_error(&self) -> bool {
|
||||||
self.local_error.is_some() || self.nip17_relays_not_found
|
self.error.is_some() || self.relays_not_found
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_sent_success(&self) -> bool {
|
pub fn is_sent_success(&self) -> bool {
|
||||||
if let Some(output) = self.output.as_ref() {
|
if let Some(output) = self.status.as_ref() {
|
||||||
!output.success.is_empty()
|
!output.success.is_empty()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@@ -338,19 +345,21 @@ impl Room {
|
|||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
|
let public_key = members[members.len() - 1];
|
||||||
|
|
||||||
let filter = Filter::new()
|
let sent = Filter::new()
|
||||||
.kind(Kind::PrivateDirectMessage)
|
.kind(Kind::PrivateDirectMessage)
|
||||||
.authors(members.clone())
|
.author(public_key)
|
||||||
.pubkeys(members.clone());
|
.pubkeys(members.clone());
|
||||||
|
|
||||||
let events: Vec<Event> = client
|
let recv = Filter::new()
|
||||||
.database()
|
.kind(Kind::PrivateDirectMessage)
|
||||||
.query(filter)
|
.authors(members)
|
||||||
.await?
|
.pubkey(public_key);
|
||||||
.into_iter()
|
|
||||||
.filter(|ev| ev.compare_pubkeys(&members))
|
let sent_events = client.database().query(sent).await?;
|
||||||
.collect();
|
let recv_events = client.database().query(recv).await?;
|
||||||
|
let events: Vec<Event> = sent_events.merge(recv_events).into_iter().collect();
|
||||||
|
|
||||||
Ok(events)
|
Ok(events)
|
||||||
})
|
})
|
||||||
@@ -398,17 +407,7 @@ impl Room {
|
|||||||
event
|
event
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a message to all members in the background task
|
/// Create a task to sends a message to all members in the background
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `content` - The content of the message to send
|
|
||||||
/// * `cx` - The App context
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// A Task that resolves to Result<Vec<String>, Error> where the
|
|
||||||
/// strings contain error messages for any failed sends
|
|
||||||
pub fn send_in_background(
|
pub fn send_in_background(
|
||||||
&self,
|
&self,
|
||||||
content: &str,
|
content: &str,
|
||||||
@@ -422,20 +421,21 @@ impl Room {
|
|||||||
let mut public_keys = self.members.clone();
|
let mut public_keys = self.members.clone();
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
|
let css = css();
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
let mut tags = public_keys
|
let mut tags: Vec<Tag> = public_keys
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|pubkey| {
|
.filter_map(|&this| {
|
||||||
if pubkey != &public_key {
|
if this != public_key {
|
||||||
Some(Tag::public_key(*pubkey))
|
Some(Tag::public_key(this))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect_vec();
|
.collect();
|
||||||
|
|
||||||
// Add event reference if it's present (replying to another event)
|
// Add event reference if it's present (replying to another event)
|
||||||
if replies.len() == 1 {
|
if replies.len() == 1 {
|
||||||
@@ -468,44 +468,50 @@ impl Room {
|
|||||||
// Stored all send errors
|
// Stored all send errors
|
||||||
let mut reports = vec![];
|
let mut reports = vec![];
|
||||||
|
|
||||||
for receiver in public_keys.into_iter() {
|
for pubkey in public_keys.into_iter() {
|
||||||
match client
|
match client
|
||||||
.send_private_msg(receiver, &content, tags.clone())
|
.send_private_msg(pubkey, &content, tags.clone())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
if output
|
let id = output.id().to_owned();
|
||||||
.failed
|
let auth_required = output.failed.iter().any(|m| m.1.starts_with("auth-"));
|
||||||
.iter()
|
let report = SendReport::new(pubkey).status(output).tags(&tags);
|
||||||
.any(|(_, msg)| msg.starts_with("auth-required:"))
|
|
||||||
{
|
|
||||||
let id = output.id();
|
|
||||||
|
|
||||||
|
if auth_required {
|
||||||
// Wait for authenticated and resent event successfully
|
// Wait for authenticated and resent event successfully
|
||||||
for attempt in 0..=SEND_RETRY {
|
for attempt in 0..=SEND_RETRY {
|
||||||
// Check if event was successfully resent
|
// Check if event was successfully resent
|
||||||
if let Some(output) =
|
if let Some(output) = css
|
||||||
css().resent_ids.read().await.iter().find(|o| o.id() == id)
|
.resent_ids
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.find(|e| e.id() == &id)
|
||||||
|
.cloned()
|
||||||
{
|
{
|
||||||
reports.push(SendReport::output(receiver, output.to_owned()));
|
let output = SendReport::new(pubkey).status(output).tags(&tags);
|
||||||
|
reports.push(output);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if retry limit exceeded
|
||||||
if attempt == SEND_RETRY {
|
if attempt == SEND_RETRY {
|
||||||
|
reports.push(report);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
smol::Timer::after(Duration::from_secs(1)).await;
|
smol::Timer::after(Duration::from_millis(1200)).await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reports.push(SendReport::output(receiver, output));
|
reports.push(report);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let nostr_sdk::client::Error::PrivateMsgRelaysNotFound = e {
|
if let nostr_sdk::client::Error::PrivateMsgRelaysNotFound = e {
|
||||||
reports.push(SendReport::nip17_relays_not_found(receiver));
|
reports.push(SendReport::new(pubkey).not_found().tags(&tags));
|
||||||
} else {
|
} else {
|
||||||
reports.push(SendReport::error(receiver, e.to_string()));
|
reports.push(SendReport::new(pubkey).error(e.to_string()).tags(&tags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -518,13 +524,14 @@ impl Room {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
reports.push(SendReport::output(public_key, output));
|
reports.push(SendReport::new(public_key).status(output).tags(&tags));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let nostr_sdk::client::Error::PrivateMsgRelaysNotFound = e {
|
if let nostr_sdk::client::Error::PrivateMsgRelaysNotFound = e {
|
||||||
reports.push(SendReport::nip17_relays_not_found(public_key));
|
reports.push(SendReport::new(public_key).not_found());
|
||||||
} else {
|
} else {
|
||||||
reports.push(SendReport::error(public_key, e.to_string()));
|
reports
|
||||||
|
.push(SendReport::new(public_key).error(e.to_string()).tags(&tags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -533,4 +540,57 @@ impl Room {
|
|||||||
Ok(reports)
|
Ok(reports)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a task to resend a failed message
|
||||||
|
pub fn resend(
|
||||||
|
&self,
|
||||||
|
reports: Vec<SendReport>,
|
||||||
|
message: String,
|
||||||
|
backup: bool,
|
||||||
|
cx: &App,
|
||||||
|
) -> Task<Result<Vec<SendReport>, Error>> {
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let client = nostr_client();
|
||||||
|
let mut resend_reports = vec![];
|
||||||
|
let mut resend_tag = vec![];
|
||||||
|
|
||||||
|
for report in reports.into_iter() {
|
||||||
|
if let Some(output) = report.status {
|
||||||
|
let id = output.id();
|
||||||
|
let urls: Vec<&RelayUrl> = output.failed.keys().collect();
|
||||||
|
|
||||||
|
if let Some(event) = client.database().event_by_id(id).await? {
|
||||||
|
for url in urls.into_iter() {
|
||||||
|
let relay = client.pool().relay(url).await?;
|
||||||
|
let id = relay.send_event(&event).await?;
|
||||||
|
let resent: Output<EventId> = Output {
|
||||||
|
val: id,
|
||||||
|
success: HashSet::from([url.to_owned()]),
|
||||||
|
failed: HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
resend_reports.push(SendReport::new(report.receiver).status(resent));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tags) = report.tags {
|
||||||
|
resend_tag.extend(tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only send a backup message to current user if sent successfully to others
|
||||||
|
if backup && !resend_reports.is_empty() {
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
let output = client
|
||||||
|
.send_private_msg(public_key, message, resend_tag)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
resend_reports.push(SendReport::new(public_key).status(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(resend_reports)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ common:
|
|||||||
en: "Relay URL is not valid."
|
en: "Relay URL is not valid."
|
||||||
recommended:
|
recommended:
|
||||||
en: "Recommended:"
|
en: "Recommended:"
|
||||||
|
resend:
|
||||||
|
en: "Resend"
|
||||||
|
|
||||||
auto_update:
|
auto_update:
|
||||||
updating:
|
updating:
|
||||||
|
|||||||
Reference in New Issue
Block a user