SkateSpots/src/lib/imageFallback.ts
SkateSpots 7611f3d266 feat(media): self-host Blossom at blossom.skate-spots.org (primary upload target)
Newlay's Blossom enabled on the relay box (blobs inside the data volume); Caddy + DNS + TLS wired. The app now uploads there first, keeping blossom.primal.net and blossom.band as mirrors, and includes it in BLOSSOM_MIRROR_HOSTS for read fallback.
2026-10-12 13:12:14 +07:00

51 lines
1.7 KiB
TypeScript

/**
* Photo redundancy helpers.
*
* SkateSpots uploads images to Blossom, which is CONTENT-ADDRESSED: a blob's URL
* is `<host>/<sha256>` (optionally with a file extension). That means a mirror of
* the same file on another known host is derivable from the URL alone — no extra
* bookkeeping needed — so a dead photo host never has to leave a blank spot.
*
* We only derive mirrors for Blossom-style hash paths; anything else is left
* untouched (an external image URL has no meaningful alternative location).
*/
/** Hosts the app uploads to, and therefore the hosts we can mirror across. */
export const BLOSSOM_MIRROR_HOSTS = ['blossom.skate-spots.org', 'blossom.primal.net', 'blossom.band'];
/** `<64-hex>` or `<64-hex>.<ext>` */
const HASH_RE = /^([0-9a-f]{64})(\.[a-z0-9]+)?$/i;
/**
* Ordered candidate URLs for an image: the original first, then any explicitly
* supplied fallbacks, then same-hash mirrors on the other known Blossom hosts.
*/
export function imageCandidates(
src: string | undefined | null,
extra: string[] = [],
): string[] {
const out: string[] = [];
const add = (url?: string | null) => {
if (typeof url === 'string' && url && !out.includes(url)) out.push(url);
};
add(src);
for (const url of extra) add(url);
if (!src) return out;
try {
const parsed = new URL(src);
const last = parsed.pathname.split('/').filter(Boolean).pop() ?? '';
const match = HASH_RE.exec(last);
if (match) {
for (const host of BLOSSOM_MIRROR_HOSTS) {
if (host === parsed.hostname) continue;
add(`${parsed.protocol}//${host}/${match[1]}${match[2] ?? ''}`);
}
}
} catch {
// Relative or malformed URL — nothing to mirror.
}
return out;
}