From ed18446e050b32dbd949faa211b234144085a15a Mon Sep 17 00:00:00 2001 From: svemagie <869694+svemagie@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:24:03 +0100 Subject: [PATCH] fix(mastodon): use lookupWithSecurity for remote profile resolution Replace direct ctx.lookupObject() call in resolveRemoteAccount with lookupWithSecurity() so servers that reject signed GETs are retried unsigned. Also add 5 s Promise.race timeouts to followers/following/ outbox collection fetches to prevent profile loads from hanging on slow remote servers. Fixes missing profile pictures and zero follower stats in Mastodon client views. Co-Authored-By: Claude Sonnet 4.6 --- lib/mastodon/helpers/resolve-account.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/mastodon/helpers/resolve-account.js b/lib/mastodon/helpers/resolve-account.js index cd4cbe4..811090c 100644 --- a/lib/mastodon/helpers/resolve-account.js +++ b/lib/mastodon/helpers/resolve-account.js @@ -6,6 +6,7 @@ */ import { serializeAccount } from "../entities/account.js"; import { cacheAccountStats } from "./account-cache.js"; +import { lookupWithSecurity } from "../../lookup-helpers.js"; /** * @param {string} acct - Account identifier (user@domain or URL) @@ -37,7 +38,9 @@ export async function resolveRemoteAccount(acct, pluginOptions, baseUrl) { return null; } - const actor = await ctx.lookupObject(actorUri); + // 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; // Extract data from the Fedify actor object @@ -61,20 +64,23 @@ export async function resolveRemoteAccount(acct, pluginOptions, baseUrl) { headerUrl = image?.url?.href || ""; } catch { /* ignore */ } - // Get collection counts (followers, following, outbox) + // Get collection counts (followers, following, outbox) — with 5 s timeout each + const withTimeout = (promise, ms = 5000) => + Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), ms))]); + let followersCount = 0; let followingCount = 0; let statusesCount = 0; try { - const followers = await actor.getFollowers(); + const followers = await withTimeout(actor.getFollowers()); if (followers?.totalItems != null) followersCount = followers.totalItems; } catch { /* ignore */ } try { - const following = await actor.getFollowing(); + const following = await withTimeout(actor.getFollowing()); if (following?.totalItems != null) followingCount = following.totalItems; } catch { /* ignore */ } try { - const outbox = await actor.getOutbox(); + const outbox = await withTimeout(actor.getOutbox()); if (outbox?.totalItems != null) statusesCount = outbox.totalItems; } catch { /* ignore */ }