openvibers/openvibe.network Commits

Frame feed relay: the new kinds and the filters pass to Community (#716)

OP OpenVibers committed 4 days ago · 1a1335aa2f · 2 files, +19 −2

Changes

ddiff --git a/server/frame/service.js b/server/frame/service.js
iindex b5981922..0d9270ec 100644
---- a/server/frame/service.js
++++ b/server/frame/service.js
@@@ -435,14 +435,15 @@ async function createFrameService(db, config, analytics, { privateKey = null, is
res.send(JSON.stringify(pulse || { live_now: null, watching: null, sites: orderedOpen().length, views_7d: null, live: null, updated: null }));
});
-- // GET /api/frame/feed?kind=&cursor=&limit= and GET /api/frame/wall?limit=&after=&for= — the menu's Feed and wall
++ // GET /api/frame/feed?kind=&site=&q=&following=1&cursor=&limit= and GET /api/frame/wall?limit=&after=&for= — the menu's Feed and wall
// (openvibe-shared ov-home.js) on every site, asked through Network because every site's connect-src lets the navbar
// reach openvibe.network, and the sites' policies refused openvibe.community (11-09: the Feed fell back to its old
// sections everywhere but five sites). Community's /api/v1/feed and /api/v1/pulse answer; the person's Bearer token
// goes along when the menu sends one (a personal wall, the composer's can_post), never a cookie. Anonymous answers are
// kept 30 s per query; personal ones are not kept. Community not answering is an empty page, never an error.
const relayed = new Map();
-- const FEED_KINDS = ['live', 'stream', 'clip', 'video', 'pic', 'post', 'meme', 'game', 'update'];
++ // Community's feed kinds (server/pulse/feed.js KINDS; #310 added wiki, quest, chat, event and member).
++ const FEED_KINDS = ['live', 'stream', 'clip', 'video', 'pic', 'post', 'meme', 'game', 'update', 'wiki', 'quest', 'chat', 'event', 'member'];
async function relay(req, res, path, params) {
res.set({ 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Authorization', 'Vary': 'Authorization', 'Content-Type': 'application/json; charset=utf-8' });
const auth = /^Bearer [A-Za-z0-9._~+/=-]{10,4096}$/.test(String(req.get('authorization') || '')) ? req.get('authorization') : '';
@@@ -483,8 +484,14 @@ async function createFrameService(db, config, analytics, { privateKey = null, is
return res.status(r.status).send(text.slice(0, 64 * 1024));
} catch { return res.status(502).send('{"error":"Community did not answer"}'); }
});
++ // The feed's filters pass through: a site (a service id), words (q, at most 80 characters, one line) and the
++ // signed-in person's follows (following=1 only with their Bearer token: Community answers 401 without one).
++ const qSite = (v) => (/^[a-z0-9][a-z0-9:-]{0,39}$/.test(String(v || '')) ? String(v) : '');
++ const qWords = (v) => String(v == null ? '' : v).replace(/[\u0000-\u001f\u007f]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 80);
router.get('/feed', (req, res) => relay(req, res, '/api/v1/feed', {
kind: FEED_KINDS.includes(String(req.query.kind || '')) ? String(req.query.kind) : '',
++ site: qSite(req.query.site), q: qWords(req.query.q),
++ following: /^(1|true)$/.test(String(req.query.following || '')) ? '1' : '',
cursor: qCursor(req.query.cursor), limit: qLimit(req.query.limit, 40),
}));
router.get('/wall', (req, res) => relay(req, res, '/api/v1/pulse', {
ddiff --git a/test/frame-feed.test.js b/test/frame-feed.test.js
iindex e912a293..cb582155 100644
---- a/test/frame-feed.test.js
++++ b/test/frame-feed.test.js
@@@ -52,6 +52,16 @@ const { createFrameService } = require('../server/frame/service');
const odd = new URL(asked[asked.length - 1].url, 'http://x');
assert.ok(!odd.searchParams.has('kind') && !odd.searchParams.has('cursor') && odd.searchParams.get('limit') === '40', 'an odd kind and cursor are dropped and the limit capped');
++ // The new kinds and the filters reach Community; odd values do not.
++ ({ d } = await get('/feed?kind=quest&site=wiki&q=%20head%0Aslider%20&following=1', { Authorization: 'Bearer abcdefghijklmnop.qrstu' }));
++ const f = new URL(asked[asked.length - 1].url, 'http://x');
++ assert.deepStrictEqual([f.searchParams.get('kind'), f.searchParams.get('site'), f.searchParams.get('q'), f.searchParams.get('following')], ['quest', 'wiki', 'head slider', '1'], 'kind, site, words and following pass');
++ assert.strictEqual(asked[asked.length - 1].auth, 'Bearer abcdefghijklmnop.qrstu', 'following goes with the person');
++ for (const k of ['wiki', 'chat', 'event', 'member']) { ({ d } = await get(`/feed?kind=${k}`)); assert.strictEqual(new URL(asked[asked.length - 1].url, 'http://x').searchParams.get('kind'), k, `${k} passes`); }
++ ({ d } = await get('/feed?site=../x&q=' + 'a'.repeat(200) + '&following=yes'));
++ const g = new URL(asked[asked.length - 1].url, 'http://x');
++ assert.ok(!g.searchParams.has('site') && g.searchParams.get('q').length === 80 && !g.searchParams.has('following'), 'an odd site and following are dropped, words capped at 80');
++
const pre = await fetch(`${base}/feed`, { method: 'OPTIONS', headers: { Origin: 'https://openvibe.video', 'Access-Control-Request-Method': 'GET', 'Access-Control-Request-Headers': 'authorization' } });
assert.strictEqual(pre.status, 204);
assert.match(pre.headers.get('access-control-allow-headers') || '', /Authorization/i);