diff --git a/apps/web/app/api/og/route.ts b/apps/web/app/api/og/route.ts
index 5ca6e44c..97f024a5 100644
--- a/apps/web/app/api/og/route.ts
+++ b/apps/web/app/api/og/route.ts
@@ -1,6 +1,4 @@
-import ogs from "open-graph-scraper"
-
-export const runtime = "nodejs"
+export const runtime = "edge"
interface OGResponse {
title: string
@@ -20,7 +18,6 @@ function isValidUrl(urlString: string): boolean {
function isPrivateHost(hostname: string): boolean {
const lowerHost = hostname.toLowerCase()
- // Block localhost variants
if (
lowerHost === "localhost" ||
lowerHost === "127.0.0.1" ||
@@ -31,7 +28,6 @@ function isPrivateHost(hostname: string): boolean {
return true
}
- // Block RFC 1918 private IP ranges
const privateIpPatterns = [
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[01])\./,
@@ -41,25 +37,20 @@ function isPrivateHost(hostname: string): boolean {
return privateIpPatterns.some((pattern) => pattern.test(hostname))
}
-function extractImageUrl(image: unknown): string | undefined {
- if (!image) return undefined
-
- if (typeof image === "string") {
- return image
- }
-
- if (Array.isArray(image) && image.length > 0) {
- const first = image[0]
- if (first && typeof first === "object" && "url" in first) {
- return String(first.url)
+function extractMetaTag(html: string, patterns: RegExp[]): string {
+ for (const pattern of patterns) {
+ const match = html.match(pattern)
+ if (match?.[1]) {
+ return match[1]
+ .replace(/&/g, "&")
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+ .replace(/"/g, '"')
+ .replace(/'/g, "'")
+ .trim()
}
}
-
- if (typeof image === "object" && image !== null && "url" in image) {
- return String(image.url)
- }
-
- return undefined
+ return ""
}
function resolveImageUrl(
@@ -110,46 +101,68 @@ export async function GET(request: Request) {
)
}
- const { result, error } = await ogs({
- url: trimmedUrl,
- timeout: 8000,
- fetchOptions: {
- headers: {
- "User-Agent":
- "Mozilla/5.0 (compatible; SuperMemory/1.0; +https://supermemory.ai)",
- },
+ const controller = new AbortController()
+ const timeoutId = setTimeout(() => controller.abort(), 8000)
+
+ const response = await fetch(trimmedUrl, {
+ signal: controller.signal,
+ headers: {
+ "User-Agent":
+ "Mozilla/5.0 (compatible; SuperMemory/1.0; +https://supermemory.ai)",
},
})
- if (error || !result) {
- console.error("OG scraping error:", error)
+ clearTimeout(timeoutId)
+
+ if (!response.ok) {
return Response.json(
- { error: "Failed to fetch Open Graph data" },
- { status: 500 },
+ { error: "Failed to fetch URL" },
+ { status: response.status },
)
}
- const ogTitle = result.ogTitle || result.twitterTitle || ""
- const ogDescription =
- result.ogDescription || result.twitterDescription || ""
+ const html = await response.text()
- const ogImageUrl =
- extractImageUrl(result.ogImage) || extractImageUrl(result.twitterImage)
+ const titlePatterns = [
+ /([^<]+)<\/title>/i,
+ ]
- const resolvedImageUrl = resolveImageUrl(ogImageUrl, trimmedUrl)
+ const descriptionPatterns = [
+ /
{label}
diff --git a/apps/web/components/new/document-icon.tsx b/apps/web/components/new/document-icon.tsx index a2a502e1..4861e978 100644 --- a/apps/web/components/new/document-icon.tsx +++ b/apps/web/components/new/document-icon.tsx @@ -53,13 +53,7 @@ function getFaviconUrl(url: string): string { } } -function FaviconIcon({ - url, - className, -}: { - url: string - className?: string -}) { +function FaviconIcon({ url, className }: { url: string; className?: string }) { const [hasError, setHasError] = useState(false) const faviconUrl = getFaviconUrl(url) diff --git a/apps/web/components/new/document-modal/content/google-doc.tsx b/apps/web/components/new/document-modal/content/google-doc.tsx index 562bac12..78dc4359 100644 --- a/apps/web/components/new/document-modal/content/google-doc.tsx +++ b/apps/web/components/new/document-modal/content/google-doc.tsx @@ -2,10 +2,7 @@ import { useState } from "react" import { Loader2 } from "lucide-react" -import { - extractGoogleDocId, - getGoogleEmbedUrl, -} from "@/lib/url-helpers" +import { extractGoogleDocId, getGoogleEmbedUrl } from "@/lib/url-helpers" interface GoogleDocViewerProps { url: string | null | undefined diff --git a/apps/web/components/new/document-modal/content/index.tsx b/apps/web/components/new/document-modal/content/index.tsx index c06bc550..39c5a2f0 100644 --- a/apps/web/components/new/document-modal/content/index.tsx +++ b/apps/web/components/new/document-modal/content/index.tsx @@ -56,10 +56,7 @@ function getContentType(document: DocumentWithMemories | null): ContentType { document.metadata?.mimeType?.toString().startsWith("image/") if (isImage && document.url) return "image" - if ( - document.type === "tweet" || - (document.url && isTwitterUrl(document.url)) - ) + if (document.type === "tweet" || (document.url && isTwitterUrl(document.url))) return "tweet" if (document.type === "text") return "text" if (document.type === "pdf") return "pdf" @@ -83,9 +80,7 @@ export function DocumentContent({ switch (contentType) { case "image": - return ( -