@rmdes/indiekit-endpoint-activitypub 3.5.0 → 3.5.3
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 +24 -6
- 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);
|
|
@@ -174,7 +174,7 @@ router.get("/.well-known/oauth-authorization-server", (req, res) => {
|
|
|
174
174
|
|
|
175
175
|
router.get("/oauth/authorize", async (req, res, next) => {
|
|
176
176
|
try {
|
|
177
|
-
|
|
177
|
+
let {
|
|
178
178
|
client_id,
|
|
179
179
|
redirect_uri,
|
|
180
180
|
response_type,
|
|
@@ -184,6 +184,21 @@ router.get("/oauth/authorize", async (req, res, next) => {
|
|
|
184
184
|
force_login,
|
|
185
185
|
} = req.query;
|
|
186
186
|
|
|
187
|
+
// Restore OAuth params from session after login redirect.
|
|
188
|
+
// Indiekit's login flow doesn't re-encode the redirect param, so query
|
|
189
|
+
// params with & are stripped during the /session/login → /session/auth
|
|
190
|
+
// round-trip. We store them in the session before redirecting.
|
|
191
|
+
if (!response_type && req.session?.pendingOAuth) {
|
|
192
|
+
const p = req.session.pendingOAuth;
|
|
193
|
+
delete req.session.pendingOAuth;
|
|
194
|
+
client_id = p.client_id;
|
|
195
|
+
redirect_uri = p.redirect_uri;
|
|
196
|
+
response_type = p.response_type;
|
|
197
|
+
scope = p.scope;
|
|
198
|
+
code_challenge = p.code_challenge;
|
|
199
|
+
code_challenge_method = p.code_challenge_method;
|
|
200
|
+
}
|
|
201
|
+
|
|
187
202
|
if (response_type !== "code") {
|
|
188
203
|
return res.status(400).json({
|
|
189
204
|
error: "unsupported_response_type",
|
|
@@ -219,11 +234,14 @@ router.get("/oauth/authorize", async (req, res, next) => {
|
|
|
219
234
|
// Check if user is logged in via IndieAuth session
|
|
220
235
|
const session = req.session;
|
|
221
236
|
if (!session?.access_token && !force_login) {
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
237
|
+
// Store OAuth params in session — they won't survive Indiekit's
|
|
238
|
+
// login redirect chain due to a re-encoding bug in indieauth.js.
|
|
239
|
+
req.session.pendingOAuth = {
|
|
240
|
+
client_id, redirect_uri, response_type, scope,
|
|
241
|
+
code_challenge, code_challenge_method,
|
|
242
|
+
};
|
|
243
|
+
// Redirect to Indiekit's login page with a simple return path.
|
|
244
|
+
return res.redirect("/session/login?redirect=/oauth/authorize");
|
|
227
245
|
}
|
|
228
246
|
|
|
229
247
|
// Render simple authorization page
|
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.3",
|
|
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",
|