Changeset e27b8db in Klonkt for src/routes
- Timestamp:
- 07/29/2026 10:04:36 AM (6 weeks ago)
- Branches:
- main
- Children:
- 329873e
- Parents:
- 88d7c8f
- Location:
- src/routes
- Files:
-
- 3 edited
-
activitypub.js (modified) (3 diffs)
-
guardian.js (modified) (6 diffs)
-
posts.js (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/activitypub.js
r88d7c8f re27b8db 154 154 const isWard = (() => { try { return Guardianship.listGuardians(auth.site.slug).length > 0; } catch { return false; } })(); 155 155 const embedsAllowed = Guardianship.externalEmbedsAllowed(auth.site.external_embeds, isWard); 156 // The heavier sibling (5.6): may a third party's PLAYER run inside the app, 157 // and may a link hand the child over to a browser? Both are the guardians' 158 // call, both default to off for a ward, and both need the preview gate open 159 // first: you cannot play, or follow, what you may not see. Served here so 160 // the app knows what it may offer instead of guessing. 161 const playbackAllowed = embedsAllowed 162 && Guardianship.externalPlaybackAllowed(auth.site.external_playback, isWard); 156 163 const items = AP.getTimeline(auth.site.slug, 60).map((t) => ({ 157 164 id: `${t.id}#create`, … … 206 213 // An external (non-fediverse) embed, thumbnail-only and never an iframe. 207 214 // Omitted entirely when the gate is closed (see above). 208 'shaer:embed': embedsAllowed ? AP.timelineEmbed(t.embed_json) : undefined, 215 // Carries shaer:playerUrl only when the playback gate is open too. 216 'shaer:embed': embedsAllowed ? AP.timelineEmbed(t.embed_json, { playback: playbackAllowed }) : undefined, 209 217 }, 210 218 })); … … 213 221 id: `${base}/ap/users/${auth.site.slug}/inbox`, 214 222 type: 'OrderedCollection', 223 // What this account may do with what is in here (FEP-633c 5.6). Owner-only 224 // by construction, and never on the public actor document: it says 225 // something about a child, and only the child and its guardians need it. 226 'shaer:capabilities': { 227 'shaer:externalEmbeds': embedsAllowed, 228 'shaer:externalPlayback': playbackAllowed, 229 // Leaving the app is the same decision as playing inside it: with the 230 // gate shut a link is shown but not followed, so the door is closed too 231 // and not just the picture over it. 232 'shaer:externalLinks': playbackAllowed, 233 }, 215 234 totalItems: items.length, 216 235 orderedItems: items, -
src/routes/guardian.js
r88d7c8f re27b8db 54 54 'away_title', 'away_sub', 'away_week', 'away_month', 'away_done', 55 55 // A gated-setting proposal from a fellow guardian (5.6). 56 'gated_title', 'gated_line_on', 'gated_line_off', 'gated_agree', 'gated_disagree']; 56 'gated_title', 'gated_line_on', 'gated_line_off', 'gated_agree', 'gated_disagree', 57 'play_propose', 'play_on', 'play_off']; 57 58 const s = Object.fromEntries(keys.map((k) => [k, i18nT(L, `guardian.${k}`)])); 58 59 s.wave = i18nT(L, 'guardian.wave'); … … 90 91 ...w, 91 92 embeds: wardEmbedSetting(w.other_uri), 93 playback: wardPlaybackSetting(w.other_uri), 92 94 guardians: wardGuardianStatuses(w.other_uri), 93 95 })), … … 466 468 * the ward lives elsewhere and the setting is not ours to show. 467 469 */ 468 function wardEmbedSetting(uri) { 470 function wardEmbedSetting(uri) { return wardGateSetting(uri, 'external_embeds'); } 471 /** The playback gate of a ward we host (5.6): the heavier sibling. */ 472 function wardPlaybackSetting(uri) { return wardGateSetting(uri, 'external_playback'); } 473 function wardGateSetting(uri, column) { 469 474 const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''); 470 475 if (!base || !String(uri || '').startsWith(`${base}/`)) return null; 471 476 const slug = String(uri).trim().replace(/\/+$/, '').split('/').pop(); 472 const row = slug ? db.prepare( 'SELECT external_embeds FROM sites WHERE slug = ?').get(slug) : null;477 const row = slug ? db.prepare(`SELECT ${column === 'external_playback' ? 'external_playback' : 'external_embeds'} AS v FROM sites WHERE slug = ?`).get(slug) : null; 473 478 if (!row) return null; 474 return row. external_embeds === null || row.external_embeds === undefined ? false : row.external_embeds=== 1;479 return row.v === null || row.v === undefined ? false : row.v === 1; 475 480 } 476 481 … … 480 485 // can move, and only a committed guardian of THAT ward may move it. 481 486 router.post('/wards/embeds', requireAuth, express.json({ limit: '4kb' }), (req, res) => { 487 req.body = { ...req.body, feature: req.body?.feature === 'shaer:externalPlayback' ? 'shaer:externalPlayback' : 'shaer:externalEmbeds' }; 488 return proposeGated(req, res); 489 }); 490 function proposeGated(req, res) { 482 491 const site = siteForUser(req); 483 492 if (!site) return res.status(404).json({ error: 'no_site' }); … … 496 505 const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''); 497 506 const me = AP.actorId(base, site.slug); 498 const feature = 'shaer:externalEmbeds';507 const feature = req.body.feature; // normalised by the route above 499 508 const offerId = `${me}/gated/${Date.now().toString(36)}${Math.floor(Math.random() * 1e4).toString(36)}`; 500 509 const offer = Guardianship.gated.buildGatedOffer(offerId, me, uri, feature, allow); … … 504 513 Guardianship.gated.rememberGatedOffer(offerId, localWard.slug, feature, allow); 505 514 const r = Guardianship.gated.recordGatedVote(localWard.slug, feature, me, allow); 515 // Same forward as the S2S path: without it the other guardians never learn 516 // the proposal exists and a threshold of two can never be met. 517 if (r.state === 'open') { 518 for (const g of Guardianship.listGuardians(localWard.slug).map((x) => x.other_uri)) { 519 if (g === me) continue; 520 AP.deliverToActor(site, g, { ...offer, to: [g] }).catch(() => { /* queued */ }); 521 } 522 } 506 523 return res.json({ ok: true, allow, state: r.state, need: r.need, of: r.of }); 507 524 } -
src/routes/posts.js
r88d7c8f re27b8db 1084 1084 function gateEmbeds(site, rows) { 1085 1085 if (!site || !rows.length) return rows; 1086 let isWard = false; 1087 try { isWard = Guardianship.listGuardians(site.slug).length > 0; } catch { /* no relations yet */ } 1088 if (Guardianship.externalEmbedsAllowed(site.external_embeds, isWard)) return rows; 1086 if (embedsAllowedFor(site)) return rows; 1089 1087 return rows.map((r) => (r && r.embed_json ? { ...r, embed_json: null } : r)); 1088 } 1089 1090 function isWardSite(site) { 1091 try { return !!site && Guardianship.listGuardians(site.slug).length > 0; } catch { return false; } 1092 } 1093 function embedsAllowedFor(site) { 1094 return !site || Guardianship.externalEmbedsAllowed(site.external_embeds, isWardSite(site)); 1095 } 1096 /** 1097 * May a third-party PLAYER run inside this page? (FEP-633c 5.6, the heavier 1098 * sibling of the preview gate.) This was the hole: the player iframe is built 1099 * from the note's content by timelineEmbedHtml, on a path that never touched 1100 * gateEmbeds. A ward whose guardians had allowed nothing still got the full 1101 * YouTube player on the web, while the app showed nothing at all: the heavy 1102 * thing open, the light thing shut. Playback also requires the preview gate, 1103 * because you cannot play what you may not see. 1104 */ 1105 function playbackAllowedFor(site) { 1106 if (!site) return true; 1107 if (!embedsAllowedFor(site)) return false; 1108 return Guardianship.externalPlaybackAllowed(site.external_playback, isWardSite(site)); 1090 1109 } 1091 1110 … … 1098 1117 const rows = gateEmbeds(site, site ? ActivityPubService.getTimeline(site.slug, FEED_PAGE + 1, offset) : []); 1099 1118 const hasMore = rows.length > FEED_PAGE; 1119 // Players (a third party's engine inside our page) ride the playback gate; 1120 // a Klonkt site's own audio embed is ours and stays. 1121 const mayPlay = playbackAllowedFor(site); 1100 1122 const timeline = rows.slice(0, FEED_PAGE).map((p) => { 1101 let embedHtml = timelineEmbedHtml(p.content);1123 let embedHtml = mayPlay ? timelineEmbedHtml(p.content) : null; 1102 1124 let content = p.content; 1103 1125 let embedUrl = null;
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)