fix(favorites): normalize scheme-less URLs so favicons resolve
A favorite entered as a bare host ("ctl.de") passed @IsUrl() but had no
scheme, so `new URL()` threw inside icon discovery and it silently fell
back to a relative "/favicon.ico" — which 502'd through the icon proxy
and left the widget showing the first-letter placeholder ("C").
- add normalizeUrl() (prepend https:// when no scheme present)
- apply it in discoverFavoriteIconUrl and when storing the favorite url,
so both the link and discovery use the normalized value
- on update, re-run discovery when the icon field is cleared, so editing
a previously-broken favorite repairs its icon
- tests: normalizeUrl cases + end-to-end discovery (apple-touch extraction,
scheme-less fallback stays absolute)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,21 @@ export async function isPublicHttpUrl(url: URL): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a user-entered site URL by prepending https:// when no scheme is
|
||||
* present, so "ctl.de" becomes "https://ctl.de". Without this, `new URL()`
|
||||
* throws on a bare host and icon discovery silently falls back to a broken
|
||||
* relative "/favicon.ico" (which then 502s through the icon proxy and the
|
||||
* widget shows the first-letter placeholder instead of the real favicon).
|
||||
*/
|
||||
export function normalizeUrl(raw: string): string {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return trimmed;
|
||||
// Already has a scheme (http://, https://, ftp://, ...) — leave untouched.
|
||||
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) return trimmed;
|
||||
return `https://${trimmed}`;
|
||||
}
|
||||
|
||||
function getOriginFaviconUrl(pageUrl: string): string {
|
||||
try {
|
||||
const url = new URL(pageUrl);
|
||||
@@ -301,10 +316,11 @@ export class IconDiscoveryService {
|
||||
* private IP ranges, blocked hostnames, and forced-proxy vectors (T-08-05).
|
||||
*/
|
||||
async discoverFavoriteIconUrl(pageUrl: string): Promise<string> {
|
||||
const fallback = getOriginFaviconUrl(pageUrl);
|
||||
const normalized = normalizeUrl(pageUrl);
|
||||
const fallback = getOriginFaviconUrl(normalized);
|
||||
|
||||
try {
|
||||
const url = new URL(pageUrl);
|
||||
const url = new URL(normalized);
|
||||
const htmlResult = await fetchHtml(url);
|
||||
|
||||
if (!htmlResult) return fallback;
|
||||
|
||||
Reference in New Issue
Block a user