History

feat(core): add favicon URL resolution utility

Add getRegistrableDomain() for extracting root domains from hostnames (with compound suffix support: .co.uk, .com.au, etc.) and getFaviconUrlForRemoteUrl() for converting remote URLs to Google Favicons API URLs. Used by GraphQL/OpenAPI/MCP plugins for iconUrl auto-generation.

Saatvik Arya committed 7 months ago

Commit 23e13771 parent d39b74d2 files · +87 −0Browse files

Unified · Split

2 files
  1. packages/sources/core/src/favicon.ts +86 −0
  2. packages/sources/core/src/index.ts +1 −0
packages/sources/core/src/favicon.ts+86−0
@@ -0,0 +1,86 @@
1+const COMMON_COMPOUND_SUFFIXES = new Set([
2+ "ac.uk",
3+ "co.in",
4+ "co.jp",
5+ "co.nz",
6+ "co.uk",
7+ "com.au",
8+ "com.br",
9+ "com.mx",
10+ "net.au",
11+ "org.au",
12+ "org.uk",
13+]);
14+
15+const RAW_HOSTS = new Set([
16+ "cdn.jsdelivr.net",
17+ "raw.github.com",
18+ "raw.githubusercontent.com",
19+ "unpkg.com",
20+]);
21+
22+function trimOrNull(value: string | null | undefined): string | null {
23+ if (value == null) {
24+ return null;
25+ }
26+
27+ const trimmed = value.trim();
28+ return trimmed.length > 0 ? trimmed : null;
29+}
30+
31+function isIpv4Address(value: string): boolean {
32+ return /^\d{1,3}(?:\.\d{1,3}){3}$/.test(value);
33+}
34+
35+export function getRegistrableDomain(hostname: string): string | null {
36+ const normalized = hostname.trim().toLowerCase().replace(/^\.+|\.+$/g, "");
37+ if (!normalized) {
38+ return null;
39+ }
40+
41+ if (normalized === "localhost" || isIpv4Address(normalized)) {
42+ return normalized;
43+ }
44+
45+ const parts = normalized.split(".").filter((part) => part.length > 0);
46+ if (parts.length < 2) {
47+ return null;
48+ }
49+
50+ const suffix = parts.slice(-2).join(".");
51+ if (parts.length >= 3 && COMMON_COMPOUND_SUFFIXES.has(suffix)) {
52+ return parts.slice(-3).join(".");
53+ }
54+
55+ return parts.slice(-2).join(".");
56+}
57+
58+export function getFaviconUrlForRemoteUrl(
59+ value: string | null | undefined,
60+ options: {
61+ allowRawHosts?: boolean;
62+ } = {},
63+): string | null {
64+ const trimmed = trimOrNull(value);
65+ if (!trimmed) {
66+ return null;
67+ }
68+
69+ try {
70+ const url = new URL(trimmed);
71+ if (url.protocol !== "http:" && url.protocol !== "https:") {
72+ return null;
73+ }
74+
75+ if (!options.allowRawHosts && RAW_HOSTS.has(url.hostname)) {
76+ return null;
77+ }
78+
79+ const domain = getRegistrableDomain(url.hostname);
80+ return domain
81+ ? `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=32`
82+ : null;
83+ } catch {
84+ return null;
85+ }
86+}
packages/sources/core/src/index.ts+1−0
@@ -3,6 +3,7 @@ export * from "./catalog-sync-result"; · 2 unmodified lines
33 export * from "./discovery";
44 export * from "./discovery-models";
55 export * from "./effect-errors";
6+export * from "./favicon";
67 export * from "./shared";
78 export * from "./source-models";
89 export * from "./types";

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t