fix(web): render YouTube Shorts and live videos

This commit is contained in:
shamAnimates 2026-08-16 18:07:03 +05:30
parent e651045ac5
commit 641b280617
6 changed files with 153 additions and 82 deletions

View file

@ -29,5 +29,9 @@ jobs:
- name: Run TypeScript type checking
run: bunx turbo run check-types --filter='@supermemory/ai-sdk' --filter='@supermemory/memory-graph'
- name: Run web unit tests
working-directory: apps/web
run: bun test
- name: Run Biome CI (format & lint on changed files)
run: bunx biome ci --changed --since=origin/main --no-errors-on-unmatched

View file

@ -1,54 +1,12 @@
"use client"
import { useState, useEffect } from "react"
import { extractYouTubeVideoId } from "@/lib/url-helpers"
interface YoutubeVideoProps {
url: string | null | undefined
}
// Extract YouTube video ID from various URL formats
function extractVideoId(url: string): string | null {
if (!url) return null
const patterns = [
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
/youtube\.com\/watch\?.*v=([^&\n?#]+)/,
]
for (const pattern of patterns) {
const match = url.match(pattern)
if (match?.[1]) {
return match[1]
}
}
return null
}
export function YoutubeVideo({ url }: YoutubeVideoProps) {
const [videoId, setVideoId] = useState<string | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (!url) {
setError("No YouTube URL provided")
setLoading(false)
return
}
const id = extractVideoId(url)
if (!id) {
setError("Invalid YouTube URL format")
setLoading(false)
return
}
setVideoId(id)
setLoading(false)
setError(null)
}, [url])
if (!url) {
return (
<div className="flex items-center justify-center h-full text-gray-400">
@ -57,18 +15,11 @@ export function YoutubeVideo({ url }: YoutubeVideoProps) {
)
}
if (loading) {
return (
<div className="flex items-center justify-center h-full text-gray-400">
Loading video
</div>
)
}
if (error || !videoId) {
const videoId = extractYouTubeVideoId(url)
if (!videoId) {
return (
<div className="flex items-center justify-center h-full text-red-400">
Error: {error || "Failed to extract video ID"}
Error: Invalid YouTube URL format
</div>
)
}

View file

@ -1,35 +1,9 @@
"use client"
import { useQuery } from "@tanstack/react-query"
import { isYouTubeUrl } from "@/lib/url-helpers"
import { extractYouTubeVideoId, isYouTubeUrl } from "@/lib/url-helpers"
export { isYouTubeUrl }
export function extractYouTubeVideoId(
url: string | undefined | null,
): string | null {
if (!url) return null
// Handle youtu.be format
const youtuBeMatch = url.match(/(?:youtu\.be\/)([a-zA-Z0-9_-]{11})/)
if (youtuBeMatch?.[1]) return youtuBeMatch[1]
// Handle youtube.com/watch?v= format
const watchMatch = url.match(/(?:youtube\.com\/watch\?v=)([a-zA-Z0-9_-]{11})/)
if (watchMatch?.[1]) return watchMatch[1]
// Handle youtube.com/embed/ format
const embedMatch = url.match(/(?:youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/)
if (embedMatch?.[1]) return embedMatch[1]
// Handle m.youtube.com format
const mobileMatch = url.match(
/(?:m\.youtube\.com\/watch\?v=)([a-zA-Z0-9_-]{11})/,
)
if (mobileMatch?.[1]) return mobileMatch[1]
return null
}
export { extractYouTubeVideoId, isYouTubeUrl }
export function useYouTubeChannelName(url: string | undefined | null) {
const videoId = extractYouTubeVideoId(url)

View file

@ -0,0 +1,40 @@
import { beforeAll, describe, expect, it, mock } from "bun:test"
import { createElement, type ComponentProps } from "react"
import { renderToStaticMarkup } from "react-dom/server"
const VIDEO_ID = "dQw4w9WgXcQ"
const SHORTS_URL = `https://www.youtube.com/shorts/${VIDEO_ID}`
mock.module("@/lib/fonts", () => ({ dmSansClassName: () => "" }))
let YoutubePreview: typeof import("./document-cards/youtube-preview").YoutubePreview
let YoutubeVideo: typeof import("./document-modal/content/yt-video").YoutubeVideo
beforeAll(async () => {
;({ YoutubePreview } = await import("./document-cards/youtube-preview"))
;({ YoutubeVideo } = await import("./document-modal/content/yt-video"))
})
describe("YouTube Shorts rendering", () => {
it("renders a Shorts embed in the memory card", () => {
const document = {
url: SHORTS_URL,
title: "A short",
content: null,
} as ComponentProps<typeof YoutubePreview>["document"]
const html = renderToStaticMarkup(
createElement(YoutubePreview, { document }),
)
expect(html).toContain(`src="https://www.youtube.com/embed/${VIDEO_ID}"`)
})
it("renders the same Shorts embed in the document modal", () => {
const html = renderToStaticMarkup(
createElement(YoutubeVideo, { url: SHORTS_URL }),
)
expect(html).toContain(`src="https://www.youtube.com/embed/${VIDEO_ID}"`)
})
})

View file

@ -1,5 +1,7 @@
import { describe, expect, it } from "bun:test"
import { isYouTubeUrl } from "./url-helpers"
import { extractYouTubeVideoId, isYouTubeUrl } from "./url-helpers"
const VIDEO_ID = "dQw4w9WgXcQ"
describe("isYouTubeUrl", () => {
it("matches canonical youtube.com watch URLs", () => {
@ -71,3 +73,68 @@ describe("isYouTubeUrl", () => {
expect(isYouTubeUrl(undefined)).toBe(false)
})
})
describe("extractYouTubeVideoId", () => {
it("extracts exact IDs from supported YouTube video URL formats", () => {
expect(
extractYouTubeVideoId(
`https://www.youtube.com/watch?feature=share&v=${VIDEO_ID}`,
),
).toBe(VIDEO_ID)
expect(extractYouTubeVideoId(`https://youtu.be/${VIDEO_ID}?si=abc`)).toBe(
VIDEO_ID,
)
expect(
extractYouTubeVideoId(
`https://www.youtube.com/embed/${VIDEO_ID}?start=10`,
),
).toBe(VIDEO_ID)
expect(
extractYouTubeVideoId(
`https://www.youtube.com/shorts/${VIDEO_ID}?feature=share`,
),
).toBe(VIDEO_ID)
expect(
extractYouTubeVideoId(`https://www.youtube.com/live/${VIDEO_ID}`),
).toBe(VIDEO_ID)
})
it("supports mobile, case-insensitive hosts, and scheme-less URLs", () => {
expect(
extractYouTubeVideoId(`https://m.youtube.com/watch?v=${VIDEO_ID}`),
).toBe(VIDEO_ID)
expect(
extractYouTubeVideoId(`HTTPS://WWW.YOUTUBE.COM/shorts/${VIDEO_ID}`),
).toBe(VIDEO_ID)
expect(extractYouTubeVideoId(`youtu.be/${VIDEO_ID}`)).toBe(VIDEO_ID)
})
it("rejects lookalike hosts and URLs that only mention YouTube in a path", () => {
expect(
extractYouTubeVideoId(
`https://youtube.com.evil.example/watch?v=${VIDEO_ID}`,
),
).toBeNull()
expect(
extractYouTubeVideoId(
`https://evil.example/youtube.com/shorts/${VIDEO_ID}`,
),
).toBeNull()
expect(
extractYouTubeVideoId(`https://youtube.com@evil.example/${VIDEO_ID}`),
).toBeNull()
})
it("rejects malformed IDs and unsupported YouTube routes", () => {
expect(
extractYouTubeVideoId("https://www.youtube.com/shorts/too-short"),
).toBeNull()
expect(
extractYouTubeVideoId(`https://www.youtube.com/embed/${VIDEO_ID}x`),
).toBeNull()
expect(
extractYouTubeVideoId(`https://www.youtube.com/channel/${VIDEO_ID}`),
).toBeNull()
expect(extractYouTubeVideoId(null)).toBeNull()
})
})

View file

@ -169,6 +169,11 @@ const hostnameMatches = (hostname: string, domain: string): boolean => {
)
}
const YOUTUBE_VIDEO_ID_PATTERN = /^[a-zA-Z0-9_-]{11}$/
const validYouTubeVideoId = (candidate: string | null | undefined) =>
candidate && YOUTUBE_VIDEO_ID_PATTERN.test(candidate) ? candidate : null
/**
* Checks if a URL is a Twitter/X URL.
*/
@ -197,6 +202,36 @@ export const isYouTubeUrl = (url: string | undefined | null): boolean => {
)
}
/**
* Extracts an exact 11-character video ID from supported YouTube video URLs.
*/
export const extractYouTubeVideoId = (
url: string | undefined | null,
): string | null => {
if (!url) return null
const parsed = parseWebUrl(url)
if (!parsed) return null
const segments = parsed.pathname.split("/").filter(Boolean)
if (hostnameMatches(parsed.hostname, "youtu.be")) {
return segments.length === 1 ? validYouTubeVideoId(segments[0]) : null
}
if (!hostnameMatches(parsed.hostname, "youtube.com")) return null
const route = segments[0]
if (route === "watch" && segments.length === 1) {
return validYouTubeVideoId(parsed.searchParams.get("v"))
}
if (
(route === "embed" || route === "shorts" || route === "live") &&
segments.length === 2
) {
return validYouTubeVideoId(segments[1])
}
return null
}
/**
* Checks if a URL is a LinkedIn profile URL (not a company page).
*/