feat(favicon): per-feed icon from the last sender's domain
Resolve each feed's most recent sender domain and serve its favicon at GET /favicon/:feedId, falling back to the project icon. Icons are fetched in the background on ingestion (direct /favicon.ico then a DuckDuckGo fallback), cached base64 in KV keyed by domain with a 1-week TTL so the fetch only fires when absent. Exposed via RSS <image> / Atom <icon>/<logo> and rendered in the admin feed list, plus a landing-page feature card. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
d299c8891d
commit
eb12f21894
19 files changed
+592
-30
No files matched your search
@@ -8,6 +8,10 @@ import {
|
||||
} from "../types";
|
||||
import { notifySubscribers } from "../utils/websub";
|
||||
import { bumpCounters } from "../utils/stats";
|
||||
import {
|
||||
cacheFaviconForDomain,
|
||||
extractEmailDomain,
|
||||
} from "../utils/favicon-fetcher";
|
||||
import { logger } from "./logger";
|
||||
import { FEED_MAX_BYTES } from "../config/constants";
|
||||
|
||||
@@ -213,6 +217,12 @@ export async function storeEmail(
|
||||
};
|
||||
feedMetadata.emails.unshift(newEntry);
|
||||
|
||||
// Track the latest sender's domain so the feed icon follows the source.
|
||||
const iconDomain = extractEmailDomain(input.from);
|
||||
if (iconDomain) {
|
||||
feedMetadata.iconDomain = iconDomain;
|
||||
}
|
||||
|
||||
let totalSize = feedMetadata.emails.reduce(
|
||||
(sum, e) => sum + (e.size ?? 0),
|
||||
0,
|
||||
@@ -240,6 +250,9 @@ export async function storeEmail(
|
||||
logger.info("Email processed", { feedId });
|
||||
if (ctx) {
|
||||
ctx.waitUntil(notifySubscribers(feedId, env));
|
||||
if (iconDomain) {
|
||||
ctx.waitUntil(cacheFaviconForDomain(iconDomain, env));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user