Prefer reference manifest over fallback

This commit is contained in:
fn
2026-06-08 18:59:34 +01:00
parent d02bca8646
commit 71a8a23054
3 changed files with 60 additions and 129 deletions

View File

@@ -328,20 +328,17 @@ async function getLatestManifestFromAo() {
} }
} }
function timestampMs(value) { async function fetchPostSlugs(gateway, blogManifestTxId) {
if (typeof value !== "string" || !value) return 0; if (!blogManifestTxId) return null;
const parsed = Date.parse(value);
return Number.isNaN(parsed) ? 0 : parsed;
}
function manifestFreshness(payload) { const response = await fetch(`${gateway.replace(/\/+$/, "")}/${blogManifestTxId}`);
const posts = Array.isArray(payload?.posts) ? payload.posts : []; if (!response.ok) return null;
return Math.max( const payload = await response.json();
timestampMs(payload?.updated), if (!Array.isArray(payload?.posts)) return null;
timestampMs(payload?.date),
timestampMs(payload?.generatedAt), return payload.posts
...posts.map((post) => Math.max(timestampMs(post?.updated), timestampMs(post?.publishedAt))) .map((post) => (typeof post?.slug === "string" ? post.slug.trim() : ""))
); .filter((slug) => slug.length > 0);
} }
async function resolvePostSlugs(gateway) { async function resolvePostSlugs(gateway) {
@@ -351,28 +348,16 @@ async function resolvePostSlugs(gateway) {
await resolveBlogManifestTxId() await resolveBlogManifestTxId()
].filter(Boolean); ].filter(Boolean);
const manifests = [];
for (const blogManifestTxId of candidates) { for (const blogManifestTxId of candidates) {
try { try {
const response = await fetch(`${gateway.replace(/\/+$/, "")}/${blogManifestTxId}`); const slugs = await fetchPostSlugs(gateway, blogManifestTxId);
if (!response.ok) continue; if (slugs) return slugs;
const payload = await response.json();
if (!Array.isArray(payload?.posts)) continue;
manifests.push({
txId: blogManifestTxId,
freshness: manifestFreshness(payload),
posts: payload.posts
});
} catch { } catch {
// Try the next manifest source. // Try the next manifest source.
} }
} }
if (!manifests.length) return []; return [];
manifests.sort((a, b) => b.freshness - a.freshness);
return manifests[0].posts
.map((post) => (typeof post?.slug === "string" ? post.slug.trim() : ""))
.filter((slug) => slug.length > 0);
} }
async function main() { async function main() {

View File

@@ -121,20 +121,6 @@ const parseManifest = (input) => {
}); });
}; };
const timestampMs = (value) => {
if (typeof value !== "string" || !value) return 0;
const parsed = Date.parse(value);
return Number.isNaN(parsed) ? 0 : parsed;
};
const manifestFreshness = (payload, posts) =>
Math.max(
timestampMs(payload?.updated),
timestampMs(payload?.date),
timestampMs(payload?.generatedAt),
...posts.map((post) => Math.max(timestampMs(post.updated), timestampMs(post.publishedAt)))
);
const fetchManifestPosts = async (manifestTxId) => { const fetchManifestPosts = async (manifestTxId) => {
if (!manifestTxId) return []; if (!manifestTxId) return [];
const url = `https://arweave.net/${manifestTxId}`; const url = `https://arweave.net/${manifestTxId}`;
@@ -142,43 +128,7 @@ const fetchManifestPosts = async (manifestTxId) => {
if (!response.ok) throw new Error(`Manifest fetch failed (${response.status})`); if (!response.ok) throw new Error(`Manifest fetch failed (${response.status})`);
const payload = await response.json(); const payload = await response.json();
const posts = parseManifest(payload); const posts = parseManifest(payload);
return { txId: manifestTxId, posts, freshness: manifestFreshness(payload, posts) }; return { txId: manifestTxId, posts };
};
const resolveManifestTxIds = async (config) => {
const candidates = [];
if (config.MANIFEST_REFERENCE_NAME) {
try {
const names = new ReferenceClient({ gateway: config.ARWEAVE_GATEWAY || DEFAULTS.ARWEAVE_GATEWAY });
const value = await names.resolveName(config.MANIFEST_REFERENCE_NAME);
if (typeof value === "string" && value.length > 0) candidates.push(value);
else throw new Error(`Reference ${config.MANIFEST_REFERENCE_NAME} did not resolve to a manifest id`);
} catch (error) {
console.warn(
`[prerender-meta] Trying configured manifest; reference ${config.MANIFEST_REFERENCE_NAME} failed: ${error.message}`
);
}
}
if (config.MANIFEST_TX_ID) candidates.push(config.MANIFEST_TX_ID);
return [...new Set(candidates)];
};
const fetchBestManifestPosts = async (config) => {
const candidates = await resolveManifestTxIds(config);
const loaded = [];
for (const manifestTxId of candidates) {
try {
loaded.push(await fetchManifestPosts(manifestTxId));
} catch (error) {
console.warn(`[prerender-meta] Manifest ${manifestTxId} failed: ${error.message}`);
}
}
if (!loaded.length) return [];
loaded.sort((a, b) => b.freshness - a.freshness);
return loaded[0].posts;
}; };
const resolveManifestTxId = async (config) => { const resolveManifestTxId = async (config) => {
@@ -198,6 +148,22 @@ const resolveManifestTxId = async (config) => {
return config.MANIFEST_TX_ID; return config.MANIFEST_TX_ID;
}; };
const fetchCurrentManifestPosts = async (config) => {
const manifestTxId = await resolveManifestTxId(config);
if (!manifestTxId) return [];
try {
return (await fetchManifestPosts(manifestTxId)).posts;
} catch (error) {
if (!config.MANIFEST_REFERENCE_NAME || manifestTxId === config.MANIFEST_TX_ID) throw error;
console.warn(
`[prerender-meta] Falling back to configured manifest; reference manifest ${manifestTxId} failed: ${error.message}`
);
}
return config.MANIFEST_TX_ID ? (await fetchManifestPosts(config.MANIFEST_TX_ID)).posts : [];
};
const buildMetaTags = ({ const buildMetaTags = ({
title, title,
description, description,
@@ -270,7 +236,7 @@ const main = async () => {
let posts = []; let posts = [];
try { try {
posts = await fetchBestManifestPosts(config); posts = await fetchCurrentManifestPosts(config);
} catch (error) { } catch (error) {
console.warn( console.warn(
`[prerender-meta] Could not fetch manifest; generating root metadata only: ${error.message}` `[prerender-meta] Could not fetch manifest; generating root metadata only: ${error.message}`

View File

@@ -73,29 +73,12 @@ export const getReadableDate = (date?: string | null): string | null => {
}; };
export const loadManifest = async (): Promise<ManifestPost[]> => { export const loadManifest = async (): Promise<ManifestPost[]> => {
return (await loadBestManifest()).posts; return (await loadCurrentManifest()).posts;
};
const timestampMs = (value: unknown): number => {
if (typeof value !== "string" || !value) return 0;
const parsed = Date.parse(value);
return Number.isNaN(parsed) ? 0 : parsed;
};
const manifestFreshness = (payload: unknown, posts: ManifestPost[]): number => {
const root = isObject(payload) ? payload : {};
return Math.max(
timestampMs(root.updated),
timestampMs(root.date),
timestampMs(root.generatedAt),
...posts.map((post) => Math.max(timestampMs(post.updated), timestampMs(post.publishedAt)))
);
}; };
const fetchManifest = async (manifestTxId: string): Promise<{ const fetchManifest = async (manifestTxId: string): Promise<{
txId: string; txId: string;
posts: ManifestPost[]; posts: ManifestPost[];
freshness: number;
}> => { }> => {
const response = await fetch(arweaveUrl(manifestTxId)); const response = await fetch(arweaveUrl(manifestTxId));
if (!response.ok) { if (!response.ok) {
@@ -104,49 +87,46 @@ const fetchManifest = async (manifestTxId: string): Promise<{
const payload: unknown = await response.json(); const payload: unknown = await response.json();
const posts = parseManifest(payload); const posts = parseManifest(payload);
return { txId: manifestTxId, posts, freshness: manifestFreshness(payload, posts) }; return { txId: manifestTxId, posts };
}; };
const resolveManifestCandidates = async (): Promise<string[]> => { const resolveReferenceManifestTxId = async (): Promise<string> => {
const candidates: string[] = []; if (!MANIFEST_REFERENCE_NAME) return "";
if (MANIFEST_REFERENCE_NAME) {
try {
const names = new ReferenceClient({ gateway: ARWEAVE_GATEWAY }); const names = new ReferenceClient({ gateway: ARWEAVE_GATEWAY });
const value = await names.resolveName(MANIFEST_REFERENCE_NAME); const value = await names.resolveName(MANIFEST_REFERENCE_NAME);
if (typeof value === "string" && value.length > 0) candidates.push(value); if (typeof value !== "string" || value.length === 0) {
else throw new Error(`Reference ${MANIFEST_REFERENCE_NAME} did not resolve to a manifest id`); throw new Error(`Reference ${MANIFEST_REFERENCE_NAME} did not resolve to a manifest id`);
} catch (error) {
console.warn(
`Could not resolve manifest reference ${MANIFEST_REFERENCE_NAME}; trying configured manifest`,
error
);
}
} }
if (MANIFEST_TX_ID) candidates.push(MANIFEST_TX_ID); return value;
return [...new Set(candidates)];
}; };
const loadBestManifest = async () => { const loadReferenceManifest = async () => {
const candidates = await resolveManifestCandidates(); if (!MANIFEST_REFERENCE_NAME) return null;
if (!candidates.length) throw new Error("No manifest reference or fallback manifest configured");
const loaded = [];
for (const candidate of candidates) {
try { try {
loaded.push(await fetchManifest(candidate)); return await fetchManifest(await resolveReferenceManifestTxId());
} catch (error) { } catch (error) {
console.warn(`Could not load manifest ${candidate}; trying other configured candidates`, error); console.warn(
} `Could not load manifest reference ${MANIFEST_REFERENCE_NAME}; trying configured manifest`,
error
);
return null;
} }
};
if (!loaded.length) throw new Error("No configured manifest could be loaded"); const loadConfiguredManifest = async () => {
loaded.sort((a, b) => b.freshness - a.freshness); if (!MANIFEST_TX_ID) throw new Error("No manifest reference or fallback manifest configured");
return loaded[0]; return fetchManifest(MANIFEST_TX_ID);
};
const loadCurrentManifest = async () => {
return (await loadReferenceManifest()) ?? (await loadConfiguredManifest());
}; };
export const resolveManifestTxId = async (): Promise<string> => { export const resolveManifestTxId = async (): Promise<string> => {
return (await loadBestManifest()).txId; return (await loadCurrentManifest()).txId;
}; };
const isObject = (value: unknown): value is Record<string, unknown> => const isObject = (value: unknown): value is Record<string, unknown> =>