openvibers/openvibe.community Commits

Pastes: the guarded screenshot address, never Media's private object URL (broken images) (#273)

OP OpenVibers committed 6 days ago · 7a8b1c36ce · 3 files, +8 −3

Screenshots are stored private in Media since the sharing work, so the /o/<id> URL the API handed out
was a 404 in every browser: every image paste and AI moment frame on openvibe.live showed a broken
image. The API and the forum's paste cards now name /p/<slug>/screenshot, which checks the paste's
audience and streams the bytes. A locked paste names none.

Changes

ddiff --git a/server/forum/service.js b/server/forum/service.js
iindex a3540f0..c8e10cf 100644
---- a/server/forum/service.js
++++ b/server/forum/service.js
@@@ -313,7 +313,7 @@ function createForumService({ db, network = null, pulse = null, relay = null, vi
for (const r of rows) {
const lines = String(r.content || '').split('\n');
if (!out.has(r.post_id)) out.set(r.post_id, []);
-- out.get(r.post_id).push({ slug: r.slug, title: r.title, language: r.language || 'text', type: r.type, url: `/p/${r.slug}`, screenshot_url: r.type === 'screenshot' ? r.screenshot_url : null,
++ out.get(r.post_id).push({ slug: r.slug, title: r.title, language: r.language || 'text', type: r.type, url: `/p/${r.slug}`, screenshot_url: r.type === 'screenshot' && r.screenshot_url ? `/p/${encodeURIComponent(r.slug)}/screenshot` : null,
lines: r.content ? lines.length : 0, excerpt: r.type === 'screenshot' ? null : lines.slice(0, 12).join('\n').slice(0, 1500) });
}
return out;
ddiff --git a/server/pastes/service.js b/server/pastes/service.js
iindex 4d2ea69..a30c9b5 100644
---- a/server/pastes/service.js
++++ b/server/pastes/service.js
@@@ -252,7 +252,10 @@ function createPasteService({ db, network = null, media = null, config = {}, lim
envelope_url: locked ? `/p/${encodeURIComponent(p.slug)}/raw` : null,
stream_id: streamIdOf(ref),
stream_ref: ref,
-- screenshot_url: p.screenshot_url || null,
++ // The guarded address, never Media's own object URL: screenshots are stored private in Media (the share
++ // rules decide who sees one), so /o/<id> is a 404 to every browser. /p/<slug>/screenshot checks the paste's
++ // audience and streams the bytes (2026-11-07: every image paste and AI moment on openvibe.live was broken).
++ screenshot_url: p.screenshot_url && !locked ? `${site || ''}/p/${encodeURIComponent(p.slug)}/screenshot` : null,
metadata: metadataFor(p.metadata, isOwner(v, p) || isService(v)),
burn_after_read: !!p.burn_after_read,
forked_from: p.forked_from || null,
ddiff --git a/test/pastes-api.test.js b/test/pastes-api.test.js
iindex 42bbaef..31b6be6 100644
---- a/test/pastes-api.test.js
++++ b/test/pastes-api.test.js
@@@ -273,7 +273,9 @@ const { boot, check, done } = require('./helpers/app');
assert.strictEqual(p.owner_subject, alex.subject_id);
assert.ok(up.v2 && up.kind === 'screenshot' && up.visibility === 'private', 'a v2 Media object: private screenshot');
assert.strictEqual(up.owner, alex.subject_id, 'owned by the person');
-- assert.strictEqual(p.screenshot_url, `https://openvibe.media/o/${up.key}`);
++ // The object is private in Media, so the answer names the guarded address, never Media's /o/<id> (a 404 to a browser).
++ assert.ok(/\/p\/[a-z0-9-]+\/screenshot$/.test(p.screenshot_url) && p.screenshot_url.endsWith(`/p/${p.slug}/screenshot`), p.screenshot_url);
++ assert.ok(!p.screenshot_url.includes('/o/'), 'never the Media object URL');
assert.strictEqual(p.title, 'My desk');
assert.strictEqual(p.content, 'look');
const meta = JSON.parse(p.metadata);