Changeset dc8e9bd in Klonkt


Ignore:
Timestamp:
06/24/2026 02:31:09 PM (3 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
40cf2ef
Parents:
59f0170
Message:

fix(activitypub): deliver nested replies up the WHOLE chain (post author included)

resolveRemoteNote now walks the full inReplyTo chain and collects every ancestor
author's inbox, so a reply to a deeply-nested comment reaches the root post's
instance (and threads there), not just the immediate parent's.

Co-Authored-By: Claude <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    r59f0170 rdc8e9bd  
    526526    if (a) inboxes.add((a.endpoints && a.endpoints.sharedInbox) || a.inbox);
    527527  }
    528   if (parent.threadInbox) inboxes.add(parent.threadInbox); // post author's server (nesting)
     528  if (parent.threadInbox) inboxes.add(parent.threadInbox); // back-compat (single)
     529  (parent.threadInboxes || []).forEach((i) => inboxes.add(i)); // whole ancestor chain
    529530  for (const f of fStmts().list.all(site.slug)) inboxes.add(f.shared_inbox || f.inbox);
    530531  inboxes.delete(`${me}/inbox`);       // never deliver to ourselves (already in ap_outbox)
     
    552553  // link our reply to that local post so it shows nested in the post thread.
    553554  const localTgt = findThreadTarget(note.id, (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''));
    554   // If this note is itself a reply (a comment), also reach the original post's
    555   // author so THEIR server threads our reply under the comment.
    556   let threadInbox = null;
    557   if (note.inReplyTo) {
    558     const parentUrl = typeof note.inReplyTo === 'string' ? note.inReplyTo : (note.inReplyTo && note.inReplyTo.id);
    559     const parentNote = parentUrl ? await fetchActor(parentUrl).catch(() => null) : null;
    560     const pAtt = parentNote && (typeof parentNote.attributedTo === 'string' ? parentNote.attributedTo : (parentNote.attributedTo && parentNote.attributedTo.id));
    561     if (pAtt && pAtt !== actorUri) {
    562       const pa = await fetchActor(pAtt).catch(() => null);
    563       threadInbox = pa && ((pa.endpoints && pa.endpoints.sharedInbox) || pa.inbox);
     555  // Walk the WHOLE reply chain upward (comment → parent comment → … → root post)
     556  // and collect every ancestor author's inbox, so each participant's server —
     557  // including the original post's author — receives + threads our reply.
     558  const threadInboxes = [];
     559  const seenInbox = new Set();
     560  let cursor = note.inReplyTo, guard = 0;
     561  while (cursor && guard++ < 6) {
     562    const url = typeof cursor === 'string' ? cursor : (cursor && cursor.id);
     563    if (!url) break;
     564    const pn = await fetchActor(url).catch(() => null);
     565    if (!pn) break;
     566    const pa = typeof pn.attributedTo === 'string' ? pn.attributedTo : (pn.attributedTo && pn.attributedTo.id);
     567    if (pa && pa !== actorUri) {
     568      const paDoc = await fetchActor(pa).catch(() => null);
     569      const inbox = paDoc && ((paDoc.endpoints && paDoc.endpoints.sharedInbox) || paDoc.inbox);
     570      if (inbox && !seenInbox.has(inbox)) { seenInbox.add(inbox); threadInboxes.push(inbox); }
    564571    }
     572    cursor = pn.inReplyTo; // climb to the next ancestor
    565573  }
    566574  const rawHtml = String(note.content || '').replace(/\[\[(track|album|playlist):[^\]]+\]\]/gi, '');
     
    578586    content: HtmlSanitizerService.sanitize(rawHtml),       // full, sanitized
    579587    images,
    580     threadInbox,                                            // post author's inbox (if a comment)
     588    threadInboxes,                                          // every ancestor author's inbox
    581589    localPostId: localTgt ? localTgt.post_id : '',          // our post this belongs to (if any)
    582590    preview: HtmlSanitizerService.toPlainText(note.content || '').slice(0, 240),
Note: See TracChangeset for help on using the changeset viewer.