openvibers/openvibe.network Commits

Frame: the Sources popover's provider offers (#860)

OP OpenVibers committed 1 days ago · 94fa206018 · 2 files, +37 −0

GET /api/frame/query/providers forwards to Search's /api/v1/query/providers through Network, for the same
connect-src reason as /query: a person's token goes along and nothing is kept; signed out the offers are kept
60 s. This is the route openvibe-sdk/frame's providers() calls.

Changes

ddiff --git a/server/frame/service.js b/server/frame/service.js
iindex e43cd259..54ab1198 100644
---- a/server/frame/service.js
++++ b/server/frame/service.js
@@@ -1023,6 +1023,29 @@ async function createFrameService(db, config, analytics, { privateKey = null, is
// /api/v1/runs/:id/events (mounted in server/index.js), replayable from Last-Event-ID.
router.post('/ask', ...ask.ask);
++ // GET /api/frame/query/providers — the Sources popover's web ladder: Search's offers (provider, scopes, price,
++ // availability, today's use), asked through Network for the same connect-src reason as /query. Same CORS and
++ // caching: a person's token goes along and nothing is kept; signed out the offers are kept 60 s.
++ const providerOffers = new Map();
++ router.get('/query/providers', async (req, res) => {
++ res.set({ 'Content-Type': 'application/json; charset=utf-8', Vary: 'Authorization, Origin' });
++ const auth = /^Bearer [A-Za-z0-9._~+/=-]{20,4096}$/.test(String(req.headers.authorization || '')) ? String(req.headers.authorization) : '';
++ const u = new URL('/api/v1/query/providers', svcUrl('search', 'http://127.0.0.1:4710'));
++ const headers = { ...(req.ip ? { 'X-Forwarded-For': req.ip } : {}), ...(auth ? { Authorization: auth } : {}) };
++ if (auth) {
++ res.set('Cache-Control', 'private, no-store');
++ const d = await getJson(u.href, headers, 10000);
++ return res.send(JSON.stringify(d || { providers: [] }));
++ }
++ res.set('Cache-Control', 'public, max-age=60');
++ const kept = providerOffers.get('offers');
++ if (kept && Date.now() - kept.at < 60_000) return res.send(kept.body);
++ const d = await getJson(u.href, headers, 10000);
++ const body = JSON.stringify(d || { providers: [] });
++ providerOffers.set('offers', { at: Date.now(), body });
++ res.send(body);
++ });
++
// POST /api/frame/hit — one anonymous count for the calling page's host. The host comes from the
// browser-set Origin header (never the body) and must be one of ours or a registered tool domain.
const recent = new Map(); // ip → [count, windowStart]: 40/min is plenty for a person, useless for stuffing
ddiff --git a/test/frame-query.test.js b/test/frame-query.test.js
iindex 3c65a2ef..007ab31f 100644
---- a/test/frame-query.test.js
++++ b/test/frame-query.test.js
@@@ -14,6 +14,7 @@ const { createFrameService } = require('../server/frame/service');
asked.push({ url: req.url, auth: req.headers.authorization || '', xff: req.headers['x-forwarded-for'] });
if (down) { res.statusCode = 503; return res.end('{}'); }
res.setHeader('content-type', 'application/json');
++ if (req.url.startsWith('/api/v1/query/providers')) return res.end(JSON.stringify({ providers: [{ provider: 'deepseek', scopes: ['web'], available: true }] }));
res.end(JSON.stringify({ results: [{ scope: 'network', kind: 'document', title: 'surf_utopia', url: 'https://openvibe.live/clips/1' }], groups: { network: 1 }, answer: null, citations: [], route: { explain: [], cost_usd: 0 }, next_cursor: null }));
});
await new Promise((r) => search.listen(0, '127.0.0.1', r));
@@@ -60,6 +61,19 @@ const { createFrameService } = require('../server/frame/service');
assert.strictEqual(r.status, 200);
assert.deepStrictEqual([d.results, d.next_cursor], [[], null], 'Search not answering is an empty answer');
++ // The Sources popover's own read: Search's web ladder offers, same CORS and caching.
++ down = false;
++ let prov = await fetch(`${base.replace('/query', '/query/providers')}`).then((x) => x.json());
++ assert.deepStrictEqual(prov.providers.map((p) => p.provider), ['deepseek']);
++ const providersHit = asked[asked.length - 1];
++ assert.strictEqual(new URL(providersHit.url, 'http://x').pathname, '/api/v1/query/providers');
++ assert.strictEqual(providersHit.auth, '', 'anonymous providers');
++ await fetch(`${base.replace('/query', '/query/providers')}`);
++ assert.strictEqual(asked.filter((x) => x.url.startsWith('/api/v1/query/providers')).length, 1, 'the anonymous offers are kept 60 s');
++ const provAuth = await fetch(`${base.replace('/query', '/query/providers')}`, { headers: { Authorization: token } });
++ assert.strictEqual(provAuth.headers.get('cache-control'), 'private, no-store');
++ assert.strictEqual(asked[asked.length - 1].auth, token, "the person's token goes along");
++
server.close(); search.close();
console.log('frame query: the fabric through Network, as the person or kept 60 s ok');
process.exit(0);