Changeset fb30b5d in Klonkt


Ignore:
Timestamp:
07/21/2026 11:10:32 PM (7 weeks ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
4c70ecb
Parents:
e2ea5c4
Message:

C2S inbox carries friends' media as AS2 attachments

Shaer showed images on own posts (outbox serialises attachment) but not on
friends' posts: the C2S inbox GET built its Create(Note) items without the
stored media. The timeline rows already keep media_json ([{url, type}],
written on the inbound Create); it just never reached the wire. A new pure
helper maps it to an AS2 attachment array so clients render friends' images
and audio exactly like own outbox posts.

Changed files:
src/services/ActivityPubService.js

  • timelineAttachments(mediaJson): media_json -> AS2 attachment array (Document + mediaType + url); empty or malformed JSON yields undefined and never blocks the item
  • exported alongside getTimeline

src/routes/activitypub.js

  • inbox GET: object.attachment = AP.timelineAttachments(t.media_json)

test/c2s-inbox.test.js

  • covers the mapping, the unknown-mediaType case, dropped rows without url, and malformed media_json (148 tests total, all green)

Alt text is not in media_json yet, so attachment.name stays empty for now.

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

Files:
3 edited

Legend:

Unmodified
Added
Removed
  • src/routes/activitypub.js

    re2ea5c4 rfb30b5d  
    112112      sensitive: !!t.nsfw,
    113113      summary: t.cw || undefined,
     114      // Friends' media travels along (media_json → AS2 attachment), so the
     115      // client renders their images/audio like own outbox posts.
     116      attachment: AP.timelineAttachments(t.media_json),
    114117    },
    115118  }));
  • src/services/ActivityPubService.js

    re2ea5c4 rfb30b5d  
    24342434export function getTimeline(slug, limit, offset) { return tlStmts().list.all(slug, limit || 50, offset || 0); }
    24352435
     2436// Inbox C2S read: a timeline row's media_json ([{url, type}], written on the
     2437// inbound Create) → AS2 `attachment` array, so a client (Shaer) can render a
     2438// friend's images/audio/video natively, exactly like own outbox posts. The
     2439// stored `type` is the mediaType and may be ''. Malformed JSON yields
     2440// undefined and never blocks the item.
     2441export function timelineAttachments(mediaJson) {
     2442  try {
     2443    const list = mediaJson ? JSON.parse(mediaJson) : [];
     2444    const rows = (Array.isArray(list) ? list : [])
     2445      .filter((m) => m && m.url)
     2446      .map((m) => ({ type: 'Document', mediaType: m.type || undefined, url: m.url }));
     2447    return rows.length ? rows : undefined;
     2448  } catch { return undefined; }
     2449}
     2450
    24362451// ── Cirkel = posts from the accounts you auto-boost ("feature an artist") ──
    24372452let _abCount, _cirkelPosts, _cirkelMembers;
     
    30903105  getInteractions, getInteractionById, setInteractionBoosted, setInteractionLiked, setMyReaction, getMyReactions, buildReplyNote, getOutboxNote, deliverReply, resolveRemoteNote,
    30913106  listOutbox, deliverOutboxDelete, deliverOutboxUpdate, deliverDirectNote,
    3092   webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, sendInteraction, voteOnPoll, voteOnRemotePoll,
     3107  webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, timelineAttachments, sendInteraction, voteOnPoll, voteOnRemotePoll,
    30933108  parseOwnPoll, pollTally, ownPollView, deliverPollUpdate, maybeCrawlThread, sendReport, localMentionSlugs,
    30943109  autoBoostCount, boostedCount, markBoosted, unmarkBoosted, markLiked, unmarkLiked, getTimelineReaction, upsertBoostedNote, getCirkelPosts, getCirkelMembers, selfHealTimeline,
  • test/c2s-inbox.test.js

    re2ea5c4 rfb30b5d  
    2626  assert.ok(t.published);
    2727});
     28
     29// Friends' media must reach the client: media_json ([{url, type}]) becomes the
     30// AS2 attachment array on the inbox item, like own outbox posts (Shaer P2).
     31test('media_json maps to AS2 attachments', () => {
     32  const rows = AP.timelineAttachments(JSON.stringify([
     33    { url: 'https://r.test/m/p.png', type: 'image/png' },
     34    { url: 'https://r.test/m/a.mp3', type: 'audio/mpeg' },
     35  ]));
     36  assert.equal(rows.length, 2);
     37  assert.deepEqual(rows[0], { type: 'Document', mediaType: 'image/png', url: 'https://r.test/m/p.png' });
     38  assert.deepEqual(rows[1], { type: 'Document', mediaType: 'audio/mpeg', url: 'https://r.test/m/a.mp3' });
     39});
     40
     41test('unknown mediaType stays undefined, bad rows drop', () => {
     42  const rows = AP.timelineAttachments(JSON.stringify([
     43    { url: 'https://r.test/m/x.bin', type: '' },
     44    { type: 'image/png' },              // no url -> dropped
     45  ]));
     46  assert.equal(rows.length, 1);
     47  assert.equal(rows[0].mediaType, undefined);
     48});
     49
     50test('empty or malformed media_json yields undefined and never throws', () => {
     51  assert.equal(AP.timelineAttachments(null), undefined);
     52  assert.equal(AP.timelineAttachments('[]'), undefined);
     53  assert.equal(AP.timelineAttachments('not json'), undefined);
     54  assert.equal(AP.timelineAttachments('{"not":"a list"}'), undefined);
     55});
Note: See TracChangeset for help on using the changeset viewer.