openvibers/openvibe.host Commits
Terms, privacy and DMCA pages send Host's own page list (#393)
Changes
@@@ -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);@@@ -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 };nnew file mode 100644@@@ -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); });
@@ -226,7 +226,15 @@ async function createApp(opts = {}) {
@@ -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.
// /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/', rateLimit({ windowMs: 15 * 60_000, max: 60, skip: (req) => req.path === '/me', standardHeaders: true, legacyHeaders: false }));
app.use('/auth', createHostSession({ auth, config }).router(express));
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());
app.use('/shared', require('openvibe-shared/serve').handler());
// GET /<key>.txt — the IndexNow key file, only when a key is configured.
// GET /<key>.txt — the IndexNow key file, only when a key is configured.
if (indexnow.enabled) app.use(indexnow.keyFile);
if (indexnow.enabled) app.use(indexnow.keyFile);
@@ -142,4 +142,4 @@ document.addEventListener('DOMContentLoaded', function () {
@@ -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 };
new file mode 100644
new file mode 100644
@@ -0,0 +1,51 @@
@@ -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); });