@rmdes/indiekit-endpoint-activitypub 1.0.26 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -149,11 +149,11 @@ export default class ActivityPubEndpoint {
149
149
  router.get("/admin/following", followingController(mp));
150
150
  router.get("/admin/activities", activitiesController(mp));
151
151
  router.get("/admin/featured", featuredGetController(mp));
152
- router.post("/admin/featured/pin", featuredPinController());
153
- router.post("/admin/featured/unpin", featuredUnpinController());
152
+ router.post("/admin/featured/pin", featuredPinController(mp));
153
+ router.post("/admin/featured/unpin", featuredUnpinController(mp));
154
154
  router.get("/admin/tags", featuredTagsGetController(mp));
155
- router.post("/admin/tags/add", featuredTagsAddController());
156
- router.post("/admin/tags/remove", featuredTagsRemoveController());
155
+ router.post("/admin/tags/add", featuredTagsAddController(mp));
156
+ router.post("/admin/tags/remove", featuredTagsRemoveController(mp));
157
157
  router.get("/admin/profile", profileGetController(mp));
158
158
  router.post("/admin/profile", profilePostController(mp));
159
159
  router.get("/admin/migrate", migrateGetController(mp, this.options));
@@ -12,6 +12,9 @@ export function dashboardController(mountPath) {
12
12
  const followingCollection = application?.collections?.get("ap_following");
13
13
  const activitiesCollection =
14
14
  application?.collections?.get("ap_activities");
15
+ const featuredCollection = application?.collections?.get("ap_featured");
16
+ const featuredTagsCollection =
17
+ application?.collections?.get("ap_featured_tags");
15
18
 
16
19
  const followerCount = followersCollection
17
20
  ? await followersCollection.countDocuments()
@@ -19,6 +22,12 @@ export function dashboardController(mountPath) {
19
22
  const followingCount = followingCollection
20
23
  ? await followingCollection.countDocuments()
21
24
  : 0;
25
+ const pinnedCount = featuredCollection
26
+ ? await featuredCollection.countDocuments()
27
+ : 0;
28
+ const tagCount = featuredTagsCollection
29
+ ? await featuredTagsCollection.countDocuments()
30
+ : 0;
22
31
 
23
32
  const recentActivities = activitiesCollection
24
33
  ? await activitiesCollection
@@ -38,6 +47,8 @@ export function dashboardController(mountPath) {
38
47
  title: response.locals.__("activitypub.title"),
39
48
  followerCount,
40
49
  followingCount,
50
+ pinnedCount,
51
+ tagCount,
41
52
  recentActivities,
42
53
  refollowStatus,
43
54
  mountPath,
@@ -24,7 +24,7 @@ export function featuredTagsGetController(mountPath) {
24
24
  };
25
25
  }
26
26
 
27
- export function featuredTagsAddController() {
27
+ export function featuredTagsAddController(mountPath) {
28
28
  return async (request, response, next) => {
29
29
  try {
30
30
  const { application } = request.app.locals;
@@ -44,14 +44,14 @@ export function featuredTagsAddController() {
44
44
  { upsert: true },
45
45
  );
46
46
 
47
- response.redirect("back");
47
+ response.redirect(`${mountPath}/admin/tags`);
48
48
  } catch (error) {
49
49
  next(error);
50
50
  }
51
51
  };
52
52
  }
53
53
 
54
- export function featuredTagsRemoveController() {
54
+ export function featuredTagsRemoveController(mountPath) {
55
55
  return async (request, response, next) => {
56
56
  try {
57
57
  const { application } = request.app.locals;
@@ -63,7 +63,7 @@ export function featuredTagsRemoveController() {
63
63
 
64
64
  await collection.deleteOne({ tag });
65
65
 
66
- response.redirect("back");
66
+ response.redirect(`${mountPath}/admin/tags`);
67
67
  } catch (error) {
68
68
  next(error);
69
69
  }
@@ -69,7 +69,7 @@ export function featuredGetController(mountPath) {
69
69
  };
70
70
  }
71
71
 
72
- export function featuredPinController() {
72
+ export function featuredPinController(mountPath) {
73
73
  return async (request, response, next) => {
74
74
  try {
75
75
  const { application } = request.app.locals;
@@ -90,14 +90,14 @@ export function featuredPinController() {
90
90
  { upsert: true },
91
91
  );
92
92
 
93
- response.redirect("back");
93
+ response.redirect(`${mountPath}/admin/featured`);
94
94
  } catch (error) {
95
95
  next(error);
96
96
  }
97
97
  };
98
98
  }
99
99
 
100
- export function featuredUnpinController() {
100
+ export function featuredUnpinController(mountPath) {
101
101
  return async (request, response, next) => {
102
102
  try {
103
103
  const { application } = request.app.locals;
@@ -109,7 +109,7 @@ export function featuredUnpinController() {
109
109
 
110
110
  await collection.deleteOne({ postUrl });
111
111
 
112
- response.redirect("back");
112
+ response.redirect(`${mountPath}/admin/featured`);
113
113
  } catch (error) {
114
114
  next(error);
115
115
  }
@@ -85,9 +85,14 @@ export function registerInboxListeners(inboxChain, options) {
85
85
  });
86
86
  })
87
87
  .on(Undo, async (ctx, undo) => {
88
- const actorObj = await undo.getActor();
89
- const actorUrl = actorObj?.id?.href || "";
90
- const inner = await undo.getObject();
88
+ const actorUrl = undo.actorId?.href || "";
89
+ let inner;
90
+ try {
91
+ inner = await undo.getObject();
92
+ } catch {
93
+ // Inner activity not dereferenceable — can't determine what was undone
94
+ return;
95
+ }
91
96
 
92
97
  if (inner instanceof Follow) {
93
98
  await collections.ap_followers.deleteOne({ actorUrl });
@@ -98,14 +103,14 @@ export function registerInboxListeners(inboxChain, options) {
98
103
  summary: `${actorUrl} unfollowed you`,
99
104
  });
100
105
  } else if (inner instanceof Like) {
101
- const objectId = (await inner.getObject())?.id?.href || "";
106
+ const objectId = inner.objectId?.href || "";
102
107
  await collections.ap_activities.deleteOne({
103
108
  type: "Like",
104
109
  actorUrl,
105
110
  objectUrl: objectId,
106
111
  });
107
112
  } else if (inner instanceof Announce) {
108
- const objectId = (await inner.getObject())?.id?.href || "";
113
+ const objectId = inner.objectId?.href || "";
109
114
  await collections.ap_activities.deleteOne({
110
115
  type: "Announce",
111
116
  actorUrl,
@@ -194,18 +199,27 @@ export function registerInboxListeners(inboxChain, options) {
194
199
  }
195
200
  })
196
201
  .on(Like, async (ctx, like) => {
197
- const objectId = (await like.getObject())?.id?.href || "";
202
+ // Use .objectId to get the URL without dereferencing the remote object.
203
+ // Calling .getObject() would trigger an HTTP fetch to the remote server,
204
+ // which fails with 404 when the server has Authorized Fetch (Secure Mode)
205
+ // enabled — causing pointless retries and log spam.
206
+ const objectId = like.objectId?.href || "";
198
207
 
199
208
  // Only log likes of our own content
200
209
  const pubUrl = collections._publicationUrl;
201
210
  if (!objectId || (pubUrl && !objectId.startsWith(pubUrl))) return;
202
211
 
203
- const actorObj = await like.getActor();
204
- const actorUrl = actorObj?.id?.href || "";
205
- const actorName =
206
- actorObj?.name?.toString() ||
207
- actorObj?.preferredUsername?.toString() ||
208
- actorUrl;
212
+ const actorUrl = like.actorId?.href || "";
213
+ let actorName = actorUrl;
214
+ try {
215
+ const actorObj = await like.getActor();
216
+ actorName =
217
+ actorObj?.name?.toString() ||
218
+ actorObj?.preferredUsername?.toString() ||
219
+ actorUrl;
220
+ } catch {
221
+ /* actor not dereferenceable — use URL */
222
+ }
209
223
 
210
224
  await logActivity(collections, storeRawActivities, {
211
225
  direction: "inbound",
@@ -217,18 +231,24 @@ export function registerInboxListeners(inboxChain, options) {
217
231
  });
218
232
  })
219
233
  .on(Announce, async (ctx, announce) => {
220
- const objectId = (await announce.getObject())?.id?.href || "";
234
+ // Use .objectId no remote fetch needed (see Like handler comment)
235
+ const objectId = announce.objectId?.href || "";
221
236
 
222
237
  // Only log boosts of our own content
223
238
  const pubUrl = collections._publicationUrl;
224
239
  if (!objectId || (pubUrl && !objectId.startsWith(pubUrl))) return;
225
240
 
226
- const actorObj = await announce.getActor();
227
- const actorUrl = actorObj?.id?.href || "";
228
- const actorName =
229
- actorObj?.name?.toString() ||
230
- actorObj?.preferredUsername?.toString() ||
231
- actorUrl;
241
+ const actorUrl = announce.actorId?.href || "";
242
+ let actorName = actorUrl;
243
+ try {
244
+ const actorObj = await announce.getActor();
245
+ actorName =
246
+ actorObj?.name?.toString() ||
247
+ actorObj?.preferredUsername?.toString() ||
248
+ actorUrl;
249
+ } catch {
250
+ /* actor not dereferenceable — use URL */
251
+ }
232
252
 
233
253
  await logActivity(collections, storeRawActivities, {
234
254
  direction: "inbound",
@@ -240,11 +260,23 @@ export function registerInboxListeners(inboxChain, options) {
240
260
  });
241
261
  })
242
262
  .on(Create, async (ctx, create) => {
243
- const object = await create.getObject();
263
+ let object;
264
+ try {
265
+ object = await create.getObject();
266
+ } catch {
267
+ // Remote object not dereferenceable (Authorized Fetch, deleted, etc.)
268
+ return;
269
+ }
244
270
  if (!object) return;
245
271
 
246
- const actorObj = await create.getActor();
247
- const actorUrl = actorObj?.id?.href || "";
272
+ const actorUrl = create.actorId?.href || "";
273
+ let actorObj;
274
+ try {
275
+ actorObj = await create.getActor();
276
+ } catch {
277
+ // Actor not dereferenceable — use URL as fallback
278
+ actorObj = null;
279
+ }
248
280
  const actorName =
249
281
  actorObj?.name?.toString() ||
250
282
  actorObj?.preferredUsername?.toString() ||
@@ -284,7 +316,7 @@ export function registerInboxListeners(inboxChain, options) {
284
316
  });
285
317
  })
286
318
  .on(Delete, async (ctx, del) => {
287
- const objectId = (await del.getObject())?.id?.href || "";
319
+ const objectId = del.objectId?.href || "";
288
320
  if (objectId) {
289
321
  await collections.ap_activities.deleteMany({ objectUrl: objectId });
290
322
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-activitypub",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
5
5
  "keywords": [
6
6
  "indiekit",
@@ -22,6 +22,14 @@
22
22
  title: __("activitypub.activities"),
23
23
  url: mountPath + "/admin/activities"
24
24
  },
25
+ {
26
+ title: pinnedCount + " " + __("activitypub.featured"),
27
+ url: mountPath + "/admin/featured"
28
+ },
29
+ {
30
+ title: tagCount + " " + __("activitypub.featuredTags"),
31
+ url: mountPath + "/admin/tags"
32
+ },
25
33
  {
26
34
  title: __("activitypub.profile.title"),
27
35
  url: mountPath + "/admin/profile"