openvibers/openvibe.host Commits

Terms, privacy and DMCA pages send Host's own page list (#393)

OP OpenVibers committed 1 days ago · 980c1a9518 · 3 files, +61 −2

Shared's legal template starts the navbar without links, so the three legal pages showed its fallback (Home, My Account, Themes) instead of the catalogue. The handler now adds Host's list to that init call, and the test checks every page and every row's route.

Changes

ddiff --git a/server/app.js b/server/app.js
iindex f9d873a..aa3c1c9 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -226,7 +226,15 @@ async function createApp(opts = {}) {
// /auth/me is the navbar's who-is-signed-in read on every page, so it is not counted with sign-ins: 60 page views in 15 minutes made a signed-in visitor look signed out.
app.use('/auth/', rateLimit({ windowMs: 15 * 60_000, max: 60, skip: (req) => req.path === '/me', standardHeaders: true, legacyHeaders: false }));
app.use('/auth', createHostSession({ auth, config }).router(express));
-- { const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'host', service: 'host', host: 'openvibe.host', name: 'OpenVibe.Host', profile: 'ugc' })); }
++ { const legal = require('openvibe-shared/legal');
++ // Shared's legal template starts the navbar without links, so /terms, /privacy and /dmca showed its fallback
++ // (Home, My Account, Themes) instead of Host's catalogue. Add Host's own list to that init call.
++ const navLinks = JSON.stringify(require('./render/layout').NAV_LINKS());
++ app.get(legal.PATHS, (req, res, next) => {
++ const send = res.send.bind(res);
++ res.send = (body) => send(String(body).replace('try{OpenVibeNavbar.init({service:', `try{OpenVibeNavbar.init({links:${navLinks},service:`));
++ next();
++ }, legal.handler({ id: 'host', service: 'host', host: 'openvibe.host', name: 'OpenVibe.Host', profile: 'ugc' })); }
app.use('/shared', require('openvibe-shared/serve').handler());
// GET /<key>.txt — the IndexNow key file, only when a key is configured.
if (indexnow.enabled) app.use(indexnow.keyFile);
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex a2fcee8..5672830 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -142,4 +142,4 @@ document.addEventListener('DOMContentLoaded', function () {
});
}
--module.exports = { renderPage, esc, asset, assetVersion, setRelease, SITE_NAME, NETWORK_URL, canonicalOf, twinHeader };
++module.exports = { renderPage, esc, asset, assetVersion, setRelease, SITE_NAME, NETWORK_URL, canonicalOf, twinHeader, NAV_LINKS };
ddiff --git a/test/page-links.test.js b/test/page-links.test.js
nnew file mode 100644
iindex 0000000..a6b7ffa
---- /dev/null
++++ b/test/page-links.test.js
@@@ -0,0 +1,51 @@
++'use strict';
++// Every page of openvibe.host gives the shared navbar the catalogue's own page list (the owner, 2026-11-12).
++// Shared's legal template used to fall back to Home / My Account / Themes on /terms, /privacy and /dmca.
++const assert = require('assert');
++const { boot, check, done } = require('./helpers/boot');
++const { NAV_LINKS } = require('../server/render/layout');
++
++/** The links inside a page's OpenVibeNavbar.init({...}) call. */
++function initLinks(html) {
++ const at = html.indexOf('OpenVibeNavbar.init');
++ if (at < 0) return null;
++ const start = html.indexOf('links', at);
++ const open = html.indexOf('[', start);
++ let depth = 0, q = null, esc = false;
++ for (let i = open; i < html.length; i += 1) {
++ const c = html[i];
++ if (q) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === q) q = null; continue; }
++ if (c === '"' || c === "'") { q = c; continue; }
++ if (c === '[') depth += 1; else if (c === ']') { depth -= 1; if (!depth) return JSON.parse(html.slice(open, i + 1)); }
++ }
++ return null;
++}
++
++(async () => {
++ const t = await boot();
++ try {
++ const nav = NAV_LINKS().map(({ label, href }) => ({ label, href }));
++ await check('every public page sends Host\'s own list, not the shared fallback', async () => {
++ for (const path of ['/', ...nav.map((l) => l.href), '/terms', '/privacy', '/dmca']) {
++ const r = await t.api('GET', path);
++ assert.ok([200, 302, 303].includes(r.status), `${path} answered ${r.status}`);
++ if (r.status !== 200) continue;
++ const own = r.text.includes('window.__OV_PAGE')
++ ? JSON.parse(r.text.match(/window\.__OV_PAGE = (.+);/)[1]).navbar.links.map(({ label, href }) => ({ label, href }))
++ : (initLinks(r.text) || []).map(({ label, href }) => ({ label, href }));
++ assert.deepStrictEqual(own, nav, `${path} sends Host's list`);
++ }
++ });
++ await check('every row is a route of this server', async () => {
++ for (const { href } of nav) {
++ const r = await t.api('GET', href);
++ assert.notStrictEqual(r.status, 404, `${href} is a route`);
++ assert.ok(r.status < 500, `${href} does not fail (${r.status})`);
++ }
++ });
++ await check('the home row is not called "Home"', async () => {
++ assert.ok(!nav.some((l) => l.label === 'Home'), 'Host\'s list has no "Home" row');
++ });
++ } finally { await t.close(); }
++ done();
++})().catch((e) => { console.error(e); process.exit(1); });