Repository: atomicdata-dev/atomic-server (language: rust)

Issue:
Bookmark endpoint does not handle meta images with relative url
When extracting the content of an og:image meta tag. The enpoint just takes the value of content and sets it on the resource. However there are cases where these urls are relative causing them not to work when displayed in the data-browser.


Discussion / hints:

Fix patch:
diff --git a/lib/src/plugins/bookmark.rs b/lib/src/plugins/bookmark.rs
index e804c2c07..ffe6861cf 100644
--- a/lib/src/plugins/bookmark.rs
+++ b/lib/src/plugins/bookmark.rs
@@ -135,39 +135,37 @@ impl Parser {
 
     pub fn get_meta(&self) -> SiteMeta {
         let document = parse_html().one(self.internal_html.clone());
-        let mut title = None;
-        let mut description = None;
-        let mut image = None;
 
-        if let Ok(title_element) = document.select_first("title") {
-            title = Some(title_element.text_contents());
-        }
+        let title = document
+            .select_first("title")
+            .ok()
+            .map(|element| element.text_contents());
 
-        if let Ok(description_element) =
-            document.select_first("meta[name='description'], meta[property='og:description']")
-        {
-            description = Some(
-                description_element
+        let description = document
+            .select_first("meta[name='description'], meta[property='og:description']")
+            .ok()
+            .map(|element| {
+                element
                     .attributes
                     .borrow()
                     .get("content")
                     .unwrap_or("")
-                    .to_string(),
-            );
-        }
-
-        if let Ok(image_element) =
-            document.select_first("meta[property='og:image'], meta[name='twitter:image']")
-        {
-            image = Some(
-                image_element
+                    .to_string()
+            });
+
+        let image = document
+            .select_first("meta[property='og:image'], meta[name='twitter:image']")
+            .ok()
+            .map(|element| {
+                let image_url = element
                     .attributes
                     .borrow()
                     .get("content")
                     .unwrap_or("")
-                    .to_string(),
-            );
-        }
+                    .to_string();
+
+                self.relative_to_absolute_url(&image_url)
+            });
 
         SiteMeta {
             title,
@@ -184,6 +182,34 @@ impl Parser {
         Ok(self.internal_html.clone())
     }
 
+    fn relative_to_absolute_url(&self, url: &str) -> String {
+        if url.starts_with('/') {
+            // If it starts with //, it's protocol-relative
+            if url.starts_with("//") {
+                return format!("https:{}", url);
+            }
+            // Get the base URL (scheme + authority)
+            let base = format!(
+                "{}://{}",
+                self.url.scheme(),
+                self.url.host_str().unwrap_or("")
+            );
+
+            return format!("{}{}", base, url);
+        }
+
+        if !url.contains("://") {
+            // Handle relative URLs without leading slash
+            return self
+                .url
+                .join(url)
+                .map(|u| u.to_string())
+                .unwrap_or(url.to_string());
+        }
+
+        url.to_string()
+    }
+
     fn resolve_url(&self, url: &str) -> String {
         if Url::parse(url).is_err() {
             return self.url.join(url).unwrap().as_str().to_string();


Test patch:
diff --git a/browser/e2e/playwright.config.ts b/browser/e2e/playwright.config.ts
index c398f74fd..50872b294 100644
--- a/browser/e2e/playwright.config.ts
+++ b/browser/e2e/playwright.config.ts
@@ -9,6 +9,15 @@ const config: PlaywrightTestConfig = {
     timezoneId: 'Europe/Amsterdam',
     actionTimeout: 5000,
     trace: 'retain-on-failure',
+    storageState: {
+      cookies: [],
+      origins: [
+        {
+          origin: 'http://localhost:5173',
+          localStorage: [{ name: 'viewTransitionsDisabled', value: 'true' }],
+        },
+      ],
+    },
   },
   reporter: [
     [
diff --git a/browser/e2e/tests/test-utils.ts b/browser/e2e/tests/test-utils.ts
index 86ba75ac1..79451b7c1 100644
--- a/browser/e2e/tests/test-utils.ts
+++ b/browser/e2e/tests/test-utils.ts
@@ -73,19 +73,8 @@ export async function setTitle(page: Page, title: string) {
   await waiter;
 }
 
-export async function disableViewTransition(page: Page) {
-  await page.getByRole('link', { name: 'Settings' }).click();
-  const checkbox = page.getByLabel('Disable page transition animations');
-
-  await expect(checkbox).toBeVisible();
-
-  await checkbox.check();
-  await page.goBack();
-}
-
 /** Signs in using an AtomicData.dev test user */
 export async function signIn(page: Page) {
-  await disableViewTransition(page);
   await page.click('text=Login');
   await expect(page.locator('text=edit data and sign Commits')).toBeVisible();
   // If there are any issues with this agent, try creating a new one https://atomicdata.dev/invites/1