History

Merge pull request #726 from RhysSullivan/hotfix/source-icons-previous-behavior

Restore source icon favicon lookup

Rhys Sullivan committed 6 months ago

Commit 0061e642 parents 0506d87 556a3372 files · +9 −33Browse files

Unified · Split

2 files
  1. packages/react/src/components/source-favicon.test.tsx +5 −6
  2. packages/react/src/components/source-favicon.tsx +4 −27
packages/react/src/components/source-favicon.test.tsx+5−6
@@ -3,21 +3,20 @@ import { describe, expect, it } from "@effect/vitest"; · 2 unmodified lines
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+4−27
@@ -3,38 +3,15 @@ import { useState } from "react"; · 2 unmodified lines
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 }) {