Use reference-backed hyperzine manifest
This commit is contained in:
74
src/lib.ts
74
src/lib.ts
@@ -1,7 +1,8 @@
|
||||
import DOMPurify from "dompurify";
|
||||
import { marked, type Tokens } from "marked";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import { ARWEAVE_GATEWAY, MANIFEST_TX_ID } from "./config";
|
||||
import { ReferenceClient } from "@permaweb/references";
|
||||
import { ARWEAVE_GATEWAY, MANIFEST_REFERENCE_NAME, MANIFEST_TX_ID } from "./config";
|
||||
import { parseManifest, type Frontmatter, type ManifestPost } from "./types";
|
||||
|
||||
const formatDate = (date: string): string =>
|
||||
@@ -72,13 +73,80 @@ export const getReadableDate = (date?: string | null): string | null => {
|
||||
};
|
||||
|
||||
export const loadManifest = async (): Promise<ManifestPost[]> => {
|
||||
const response = await fetch(arweaveUrl(MANIFEST_TX_ID));
|
||||
return (await loadBestManifest()).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<{
|
||||
txId: string;
|
||||
posts: ManifestPost[];
|
||||
freshness: number;
|
||||
}> => {
|
||||
const response = await fetch(arweaveUrl(manifestTxId));
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load manifest (${response.status})`);
|
||||
}
|
||||
|
||||
const payload: unknown = await response.json();
|
||||
return parseManifest(payload);
|
||||
const posts = parseManifest(payload);
|
||||
return { txId: manifestTxId, posts, freshness: manifestFreshness(payload, posts) };
|
||||
};
|
||||
|
||||
const resolveManifestCandidates = async (): Promise<string[]> => {
|
||||
const candidates: string[] = [];
|
||||
if (MANIFEST_REFERENCE_NAME) {
|
||||
try {
|
||||
const names = new ReferenceClient({ gateway: ARWEAVE_GATEWAY });
|
||||
const value = await names.resolveName(MANIFEST_REFERENCE_NAME);
|
||||
if (typeof value === "string" && value.length > 0) candidates.push(value);
|
||||
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 [...new Set(candidates)];
|
||||
};
|
||||
|
||||
const loadBestManifest = async () => {
|
||||
const candidates = await resolveManifestCandidates();
|
||||
if (!candidates.length) throw new Error("No manifest reference or fallback manifest configured");
|
||||
|
||||
const loaded = [];
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
loaded.push(await fetchManifest(candidate));
|
||||
} catch (error) {
|
||||
console.warn(`Could not load manifest ${candidate}; trying other configured candidates`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (!loaded.length) throw new Error("No configured manifest could be loaded");
|
||||
loaded.sort((a, b) => b.freshness - a.freshness);
|
||||
return loaded[0];
|
||||
};
|
||||
|
||||
export const resolveManifestTxId = async (): Promise<string> => {
|
||||
return (await loadBestManifest()).txId;
|
||||
};
|
||||
|
||||
const isObject = (value: unknown): value is Record<string, unknown> =>
|
||||
|
||||
Reference in New Issue
Block a user