1import fs from 'fs/promises' 2import { existsSync, createWriteStream } from 'fs' 4import { Readable } from 'stream' 5import { pipeline } from 'stream/promises' 9export const mediaSiteBaseUrlA = [`https://${ossEnsName}.demokluster.com`, `https://${ossEnsName.replace('.eth', '')}.eth.limo`] 11export const isTransientStreamErr = (err: any): boolean => { 12 const code = err?.code ?? err?.cause?.code 13 if (code === 'ERR_HTTP2_STREAM_ERROR' || code === 'ECONNRESET' || code === 'ETIMEDOUT') return true 14 const msg = String(err?.message ?? '') 15 return msg.includes('terminated') || msg.includes('other side closed') 18export const fetchMediaWithFallback = async (relPath: string, {fetchImpl = fetch, baseUrlA = mediaSiteBaseUrlA}: {fetchImpl?: typeof fetch, baseUrlA?: string[]} = {}) => { 19 for (const baseUrl of baseUrlA) { 20 for (let attempt = 0; attempt < 2; attempt++) { 22 const resp = await fetchImpl(`${baseUrl}/${relPath}`) 23 if (resp.ok) return resp 24 await resp.text().catch(() => {}) 27 if (attempt === 0 && isTransientStreamErr(err)) { 28 console.log(`[media-proxy] transient upstream error for ${relPath}: ${err?.message ?? err} — retrying`) 39const inflightH: Record<string, Promise<boolean>> = {} 41const fetchToDiskOnce = async ({relPath, destPath, baseUrlA, fetchImpl}: {relPath: string, destPath: string, baseUrlA: string[], fetchImpl: typeof fetch}): Promise<boolean> => { 42 const partial = `${destPath}.partial` 44 const resp = await fetchMediaWithFallback(relPath, {fetchImpl, baseUrlA}) 45 if (!resp) return false 46 await fs.mkdir(path.dirname(destPath), {recursive: true}) 47 await pipeline(Readable.fromWeb(resp.body as any), createWriteStream(partial)) 48 await fs.rename(partial, destPath) 51 await fs.rm(partial, {force: true}).catch(() => {}) 56const fetchToDisk = async (opts: {relPath: string, destPath: string, baseUrlA: string[], fetchImpl: typeof fetch}): Promise<boolean> => { 58 for (let attempt = 1; attempt <= maxAttempts; attempt++) { 60 return await fetchToDiskOnce(opts) 62 if (attempt === maxAttempts) return false 63 console.log(`[media-proxy] upstream stream error for ${opts.relPath} (attempt ${attempt}/${maxAttempts}): ${err?.message ?? err}`) 70// Write-through disk cache for the devserver proxy: first request downloads to destPath 71// (concurrent requests dedup via inflightH), later requests serve the file statically. 72export const proxyMediaCached = async ({relPath, destPath, baseUrlA = mediaSiteBaseUrlA, fetchImpl = fetch}: {relPath: string, destPath: string, baseUrlA?: string[], fetchImpl?: typeof fetch}): Promise<boolean> => { 73 if (existsSync(destPath)) return true 74 inflightH[destPath] ??= fetchToDisk({relPath, destPath, baseUrlA, fetchImpl}).finally(() => { delete inflightH[destPath] }) 75 return inflightH[destPath] 78const mediaSitePrefixH: [string, string][] = [ 79 ['threerulecycle/', 'pt0/threerulecycleapp/public/'], 80 ['blogposts/', 'pt0/peatsite/blogposts/'], 83export const resolveMediaRepoPath = (siteRelPath: string): string => { 84 for (const [sitePrefix, repoPrefix] of mediaSitePrefixH) { 85 if (siteRelPath.startsWith(sitePrefix)) return repoPrefix + siteRelPath.slice(sitePrefix.length) 87 return `pt0/peatsite/public/${siteRelPath}`