| 1 | /**
|
|---|
| 2 | * Guardianship (FEP-633c) — the owner-only dashboard queues.
|
|---|
| 3 | *
|
|---|
| 4 | * Three OrderedCollections on the actor (shaer:queues), same contract as the
|
|---|
| 5 | * Shaer test daemon so the iOS/Android dashboards read them as-is:
|
|---|
| 6 | * - offers: pending handshake offers where I am a party (§3), with the full
|
|---|
| 7 | * accept tally so the client shows the right action
|
|---|
| 8 | * - follows: pending gated follows ON my wards (§5.3), Fase 2 (shaer-jdb)
|
|---|
| 9 | * - wards: my committed wards
|
|---|
| 10 | */
|
|---|
| 11 | import * as offers from './offers.js';
|
|---|
| 12 | import * as relations from './relations.js';
|
|---|
| 13 | import * as availability from './availability.js';
|
|---|
| 14 | import * as outgoing from './outgoing.js';
|
|---|
| 15 | import * as follows from './follows.js';
|
|---|
| 16 | import * as handshake from './handshake.js';
|
|---|
| 17 |
|
|---|
| 18 | const collection = (id, items) => ({
|
|---|
| 19 | id, type: 'OrderedCollection', totalItems: items.length, orderedItems: items,
|
|---|
| 20 | });
|
|---|
| 21 |
|
|---|
| 22 | /** Pending offers where the local site is a party, each with its accept
|
|---|
| 23 | * tally. The same collection carries the running lapses (§3.6.3) this
|
|---|
| 24 | * account is a party to, exactly as the daemon serves them, so the Shaer
|
|---|
| 25 | * clients render both without a second fetch. */
|
|---|
| 26 | export function offersCollection(id, slug, me) {
|
|---|
| 27 | // §4.2: a handshake whose candidate could not be dereferenced is deferred,
|
|---|
| 28 | // not decided, and the last Accept may already have landed — so nothing else
|
|---|
| 29 | // would ever retry it. This poll is the schedule. Not awaited: the read
|
|---|
| 30 | // answers with what is true now, and a retry that succeeds surfaces in the
|
|---|
| 31 | // next one. `listForParty` settles closed windows on the way past.
|
|---|
| 32 | handshake.retryDeferred(slug).catch(() => { /* the next read tries again */ });
|
|---|
| 33 | const items = offers.listForParty(slug, me).map((o) => offers.queueItem(o, me));
|
|---|
| 34 | items.push(...availability.lapseQueueItems(slug, me, Date.now()));
|
|---|
| 35 | return collection(id, items);
|
|---|
| 36 | }
|
|---|
| 37 |
|
|---|
| 38 | /**
|
|---|
| 39 | * Gate-verzoeken OP mijn wards die op mijn antwoord wachten (Guardianship Fase 2,
|
|---|
| 40 | * shaer-jdb). Dit was een lege stub: de gating zelf werkt sinds shaer-hxg, maar
|
|---|
| 41 | * werd nooit aan een C2S-client doorgegeven omdat de koers toen op de PWA lag.
|
|---|
| 42 | *
|
|---|
| 43 | * Twee bronnen, want een guardian kan wards op andere servers hebben en (nog)
|
|---|
| 44 | * op deze:
|
|---|
| 45 | * - ap_follow_reviews: de doorgestuurde kopie van een REMOTE ward
|
|---|
| 46 | * - ap_pending_follows: een ward op deze instance
|
|---|
| 47 | * Zie shaer-h6u: die tweede hoort op termijn ook over de lijn te gaan.
|
|---|
| 48 | */
|
|---|
| 49 | export function followsCollection(id, slug, me) {
|
|---|
| 50 | const items = follows.listReviewsByDirection(slug, 'incoming')
|
|---|
| 51 | .map((r) => follows.reviewQueueItem(r, me));
|
|---|
| 52 | for (const w of relations.listWards(slug)) {
|
|---|
| 53 | const wardSlug = slugOf(w.other_uri);
|
|---|
| 54 | if (!wardSlug) continue;
|
|---|
| 55 | for (const p of follows.listForWard(wardSlug)) {
|
|---|
| 56 | items.push({
|
|---|
| 57 | id: p.id, type: 'Follow', actor: p.follower_uri, object: w.other_uri,
|
|---|
| 58 | 'shaer:direction': 'incoming', 'shaer:ward': w.other_uri,
|
|---|
| 59 | 'shaer:follower': p.follower_uri, 'shaer:followerHandle': p.follower_handle || undefined,
|
|---|
| 60 | 'shaer:quorum': p.quorum || 'any', published: p.created_at,
|
|---|
| 61 | });
|
|---|
| 62 | }
|
|---|
| 63 | }
|
|---|
| 64 | return collection(id, items);
|
|---|
| 65 | }
|
|---|
| 66 |
|
|---|
| 67 | /** De slug van een actor-uri op DEZE instance, of null als hij elders woont. */
|
|---|
| 68 | function slugOf(uri) {
|
|---|
| 69 | const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
|
|---|
| 70 | if (!base || !String(uri || '').startsWith(`${base}/ap/users/`)) return null;
|
|---|
| 71 | return decodeURIComponent(String(uri).slice(`${base}/ap/users/`.length).split(/[/?#]/)[0]) || null;
|
|---|
| 72 | }
|
|---|
| 73 |
|
|---|
| 74 | /**
|
|---|
| 75 | * §5.3 uitgaand. Twee lezers, een wachtrij, en dat kan omdat §1 een ward en een
|
|---|
| 76 | * guardian wederzijds uitsluit: je bent het een of het ander.
|
|---|
| 77 | *
|
|---|
| 78 | * ALS WARD wat IK wil volgen en waar mijn guardians nog over moeten
|
|---|
| 79 | * ALS GUARDIAN wat mijn WARDS willen volgen en waar IK over moet (shaer-jdb)
|
|---|
| 80 | *
|
|---|
| 81 | * Dat tweede ontbrak. De wachtrij serveerde alleen listForWard(slug), en voor
|
|---|
| 82 | * een guardian is dat per definitie leeg -- dus het scherm "Your wards want to
|
|---|
| 83 | * follow" kon nooit iets tonen.
|
|---|
| 84 | */
|
|---|
| 85 | export function outgoingFollowsCollection(id, slug, me) {
|
|---|
| 86 | const items = outgoing.listForWard(slug).map((o) => outgoing.queueItem(o, me));
|
|---|
| 87 | for (const r of follows.listReviewsByDirection(slug, 'outgoing')) {
|
|---|
| 88 | items.push(follows.reviewQueueItem(r, me));
|
|---|
| 89 | }
|
|---|
| 90 | return collection(id, items);
|
|---|
| 91 | }
|
|---|
| 92 |
|
|---|
| 93 | /** The guardian's committed wards, with cached handle for display. */
|
|---|
| 94 | export function wardsCollection(id, slug) {
|
|---|
| 95 | const items = relations.listWards(slug)
|
|---|
| 96 | .map((r) => ({ id: r.other_uri, 'shaer:handle': r.other_handle || undefined, since: r.created_at }));
|
|---|
| 97 | return collection(id, items);
|
|---|
| 98 | }
|
|---|
| 99 |
|
|---|
| 100 | /** The ward's guardians with their availability (§3.6.1: never public,
|
|---|
| 101 | * owner-only): the real size of the safety net. Same shape as the daemon. */
|
|---|
| 102 | export function guardiansCollection(id, slug) {
|
|---|
| 103 | const uris = relations.listGuardians(slug).map((r) => r.other_uri);
|
|---|
| 104 | return collection(id, availability.statusesFor(slug, uris, Date.now()));
|
|---|
| 105 | }
|
|---|
| 106 |
|
|---|
| 107 | export default { offersCollection, followsCollection, outgoingFollowsCollection, wardsCollection, guardiansCollection };
|
|---|