openvibers/openvibe.community Commits

Screenshot cards: a 320/640 WebP from Media, eager only above the fold (#284)

OP OpenVibers committed 6 days ago · 3e37385dd0 · 4 files, +74 −13

{
	"message": "API rate limit exceeded for user ID 279216588. If you reach out to GitHub Support for help, please include the request ID AE62:10487D:15439FE:1E88D84:6AEF2014 and timestamp 2026-11-07 12:56:52 UTC. For more on scraping GitHub and how it may affect your rights, please review our Terms of Service (https:\/\/docs.github.com\/en\/site-policy\/github-terms\/github-terms-of-service)",
	"documentation_url": "https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api#rate-limiting",
	"status": "403"
}

Changes

ddiff --git a/server/app.js b/server/app.js
iindex 107abc6..60b0214 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -71,6 +71,16 @@ const cache = require('openvibe-shared/cache-policy');
const PUBLIC_DIR = path.join(__dirname, '..', 'public');
const SLUG_RE = /^[A-Za-z0-9_-]{1,80}$/;
++// A card asks the screenshot proxy for one of Media's two card widths (?w=320|640&fmt=webp). The
++// format is in the URL, never negotiated: a shared cache stores by URL and ignores Vary.
++const SHOT_CARD_WIDTHS = new Set([320, 640]);
++function screenshotVariant(query = {}) {
++ if (query.w === undefined && query.fmt === undefined) return null;
++ const w = Number(query.w);
++ const fmt = String(query.fmt || 'webp').toLowerCase();
++ if (!SHOT_CARD_WIDTHS.has(w) || fmt !== 'webp') return { error: 'w must be 320 or 640 and fmt webp' };
++ return { w, fmt };
++}
const pasteRef = (slug) => ({ service: 'community', type: 'paste', id: String(slug) });
const VERSION = require('../package.json').version;
// The date /.well-known/security.txt must be refreshed by: one year on, a person re-reads and re-commits it.
@@@ -188,18 +198,21 @@ async function createApp(opts = {}) {
return { key: o.id, url: o.url, size: o.size_bytes, mime, media_ref: o.id, private: visibility === 'private' };
},
/** A paste's screenshot bytes, for the proxy (a private object through a signed download). */
-- async bytes(row) {
++ async bytes(row, variant = null) {
++ // A card's ?w=&fmt= rides on the same URL Media answered: public objects take it openly, a
++ // private one through the signature already in the URL (Media checks both before resizing).
++ const withVariant = (url) => (!variant ? url : `${url}${url.includes('?') ? '&' : '?'}w=${variant.w}&fmt=${variant.fmt}`);
const ref = String(row.media_ref || '');
const id = /^med_[0-9A-HJKMNP-TV-Z]{26}$/.test(ref) ? ref : (String(row.screenshot_url || '').match(/\/o\/(med_[0-9A-HJKMNP-TV-Z]{26})/) || [])[1];
if (id && mediaObjects.configured) {
try {
-- const url = await mediaObjects.downloadUrl(id);
++ const url = withVariant(await mediaObjects.downloadUrl(id));
const res = await (opts.fetchImpl || globalThis.fetch)(url, { signal: AbortSignal.timeout(30_000) });
if (res.ok) return { body: Buffer.from(await res.arrayBuffer()), type: res.headers.get('content-type') || 'application/octet-stream' };
} catch { /* an unlisted object still answers its stored URL below */ }
}
if (!row.screenshot_url) return null;
-- const res = await (opts.fetchImpl || globalThis.fetch)(row.screenshot_url, { signal: AbortSignal.timeout(30_000) });
++ const res = await (opts.fetchImpl || globalThis.fetch)(withVariant(row.screenshot_url), { signal: AbortSignal.timeout(30_000) });
if (!res.ok) return null;
return { body: Buffer.from(await res.arrayBuffer()), type: res.headers.get('content-type') || 'application/octet-stream' };
},
@@@ -631,10 +644,12 @@ async function createApp(opts = {}) {
// token. The Media object's own URL is never handed to a browser.
app.get('/p/:slug/screenshot', withUser, async (req, res) => {
if (!SLUG_RE.test(req.params.slug)) return notFound(res);
++ const variant = screenshotVariant(req.query);
++ if (variant && variant.error) return res.status(400).type('text/plain').set('X-Content-Type-Options', 'nosniff').send(variant.error);
try {
await app.locals.pastes.screenshotUrl(req.viewer, req.params.slug);
const row = await db.prepare('SELECT media_ref, screenshot_url, visibility, burn_after_read, locked FROM pastes WHERE slug = ? AND deleted_at IS NULL').get(req.params.slug);
-- const got = row ? await app.locals.pasteMedia.bytes(row) : null;
++ const got = row ? await app.locals.pasteMedia.bytes(row, variant) : null;
if (!got) return notFound(res);
// A public paste's screenshot never changes under this URL, so it is cached like any other
// content-addressed asset: the home page shows six of them and each is several hundred KB,
ddiff --git a/server/render/pages.js b/server/render/pages.js
iindex bcfa84e..79b4ffa 100644
---- a/server/render/pages.js
++++ b/server/render/pages.js
@@@ -36,6 +36,13 @@ function mediaW(u, w) {
}
/** A screenshot's address: Community's checked proxy, never the Media object's own URL. */
function screenshotSrc(p) { return `/p/${encodeURIComponent(p.slug)}/screenshot`; }
++/** A card-sized screenshot through the same proxy: Media's ladder has 320 and 640, webp by URL. */
++function screenshotVariant(p, w) { return `${screenshotSrc(p)}?w=${w}&fmt=webp`; }
++/** A card screenshot: two widths, the card's shape in width/height (no shift), lazy unless it is the LCP. */
++function screenshotImg(p, { eager = false, sizes = '(max-width: 620px) calc(100vw - 40px), 300px' } = {}) {
++ const srcset = [320, 640].map((w) => `${screenshotVariant(p, w)} ${w}w`).join(', ');
++ return `<img src="${esc(screenshotVariant(p, 640))}" srcset="${esc(srcset)}" sizes="${esc(sizes)}" width="640" height="360" alt="${esc(p.title || 'Screenshot')}"${eager ? ' fetchpriority="high"' : ' loading="lazy"'} decoding="async">`;
++}
function timeAgo(v) {
const iso = ld.isoDate(v);
@@@ -107,13 +114,13 @@ function pagerHref(state, page) {
}
// ── Paste card (grid item) ───────────────────────────────────
--function pasteCard(p) {
++function pasteCard(p, { eager = false } = {}) {
const isShot = p.type === 'screenshot';
const locked = !!p.locked;
const preview = locked
? `<div class="card-locked"><i class="fa-solid fa-lock" aria-hidden="true"></i> Locked — OpenVibe can’t read what’s inside</div>`
: isShot
-- ? (p.screenshot_url ? `<div class="card-shot"><img src="${esc(screenshotSrc(p))}" alt="${esc(p.title || 'Screenshot')}" loading="lazy"></div>` : '')
++ ? (p.screenshot_url ? `<div class="card-shot">${screenshotImg(p, { eager })}</div>` : '')
: `<pre class="card-code" aria-hidden="true">${esc(String(p.content || '').slice(0, 220))}${String(p.content || '').length > 220 ? '…' : ''}</pre>`;
const flags = `${p.pinned ? '<i class="fa-solid fa-thumbtack" title="Pinned"></i> ' : ''}${p.burn_after_read ? '<i class="fa-solid fa-fire" title="Burns after reading"></i> ' : ''}${locked ? '<i class="fa-solid fa-lock" title="Locked"></i> ' : ''}${p.is_nsfw ? '<span class="badge badge-nsfw">NSFW</span> ' : ''}`;
return `<article class="card${p.is_nsfw ? ' card-nsfw' : ''}${locked ? ' card-is-locked' : ''}">
@@@ -129,7 +136,9 @@ function pasteCard(p) {
}
function cardGrid(pastes, empty = 'Nothing here yet.') {
if (!pastes || !pastes.length) return `<p class="empty">${esc(empty)}</p>`;
-- return `<div class="grid">${pastes.map(pasteCard).join('\n')}</div>`;
++ // The first picture in the grid is the one above the fold: it loads at high priority, the rest stay lazy.
++ const firstShot = pastes.findIndex((p) => p.type === 'screenshot' && p.screenshot_url && !p.locked);
++ return `<div class="grid">${pastes.map((p, i) => pasteCard(p, { eager: i === firstShot })).join('\n')}</div>`;
}
// ── Home ─────────────────────────────────────────────────────
@@@ -174,10 +183,10 @@ function homeStats(c = {}) {
* says NSFW), else its first lines in the media (kit.card preview; the lists carry 300 characters) with its language in
* the corner; who, views and when under the title.
*/
--function pasteKitCard(p) {
++function pasteKitCard(p, { eager = false } = {}) {
const isShot = p.type === 'screenshot';
-- const image = isShot && !p.is_nsfw ? screenshotSrc(p) : '';
-- return kit.card({
++ const image = isShot && !p.is_nsfw ? screenshotVariant(p, 640) : '';
++ const card = kit.card({
href: `/p/${p.slug}`, title: p.title || 'Untitled', type: 'post', ratio: '16/9', image: image || '',
badge: p.is_nsfw ? 'NSFW' : p.origin === 'ai' ? 'AI' : p.pinned ? 'Pinned' : '',
corner: isShot ? '' : languageLabel(p.language || 'text'),
@@@ -185,6 +194,9 @@ function pasteKitCard(p) {
who: authorName(p), avatar: avatarUrl(p, 68) || '',
count: [Number(p.views) || 0, Number(p.views) === 1 ? 'view' : 'views'], when: p.created_at || null,
});
++ // kit.card's own picture is one plain <img> before the avatar; give it both card widths, the
++ // shape the card draws and the LCP hint. Its markup is pinned with the shared version.
++ return image ? card.replace(/<img\b[^>]*>/, screenshotImg(p, { eager, sizes: '(max-width: 620px) 80vw, 300px' })) : card;
}
/** A section's head in the rails' look (room.css): its heading and the one link to the whole list. */
@@@ -201,7 +213,10 @@ function kindChips(languages) {
}
function latestShelf(latest, languages) {
-- const rail = kit.rail({ id: 'latest', title: 'Latest pastes', more: { href: '/pastes', label: 'See all' }, items: (latest || []).map(pasteKitCard) });
++ const list = latest || [];
++ // The newest screenshot in the first rail is the home's LCP picture on a phone: load it at high priority.
++ const firstShot = list.findIndex((p) => p.type === 'screenshot' && p.screenshot_url && !p.is_nsfw && !p.locked);
++ const rail = kit.rail({ id: 'latest', title: 'Latest pastes', more: { href: '/pastes', label: 'See all' }, items: list.map((p, i) => pasteKitCard(p, { eager: i === firstShot })) });
const none = `<section id="latest" aria-labelledby="latest-h">${shelfHead('latest-h', 'Latest pastes', { href: '/pastes', label: 'See all' })}${kit.empty({ title: 'No pastes yet', text: 'Be the first: paste something and share the link.', action: { href: '/new', label: 'Start a paste' } })}</section>`;
return `<div class="cm-sec">${rail || none}${languages && languages.length ? kindChips(languages) : ''}</div>`;
}
ddiff --git a/test/helpers/mock-media.js b/test/helpers/mock-media.js
iindex 3a9d702..8d2f9bc 100644
---- a/test/helpers/mock-media.js
++++ b/test/helpers/mock-media.js
@@@ -21,11 +21,17 @@ function start({ publicPem, issuer }) {
req.on('end', () => {
const json = (status, obj) => { res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(obj)); };
if (req.method === 'GET' && req.url === '/healthz') return json(200, { ok: true, service: 'mock-media' });
-- // A public object's bytes (a private one is reached through /download, below).
-- const open = req.url.match(/^\/o\/(med_[0-9A-Z]+)$/);
++ // A public object's bytes (a private one is reached through /download, below). ?w=&fmt= asks
++ // for Media's resized variant: answer webp with the width in the body, as Media would.
++ const open = req.url.match(/^\/o\/(med_[0-9A-Z]+)(\?[^#]*)?$/);
if (open && req.method === 'GET') {
const o = uploads.find((u) => u.key === open[1]);
if (!o) return json(404, { error: 'not found' });
++ const q = new URL(req.url, 'http://mock-media').searchParams;
++ if (q.get('w')) {
++ res.writeHead(200, { 'Content-Type': 'image/webp' });
++ return res.end(Buffer.concat([Buffer.from(`WEBP-${q.get('w')}-${q.get('fmt') || 'webp'}`), o.bytes]));
++ }
res.writeHead(200, { 'Content-Type': o.mime }); return res.end(o.bytes);
}
const del = req.url.match(/^\/api\/v2\/community\/objects\/(med_[0-9A-Z]+)$/);
ddiff --git a/test/paste-sharing.test.js b/test/paste-sharing.test.js
iindex b416281..56a6fd1 100644
---- a/test/paste-sharing.test.js
++++ b/test/paste-sharing.test.js
@@@ -354,6 +354,31 @@ const pastesApiModule = require('../server/pastes/api');
assert.strictEqual(got.headers.get('cache-control'), 'public, max-age=31536000, immutable', 'a public screenshot is an immutable asset');
});
++ await check('a card asks for a 320/640 webp variant through the same proxy, and the first above the fold is eager', async () => {
++ const jpeg = Buffer.concat([Buffer.from([0xff, 0xd8]), Buffer.from([0xff, 0xda, 0x00, 0x04, 0x00, 0x00]), Buffer.from('CARD-PIXELS'), Buffer.from([0xff, 0xd9])]);
++ const fd = new FormData();
++ fd.append('title', 'Card shot');
++ fd.append('audience', 'public');
++ fd.append('screenshot', new Blob([jpeg], { type: 'image/jpeg' }), 'c.jpg');
++ const made = await t.get('/api/pastes/screenshot', { method: 'POST', cookies: [`ov_token=${alexJwt}`], body: fd });
++ assert.strictEqual(made.status, 201, made.text);
++ const slug = made.json().slug;
++ const home = await call('/');
++ assert.strictEqual(home.status, 200);
++ assert.match(home.text, new RegExp(`srcset="[^"]*/p/${slug}/screenshot\\?w=320&amp;fmt=webp 320w, [^"]*/p/${slug}/screenshot\\?w=640&amp;fmt=webp 640w"`), 'both card widths');
++ assert.match(home.text, /sizes="[^"]*"/, 'sizes is set');
++ assert.match(home.text, /width="640" height="360"/, 'the card shape is declared');
++ assert.match(home.text, /fetchpriority="high"/, 'the first picture is not lazy');
++ const mine = await call('/my', { cookie: alexJwt });
++ assert.match(mine.text, new RegExp(`<img src="/p/${slug}/screenshot\\?w=640&amp;fmt=webp"`), 'a grid card uses the 640 picture');
++ const at320 = await call(`/p/${slug}/screenshot?w=320&fmt=webp`);
++ assert.strictEqual(at320.status, 200);
++ assert.strictEqual(at320.headers.get('content-type'), 'image/webp');
++ assert.strictEqual(at320.headers.get('cache-control'), 'public, max-age=31536000, immutable', 'a public card variant is immutable too');
++ assert.ok(at320.text.includes('WEBP-320-webp'), 'the proxy asked Media for the 320 webp variant');
++ assert.strictEqual((await call(`/p/${slug}/screenshot?w=1000&fmt=webp`)).status, 400, 'only the two card widths are accepted');
++ });
++
await check('the move script re-hosts, deletes the old object, and retries a failed delete; --keep-old keeps it', async () => {
const { createMediaObjects } = require('../server/media/objects');
const { moveScreenshotsPrivate } = require('../scripts/move-screenshots-private');