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 followingCollection = application?.collections?.get("ap_following");
|
||||||
const activitiesCollection =
|
const activitiesCollection =
|
||||||
application?.collections?.get("ap_activities");
|
application?.collections?.get("ap_activities");
|
||||||
|
const featuredCollection = application?.collections?.get("ap_featured");
|
||||||
|
const featuredTagsCollection =
|
||||||
|
application?.collections?.get("ap_featured_tags");
|
||||||
|
|
||||||
const followerCount = followersCollection
|
const followerCount = followersCollection
|
||||||
? await followersCollection.countDocuments()
|
? await followersCollection.countDocuments()
|
||||||
@@ -19,6 +22,12 @@ export function dashboardController(mountPath) {
|
|||||||
const followingCount = followingCollection
|
const followingCount = followingCollection
|
||||||
? await followingCollection.countDocuments()
|
? await followingCollection.countDocuments()
|
||||||
: 0;
|
: 0;
|
||||||
|
const pinnedCount = featuredCollection
|
||||||
|
? await featuredCollection.countDocuments()
|
||||||
|
: 0;
|
||||||
|
const tagCount = featuredTagsCollection
|
||||||
|
? await featuredTagsCollection.countDocuments()
|
||||||
|
: 0;
|
||||||
|
|
||||||
const recentActivities = activitiesCollection
|
const recentActivities = activitiesCollection
|
||||||
? await activitiesCollection
|
? await activitiesCollection
|
||||||
@@ -38,6 +47,8 @@ export function dashboardController(mountPath) {
|
|||||||
title: response.locals.__("activitypub.title"),
|
title: response.locals.__("activitypub.title"),
|
||||||
followerCount,
|
followerCount,
|
||||||
followingCount,
|
followingCount,
|
||||||
|
pinnedCount,
|
||||||
|
tagCount,
|
||||||
recentActivities,
|
recentActivities,
|
||||||
refollowStatus,
|
refollowStatus,
|
||||||
mountPath,
|
mountPath,
|
||||||
|
|||||||
+55
-23
@@ -85,9 +85,14 @@ export function registerInboxListeners(inboxChain, options) {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on(Undo, async (ctx, undo) => {
|
.on(Undo, async (ctx, undo) => {
|
||||||
const actorObj = await undo.getActor();
|
const actorUrl = undo.actorId?.href || "";
|
||||||
const actorUrl = actorObj?.id?.href || "";
|
let inner;
|
||||||
const inner = await undo.getObject();
|
try {
|
||||||
|
inner = await undo.getObject();
|
||||||
|
} catch {
|
||||||
|
// Inner activity not dereferenceable — can't determine what was undone
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (inner instanceof Follow) {
|
if (inner instanceof Follow) {
|
||||||
await collections.ap_followers.deleteOne({ actorUrl });
|
await collections.ap_followers.deleteOne({ actorUrl });
|
||||||
@@ -98,14 +103,14 @@ export function registerInboxListeners(inboxChain, options) {
|
|||||||
summary: `${actorUrl} unfollowed you`,
|
summary: `${actorUrl} unfollowed you`,
|
||||||
});
|
});
|
||||||
} else if (inner instanceof Like) {
|
} else if (inner instanceof Like) {
|
||||||
const objectId = (await inner.getObject())?.id?.href || "";
|
const objectId = inner.objectId?.href || "";
|
||||||
await collections.ap_activities.deleteOne({
|
await collections.ap_activities.deleteOne({
|
||||||
type: "Like",
|
type: "Like",
|
||||||
actorUrl,
|
actorUrl,
|
||||||
objectUrl: objectId,
|
objectUrl: objectId,
|
||||||
});
|
});
|
||||||
} else if (inner instanceof Announce) {
|
} else if (inner instanceof Announce) {
|
||||||
const objectId = (await inner.getObject())?.id?.href || "";
|
const objectId = inner.objectId?.href || "";
|
||||||
await collections.ap_activities.deleteOne({
|
await collections.ap_activities.deleteOne({
|
||||||
type: "Announce",
|
type: "Announce",
|
||||||
actorUrl,
|
actorUrl,
|
||||||
@@ -194,18 +199,27 @@ export function registerInboxListeners(inboxChain, options) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on(Like, async (ctx, like) => {
|
.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
|
// Only log likes of our own content
|
||||||
const pubUrl = collections._publicationUrl;
|
const pubUrl = collections._publicationUrl;
|
||||||
if (!objectId || (pubUrl && !objectId.startsWith(pubUrl))) return;
|
if (!objectId || (pubUrl && !objectId.startsWith(pubUrl))) return;
|
||||||
|
|
||||||
const actorObj = await like.getActor();
|
const actorUrl = like.actorId?.href || "";
|
||||||
const actorUrl = actorObj?.id?.href || "";
|
let actorName = actorUrl;
|
||||||
const actorName =
|
try {
|
||||||
actorObj?.name?.toString() ||
|
const actorObj = await like.getActor();
|
||||||
actorObj?.preferredUsername?.toString() ||
|
actorName =
|
||||||
actorUrl;
|
actorObj?.name?.toString() ||
|
||||||
|
actorObj?.preferredUsername?.toString() ||
|
||||||
|
actorUrl;
|
||||||
|
} catch {
|
||||||
|
/* actor not dereferenceable — use URL */
|
||||||
|
}
|
||||||
|
|
||||||
await logActivity(collections, storeRawActivities, {
|
await logActivity(collections, storeRawActivities, {
|
||||||
direction: "inbound",
|
direction: "inbound",
|
||||||
@@ -217,18 +231,24 @@ export function registerInboxListeners(inboxChain, options) {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on(Announce, async (ctx, announce) => {
|
.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
|
// Only log boosts of our own content
|
||||||
const pubUrl = collections._publicationUrl;
|
const pubUrl = collections._publicationUrl;
|
||||||
if (!objectId || (pubUrl && !objectId.startsWith(pubUrl))) return;
|
if (!objectId || (pubUrl && !objectId.startsWith(pubUrl))) return;
|
||||||
|
|
||||||
const actorObj = await announce.getActor();
|
const actorUrl = announce.actorId?.href || "";
|
||||||
const actorUrl = actorObj?.id?.href || "";
|
let actorName = actorUrl;
|
||||||
const actorName =
|
try {
|
||||||
actorObj?.name?.toString() ||
|
const actorObj = await announce.getActor();
|
||||||
actorObj?.preferredUsername?.toString() ||
|
actorName =
|
||||||
actorUrl;
|
actorObj?.name?.toString() ||
|
||||||
|
actorObj?.preferredUsername?.toString() ||
|
||||||
|
actorUrl;
|
||||||
|
} catch {
|
||||||
|
/* actor not dereferenceable — use URL */
|
||||||
|
}
|
||||||
|
|
||||||
await logActivity(collections, storeRawActivities, {
|
await logActivity(collections, storeRawActivities, {
|
||||||
direction: "inbound",
|
direction: "inbound",
|
||||||
@@ -240,11 +260,23 @@ export function registerInboxListeners(inboxChain, options) {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on(Create, async (ctx, create) => {
|
.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;
|
if (!object) return;
|
||||||
|
|
||||||
const actorObj = await create.getActor();
|
const actorUrl = create.actorId?.href || "";
|
||||||
const actorUrl = actorObj?.id?.href || "";
|
let actorObj;
|
||||||
|
try {
|
||||||
|
actorObj = await create.getActor();
|
||||||
|
} catch {
|
||||||
|
// Actor not dereferenceable — use URL as fallback
|
||||||
|
actorObj = null;
|
||||||
|
}
|
||||||
const actorName =
|
const actorName =
|
||||||
actorObj?.name?.toString() ||
|
actorObj?.name?.toString() ||
|
||||||
actorObj?.preferredUsername?.toString() ||
|
actorObj?.preferredUsername?.toString() ||
|
||||||
@@ -284,7 +316,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on(Delete, async (ctx, del) => {
|
.on(Delete, async (ctx, del) => {
|
||||||
const objectId = (await del.getObject())?.id?.href || "";
|
const objectId = del.objectId?.href || "";
|
||||||
if (objectId) {
|
if (objectId) {
|
||||||
await collections.ap_activities.deleteMany({ objectUrl: objectId });
|
await collections.ap_activities.deleteMany({ objectUrl: objectId });
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
"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.",
|
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"indiekit",
|
"indiekit",
|
||||||
|
|||||||
@@ -22,6 +22,14 @@
|
|||||||
title: __("activitypub.activities"),
|
title: __("activitypub.activities"),
|
||||||
url: mountPath + "/admin/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"),
|
title: __("activitypub.profile.title"),
|
||||||
url: mountPath + "/admin/profile"
|
url: mountPath + "/admin/profile"
|
||||||
|
|||||||
Reference in New Issue
Block a user