From f008a65ea239c05c84d4bbcb026a58dc17561d0b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 1 Apr 2026 15:08:53 -0400 Subject: [PATCH] feat: record AP parsing failures, save activity in db for 24h --- .../language/en-GB/admin/settings/activitypub.json | 3 ++- src/activitypub/index.js | 12 ++++++++++++ src/controllers/activitypub/index.js | 1 + src/views/admin/federation/analytics.tpl | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/public/language/en-GB/admin/settings/activitypub.json b/public/language/en-GB/admin/settings/activitypub.json index 7933b2024d..307c4c26a6 100644 --- a/public/language/en-GB/admin/settings/activitypub.json +++ b/public/language/en-GB/admin/settings/activitypub.json @@ -65,7 +65,8 @@ "content.break-string-help": "This delimiter can be manually inserted by power users when composing new topics. It instructs NodeBB to use content up until that point as part of the summary. If this string is not used, then the character count fallback applies. (Default: [...])", "content.world-default-cid": "Default category ID for "World" page composer", - "analytics.intro": "From this page you can view the state of your instance's federation with other servers", + "analytics.intro": "This page provides an overview of the state of your forum's federation with other instances/servers", + "analytics.details": "NodeBB tracks ActivityPub activity receipts and sends, and you are able to view them by hostname, either hourly or daily. Note that the metrics are only incremented on successful receipts and sends.", "analytics.by-hostname": "Filter by Hostname", "analytics.received": "Received Activities", "analytics.sent": "Sent Activites", diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 9959f97ffb..88952e35a6 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -481,6 +481,18 @@ ActivityPub.record.send = async ({ type, target }) => { ]); }; +ActivityPub.record.receiptError = async (body) => { + const { id, actor } = body; + const now = Date.now(); + const { hostname } = new URL(actor); + await Promise.all([ + db.sortedSetAdd('ap.errors', now, id), + db.set(`ap.errors:${id}`, JSON.stringify(body)), + analytics.increment(['ap.inErr', `ap.inErr:byHost:${hostname}`]), + ]); + await db.expire(`ap.errors:${id}`, 60 * 60 * 24); // 24 hours +}; + ActivityPub.buildRecipients = async function (object, options) { /** * - Builds a list of targets for activitypub.send to consume diff --git a/src/controllers/activitypub/index.js b/src/controllers/activitypub/index.js index 6291a451b0..5d9c9b0b94 100644 --- a/src/controllers/activitypub/index.js +++ b/src/controllers/activitypub/index.js @@ -272,6 +272,7 @@ Controller.postInbox = async (req, res) => { await activitypub.record.receipt(req.body); await helpers.formatApiResponse(202, res); } catch (e) { + activitypub.record.receiptError(req.body); helpers.formatApiResponse(500, res, e).catch(err => winston.error(err.stack)); } }; diff --git a/src/views/admin/federation/analytics.tpl b/src/views/admin/federation/analytics.tpl index 01011731ca..3d4d37a9ee 100644 --- a/src/views/admin/federation/analytics.tpl +++ b/src/views/admin/federation/analytics.tpl @@ -5,6 +5,7 @@

[[admin/settings/activitypub:analytics.intro]]

+

[[admin/settings/activitypub:analytics.details]]