diff --git a/scripts/patch-ap-mastodon-misc.mjs b/scripts/patch-ap-mastodon-misc.mjs index 3d87590a..d1eb40c8 100644 --- a/scripts/patch-ap-mastodon-misc.mjs +++ b/scripts/patch-ap-mastodon-misc.mjs @@ -219,4 +219,44 @@ for (const f of SEARCH_CANDIDATES) { } if (!searchDone) console.log(`[postinstall] ${SCRIPT}: ap-search-url-resolve — no target file found or no changes`); +// ── patch-ap-resolve-actor-timeout: cap actor lookup at 8 s + log failures ─── +// When Mastodon client clicks a @mention handle, /api/v1/accounts/lookup calls +// resolveRemoteAccount → lookupWithSecurity → ctx.lookupObject (no timeout). +// If the remote server hangs, the request hangs indefinitely. Even when it fails +// fast, we get 404 with no log entry explaining why. +// Fix: wrap lookupWithSecurity in an 8 s timeout; log on failure/null result. + +const ACTOR_TIMEOUT_MARKER = "// [patch] ap-resolve-actor-timeout"; +const RESOLVE_ACCOUNT_CANDIDATES = apPath("lib/mastodon/helpers/resolve-account.js"); + +const ACTOR_TIMEOUT_OLD = ` // Use signed→unsigned fallback so servers rejecting signed GETs still resolve + const documentLoader = await ctx.getDocumentLoader({ identifier: handle }); + const actor = await lookupWithSecurity(ctx, actorUri, { documentLoader }); + if (!actor) return null;`; + +const ACTOR_TIMEOUT_NEW = ` // Use signed→unsigned fallback so servers rejecting signed GETs still resolve + const documentLoader = await ctx.getDocumentLoader({ identifier: handle }); + // Timeout guard: cap actor fetch at 8 s so hung lookups fail fast. ${ACTOR_TIMEOUT_MARKER} + const _aLookupTimeout = (p, ms = 8000) => { const t = new Promise((_, rej) => setTimeout(() => rej(new Error("actor lookup timeout")), ms)); p.catch(() => {}); return Promise.race([p, t]); }; ${ACTOR_TIMEOUT_MARKER} + const actor = await _aLookupTimeout(lookupWithSecurity(ctx, actorUri, { documentLoader })).catch(err => { console.warn(\`[Mastodon API] Actor lookup failed for \${acct}: \${err.message}\`); return null; }); ${ACTOR_TIMEOUT_MARKER} + if (!actor) { console.warn(\`[Mastodon API] lookupWithSecurity returned null for \${acct}\`); return null; } ${ACTOR_TIMEOUT_MARKER}`; + +let actorTimeoutDone = false; +for (const f of RESOLVE_ACCOUNT_CANDIDATES) { + if (!(await fileExists(f))) continue; + const src = await readFile(f, "utf8"); + if (src.includes(ACTOR_TIMEOUT_MARKER)) { + console.log(`[postinstall] ${SCRIPT}: ap-resolve-actor-timeout already applied in ${f}`); + actorTimeoutDone = true; break; + } + if (!src.includes(ACTOR_TIMEOUT_OLD)) { + console.warn(`[postinstall] ${SCRIPT}: ap-resolve-actor-timeout — anchor not found in ${f}`); + continue; + } + await writeFile(f, src.replace(ACTOR_TIMEOUT_OLD, ACTOR_TIMEOUT_NEW), "utf8"); + console.log(`[postinstall] ${SCRIPT}: applied ap-resolve-actor-timeout to ${f}`); + total++; actorTimeoutDone = true; break; +} +if (!actorTimeoutDone) console.log(`[postinstall] ${SCRIPT}: ap-resolve-actor-timeout — no target file found or no changes`); + console.log(`[postinstall] ${SCRIPT}: done (${total} patch(es) applied)`);