openvibers/openvibe.live Commits

/shared serves Shared's stylesheets and images (ov-forms.css was 404) (#609)

OP OpenVibers committed 7 days ago · ceebc27656 · 3 files, +61 −7

Live served only openvibe-shared's scripts and fonts, so the navbar's ov-forms.css (Shared 4.109) answered 404
text/plain on six pages and the browser refused the stylesheet (phone sweep 11-05). The route now serves every list
the package gives sites (BROWSER, STYLES, FONTS, IMAGES) through server/web/shared-files.js, with the right
Content-Type for each.

Changes

ddiff --git a/server/index.js b/server/index.js
iindex f0cb429c..64fcabd9 100644
---- a/server/index.js
++++ b/server/index.js
@@@ -598,7 +598,7 @@ app.use(async (req, res, next) => {
// Serve openvibe-shared browser assets at /shared/ — straight from node_modules/openvibe-shared,
// the OpenVibe.Shared release package.json pins (the public/ directory is read-only under
// systemd ProtectSystem=strict on production). The package lists its own browser files.
--const sharedFiles = require('openvibe-shared/files');
++const sharedFiles = require('./web/shared-files');
const SHARED_BROWSER_FILES = sharedFiles.BROWSER;
const sharedServePath = sharedFiles.dir;
assets.setSharedDir(sharedServePath);
@@@ -614,13 +614,12 @@ console.log(`[Server] /shared: serving ${SHARED_BROWSER_FILES.length} browser fi
});
// The share cards (og:image on every page), before the browser-file allowlist below.
require('./web/share-cards').register(app);
-- // Serve only the whitelisted browser files — don't expose the entire package — and the brand face the navbar
-- // declares beside itself (files.FONTS, Shared 4.59.0).
-- const SHARED_FONTS = sharedFiles.FONTS || [];
-- const sharedFileSet = new Set([...SHARED_BROWSER_FILES, ...SHARED_FONTS]);
++ // Serve only the files the package lists for sites (scripts, stylesheets, fonts, images: server/web/shared-files.js),
++ // never the entire package, each with its own Content-Type.
app.use('/shared', (req, res, next) => {
const fileName = path.basename(req.path);
-- if (!sharedFileSet.has(fileName)) {
++ const type = sharedFiles.typeOf(fileName);
++ if (!type) {
return res.status(404).type('text/plain').send('Not found');
}
const filePath = path.join(sharedServePath, fileName);
@@@ -630,7 +629,7 @@ console.log(`[Server] /shared: serving ${SHARED_BROWSER_FILES.length} browser fi
const hashed = Boolean(req.query.v) && req.query.v === assets.hashOf('/shared/' + fileName);
res.setHeader('Cache-Control', cache.assetHeaders(req.originalUrl, { hashed }));
if (hashed) res.setHeader('CDN-Cache-Control', cache.IMMUTABLE);
-- res.setHeader('Content-Type', SHARED_FONTS.includes(fileName) ? 'font/woff2' : 'application/javascript; charset=utf-8');
++ res.setHeader('Content-Type', type);
res.sendFile(filePath, (err) => {
// A listed file that cannot be read is this server's fault, and passing: never a cacheable answer
// (its headers above said immutable; Cloudflare kept a 404 navbar through a deploy on 2026-10-17).
ddiff --git a/server/web/shared-files.js b/server/web/shared-files.js
nnew file mode 100644
iindex 00000000..3a2b1ec0
---- /dev/null
++++ b/server/web/shared-files.js
@@@ -0,0 +1,26 @@
++'use strict';
++/**
++ * The openvibe-shared files Live serves at /shared/<file>, and each one's Content-Type. The package lists them
++ * (openvibe-shared/files): BROWSER scripts, STYLES, FONTS and IMAGES. Until 2026-11-05 Live served only the scripts
++ * and fonts, so a stylesheet the navbar links (ov-forms.css, Shared 4.109) answered 404 text/plain on every page
++ * (the phone sweep of 11-05) and the browser refused it.
++ */
++const files = require('openvibe-shared/files');
++
++const KINDS = [
++ [files.BROWSER || [], 'application/javascript; charset=utf-8'],
++ [files.STYLES || [], 'text/css; charset=utf-8'],
++ [files.FONTS || [], 'font/woff2'],
++ [files.IMAGES || [], 'image/svg+xml'],
++];
++
++/** name → Content-Type, for every file the package says a site serves. */
++const TYPES = new Map();
++for (const [names, type] of KINDS) for (const name of names) if (!TYPES.has(name)) TYPES.set(name, type);
++
++/** The Content-Type for a served file, or null when the package does not list it (the route answers 404). */
++function typeOf(name) {
++ return TYPES.get(String(name)) || null;
++}
++
++module.exports = { dir: files.dir, BROWSER: files.BROWSER || [], typeOf, names: () => [...TYPES.keys()] };
ddiff --git a/test/shared-files.test.js b/test/shared-files.test.js
nnew file mode 100644
iindex 00000000..1398a2b0
---- /dev/null
++++ b/test/shared-files.test.js
@@@ -0,0 +1,29 @@
++'use strict';
++// /shared/<file> serves every file openvibe-shared lists for sites, each with its own Content-Type
++// (server/web/shared-files.js). Until 2026-11-05 Live served only scripts and fonts: the navbar's ov-forms.css
++// answered 404 text/plain on six pages and the browser refused it (phone sweep 11-05).
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const files = require('openvibe-shared/files');
++const shared = require('../server/web/shared-files');
++
++// 1. Each kind the package lists gets its type.
++assert.strictEqual(shared.typeOf('navbar.js'), 'application/javascript; charset=utf-8');
++if ((files.STYLES || []).includes('ov-forms.css')) assert.strictEqual(shared.typeOf('ov-forms.css'), 'text/css; charset=utf-8', 'the navbar links ov-forms.css');
++for (const name of files.STYLES || []) assert.strictEqual(shared.typeOf(name), 'text/css; charset=utf-8', `${name} is a stylesheet`);
++for (const name of files.FONTS || []) assert.strictEqual(shared.typeOf(name), 'font/woff2', `${name} is a font`);
++for (const name of files.IMAGES || []) assert.strictEqual(shared.typeOf(name), 'image/svg+xml', `${name} is an image`);
++
++// 2. Anything else is not served: the package's own code, a path, an unknown name.
++for (const name of ['package.json', 'files.js', '../server/index.js', 'nope.css', '']) assert.strictEqual(shared.typeOf(name), null, `${name || '(empty)'} is not served`);
++
++// 3. Every listed file exists in the package (a missing one would answer 503, never a cacheable 404).
++for (const name of shared.names()) assert.ok(fs.existsSync(path.join(shared.dir, name)), `${name} is in openvibe-shared`);
++
++// 4. The route uses this list, not a list of its own.
++const src = fs.readFileSync(path.join(__dirname, '..', 'server', 'index.js'), 'utf8');
++assert.ok(src.includes("require('./web/shared-files')") && src.includes('sharedFiles.typeOf(fileName)'), 'the /shared route asks shared-files.js');
++assert.ok(!src.includes('sharedFileSet'), 'no second list in index.js');
++
++console.log(`shared files: ${shared.names().length} files served with their own types`);