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
3import { sourceFaviconUrl } from "./source-favicon";3import { sourceFaviconUrl } from "./source-favicon";
4 4
5describe("SourceFavicon", () => {5describe("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", () => {
7 expect(sourceFaviconUrl("https://api.github.com/graphql", 20)).toBe(7 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",
9 );9 );
10 });10 });
11 11
12 it("does not request favicons for local URLs", () => {12 it("does not request favicons for local URLs", () => {
13 expect(sourceFaviconUrl("http://localhost:3000/private", 20)).toBeNull();13 expect(sourceFaviconUrl("http://localhost:3000/private", 20)).toBeNull();
14 expect(sourceFaviconUrl("http://127.0.0.1:3000/private", 20)).toBeNull();14 expect(sourceFaviconUrl("http://127.0.0.1:3000/private", 20)).toBeNull();
15 expect(sourceFaviconUrl("http://api.local/private", 20)).toBeNull();
16 });15 });
17 16
18 it("does not send source URLs to a third-party favicon service", () => {17 it("sends only the registrable domain to the favicon service", () => {
19 expect(sourceFaviconUrl("https://internal.example.test/private", 20)).not.toContain(18 expect(sourceFaviconUrl("https://api.github.com/private", 20)).toBe(
20 "google.com",19 "https://www.google.com/s2/favicons?domain=github.com&sz=40",
21 );20 );
22 });21 });
23});22});
packages/react/src/components/source-favicon.tsx+4−27
@@ -3,38 +3,15 @@ import { useState } from "react"; · 2 unmodified lines
3import { getDomain } from "tldts";3import { getDomain } from "tldts";
4 4
5// ---------------------------------------------------------------------------5// ---------------------------------------------------------------------------
6// SourceFavicon — renders the source site's own public favicon.6// SourceFavicon — renders a small favicon derived from a source URL.
7// Do not fetch third-party favicon services here; source URLs may be private.7// Falls back to a neutral icon if the URL is missing or the image fails to load.
8// ---------------------------------------------------------------------------8// ---------------------------------------------------------------------------
9 9
10const IPV4_PATTERN = /^\d{1,3}(?:\.\d{1,3}){3}$/;
11
12const isIpHostname = (hostname: string): boolean =>
13 IPV4_PATTERN.test(hostname) || hostname.includes(":");
14
15export function sourceFaviconUrl(url: string | undefined, size: number): string | null {10export function sourceFaviconUrl(url: string | undefined, size: number): string | null {
16 if (!url) return null;11 if (!url) return null;
17 if (!URL.canParse(url)) return null;12 const domain = getDomain(url) ?? (URL.canParse(url) ? getDomain(new URL(url).hostname) : 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);
33 if (!domain) return null;13 if (!domain) return null;
34 14 return `https://www.google.com/s2/favicons?domain=${domain}&sz=${size * 2}`;
35 const favicon = new URL(`https://${domain}/favicon.ico`);
36 favicon.searchParams.set("sz", String(size * 2));
37 return favicon.toString();
38}15}
39 16
40export function SourceFavicon({ url, size = 16 }: { url?: string; size?: number }) {17export function SourceFavicon({ url, size = 16 }: { url?: string; size?: number }) {