@rmdes/indiekit-endpoint-activitypub 3.5.0 → 3.5.2
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 +8 -0
- package/lib/inbox-listeners.js +23 -0
- package/lib/inbox-queue.js +21 -0
- package/lib/mastodon/routes/oauth.js +4 -3
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -224,6 +224,14 @@ export default class ActivityPubEndpoint {
|
|
|
224
224
|
// Skip Fedify for admin UI routes — they're handled by the
|
|
225
225
|
// authenticated `routes` getter, not the federation layer.
|
|
226
226
|
if (req.path.startsWith("/admin")) return next();
|
|
227
|
+
|
|
228
|
+
// Diagnostic: log inbox POSTs to detect federation stalls
|
|
229
|
+
if (req.method === "POST" && req.path.includes("inbox")) {
|
|
230
|
+
const ua = req.get("user-agent") || "unknown";
|
|
231
|
+
const bodyParsed = req.body !== undefined && Object.keys(req.body || {}).length > 0;
|
|
232
|
+
console.info(`[federation-diag] POST ${req.path} from=${ua.slice(0, 60)} bodyParsed=${bodyParsed} readable=${req.readable}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
227
235
|
return self._fedifyMiddleware(req, res, next);
|
|
228
236
|
});
|
|
229
237
|
|
package/lib/inbox-listeners.js
CHANGED
|
@@ -46,12 +46,31 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
46
46
|
|
|
47
47
|
const getAuthLoader = (ctx) => ctx.getDocumentLoader({ identifier: handle });
|
|
48
48
|
|
|
49
|
+
// Diagnostic: track listener invocations to detect federation stalls
|
|
50
|
+
const _diag = { count: 0, lastType: "", lastActor: "", lastAt: 0 };
|
|
51
|
+
const _diagInterval = setInterval(() => {
|
|
52
|
+
if (_diag.count > 0) {
|
|
53
|
+
console.info(`[inbox-diag] ${_diag.count} activities received in last 5min (last: ${_diag.lastType} from ${_diag.lastActor})`);
|
|
54
|
+
_diag.count = 0;
|
|
55
|
+
}
|
|
56
|
+
}, 5 * 60 * 1000);
|
|
57
|
+
// Prevent timer from keeping the process alive
|
|
58
|
+
_diagInterval.unref?.();
|
|
59
|
+
|
|
60
|
+
const _diagTrack = (type, actorUrl) => {
|
|
61
|
+
_diag.count++;
|
|
62
|
+
_diag.lastType = type;
|
|
63
|
+
_diag.lastActor = actorUrl?.split("/").pop() || "?";
|
|
64
|
+
_diag.lastAt = Date.now();
|
|
65
|
+
};
|
|
66
|
+
|
|
49
67
|
inboxChain
|
|
50
68
|
// ── Follow ──────────────────────────────────────────────────────
|
|
51
69
|
// Synchronous: Accept/Reject + follower storage (federation requirement)
|
|
52
70
|
// Async: notification + activity log
|
|
53
71
|
.on(Follow, async (ctx, follow) => {
|
|
54
72
|
const actorUrl = follow.actorId?.href || "";
|
|
73
|
+
_diagTrack("Follow", actorUrl);
|
|
55
74
|
if (await isServerBlocked(actorUrl, collections)) return;
|
|
56
75
|
await touchKeyFreshness(collections, actorUrl);
|
|
57
76
|
await resetDeliveryStrikes(collections, actorUrl);
|
|
@@ -226,6 +245,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
226
245
|
// ── Announce ────────────────────────────────────────────────────
|
|
227
246
|
.on(Announce, async (ctx, announce) => {
|
|
228
247
|
const actorUrl = announce.actorId?.href || "";
|
|
248
|
+
_diagTrack("Announce", actorUrl);
|
|
229
249
|
if (await isServerBlocked(actorUrl, collections)) return;
|
|
230
250
|
await touchKeyFreshness(collections, actorUrl);
|
|
231
251
|
await resetDeliveryStrikes(collections, actorUrl);
|
|
@@ -241,6 +261,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
241
261
|
// ── Create ──────────────────────────────────────────────────────
|
|
242
262
|
.on(Create, async (ctx, create) => {
|
|
243
263
|
const actorUrl = create.actorId?.href || "";
|
|
264
|
+
_diagTrack("Create", actorUrl);
|
|
244
265
|
if (await isServerBlocked(actorUrl, collections)) return;
|
|
245
266
|
await touchKeyFreshness(collections, actorUrl);
|
|
246
267
|
await resetDeliveryStrikes(collections, actorUrl);
|
|
@@ -291,6 +312,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
291
312
|
// ── Delete ──────────────────────────────────────────────────────
|
|
292
313
|
.on(Delete, async (ctx, del) => {
|
|
293
314
|
const actorUrl = del.actorId?.href || "";
|
|
315
|
+
_diagTrack("Delete", actorUrl);
|
|
294
316
|
if (await isServerBlocked(actorUrl, collections)) return;
|
|
295
317
|
await touchKeyFreshness(collections, actorUrl);
|
|
296
318
|
await resetDeliveryStrikes(collections, actorUrl);
|
|
@@ -320,6 +342,7 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
320
342
|
// ── Update ──────────────────────────────────────────────────────
|
|
321
343
|
.on(Update, async (ctx, update) => {
|
|
322
344
|
const actorUrl = update.actorId?.href || "";
|
|
345
|
+
_diagTrack("Update", actorUrl);
|
|
323
346
|
if (await isServerBlocked(actorUrl, collections)) return;
|
|
324
347
|
await touchKeyFreshness(collections, actorUrl);
|
|
325
348
|
await resetDeliveryStrikes(collections, actorUrl);
|
package/lib/inbox-queue.js
CHANGED
|
@@ -83,11 +83,32 @@ export async function enqueueActivity(collections, { activityType, actorUrl, obj
|
|
|
83
83
|
* @returns {NodeJS.Timeout} Interval ID (for cleanup)
|
|
84
84
|
*/
|
|
85
85
|
export function startInboxProcessor(collections, getCtx, handle) {
|
|
86
|
+
// Diagnostic: detect stuck processing items and log queue health
|
|
87
|
+
let _diagProcessed = 0;
|
|
88
|
+
const _diagInterval = setInterval(async () => {
|
|
89
|
+
try {
|
|
90
|
+
const stuck = await collections.ap_inbox_queue?.countDocuments({ status: "processing" }) || 0;
|
|
91
|
+
const pending = await collections.ap_inbox_queue?.countDocuments({ status: "pending" }) || 0;
|
|
92
|
+
if (stuck > 0 || _diagProcessed > 0 || pending > 10) {
|
|
93
|
+
console.info(`[inbox-queue-diag] processed=${_diagProcessed}/5min pending=${pending} stuck_processing=${stuck}`);
|
|
94
|
+
}
|
|
95
|
+
_diagProcessed = 0;
|
|
96
|
+
} catch { /* ignore */ }
|
|
97
|
+
}, 5 * 60 * 1000);
|
|
98
|
+
_diagInterval.unref?.();
|
|
99
|
+
|
|
86
100
|
const intervalId = setInterval(async () => {
|
|
87
101
|
try {
|
|
88
102
|
const ctx = getCtx();
|
|
89
103
|
if (ctx) {
|
|
104
|
+
const before = Date.now();
|
|
90
105
|
await processNextItem(collections, ctx, handle);
|
|
106
|
+
const elapsed = Date.now() - before;
|
|
107
|
+
if (elapsed > 0) _diagProcessed++;
|
|
108
|
+
// Warn if a single item takes too long (potential hang)
|
|
109
|
+
if (elapsed > 30_000) {
|
|
110
|
+
console.warn(`[inbox-queue-diag] slow item: ${elapsed}ms`);
|
|
111
|
+
}
|
|
91
112
|
}
|
|
92
113
|
} catch (error) {
|
|
93
114
|
console.error("[inbox-queue] Processor error:", error.message);
|
|
@@ -219,10 +219,11 @@ router.get("/oauth/authorize", async (req, res, next) => {
|
|
|
219
219
|
// Check if user is logged in via IndieAuth session
|
|
220
220
|
const session = req.session;
|
|
221
221
|
if (!session?.access_token && !force_login) {
|
|
222
|
-
// Not logged in — redirect to Indiekit login, then back here
|
|
223
|
-
|
|
222
|
+
// Not logged in — redirect to Indiekit's login page, then back here.
|
|
223
|
+
// Indiekit uses /session/login?redirect=<path> (see indieauth.js authenticate()).
|
|
224
|
+
// The redirect value must be a local path (validated by regex in indieauth.js).
|
|
224
225
|
return res.redirect(
|
|
225
|
-
`/
|
|
226
|
+
`/session/login?redirect=${encodeURIComponent(req.originalUrl)}`,
|
|
226
227
|
);
|
|
227
228
|
}
|
|
228
229
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.2",
|
|
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",
|