Prefer reference manifest over fallback
This commit is contained in:
@@ -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() {
|
||||||
|
|||||||
@@ -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}`
|
||||||
|
|||||||
78
src/lib.ts
78
src/lib.ts
@@ -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) {
|
||||||
if (typeof value === "string" && value.length > 0) candidates.push(value);
|
throw new Error(`Reference ${MANIFEST_REFERENCE_NAME} did not resolve to a manifest id`);
|
||||||
else 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 = [];
|
try {
|
||||||
for (const candidate of candidates) {
|
return await fetchManifest(await resolveReferenceManifestTxId());
|
||||||
try {
|
} catch (error) {
|
||||||
loaded.push(await fetchManifest(candidate));
|
console.warn(
|
||||||
} catch (error) {
|
`Could not load manifest reference ${MANIFEST_REFERENCE_NAME}; trying configured manifest`,
|
||||||
console.warn(`Could not load manifest ${candidate}; trying other configured candidates`, error);
|
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> =>
|
||||||
|
|||||||
Reference in New Issue
Block a user