fix: use .objectId accessors to prevent fetch errors from remote servers
Inbox handlers used await activity.getObject() which HTTP-fetches remote objects. This fails when remote servers have Authorized Fetch enabled or are unavailable, causing Fedify to retry ~10 times per activity. Replaced with .objectId/.actorId accessors (zero network requests) for Like, Announce, Undo, and Delete handlers. Wrapped remaining getObject() and getActor() calls in try-catch with fallback to ID accessors. Also adds Pinned Posts and Featured Tags cards to the admin dashboard.
This commit is contained in:
@@ -12,6 +12,9 @@ export function dashboardController(mountPath) {
|
||||
const followingCollection = application?.collections?.get("ap_following");
|
||||
const activitiesCollection =
|
||||
application?.collections?.get("ap_activities");
|
||||
const featuredCollection = application?.collections?.get("ap_featured");
|
||||
const featuredTagsCollection =
|
||||
application?.collections?.get("ap_featured_tags");
|
||||
|
||||
const followerCount = followersCollection
|
||||
? await followersCollection.countDocuments()
|
||||
@@ -19,6 +22,12 @@ export function dashboardController(mountPath) {
|
||||
const followingCount = followingCollection
|
||||
? await followingCollection.countDocuments()
|
||||
: 0;
|
||||
const pinnedCount = featuredCollection
|
||||
? await featuredCollection.countDocuments()
|
||||
: 0;
|
||||
const tagCount = featuredTagsCollection
|
||||
? await featuredTagsCollection.countDocuments()
|
||||
: 0;
|
||||
|
||||
const recentActivities = activitiesCollection
|
||||
? await activitiesCollection
|
||||
@@ -38,6 +47,8 @@ export function dashboardController(mountPath) {
|
||||
title: response.locals.__("activitypub.title"),
|
||||
followerCount,
|
||||
followingCount,
|
||||
pinnedCount,
|
||||
tagCount,
|
||||
recentActivities,
|
||||
refollowStatus,
|
||||
mountPath,
|
||||
|
||||
+47
-15
@@ -85,9 +85,14 @@ export function registerInboxListeners(inboxChain, options) {
|
||||
});
|
||||
})
|
||||
.on(Undo, async (ctx, undo) => {
|
||||
const actorObj = await undo.getActor();
|
||||
const actorUrl = actorObj?.id?.href || "";
|
||||
const inner = await undo.getObject();
|
||||
const actorUrl = undo.actorId?.href || "";
|
||||
let inner;
|
||||
try {
|
||||
inner = await undo.getObject();
|
||||
} catch {
|
||||
// Inner activity not dereferenceable — can't determine what was undone
|
||||
return;
|
||||
}
|
||||
|
||||
if (inner instanceof Follow) {
|
||||
await collections.ap_followers.deleteOne({ actorUrl });
|
||||
@@ -98,14 +103,14 @@ export function registerInboxListeners(inboxChain, options) {
|
||||
summary: `${actorUrl} unfollowed you`,
|
||||
});
|
||||
} else if (inner instanceof Like) {
|
||||
const objectId = (await inner.getObject())?.id?.href || "";
|
||||
const objectId = inner.objectId?.href || "";
|
||||
await collections.ap_activities.deleteOne({
|
||||
type: "Like",
|
||||
actorUrl,
|
||||
objectUrl: objectId,
|
||||
});
|
||||
} else if (inner instanceof Announce) {
|
||||
const objectId = (await inner.getObject())?.id?.href || "";
|
||||
const objectId = inner.objectId?.href || "";
|
||||
await collections.ap_activities.deleteOne({
|
||||
type: "Announce",
|
||||
actorUrl,
|
||||
@@ -194,18 +199,27 @@ export function registerInboxListeners(inboxChain, options) {
|
||||
}
|
||||
})
|
||||
.on(Like, async (ctx, like) => {
|
||||
const objectId = (await like.getObject())?.id?.href || "";
|
||||
// Use .objectId to get the URL without dereferencing the remote object.
|
||||
// Calling .getObject() would trigger an HTTP fetch to the remote server,
|
||||
// which fails with 404 when the server has Authorized Fetch (Secure Mode)
|
||||
// enabled — causing pointless retries and log spam.
|
||||
const objectId = like.objectId?.href || "";
|
||||
|
||||
// Only log likes of our own content
|
||||
const pubUrl = collections._publicationUrl;
|
||||
if (!objectId || (pubUrl && !objectId.startsWith(pubUrl))) return;
|
||||
|
||||
const actorUrl = like.actorId?.href || "";
|
||||
let actorName = actorUrl;
|
||||
try {
|
||||
const actorObj = await like.getActor();
|
||||
const actorUrl = actorObj?.id?.href || "";
|
||||
const actorName =
|
||||
actorName =
|
||||
actorObj?.name?.toString() ||
|
||||
actorObj?.preferredUsername?.toString() ||
|
||||
actorUrl;
|
||||
} catch {
|
||||
/* actor not dereferenceable — use URL */
|
||||
}
|
||||
|
||||
await logActivity(collections, storeRawActivities, {
|
||||
direction: "inbound",
|
||||
@@ -217,18 +231,24 @@ export function registerInboxListeners(inboxChain, options) {
|
||||
});
|
||||
})
|
||||
.on(Announce, async (ctx, announce) => {
|
||||
const objectId = (await announce.getObject())?.id?.href || "";
|
||||
// Use .objectId — no remote fetch needed (see Like handler comment)
|
||||
const objectId = announce.objectId?.href || "";
|
||||
|
||||
// Only log boosts of our own content
|
||||
const pubUrl = collections._publicationUrl;
|
||||
if (!objectId || (pubUrl && !objectId.startsWith(pubUrl))) return;
|
||||
|
||||
const actorUrl = announce.actorId?.href || "";
|
||||
let actorName = actorUrl;
|
||||
try {
|
||||
const actorObj = await announce.getActor();
|
||||
const actorUrl = actorObj?.id?.href || "";
|
||||
const actorName =
|
||||
actorName =
|
||||
actorObj?.name?.toString() ||
|
||||
actorObj?.preferredUsername?.toString() ||
|
||||
actorUrl;
|
||||
} catch {
|
||||
/* actor not dereferenceable — use URL */
|
||||
}
|
||||
|
||||
await logActivity(collections, storeRawActivities, {
|
||||
direction: "inbound",
|
||||
@@ -240,11 +260,23 @@ export function registerInboxListeners(inboxChain, options) {
|
||||
});
|
||||
})
|
||||
.on(Create, async (ctx, create) => {
|
||||
const object = await create.getObject();
|
||||
let object;
|
||||
try {
|
||||
object = await create.getObject();
|
||||
} catch {
|
||||
// Remote object not dereferenceable (Authorized Fetch, deleted, etc.)
|
||||
return;
|
||||
}
|
||||
if (!object) return;
|
||||
|
||||
const actorObj = await create.getActor();
|
||||
const actorUrl = actorObj?.id?.href || "";
|
||||
const actorUrl = create.actorId?.href || "";
|
||||
let actorObj;
|
||||
try {
|
||||
actorObj = await create.getActor();
|
||||
} catch {
|
||||
// Actor not dereferenceable — use URL as fallback
|
||||
actorObj = null;
|
||||
}
|
||||
const actorName =
|
||||
actorObj?.name?.toString() ||
|
||||
actorObj?.preferredUsername?.toString() ||
|
||||
@@ -284,7 +316,7 @@ export function registerInboxListeners(inboxChain, options) {
|
||||
});
|
||||
})
|
||||
.on(Delete, async (ctx, del) => {
|
||||
const objectId = (await del.getObject())?.id?.href || "";
|
||||
const objectId = del.objectId?.href || "";
|
||||
if (objectId) {
|
||||
await collections.ap_activities.deleteMany({ objectUrl: objectId });
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
||||
"version": "1.0.26",
|
||||
"version": "1.0.27",
|
||||
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
||||
"keywords": [
|
||||
"indiekit",
|
||||
|
||||
@@ -22,6 +22,14 @@
|
||||
title: __("activitypub.activities"),
|
||||
url: mountPath + "/admin/activities"
|
||||
},
|
||||
{
|
||||
title: pinnedCount + " " + __("activitypub.featured"),
|
||||
url: mountPath + "/admin/featured"
|
||||
},
|
||||
{
|
||||
title: tagCount + " " + __("activitypub.featuredTags"),
|
||||
url: mountPath + "/admin/tags"
|
||||
},
|
||||
{
|
||||
title: __("activitypub.profile.title"),
|
||||
url: mountPath + "/admin/profile"
|
||||
|
||||
Reference in New Issue
Block a user