openvibers/openvibe.host Commits

The shared update system and one account UI, on the OpenVibe Frame (openvibe-shared 1.11.0, openvibe/frame): the home shows what shipped, /updates is the shared log and the footer links it; the shared navbar handles sign in and out (logoutUrl ends this site's session) and carries this site's account links in its menu, while the page's own account bar is only for no-JS visitors

OP OpenVibers committed 24 Sept 2026 · d5fb2dc7bc · 5 files, +24 −13


Changes

ddiff --git a/package-lock.json b/package-lock.json
iindex 762481b..ba4e2a7 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -18,7 +18,7 @@
"express-rate-limit": "^7.5.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.0"
},
"bin": {
"ovhost": "bin/ovhost"
@@@ -1437,9 +1437,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.5.1",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
-- "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",
++ "version": "1.11.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.0",
++ "integrity": "sha512-edMW0I25HNFReLcfZ9ak+4q5sy2t7qq7EPG52tOerTBV2fU994C5we/02AD/9i2Vuq0+ayNBBisW0cg60L6DLQ==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex fe946e7..30a8b9c 100644
---- a/package.json
++++ b/package.json
@@@ -33,7 +33,7 @@
"express-rate-limit": "^7.5.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.11.0"
},
"engines": {
"node": ">=22.0.0"
ddiff --git a/server/app.js b/server/app.js
iindex 8740b1f..fd850ac 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -46,7 +46,7 @@ const VERSION = require('../package.json').version;
const DASHBOARD_CSP = {
'default-src': ["'self'"],
-- // The shared chrome (theme-loader, navbar) comes from the Network; the inline init is ours.
++ // The OpenVibe Frame (theme-loader, navbar, footer) comes from the Network; the inline init is ours.
'script-src': ["'self'", "'unsafe-inline'", 'https://openvibe.network'],
'style-src': ["'self'", "'unsafe-inline'", 'https://openvibe.network', 'https://fonts.googleapis.com', 'https://cdnjs.cloudflare.com'],
'font-src': ["'self'", 'data:', 'https://fonts.gstatic.com', 'https://cdnjs.cloudflare.com'],
ddiff --git a/server/http/dashboard.js b/server/http/dashboard.js
iindex d5e25c5..a981836 100644
---- a/server/http/dashboard.js
++++ b/server/http/dashboard.js
@@@ -6,6 +6,7 @@
* enough on openvibe.host). Every response is private and never cached by a shared cache.
*/
const express = require('express');
++const frame = require('openvibe-shared/frame');
const { renderPage } = require('../render/layout');
const pages = require('../render/pages');
const { csrfToken, checkCsrf, sameOrigin } = require('../auth/forms');
@@@ -61,6 +62,8 @@ function createDashboard(ctx) {
const back = (path, text, kind) => `${path}?notice=${encodeURIComponent(text)}${kind ? `&kind=${kind}` : ''}`;
// ── Pages ───────────────────────────────────────────────
++ // What shipped on OpenVibe.Host: the shared update log every OpenVibe site has.
++ router.get('/updates', view((req, res) => page(req, res, 200, 'What shipped on OpenVibe.Host', frame.updatesBody({ service: 'host', siteName: 'OpenVibe.Host' }) + frame.shippedScript(), null, { indexable: true })));
router.get('/', view((req, res) => {
// The signed-out front page is the public page of openvibe.host (sitemap.xml): indexable.
// Everything behind sign-in stays noindex and private.
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 20cacf5..a9f1715 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -2,14 +2,14 @@
/**
* Dashboard page shell. Every page is server-rendered and complete without JavaScript (all actions
-- * are plain forms); the shared chrome (navbar.js + theme-loader.js from the Network) is progressive.
++ * are plain forms); the OpenVibe Frame (navbar.js + theme-loader.js from the Network) is progressive.
* Dashboard pages are private: noindex, private caching.
*/
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const appIcon = require('openvibe-shared/app-icon');
--const chrome = require('openvibe-shared/chrome-ssr');
++const frame = require('openvibe-shared/frame');
const NETWORK_URL = 'https://openvibe.network';
const SITE_NAME = 'OpenVibe.Host';
@@@ -40,8 +40,13 @@ function renderPage(o) {
silentLogin: `${o.config.baseUrl}/auth/login?silent=1&next={url}`,
sessionUrl: '/auth/me',
loginUrl: `/auth/login?next=${loginNext}`,
++ logoutUrl: '/auth/logout?next={path}', // Sign out in the shared navbar ends this site's session too
};
const who = signedIn ? esc((viewer.user && (viewer.user.display_name || viewer.user.username)) || viewer.subject) : '';
++ // This site's own account links live in the shared navbar's account menu (the page's account
++ // bar below is only for visitors without JavaScript).
++ if (signedIn) nav.menu = { before: [] };
++ const footer = { service: 'host', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };
const account = signedIn
? `Signed in as ${who} · <a href="/auth/logout?next=%2F">Sign out</a>`
: `<a href="/auth/login?next=${loginNext}">Sign in with OpenVibe</a>`;
@@@ -59,21 +64,24 @@ ${appIcon.headTags({ site: 'host' })}
<link rel="stylesheet" href="${asset('css/host.css')}">
<script src="${NETWORK_URL}/shared/theme-loader.js" defer></script>
<script src="${NETWORK_URL}/shared/navbar.js" defer></script>
++<script src="${NETWORK_URL}/shared/footer.js" defer></script>
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<div id="navbar-mount"></div>
--${chrome.noscriptNav({ name: SITE_NAME, home: '/', links: [{ label: 'Projects', href: '/' }] })}
--<div class="account-bar" role="navigation" aria-label="Account">${account}</div>
++${frame.noscriptNav({ name: SITE_NAME, home: '/', links: [{ label: 'Projects', href: '/' }] })}
++<noscript><div class="account-bar" role="navigation" aria-label="Account">${account}</div></noscript>
<main id="main" class="page">
${notice}
${o.body || ''}
++${o.path === '/' ? frame.shipped({ service: 'host', title: `Recently shipped on ${SITE_NAME}` }) : ''}
</main>
--${chrome.footer({ service: 'host', variant: 'full' })}
++${frame.footer(footer)}
<script>
--window.__OV_PAGE = ${JSON.stringify({ navbar: nav }).replace(/</g, '\\u003c')};
++window.__OV_PAGE = ${JSON.stringify({ navbar: nav, footer }).replace(/</g, '\\u003c')};
document.addEventListener('DOMContentLoaded', function () {
-- try { if (window.OpenVibeNavbar) OpenVibeNavbar.init(window.__OV_PAGE.navbar); } catch (e) { /* the chrome is optional */ }
++ try { if (window.OpenVibeNavbar) OpenVibeNavbar.init(window.__OV_PAGE.navbar); } catch (e) { /* the Frame is optional */ }
++ try { if (window.OpenVibeFooter) OpenVibeFooter.init(window.__OV_PAGE.footer); } catch (e) { /* */ }
});
</script>
</body>