From eba399abe50fa1830b263dcfc93a786b99049f56 Mon Sep 17 00:00:00 2001
From: alemi <me@alemi.dev>
Date: Wed, 22 Jan 2025 02:07:16 +0100
Subject: [PATCH] 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
---
 web/src/app.rs             |  4 +--
 web/src/auth.rs            |  2 +-
 web/src/components/post.rs | 19 ++++++------
 web/src/components/user.rs |  2 +-
 web/src/lib.rs             | 63 +++++++++++++++++---------------------
 web/src/objects/item.rs    |  4 +--
 web/src/page/config.rs     |  2 +-
 7 files changed, 45 insertions(+), 51 deletions(-)

diff --git a/web/src/app.rs b/web/src/app.rs
index e523780..54cceff 100644
--- a/web/src/app.rs
+++ b/web/src/app.rs
@@ -130,7 +130,7 @@ pub fn App() -> impl IntoView {
 										<Route path=path!("home") view=move || if auth.present() {
 											Either::Left(view! {
 												<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 /> }
 												/>
 											}) 
@@ -155,7 +155,7 @@ pub fn App() -> impl IntoView {
 										<Route path=path!("notifications") view=move || if auth.present() {
 											Either::Left(view! {
 												<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 /> }
 												/>
 											})
diff --git a/web/src/auth.rs b/web/src/auth.rs
index 7e6cd67..ddee289 100644
--- a/web/src/auth.rs
+++ b/web/src/auth.rs
@@ -32,7 +32,7 @@ impl Auth {
 	}
 
 	pub fn outbox(&self) -> String {
-		format!("{URL_BASE}/actors/{}/outbox", self.username())
+		format!("{}/outbox", self.user_id())
 	}
 
 	pub async fn refresh(
diff --git a/web/src/components/post.rs b/web/src/components/post.rs
index 5fddf12..dd98ccc 100644
--- a/web/src/components/post.rs
+++ b/web/src/components/post.rs
@@ -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 {
 			Self::Broadcast => (
 				vec![apb::target::PUBLIC.to_string()],
-				vec![format!("{URL_BASE}/actors/{user}/followers")],
+				vec![format!("{user_id}/followers")],
 			),
 			Self::Public => (
 				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 => (
 				vec![],
-				vec![format!("{URL_BASE}/actors/{user}/followers")],
+				vec![format!("{user_id}/followers")],
 			),
 			Self::Direct => (
 				vec![],
@@ -133,7 +134,7 @@ pub fn PrivacySelector(setter: WriteSignal<Privacy>) -> impl IntoView {
 				<td>
 					{move || {
 						let p = privacy.get();
-						let (to, cc) = p.address(&auth.username());
+						let (to, cc) = p.address(&auth.user_id());
 						view! {
 							<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(tld) = domain.split('.').last() {
 							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 });
 								}
 							}
@@ -236,7 +237,7 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
 				set_posting.set(true);
 				leptos::task::spawn_local(async move {
 					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()
 						.map(|x| x.take())
 						.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>
 					</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>
 					</tr>
 				</table>
@@ -424,7 +425,7 @@ pub fn AdvancedPostBox(advanced: WriteSignal<bool>) -> impl IntoView {
 									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 {
 							Err(e) => set_error.set(Some(e.to_string())),
 							Ok(()) => set_error.set(None),
diff --git a/web/src/components/user.rs b/web/src/components/user.rs
index d8a7745..7228191 100644
--- a/web/src/components/user.rs
+++ b/web/src/components/user.rs
@@ -31,7 +31,7 @@ pub fn ActorBanner(object: crate::Doc) -> impl IntoView {
 			let uri = Uri::web(U::Actor, &uid);
 			let avatar_url = object.icon_url().unwrap_or(FALLBACK_IMAGE_URL.into());
 			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();
 			view! {
 				<div>
diff --git a/web/src/lib.rs b/web/src/lib.rs
index 9df932c..9a582b7 100644
--- a/web/src/lib.rs
+++ b/web/src/lib.rs
@@ -179,16 +179,16 @@ impl DashmapCache<Doc> {
 }
 
 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); }
-		self.fetch(user, domain).await;
+		self.fetch(user, domain, auth).await;
 		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); }
 		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
 	}
 
@@ -197,32 +197,20 @@ impl DashmapCache<String> {
 		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}");
 		self.0.insert(query.to_string(), LookupStatus::Resolving);
-		match reqwest::get(format!("{URL_BASE}/.well-known/webfinger?resource=acct:{query}")).await {
-			Ok(res) => match res.error_for_status() {
-				Ok(res) => match res.json::<jrd::JsonResourceDescriptor>().await {
-					Ok(doc) => {
-						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));
-						} else {
-							self.0.insert(query, LookupStatus::NotFound);
-						}
-					},
-					Err(e) => {
-						tracing::error!("invalid webfinger response: {e:?}");
-						self.0.remove(&query);
-					},
-				},
-				Err(e) => {
-					tracing::error!("could not resolve webfinbger: {e:?}");
+		match crate::Http::fetch::<jrd::JsonResourceDescriptor>(&format!("{URL_BASE}/.well-known/webfinger?resource=acct:{query}"), auth).await {
+			Ok(doc) => {
+				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));
+				} else {
 					self.0.insert(query, LookupStatus::NotFound);
-				},
+				}
 			},
 			Err(e) => {
-				tracing::error!("failed accessing webfinger server: {e:?}");
-				self.0.remove(&query);
+				tracing::error!("could not resolve webfinbger: {e:?}");
+				self.0.insert(query, LookupStatus::NotFound);
 			},
 		}
 	}
@@ -237,12 +225,25 @@ pub struct IdParam {
 pub struct 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>(
 		method: reqwest::Method,
 		url: &str,
 		data: Option<&T>,
 		auth: Auth,
 	) -> reqwest::Result<reqwest::Response> {
+		tracing::info!("making request to {url}");
 		use leptos::prelude::GetUntracked;
 
 		// 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
 		let mut url = url.to_string();
 		if !url.starts_with("http") {
-			static LOCATION: std::sync::OnceLock<String> = std::sync::OnceLock::new();
-			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}");
+			url = format!("{}{url}", Self::location());
 		}
 
 		let mut req = reqwest::Client::new()
@@ -311,7 +304,7 @@ impl Uri {
 	}
 
 	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)
 		} else if url.starts_with("https://") || url.starts_with("http://") {
 			uriproxy::compact(url)
diff --git a/web/src/objects/item.rs b/web/src/objects/item.rs
index 41e206d..d56f240 100644
--- a/web/src/objects/item.rs
+++ b/web/src/objects/item.rs
@@ -267,7 +267,7 @@ pub fn LikeButton(
 				let (mut to, cc) = if private {
 					(vec![], vec![])
 				} else {
-					privacy.get().address(&auth.username())
+					privacy.get().address(&auth.user_id())
 				};
 				to.push(author.clone());
 				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 !clicked.get() { return; }
 				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());
 				let payload = serde_json::Value::Object(serde_json::Map::default())
 					.set_activity_type(Some(apb::ActivityType::Announce))
diff --git a/web/src/page/config.rs b/web/src/page/config.rs
index e23568d..938d75e 100644
--- a/web/src/page/config.rs
+++ b/web/src/page/config.rs
@@ -187,7 +187,7 @@ pub fn ConfigPage(setter: WriteSignal<crate::Config>) -> impl IntoView {
 							));
 
 						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}");
 							}
 						});