Ignore:
Timestamp:
07/19/2026 03:53:49 PM (7 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
2d66d66
Parents:
bf72108
git-author:
Robin <roboburr@…> (07/19/2026 03:53:33 PM)
git-committer:
Robin <roboburr@…> (07/19/2026 03:53:49 PM)
Message:

Feature: C2S owner can read own followers/following (klonkt-demo-6kc)

The followers and following collections stay count-only for the public
(privacy), but a request carrying a C2S bearer scoped to that site (the account
owner) now returns the real actor URIs, so a client (Shaer) can build a friends
list. This was the one gap keeping the Shaer app's orbit empty against a real
Klonkt (it worked against the shaer-daemon, which serves the full lists).

  • buildFollowers/buildFollowing take an optional items array: when present, orderedItems carries the URIs and totalItems reflects them; otherwise count-only as before.
  • The two GET routes verify a bearer (OAuth.verifyBearer) and, when it is scoped to the requested slug, return the full list from ap_followers.actor_uri / ap_following.actor_uri (status=accepted); everyone else gets count-only. A private site's owner can read it even when it is not publicly listed.

3 new builder tests (count-only vs owner items vs empty owner list); 83 green.
Live-verified: owner bearer -> real URIs (alice/bob) in orderedItems; no bearer
-> orderedItems empty with the count intact; a token for another slug does not
unlock it.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    rbf72108 r4407c67  
    542542}
    543543
    544 export function buildFollowers(base, site, count) {
     544// Public callers get a count-only collection (privacy). The authenticated
     545// account owner (a C2S bearer scoped to this site) gets the real actor URIs via
     546// `items`, so their own client can build a friends list.
     547export function buildFollowers(base, site, count, items = null) {
    545548  const id = `${actorId(base, site.slug)}/followers`;
    546549  return {
     
    548551    id,
    549552    type: 'OrderedCollection',
    550     totalItems: count || 0,
    551     orderedItems: [], // hidden for privacy; count only
     553    totalItems: items ? items.length : (count || 0),
     554    orderedItems: items || [], // count-only for the public; full for the owner
    552555  };
    553556}
     
    555558// The accounts this site follows — count only, mirroring buildFollowers. The spec lists
    556559// `following` as a standard actor property; Hubzilla/Friendica + crawlers expect it.
    557 export function buildFollowing(base, site, count) {
     560export function buildFollowing(base, site, count, items = null) {
    558561  const id = `${actorId(base, site.slug)}/following`;
    559562  return {
     
    561564    id,
    562565    type: 'OrderedCollection',
    563     totalItems: count || 0,
    564     orderedItems: [], // count only
     566    totalItems: items ? items.length : (count || 0),
     567    orderedItems: items || [], // count-only for the public; full for the owner
    565568  };
    566569}
Note: See TracChangeset for help on using the changeset viewer.