Changeset 84903a1 in Klonkt


Ignore:
Timestamp:
06/26/2026 04:02:03 PM (2 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
d22b55c
Parents:
0fa59c3
Message:

feat(newspaper): embed followed Klonkt sites' audio player inline (option A)

A federated Klonkt audio post (content has the music note + a single-segment post URL)
now embeds the remote site's /embed?post=<slug> player in the feed. Only the followed
site's origin is added to THIS response's CSP frame-src (you follow them = trust); the
global policy stays locked. Mastodon multi-segment URLs are skipped.

Location:
src
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/routes/posts.js

    r0fa59c3 r84903a1  
    597597}
    598598
     599// A federated Klonkt audio post renders as "🎵 … listen on <link>". Embed the remote
     600// Klonkt player (its /embed?post=<slug>). A single-segment path = a Klonkt post slug
     601// (skips Mastodon /@user/123). The origin is whitelisted in the response CSP frame-src.
     602function klonktAudioEmbed(html, url) {
     603  if (!html || !url || html.indexOf('🎵') < 0) return null;
     604  let u; try { u = new URL(url); } catch { return null; }
     605  if (u.protocol !== 'https:' && u.protocol !== 'http:') return null;
     606  const slug = u.pathname.replace(/^\/+|\/+$/g, '');
     607  if (!slug || slug.indexOf('/') >= 0) return null; // single segment only
     608  const src = u.origin + '/embed?post=' + encodeURIComponent(slug);
     609  return { origin: u.origin, html: `<iframe class="tl-embed-frame tl-embed-klonkt" src="${src}" title="Audio" loading="lazy" frameborder="0" allow="autoplay; encrypted-media"></iframe>` };
     610}
     611
    599612router.get('/newspaper', requireSiteManager, (req, res) => {
    600613  const site = res.locals.site;
    601   const timeline = (site ? ActivityPubService.getTimeline(site.slug, 60) : [])
    602     .map((p) => ({ ...p, embedHtml: timelineEmbedHtml(p.content) }));
     614  const cspOrigins = new Set();
     615  const timeline = (site ? ActivityPubService.getTimeline(site.slug, 60) : []).map((p) => {
     616    let embedHtml = timelineEmbedHtml(p.content);
     617    if (!embedHtml) {
     618      const k = klonktAudioEmbed(p.content, p.url);
     619      if (k) { embedHtml = k.html; cspOrigins.add(k.origin); }
     620    }
     621    return { ...p, embedHtml };
     622  });
     623  // Option A: allow the followed Klonkt sites' player iframes (you follow them) by
     624  // extending ONLY this response's CSP frame-src. The global policy stays locked down.
     625  if (cspOrigins.size) {
     626    const csp = res.getHeader('Content-Security-Policy');
     627    if (csp) {
     628      const extra = [...cspOrigins].join(' ');
     629      res.setHeader('Content-Security-Policy', String(csp).replace(/frame-src ([^;]*)/i, (m, g) => `frame-src ${g} ${extra}`));
     630    }
     631  }
    603632  renderPage(req, res, 'pages/newspaper', {
    604     pageTitle: 'Tijdlijn', bodyClass: 'on-special',
     633    pageTitle: 'Newspaper', bodyClass: 'on-special',
    605634    timeline,
    606635    success: req.query.success || null, error: req.query.error || null,
  • src/views/pages/newspaper.ejs

    r0fa59c3 r84903a1  
    106106  .tl-embed-spotify { aspect-ratio: auto; height: 152px; }
    107107  .tl-embed-sc { aspect-ratio: auto; height: 166px; }
     108  .tl-embed-klonkt { aspect-ratio: auto; height: 180px; background: var(--paper, #fff); }
    108109
    109110  .tl-orig { display: inline-block; margin: .7rem 0 0; font-size: .82rem; color: var(--accent, #06c); text-decoration: none; }
Note: See TracChangeset for help on using the changeset viewer.