@rmdes/indiekit-endpoint-activitypub 1.0.25 → 1.0.27
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.
|
@@ -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,
|
package/lib/federation-bridge.js
CHANGED
|
@@ -44,8 +44,9 @@ export function fromExpressRequest(req) {
|
|
|
44
44
|
*
|
|
45
45
|
* @param {import("express").Response} res - Express response
|
|
46
46
|
* @param {Response} response - Standard Response from federation.fetch()
|
|
47
|
+
* @param {Request} [request] - Original request (for targeted patching)
|
|
47
48
|
*/
|
|
48
|
-
async function sendFedifyResponse(res, response) {
|
|
49
|
+
async function sendFedifyResponse(res, response, request) {
|
|
49
50
|
res.status(response.status);
|
|
50
51
|
response.headers.forEach((value, key) => {
|
|
51
52
|
res.setHeader(key, value);
|
|
@@ -56,6 +57,33 @@ async function sendFedifyResponse(res, response) {
|
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
// WORKAROUND: Fedify serializes endpoints with "type": "as:Endpoints"
|
|
61
|
+
// which is not a real ActivityStreams type (fails browser.pub validation).
|
|
62
|
+
// For actor JSON responses, buffer the body and strip the invalid type.
|
|
63
|
+
// See: https://github.com/fedify-dev/fedify/issues/576
|
|
64
|
+
// TODO: Remove this workaround when Fedify fixes the upstream issue.
|
|
65
|
+
const contentType = response.headers.get("content-type") || "";
|
|
66
|
+
const isActorJson =
|
|
67
|
+
contentType.includes("activity+json") ||
|
|
68
|
+
contentType.includes("ld+json");
|
|
69
|
+
|
|
70
|
+
if (isActorJson) {
|
|
71
|
+
const body = await response.text();
|
|
72
|
+
try {
|
|
73
|
+
const json = JSON.parse(body);
|
|
74
|
+
if (json.endpoints?.type) {
|
|
75
|
+
delete json.endpoints.type;
|
|
76
|
+
}
|
|
77
|
+
const patched = JSON.stringify(json);
|
|
78
|
+
res.setHeader("content-length", Buffer.byteLength(patched));
|
|
79
|
+
res.end(patched);
|
|
80
|
+
} catch {
|
|
81
|
+
// Not valid JSON — send as-is
|
|
82
|
+
res.end(body);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
59
87
|
const reader = response.body.getReader();
|
|
60
88
|
await new Promise((resolve) => {
|
|
61
89
|
function read({ done, value }) {
|
package/lib/inbox-listeners.js
CHANGED
|
@@ -85,9 +85,14 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
85
85
|
});
|
|
86
86
|
})
|
|
87
87
|
.on(Undo, async (ctx, undo) => {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
actorObj
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
actorObj
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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
|
|
247
|
-
|
|
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 =
|
|
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.
|
|
3
|
+
"version": "1.0.27",
|
|
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"
|