- Timestamp:
- 07/30/2026 11:17:05 PM (6 weeks ago)
- Branches:
- main
- Children:
- ef519a3
- Parents:
- 55eca8b
- Location:
- src
- Files:
-
- 2 edited
-
routes/activitypub.js (modified) (1 diff)
-
services/ActivityPubService.js (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/activitypub.js
r55eca8b r1a2f206 121 121 } 122 122 AP.sendAP(res, ob, audience === 'friend' ? 'private, no-store' : undefined); 123 }); 124 125 // ── Follow-QR (Robins verzoek, 31-7) ────────────────────────────── 126 // A PNG QR of share:social/follow/AP/@slug@host: the ward shows it in 127 // Account, a friend scans the SCREEN with the ordinary camera app and their 128 // Shaer opens with the follow question. Public on purpose: it encodes only 129 // the public handle, and the app's plain image loaders carry no bearer. 130 router.get('/ap/users/:slug/follow-qr.png', async (req, res) => { 131 const site = db.prepare('SELECT slug FROM sites WHERE slug = ?').get(req.params.slug); 132 if (!site) return res.status(404).end(); 133 try { 134 const host = new URL(baseUrl(req)).host; 135 const { default: QRCode } = await import('qrcode'); 136 const png = await QRCode.toBuffer(`share:social/follow/AP/@${site.slug}@${host}`, { width: 600, margin: 1 }); 137 res.set('Content-Type', 'image/png'); 138 res.set('Cache-Control', 'public, max-age=86400'); 139 res.send(png); 140 } catch (e) { 141 console.warn('[AP] follow-qr failed:', e && e.message); 142 res.status(500).end(); 143 } 123 144 }); 124 145 -
src/services/ActivityPubService.js
r55eca8b r1a2f206 2383 2383 const actorUri = c2sIdOf(object); 2384 2384 if (!actorUri) return { status: 400, error: 'missing_object' }; 2385 await followActor(site, actorUri); 2385 // The error REACHES the app (Robins melding, 31-7): swallowing it 2386 // made a failed follow look exactly like a successful one. 2387 const r = await followActor(site, actorUri); 2388 if (r && r.error) return { status: 502, error: 'follow_failed', detail: r.error }; 2386 2389 return { status: 202, url: actorUri }; 2387 2390 } … … 3645 3648 else actorUrl = null; 3646 3649 if (!actorUrl) return { error: 'not_found' }; 3647 const actor = await fetchActor(actorUrl).catch(() => null); 3650 // SIGNED, as this actor: an authorized-fetch instance refuses an anonymous 3651 // GET of the actor doc, which made following from a boost silently fail 3652 // (Robins melding, 31-7). Signed, the other side sees who asks. 3653 const actor = await signedGetJson(site.slug, actorUrl); 3648 3654 if (!actor || !actor.id || !actor.inbox) return { error: 'unreachable' }; 3649 3655 const ai = actorInfo(actor, actor.id); … … 3660 3666 // Follow + feature in one step → backfill their recent posts into the Cirkel right away. 3661 3667 if (autoBoost) backfillFromOutbox(site.slug, actor.id).catch(() => {}); 3668 // A ward's guardians are TOLD about a new follow (Robins verzoek, 31-7): 3669 // a follow brings new content into the child's feed, and the village 3670 // should know the door opened. A direct note per guardian, best-effort; 3671 // FEP-633c 5.3 gates inbound follows, the outbound notice is Shaer policy 3672 // for now (bead: spec-vraag). 3673 try { 3674 const guardians = Guardianship.listGuardians(site.slug); 3675 if (guardians.length) { 3676 const meRef = actorId(base, site.slug); 3677 const esc = (t) => String(t).replace(/[<>&]/g, (c) => ({ '<': '<', '>': '>', '&': '&' }[c])); 3678 const label = esc(ai.name || ai.handle || actor.id); 3679 for (const g of guardians) { 3680 const note = { 3681 id: `${meRef}/follow-notice/${Date.now().toString(36)}${rid()}`, 3682 type: 'Note', attributedTo: meRef, to: [g.other_uri], 3683 tag: [{ type: 'Mention', href: g.other_uri }], 3684 content: `<p>👀 ${esc(site.title || site.slug)} is now following ${label}.</p>`, 3685 }; 3686 deliverToActor(site, g.other_uri, { id: `${note.id}#create`, type: 'Create', actor: meRef, to: [g.other_uri], object: note }) 3687 .catch(() => { /* retried by the queue */ }); 3688 } 3689 console.log('[AP] follow notice →', guardians.length, 'guardian(s) of', site.slug); 3690 } 3691 } catch { /* geen guardians is geen fout */ } 3662 3692 return { ok: true, name: ai.name, handle: ai.handle, actor: actor.id }; 3663 3693 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)