Changeset 30871c1 in Klonkt for src/routes/activitypub.js


Ignore:
Timestamp:
07/20/2026 11:35:45 PM (7 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
562e1b0
Parents:
5143ccf
git-author:
Robin <roboburr@…> (07/20/2026 11:35:44 PM)
git-committer:
Robin <roboburr@…> (07/20/2026 11:35:45 PM)
Message:

Feature: enrichment is opt-in via Prefer (FEP-9876 conformance)

Klonkt is the reference implementation of FEP-9876. The owner
followers/following collections now enrich member representations only
when the client asks with Prefer: return=representation (RFC 7240),
echo Preference-Applied and always set Vary: Prefer; the default is
bare id strings, so the collections match the AP norm and existing
consumers are unaffected. The Prefer predicate is a pure, tested
function.

Changed files:
src/services/ActivityPubService.js

  • prefersEnriched(preferHeader): pure Prefer detector, exported

src/routes/activitypub.js

  • owner followers/following: enrich only on the preference, set Preference-Applied + Vary: Prefer

test/c2s-contacts.test.js

  • prefersEnriched cases

docs/shaer-c2s-api.md

  • document the Prefer opt-in

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/activitypub.js

    r5143ccf r30871c1  
    169169// URIs so their own client can build a friends list; everyone else gets the
    170170// count only (privacy).
     171// FEP-9876: enrichment is opt-in via `Prefer: return=representation` (RFC 7240).
     172// Returns true and sets the response headers when the owner asked for it.
     173function wantsEnriched(req, res) {
     174  res.set('Vary', 'Prefer');   // enriched and bare are two representations
     175  if (AP.prefersEnriched(req.get('Prefer'))) {
     176    res.set('Preference-Applied', 'return=representation');
     177    return true;
     178  }
     179  return false;
     180}
     181
    171182router.get('/ap/users/:slug/followers', (req, res) => {
    172183  const auth = OAuth.verifyBearer(req.headers.authorization);
     
    176187  if (owner) {
    177188    const uris = db.prepare('SELECT actor_uri FROM ap_followers WHERE slug = ? ORDER BY created_at').all(site.slug).map((r) => r.actor_uri);
    178     const items = uris.map((u) => AP.buildActorRef(site.slug, u));   // name + avatar (shaer-aa3)
     189    // Default = bare references; enrich only when the client asks (FEP-9876).
     190    const items = wantsEnriched(req, res) ? uris.map((u) => AP.buildActorRef(site.slug, u)) : uris;
    179191    return AP.sendAP(res, AP.buildFollowers(baseUrl(req), site, items.length, items));
    180192  }
     
    190202  if (!site) return res.status(404).end();
    191203  if (owner) {
     204    const enrich = wantsEnriched(req, res);   // FEP-9876 opt-in
    192205    let items = [];
    193     try { items = db.prepare("SELECT actor_uri FROM ap_following WHERE slug = ? AND status = 'accepted' ORDER BY created_at").all(site.slug).map((r) => AP.buildActorRef(site.slug, r.actor_uri)); } catch { /* table may not exist */ }
     206    try {
     207      const uris = db.prepare("SELECT actor_uri FROM ap_following WHERE slug = ? AND status = 'accepted' ORDER BY created_at").all(site.slug).map((r) => r.actor_uri);
     208      items = enrich ? uris.map((u) => AP.buildActorRef(site.slug, u)) : uris;
     209    } catch { /* table may not exist */ }
    194210    return AP.sendAP(res, AP.buildFollowing(baseUrl(req), site, items.length, items));
    195211  }
Note: See TracChangeset for help on using the changeset viewer.