Download ui-tui/src/lib/externalLink.ts from SaylorTwift/hermes-agent: direct link, hf CLI and curl.
- Browser
- Download file 10.6 kB
-
https://huggingface.co/SaylorTwift/hermes-agent/resolve/main/ui-tui/src/lib/externalLink.ts
- Command line
-
hf download hf://SaylorTwift/hermes-agent/ui-tui/src/lib/externalLink.ts
-
curl -L -o externalLink.ts https://huggingface.co/SaylorTwift/hermes-agent/resolve/main/ui-tui/src/lib/externalLink.ts
10.6 kB
| import { isIP } from 'node:net' | |
| import { useEffect, useMemo, useState } from 'react' | |
| const titleCache = new Map<string, string>() | |
| const titleInflight = new Map<string, Promise<string>>() | |
| const titleSubs = new Map<string, Set<(value: string) => void>>() | |
| const TITLE_CACHE_LIMIT = 500 | |
| const TITLE_MAX_LENGTH = 240 | |
| const TITLE_BYTE_BUDGET = 96 * 1024 | |
| const TITLE_TIMEOUT_MS = 5000 | |
| const TITLE_USER_AGENT = | |
| 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36' | |
| const TITLE_ERROR_RE = | |
| /\b(?:access denied|attention required|captcha|error|forbidden|just a moment|not found|request blocked|too many requests)\b/i | |
| const DOMAIN_RE = /^(?:www\.)?[a-z0-9](?:[a-z0-9-]*\.)+[a-z]{2,}(?::\d+)?(?:[/?#][^\s]*)?$/i | |
| const SKIP_PROTO_RE = /^(?:file|data|mailto|javascript|blob|chrome|about|hermes):/i | |
| const LOCAL_HOSTNAME_RE = /^(?:localhost|localhost\.localdomain)$/i | |
| const LOCAL_HOST_SUFFIXES = ['.corp', '.home', '.internal', '.lan', '.local', '.localdomain'] | |
| const STATUS_PERMALINK_HOST_RE = /^(?:mobile\.)?(?:x|twitter)\.com$/i | |
| const STATUS_PERMALINK_PATH_RE = /^\/[^/]+\/status\/\d+\/?$/i | |
| const HTML_ENTITIES: Record<string, string> = { | |
| '#39': "'", | |
| amp: '&', | |
| apos: "'", | |
| gt: '>', | |
| lt: '<', | |
| nbsp: ' ', | |
| quot: '"' | |
| } | |
| export function normalizeExternalUrl(value: string): string { | |
| const trimmed = value.trim() | |
| if (!trimmed || /^https?:\/\//i.test(trimmed)) { | |
| return trimmed | |
| } | |
| return DOMAIN_RE.test(trimmed) ? `https://${trimmed}` : trimmed | |
| } | |
| function parseUrl(value: string): null | URL { | |
| try { | |
| return new URL(normalizeExternalUrl(value)) | |
| } catch { | |
| return null | |
| } | |
| } | |
| function titleCacheKey(value: string): string { | |
| const url = parseUrl(value) | |
| if (!url) { | |
| return normalizeExternalUrl(value) | |
| } | |
| const host = url.hostname.replace(/^www\./i, '').toLowerCase() | |
| const pathname = url.pathname === '/' ? '/' : url.pathname.replace(/\/+$/, '') || '/' | |
| return `${host}${pathname}${url.search || ''}` | |
| } | |
| function cacheTitle(key: string, title: string): void { | |
| if (titleCache.size >= TITLE_CACHE_LIMIT) { | |
| titleCache.delete(titleCache.keys().next().value as string) | |
| } | |
| titleCache.set(key, title) | |
| } | |
| export function hostPathLabel(value: string): string { | |
| const url = parseUrl(value) | |
| if (!url) { | |
| return value | |
| } | |
| const host = url.hostname.replace(/^www\./, '') | |
| const path = url.pathname && url.pathname !== '/' ? url.pathname.replace(/\/$/, '') : '' | |
| return `${host}${path}` | |
| } | |
| function cleanSlug(segment: string): string { | |
| try { | |
| return decodeURIComponent(segment) | |
| .replace(/\.a\d+\..*$/i, '') | |
| .replace(/\.(?:html?|php|aspx?)$/i, '') | |
| .replace(/(?:[-_.](?:[a-z]{1,3}\d{2,}|i\d{2,}))+$/i, '') | |
| .replace(/[_-]+/g, ' ') | |
| .replace(/\s+/g, ' ') | |
| .trim() | |
| } catch { | |
| return '' | |
| } | |
| } | |
| export function urlSlugTitleLabel(value: string): string { | |
| const url = parseUrl(value) | |
| if (url && STATUS_PERMALINK_HOST_RE.test(url.hostname) && STATUS_PERMALINK_PATH_RE.test(url.pathname)) { | |
| return hostPathLabel(value) | |
| } | |
| for (const segment of url?.pathname.split('/').filter(Boolean).reverse() ?? []) { | |
| const cleaned = cleanSlug(segment) | |
| if (!cleaned || !/[a-z]/i.test(cleaned)) { | |
| continue | |
| } | |
| if (/^(?:[a-z]{1,3}\d+|\d+)$/i.test(cleaned.replace(/\s+/g, ''))) { | |
| continue | |
| } | |
| const titled = cleaned.replace(/\b[a-z]/g, c => c.toUpperCase()) | |
| if (titled.length >= 4) { | |
| return titled | |
| } | |
| } | |
| return hostPathLabel(value) | |
| } | |
| function parseIpv4Octets(value: string): null | [number, number, number, number] { | |
| const parts = value.split('.') | |
| if (parts.length !== 4) { | |
| return null | |
| } | |
| const octets: number[] = [] | |
| for (const part of parts) { | |
| if (!/^\d{1,3}$/.test(part)) { | |
| return null | |
| } | |
| const next = Number(part) | |
| if (!Number.isInteger(next) || next < 0 || next > 255) { | |
| return null | |
| } | |
| octets.push(next) | |
| } | |
| return [octets[0]!, octets[1]!, octets[2]!, octets[3]!] | |
| } | |
| function isPrivateIpv4(value: string): boolean { | |
| const octets = parseIpv4Octets(value) | |
| if (!octets) { | |
| return false | |
| } | |
| const [a, b] = octets | |
| return ( | |
| a === 0 || | |
| a === 10 || | |
| a === 127 || | |
| a === 255 || | |
| (a === 100 && b >= 64 && b <= 127) || | |
| (a === 169 && b === 254) || | |
| (a === 172 && b >= 16 && b <= 31) || | |
| (a === 192 && b === 168) || | |
| (a === 198 && (b === 18 || b === 19)) | |
| ) | |
| } | |
| function isPrivateIpv6(value: string): boolean { | |
| const normalized = value.toLowerCase() | |
| if (normalized === '::' || normalized === '::1') { | |
| return true | |
| } | |
| if (normalized.startsWith('fc') || normalized.startsWith('fd')) { | |
| return true | |
| } | |
| if ( | |
| normalized.startsWith('fe8') || | |
| normalized.startsWith('fe9') || | |
| normalized.startsWith('fea') || | |
| normalized.startsWith('feb') | |
| ) { | |
| return true | |
| } | |
| if (normalized.startsWith('::ffff:')) { | |
| return isPrivateIpv4(normalized.slice('::ffff:'.length)) | |
| } | |
| return false | |
| } | |
| function normalizeHostname(value: string): string { | |
| const withoutBrackets = value.replace(/^\[/, '').replace(/\]$/, '') | |
| const withoutZoneId = withoutBrackets.split('%', 1)[0]! | |
| return withoutZoneId.replace(/\.$/, '').toLowerCase() | |
| } | |
| function isPrivateOrLocalHost(hostname: string): boolean { | |
| const normalized = normalizeHostname(hostname) | |
| if (!normalized) { | |
| return true | |
| } | |
| if (LOCAL_HOSTNAME_RE.test(normalized)) { | |
| return true | |
| } | |
| if (LOCAL_HOST_SUFFIXES.some(suffix => normalized.endsWith(suffix))) { | |
| return true | |
| } | |
| const ipVersion = isIP(normalized) | |
| if (ipVersion === 4) { | |
| return isPrivateIpv4(normalized) | |
| } | |
| if (ipVersion === 6) { | |
| return isPrivateIpv6(normalized) | |
| } | |
| // Single-label hostnames are usually LAN names or enterprise intranet aliases. | |
| return !normalized.includes('.') | |
| } | |
| export function isTitleFetchable(value: string): boolean { | |
| if (!value || SKIP_PROTO_RE.test(value)) { | |
| return false | |
| } | |
| const url = parseUrl(value) | |
| return Boolean(url && /^https?:$/.test(url.protocol) && !isPrivateOrLocalHost(url.hostname)) | |
| } | |
| function decodeHtmlEntities(value: string): string { | |
| return value | |
| .replace(/&(amp|lt|gt|quot|apos|nbsp|#39);/gi, (_match, key: string) => HTML_ENTITIES[key.toLowerCase()] ?? '') | |
| .replace(/&#x([0-9a-f]+);/gi, (_match, hex: string) => String.fromCodePoint(parseInt(hex, 16) || 32)) | |
| .replace(/&#(\d+);/g, (_match, decimal: string) => String.fromCodePoint(parseInt(decimal, 10) || 32)) | |
| } | |
| function parseHtmlTitle(html: string): string { | |
| const raw = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1] | |
| return raw ? decodeHtmlEntities(raw).replace(/\s+/g, ' ').trim() : '' | |
| } | |
| async function readResponseSnippet(response: Response): Promise<string> { | |
| const reader = response.body?.getReader() | |
| if (!reader) { | |
| return (await response.text()).slice(0, TITLE_BYTE_BUDGET) | |
| } | |
| const chunks: Uint8Array[] = [] | |
| let done = false | |
| let bytes = 0 | |
| try { | |
| while (bytes < TITLE_BYTE_BUDGET) { | |
| const chunk = await reader.read() | |
| if (chunk.done) { | |
| done = true | |
| break | |
| } | |
| const value = chunk.value | |
| if (!value?.length) { | |
| continue | |
| } | |
| const remaining = TITLE_BYTE_BUDGET - bytes | |
| const next = value.length > remaining ? value.subarray(0, remaining) : value | |
| chunks.push(next) | |
| bytes += next.length | |
| if (next.length < value.length) { | |
| break | |
| } | |
| } | |
| } catch { | |
| return '' | |
| } finally { | |
| if (!done) { | |
| try { | |
| await reader.cancel() | |
| } catch { | |
| // Ignore stream teardown failures. | |
| } | |
| } | |
| } | |
| if (!chunks.length) { | |
| return '' | |
| } | |
| const joined = new Uint8Array(bytes) | |
| let offset = 0 | |
| for (const chunk of chunks) { | |
| joined.set(chunk, offset) | |
| offset += chunk.length | |
| } | |
| return new TextDecoder().decode(joined) | |
| } | |
| function usableTitle(value: string): string { | |
| const clean = value.replace(/\s+/g, ' ').trim() | |
| return clean && !TITLE_ERROR_RE.test(clean) ? clean : '' | |
| } | |
| async function fetchHtmlTitle(normalizedUrl: string): Promise<string> { | |
| const controller = new AbortController() | |
| const timeout = setTimeout(() => controller.abort(), TITLE_TIMEOUT_MS) | |
| try { | |
| const response = await fetch(normalizedUrl, { | |
| headers: { | |
| Accept: 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.5', | |
| 'Accept-Language': 'en-US,en;q=0.7', | |
| 'User-Agent': TITLE_USER_AGENT | |
| }, | |
| redirect: 'follow', | |
| signal: controller.signal | |
| }) | |
| if (!response.ok) { | |
| return '' | |
| } | |
| const contentType = response.headers.get('content-type') | |
| if (contentType && !/(?:html|xml|text\/html)/i.test(contentType)) { | |
| return '' | |
| } | |
| const html = await readResponseSnippet(response) | |
| return parseHtmlTitle(html).slice(0, TITLE_MAX_LENGTH) | |
| } catch { | |
| return '' | |
| } finally { | |
| clearTimeout(timeout) | |
| } | |
| } | |
| export function fetchLinkTitle(url: string): Promise<string> { | |
| const normalizedUrl = normalizeExternalUrl(url) | |
| const key = titleCacheKey(normalizedUrl) | |
| if (!isTitleFetchable(normalizedUrl)) { | |
| return Promise.resolve('') | |
| } | |
| if (titleCache.has(key)) { | |
| return Promise.resolve(titleCache.get(key) ?? '') | |
| } | |
| const pending = titleInflight.get(key) | |
| if (pending) { | |
| return pending | |
| } | |
| const promise = fetchHtmlTitle(normalizedUrl) | |
| .then(usableTitle) | |
| .catch(() => '') | |
| .then(clean => { | |
| cacheTitle(key, clean) | |
| titleSubs.get(key)?.forEach(sub => sub(clean)) | |
| return clean | |
| }) | |
| .finally(() => { | |
| titleInflight.delete(key) | |
| }) | |
| titleInflight.set(key, promise) | |
| return promise | |
| } | |
| export function useLinkTitle(url?: null | string): string { | |
| const normalizedUrl = useMemo(() => (url ? normalizeExternalUrl(url) : ''), [url]) | |
| const key = useMemo(() => (normalizedUrl ? titleCacheKey(normalizedUrl) : ''), [normalizedUrl]) | |
| const [title, setTitle] = useState(() => (key ? (titleCache.get(key) ?? '') : '')) | |
| useEffect(() => { | |
| setTitle(key ? (titleCache.get(key) ?? '') : '') | |
| if (!key || !isTitleFetchable(normalizedUrl)) { | |
| return | |
| } | |
| const subs = titleSubs.get(key) ?? new Set<(value: string) => void>() | |
| subs.add(setTitle) | |
| titleSubs.set(key, subs) | |
| void fetchLinkTitle(normalizedUrl) | |
| return () => { | |
| subs.delete(setTitle) | |
| if (!subs.size) { | |
| titleSubs.delete(key) | |
| } | |
| } | |
| }, [key, normalizedUrl]) | |
| return title | |
| } | |
| export function __resetLinkTitleCache(): void { | |
| titleCache.clear() | |
| titleInflight.clear() | |
| titleSubs.clear() | |
| } | |