Skip to content

Commit 0061e64

Browse files
authored
Merge pull request #726 from RhysSullivan/hotfix/source-icons-previous-behavior
Restore source icon favicon lookup
2 parents 0506d87 + 556a337 commit 0061e64

2 files changed

Lines changed: 9 additions & 33 deletions

File tree

‎packages/react/src/components/source-favicon.test.tsx‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,20 @@ import { describe, expect, it } from "@effect/vitest";
33
import { sourceFaviconUrl } from "./source-favicon";
44

55
describe("SourceFavicon", () => {
6-
it("uses the source site's own favicon for public URLs", () => {
6+
it("uses the favicon service that handles provider-specific icon locations", () => {
77
expect(sourceFaviconUrl("https://api.github.com/graphql", 20)).toBe(
8-
"https://github.com/favicon.ico?sz=40",
8+
"https://www.google.com/s2/favicons?domain=github.com&sz=40",
99
);
1010
});
1111

1212
it("does not request favicons for local URLs", () => {
1313
expect(sourceFaviconUrl("http://localhost:3000/private", 20)).toBeNull();
1414
expect(sourceFaviconUrl("http://127.0.0.1:3000/private", 20)).toBeNull();
15-
expect(sourceFaviconUrl("http://api.local/private", 20)).toBeNull();
1615
});
1716

18-
it("does not send source URLs to a third-party favicon service", () => {
19-
expect(sourceFaviconUrl("https://internal.example.test/private", 20)).not.toContain(
20-
"google.com",
17+
it("sends only the registrable domain to the favicon service", () => {
18+
expect(sourceFaviconUrl("https://api.github.com/private", 20)).toBe(
19+
"https://www.google.com/s2/favicons?domain=github.com&sz=40",
2120
);
2221
});
2322
});

‎packages/react/src/components/source-favicon.tsx‎

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,15 @@ import { useState } from "react";
33
import { getDomain } from "tldts";
44

55
// ---------------------------------------------------------------------------
6-
// SourceFavicon — renders the source site's own public favicon.
7-
// Do not fetch third-party favicon services here; source URLs may be private.
6+
// SourceFavicon — renders a small favicon derived from a source URL.
7+
// Falls back to a neutral icon if the URL is missing or the image fails to load.
88
// ---------------------------------------------------------------------------
99

10-
const IPV4_PATTERN = /^\d{1,3}(?:\.\d{1,3}){3}$/;
11-
12-
const isIpHostname = (hostname: string): boolean =>
13-
IPV4_PATTERN.test(hostname) || hostname.includes(":");
14-
1510
export function sourceFaviconUrl(url: string | undefined, size: number): string | null {
1611
if (!url) return null;
17-
if (!URL.canParse(url)) return null;
18-
19-
const parsed = new URL(url);
20-
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
21-
22-
const hostname = parsed.hostname.toLowerCase();
23-
if (
24-
hostname === "localhost" ||
25-
hostname.endsWith(".localhost") ||
26-
hostname.endsWith(".local") ||
27-
isIpHostname(hostname)
28-
) {
29-
return null;
30-
}
31-
32-
const domain = getDomain(hostname);
12+
const domain = getDomain(url) ?? (URL.canParse(url) ? getDomain(new URL(url).hostname) : null);
3313
if (!domain) return null;
34-
35-
const favicon = new URL(`https://${domain}/favicon.ico`);
36-
favicon.searchParams.set("sz", String(size * 2));
37-
return favicon.toString();
14+
return `https://www.google.com/s2/favicons?domain=${domain}&sz=${size * 2}`;
3815
}
3916

4017
export function SourceFavicon({ url, size = 16 }: { url?: string; size?: number }) {

0 commit comments

Comments
 (0)