fix: Accept(Follow) handler not matching incoming accepts

Two issues prevented Accept activities from transitioning ap_following
docs from refollow:sent to federation:

1. accept.getObject() often returns null because remote servers reference
   our outgoing Follow by URL, which Fedify can't resolve back. The strict
   instanceof Follow check caused early return on every Accept. Now we
   proceed to the MongoDB match if getObject() returns null or throws.

2. Batch processor sent Follow to entry.actorUrl but never updated the
   stored URL to the canonical form after resolving the remote actor.
   Now updates actorUrl to remoteActor.id.href so Accept handler matches.
This commit is contained in:
Ricardo
2026-02-20 09:40:20 +01:00
parent baac0a60bc
commit 13d939c146
3 changed files with 35 additions and 11 deletions
+17 -8
View File
@@ -233,23 +233,32 @@ async function processOneFollow(options, entry) {
throw new Error("Could not resolve remote actor");
}
// Send Follow activity
// Use the canonical actor URL (may differ from imported URL)
const canonicalUrl = remoteActor.id?.href || entry.actorUrl;
// Send Follow activity using canonical URL
const follow = new Follow({
actor: ctx.getActorUri(handle),
object: new URL(entry.actorUrl),
object: new URL(canonicalUrl),
});
await ctx.sendActivity({ identifier: handle }, remoteActor, follow);
// Mark as sent
// Mark as sent — update actorUrl to canonical form so Accept handler
// can match when the remote server responds
const updateFields = {
source: "refollow:sent",
refollowLastAttempt: new Date().toISOString(),
refollowError: null,
};
if (canonicalUrl !== entry.actorUrl) {
updateFields.actorUrl = canonicalUrl;
}
await collections.ap_following.updateOne(
{ _id: entry._id },
{
$set: {
source: "refollow:sent",
refollowLastAttempt: new Date().toISOString(),
refollowError: null,
},
$set: updateFields,
$inc: { refollowAttempts: 1 },
},
);
+17 -2
View File
@@ -125,8 +125,23 @@ export function registerInboxListeners(inboxChain, options) {
const actorUrl = actorObj?.id?.href || "";
if (!actorUrl) return;
const inner = await accept.getObject();
if (!(inner instanceof Follow)) return;
// Check if the inner object is a Follow. Some servers send the full
// Follow object, others send only a reference URL that Fedify can't
// resolve (since the original Follow was our outgoing activity).
let isFollow = false;
try {
const inner = await accept.getObject();
isFollow = inner instanceof Follow;
// If inner resolved to a non-Follow activity, skip
if (inner && !isFollow) {
console.info(
`[ActivityPub] Accept from ${actorUrl}: inner is ${inner.constructor?.name}, not Follow — skipping`,
);
return;
}
} catch {
// getObject() failed — proceed anyway if we have a pending follow
}
// Match against our following list for refollow or microsub-reader follows
const result = await collections.ap_following.findOneAndUpdate(
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@rmdes/indiekit-endpoint-activitypub",
"version": "1.0.14",
"version": "1.0.15",
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
"keywords": [
"indiekit",