import crypto from 'node:crypto'; import { injectBeforeDocumentClosingBody } from './html-document-injection.mjs'; /** * Public MindSpace pages (workspace `/MindSpace//public/*.html`) reference images via * `/api/mindspace/v1/assets/:id/download`, which is authorized per-request (owner cookie, signed * `public_token`, or `/public/` referrer). Right after an agent generates a page, the very first * image request can occasionally race asset materialization and return a transient 401/404/5xx. * * Generated pages usually ship a page-specific `onerror` fallback that permanently swaps the * `` for a static placeholder — so a single transient failure becomes a permanent "broken * image" until the visitor manually reloads. This script intercepts image load failures for * MindSpace asset URLs (any page, any author) and retries a few times with backoff BEFORE the * page's own `onerror` gets a chance to run; only once retries are exhausted does it let the * original fallback take over. */ const IMAGE_RETRY_SCRIPT = `(function(){ var MAX_RETRIES=3; var BASE_DELAY_MS=900; var ASSET_PATTERN=/\\/api\\/mindspace\\/v1\\/assets\\/[^/]+\\/download/; var retries=new WeakMap(); document.addEventListener('error',function(event){ var img=event.target; if(!img||img.tagName!=='IMG')return; var src=img.getAttribute('src')||''; if(!ASSET_PATTERN.test(src))return; var count=retries.get(img)||0; if(count>=MAX_RETRIES)return; event.stopImmediatePropagation(); retries.set(img,count+1); setTimeout(function(){ try{ var url=new URL(src,location.href); url.searchParams.set('_retry',String(count+1)); img.src=url.toString(); }catch(e){ img.src=src; } },BASE_DELAY_MS*(count+1)); },true); })();`; const IMAGE_RETRY_SCRIPT_HASH = crypto.createHash('sha256').update(IMAGE_RETRY_SCRIPT).digest('base64'); const MARKER_ATTR = 'data-mindspace-image-retry'; export function injectPublicImageRetryScript(html) { const source = String(html ?? ''); if (!source || source.includes(MARKER_ATTR)) { return { html: source, scriptHashes: [] }; } const markup = ``; if (/<\/body>/i.test(source)) { return { html: injectBeforeDocumentClosingBody(source, markup), scriptHashes: [IMAGE_RETRY_SCRIPT_HASH], }; } return { html: `${source}${markup}`, scriptHashes: [IMAGE_RETRY_SCRIPT_HASH], }; } export const publicImageRetryInternals = { IMAGE_RETRY_SCRIPT_HASH, MARKER_ATTR, };