Files
memind/mindspace-public-image-retry.mjs

69 lines
2.4 KiB
JavaScript

import crypto from 'node:crypto';
import { injectBeforeDocumentClosingBody } from './html-document-injection.mjs';
/**
* Public MindSpace pages (workspace `/MindSpace/<userId>/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
* `<img>` 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 = `<script ${MARKER_ATTR}="1">${IMAGE_RETRY_SCRIPT}</script>`;
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,
};