mirror of
https://github.com/svemagie/indiekit-endpoint-activitypub.git
synced 2026-04-02 15:44:58 +02:00
fix(mastodon-api): favourite/reblog blocks on unbound resolveAuthor HTTP requests
likePost, unlikePost and boostPost all call resolveAuthor() which makes up to 3 signed HTTP requests to the remote server (post fetch, author actor fetch, getAttributedTo) with no timeout. If the remote server is slow or unreachable, the favourite/reblog HTTP response hangs until the Node.js socket default times out (~2 min). Mastodon clients (Phanpy, Elk) give up much sooner and show "Failed to load post". Fix: wrap every resolveAuthor() call in a Promise.race() with a 5 s timeout. The interaction is still recorded in ap_interactions and the Like/Announce activity is still sent when recipient resolution succeeds within the window; if it times out, AP delivery is silently skipped (the local record is kept — the client sees a successful ⭐). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,15 @@ export async function likePost({ targetUrl, federation, handle, publicationUrl,
|
||||
);
|
||||
|
||||
const documentLoader = await ctx.getDocumentLoader({ identifier: handle });
|
||||
const recipient = await resolveAuthor(targetUrl, ctx, documentLoader, collections);
|
||||
// resolveAuthor makes up to 3 signed HTTP requests to the remote server.
|
||||
// Cap at 5 s so a slow/unreachable remote never blocks the client response.
|
||||
let recipient = null;
|
||||
try {
|
||||
recipient = await Promise.race([
|
||||
resolveAuthor(targetUrl, ctx, documentLoader, collections),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error("resolveAuthor timeout")), 5000)),
|
||||
]);
|
||||
} catch { /* skip AP delivery — interaction is still recorded locally */ }
|
||||
|
||||
const uuid = crypto.randomUUID();
|
||||
const baseUrl = publicationUrl.replace(/\/$/, "");
|
||||
@@ -95,7 +103,13 @@ export async function unlikePost({ targetUrl, federation, handle, publicationUrl
|
||||
);
|
||||
|
||||
const documentLoader = await ctx.getDocumentLoader({ identifier: handle });
|
||||
const recipient = await resolveAuthor(targetUrl, ctx, documentLoader, collections);
|
||||
let recipient = null;
|
||||
try {
|
||||
recipient = await Promise.race([
|
||||
resolveAuthor(targetUrl, ctx, documentLoader, collections),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error("resolveAuthor timeout")), 5000)),
|
||||
]);
|
||||
} catch { /* skip AP delivery */ }
|
||||
|
||||
if (recipient) {
|
||||
const like = new Like({
|
||||
@@ -160,9 +174,15 @@ export async function boostPost({ targetUrl, federation, handle, publicationUrl,
|
||||
orderingKey: targetUrl,
|
||||
});
|
||||
|
||||
// Also send directly to the original post author
|
||||
// Also send directly to the original post author (best-effort, 5 s cap)
|
||||
const documentLoader = await ctx.getDocumentLoader({ identifier: handle });
|
||||
const recipient = await resolveAuthor(targetUrl, ctx, documentLoader, collections);
|
||||
let recipient = null;
|
||||
try {
|
||||
recipient = await Promise.race([
|
||||
resolveAuthor(targetUrl, ctx, documentLoader, collections),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error("resolveAuthor timeout")), 5000)),
|
||||
]);
|
||||
} catch { /* skip author delivery — follower delivery already happened */ }
|
||||
if (recipient) {
|
||||
try {
|
||||
await ctx.sendActivity({ identifier: handle }, recipient, announce, {
|
||||
|
||||
Reference in New Issue
Block a user