fix(web): rely on user_id from server

this way we don't have to construct it ourselves every time with
URL_BASE. i think it's a bit weaker this way tho
This commit is contained in:
əlemi 2025-01-22 02:07:16 +01:00
parent d290a5e083
commit eba399abe5
Signed by: alemi
GPG key ID: A4895B84D311642C
7 changed files with 45 additions and 51 deletions

View file

@ -130,7 +130,7 @@ pub fn App() -> impl IntoView {
<Route path=path!("home") view=move || if auth.present() { <Route path=path!("home") view=move || if auth.present() {
Either::Left(view! { Either::Left(view! {
<Loadable <Loadable
base=format!("{URL_BASE}/actors/{}/inbox/page", auth.username()) base=format!("{}/inbox/page", auth.user_id())
element=move |obj| view! { <Item item=obj sep=true /> } element=move |obj| view! { <Item item=obj sep=true /> }
/> />
}) })
@ -155,7 +155,7 @@ pub fn App() -> impl IntoView {
<Route path=path!("notifications") view=move || if auth.present() { <Route path=path!("notifications") view=move || if auth.present() {
Either::Left(view! { Either::Left(view! {
<Loadable <Loadable
base=format!("{URL_BASE}/actors/{}/notifications/page", auth.username()) base=format!("{}/notifications/page", auth.user_id())
element=move |obj| view! { <Item item=obj sep=true always=true /> } element=move |obj| view! { <Item item=obj sep=true always=true /> }
/> />
}) })

View file

@ -32,7 +32,7 @@ impl Auth {
} }
pub fn outbox(&self) -> String { pub fn outbox(&self) -> String {
format!("{URL_BASE}/actors/{}/outbox", self.username()) format!("{}/outbox", self.user_id())
} }
pub async fn refresh( pub async fn refresh(

View file

@ -89,19 +89,20 @@ impl Privacy {
} }
} }
pub fn address(&self, user: &str) -> (Vec<String>, Vec<String>) { // TODO this is weird... should probably come from core or apb
pub fn address(&self, user_id: &str) -> (Vec<String>, Vec<String>) {
match self { match self {
Self::Broadcast => ( Self::Broadcast => (
vec![apb::target::PUBLIC.to_string()], vec![apb::target::PUBLIC.to_string()],
vec![format!("{URL_BASE}/actors/{user}/followers")], vec![format!("{user_id}/followers")],
), ),
Self::Public => ( Self::Public => (
vec![], vec![],
vec![apb::target::PUBLIC.to_string(), format!("{URL_BASE}/actors/{user}/followers")], vec![apb::target::PUBLIC.to_string(), format!("{user_id}/followers")],
), ),
Self::Private => ( Self::Private => (
vec![], vec![],
vec![format!("{URL_BASE}/actors/{user}/followers")], vec![format!("{user_id}/followers")],
), ),
Self::Direct => ( Self::Direct => (
vec![], vec![],
@ -133,7 +134,7 @@ pub fn PrivacySelector(setter: WriteSignal<Privacy>) -> impl IntoView {
<td> <td>
{move || { {move || {
let p = privacy.get(); let p = privacy.get();
let (to, cc) = p.address(&auth.username()); let (to, cc) = p.address(&auth.user_id());
view! { view! {
<PrivacyMarker privacy=p to=to cc=cc big=true /> <PrivacyMarker privacy=p to=to cc=cc big=true />
} }
@ -166,7 +167,7 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
if let Some((name, domain)) = stripped.split_once('@') { if let Some((name, domain)) = stripped.split_once('@') {
if let Some(tld) = domain.split('.').last() { if let Some(tld) = domain.split('.').last() {
if tld::exist(tld) { if tld::exist(tld) {
if let Some(uid) = cache::WEBFINGER.blocking_resolve(name, domain).await { if let Some(uid) = cache::WEBFINGER.blocking_resolve(name, domain, auth).await {
out.push(TextMatch::Mention { name: name.to_string(), domain: domain.to_string(), href: uid }); out.push(TextMatch::Mention { name: name.to_string(), domain: domain.to_string(), href: uid });
} }
} }
@ -236,7 +237,7 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
set_posting.set(true); set_posting.set(true);
leptos::task::spawn_local(async move { leptos::task::spawn_local(async move {
let summary = get_if_some(summary_ref); let summary = get_if_some(summary_ref);
let (mut to_vec, cc_vec) = privacy.get().address(&auth.username()); let (mut to_vec, cc_vec) = privacy.get().address(&auth.user_id());
let mut mention_tags : Vec<serde_json::Value> = mentions.get() let mut mention_tags : Vec<serde_json::Value> = mentions.get()
.map(|x| x.take()) .map(|x| x.take())
.unwrap_or_default() .unwrap_or_default()
@ -380,7 +381,7 @@ pub fn AdvancedPostBox(advanced: WriteSignal<bool>) -> impl IntoView {
<td class="w-66"><input class="w-100" type="text" node_ref=bto_ref title="bto" placeholder="bto" /></td> <td class="w-66"><input class="w-100" type="text" node_ref=bto_ref title="bto" placeholder="bto" /></td>
</tr> </tr>
<tr> <tr>
<td class="w-33"><input class="w-100" type="text" node_ref=cc_ref title="cc" placeholder="cc" value=format!("{URL_BASE}/actors/{}/followers", auth.username()) /></td> <td class="w-33"><input class="w-100" type="text" node_ref=cc_ref title="cc" placeholder="cc" value=format!("{}/followers", auth.user_id()) /></td>
<td class="w-33"><input class="w-100" type="text" node_ref=bcc_ref title="bcc" placeholder="bcc" /></td> <td class="w-33"><input class="w-100" type="text" node_ref=bcc_ref title="bcc" placeholder="bcc" /></td>
</tr> </tr>
</table> </table>
@ -424,7 +425,7 @@ pub fn AdvancedPostBox(advanced: WriteSignal<bool>) -> impl IntoView {
apb::Node::maybe_link(object_id) apb::Node::maybe_link(object_id)
} }
); );
let target_url = format!("{URL_BASE}/actors/{}/outbox", auth.username()); let target_url = auth.outbox();
match Http::post(&target_url, &payload, auth).await { match Http::post(&target_url, &payload, auth).await {
Err(e) => set_error.set(Some(e.to_string())), Err(e) => set_error.set(Some(e.to_string())),
Ok(()) => set_error.set(None), Ok(()) => set_error.set(None),

View file

@ -31,7 +31,7 @@ pub fn ActorBanner(object: crate::Doc) -> impl IntoView {
let uri = Uri::web(U::Actor, &uid); let uri = Uri::web(U::Actor, &uid);
let avatar_url = object.icon_url().unwrap_or(FALLBACK_IMAGE_URL.into()); let avatar_url = object.icon_url().unwrap_or(FALLBACK_IMAGE_URL.into());
let username = object.preferred_username().unwrap_or_default().to_string(); let username = object.preferred_username().unwrap_or_default().to_string();
let domain = object.id().unwrap_or_default().replace("https://", "").split('/').next().unwrap_or_default().to_string(); let domain = object.id().unwrap_or_default().replace("https://", "").replace("http://", "").split('/').next().unwrap_or_default().to_string();
let display_name = object.name().unwrap_or_default().to_string(); let display_name = object.name().unwrap_or_default().to_string();
view! { view! {
<div> <div>

View file

@ -179,16 +179,16 @@ impl DashmapCache<Doc> {
} }
impl DashmapCache<String> { impl DashmapCache<String> {
pub async fn blocking_resolve(&self, user: &str, domain: &str) -> Option<String> { pub async fn blocking_resolve(&self, user: &str, domain: &str, auth: Auth) -> Option<String> {
if let Some(x) = self.resource(user, domain) { return Some(x); } if let Some(x) = self.resource(user, domain) { return Some(x); }
self.fetch(user, domain).await; self.fetch(user, domain, auth).await;
self.resource(user, domain) self.resource(user, domain)
} }
pub fn resolve(&self, user: &str, domain: &str) -> Option<String> { pub fn resolve(&self, user: &str, domain: &str, auth: Auth) -> Option<String> {
if let Some(x) = self.resource(user, domain) { return Some(x); } if let Some(x) = self.resource(user, domain) { return Some(x); }
let (_self, user, domain) = (self.clone(), user.to_string(), domain.to_string()); let (_self, user, domain) = (self.clone(), user.to_string(), domain.to_string());
leptos::task::spawn_local(async move { _self.fetch(&user, &domain).await }); leptos::task::spawn_local(async move { _self.fetch(&user, &domain, auth).await });
None None
} }
@ -197,12 +197,10 @@ impl DashmapCache<String> {
self.get(&query) self.get(&query)
} }
async fn fetch(&self, user: &str, domain: &str) { async fn fetch(&self, user: &str, domain: &str, auth: Auth) {
let query = format!("{user}@{domain}"); let query = format!("{user}@{domain}");
self.0.insert(query.to_string(), LookupStatus::Resolving); self.0.insert(query.to_string(), LookupStatus::Resolving);
match reqwest::get(format!("{URL_BASE}/.well-known/webfinger?resource=acct:{query}")).await { match crate::Http::fetch::<jrd::JsonResourceDescriptor>(&format!("{URL_BASE}/.well-known/webfinger?resource=acct:{query}"), auth).await {
Ok(res) => match res.error_for_status() {
Ok(res) => match res.json::<jrd::JsonResourceDescriptor>().await {
Ok(doc) => { Ok(doc) => {
if let Some(uid) = doc.links.into_iter().find(|x| x.rel == "self").and_then(|x| x.href) { if let Some(uid) = doc.links.into_iter().find(|x| x.rel == "self").and_then(|x| x.href) {
self.0.insert(query, LookupStatus::Found(uid)); self.0.insert(query, LookupStatus::Found(uid));
@ -210,20 +208,10 @@ impl DashmapCache<String> {
self.0.insert(query, LookupStatus::NotFound); self.0.insert(query, LookupStatus::NotFound);
} }
}, },
Err(e) => {
tracing::error!("invalid webfinger response: {e:?}");
self.0.remove(&query);
},
},
Err(e) => { Err(e) => {
tracing::error!("could not resolve webfinbger: {e:?}"); tracing::error!("could not resolve webfinbger: {e:?}");
self.0.insert(query, LookupStatus::NotFound); self.0.insert(query, LookupStatus::NotFound);
}, },
},
Err(e) => {
tracing::error!("failed accessing webfinger server: {e:?}");
self.0.remove(&query);
},
} }
} }
} }
@ -237,12 +225,25 @@ pub struct IdParam {
pub struct Http; pub struct Http;
impl Http { impl Http {
// TODO not really great.... also checked only once
pub fn location() -> &'static str {
static LOCATION: std::sync::OnceLock<String> = std::sync::OnceLock::new();
LOCATION.get_or_init(||
web_sys::window()
.expect("could not access window element")
.location()
.origin()
.expect("could not access location origin")
).as_str()
}
pub async fn request<T: serde::ser::Serialize>( pub async fn request<T: serde::ser::Serialize>(
method: reqwest::Method, method: reqwest::Method,
url: &str, url: &str,
data: Option<&T>, data: Option<&T>,
auth: Auth, auth: Auth,
) -> reqwest::Result<reqwest::Response> { ) -> reqwest::Result<reqwest::Response> {
tracing::info!("making request to {url}");
use leptos::prelude::GetUntracked; use leptos::prelude::GetUntracked;
// TODO while in web environments it's ok (and i'd say good!) to fetch with relative urls, // TODO while in web environments it's ok (and i'd say good!) to fetch with relative urls,
@ -252,15 +253,7 @@ impl Http {
// prod deployments). relevant issue: https://github.com/seanmonstar/reqwest/issues/1433 // prod deployments). relevant issue: https://github.com/seanmonstar/reqwest/issues/1433
let mut url = url.to_string(); let mut url = url.to_string();
if !url.starts_with("http") { if !url.starts_with("http") {
static LOCATION: std::sync::OnceLock<String> = std::sync::OnceLock::new(); url = format!("{}{url}", Self::location());
let base = LOCATION.get_or_init(||
web_sys::window()
.expect("could not access window element")
.location()
.origin()
.expect("could not access location origin")
);
url = format!("{base}{url}");
} }
let mut req = reqwest::Client::new() let mut req = reqwest::Client::new()
@ -311,7 +304,7 @@ impl Uri {
} }
pub fn short(url: &str) -> String { pub fn short(url: &str) -> String {
if url.starts_with(URL_BASE) || url.starts_with('/') { if url.starts_with(Http::location()) || url.starts_with('/') {
uriproxy::decompose(url) uriproxy::decompose(url)
} else if url.starts_with("https://") || url.starts_with("http://") { } else if url.starts_with("https://") || url.starts_with("http://") {
uriproxy::compact(url) uriproxy::compact(url)

View file

@ -267,7 +267,7 @@ pub fn LikeButton(
let (mut to, cc) = if private { let (mut to, cc) = if private {
(vec![], vec![]) (vec![], vec![])
} else { } else {
privacy.get().address(&auth.username()) privacy.get().address(&auth.user_id())
}; };
to.push(author.clone()); to.push(author.clone());
let payload = serde_json::Value::Object(serde_json::Map::default()) let payload = serde_json::Value::Object(serde_json::Map::default())
@ -343,7 +343,7 @@ pub fn RepostButton(n: i32, target: String, author: String) -> impl IntoView {
if !auth.present() { return; } if !auth.present() { return; }
if !clicked.get() { return; } if !clicked.get() { return; }
set_clicked.set(false); set_clicked.set(false);
let (mut to, cc) = privacy.get().address(&auth.username()); let (mut to, cc) = privacy.get().address(&auth.user_id());
to.push(author.clone()); to.push(author.clone());
let payload = serde_json::Value::Object(serde_json::Map::default()) let payload = serde_json::Value::Object(serde_json::Map::default())
.set_activity_type(Some(apb::ActivityType::Announce)) .set_activity_type(Some(apb::ActivityType::Announce))

View file

@ -187,7 +187,7 @@ pub fn ConfigPage(setter: WriteSignal<crate::Config>) -> impl IntoView {
)); ));
leptos::task::spawn_local(async move { leptos::task::spawn_local(async move {
if let Err(e) = Http::post(&format!("{id}/outbox"), &payload, auth).await { if let Err(e) = Http::post(&auth.outbox(), &payload, auth).await {
tracing::error!("could not send update activity: {e}"); tracing::error!("could not send update activity: {e}");
} }
}); });