openvibers/openvibe.live Commits
/shared serves Shared's stylesheets and images (ov-forms.css was 404) (#609)
Changes
@@@ -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 ficonst 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).nnew file mode 100644@@@ -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()] };nnew file mode 100644@@@ -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`);
@@ -598,7 +598,7 @@ app.use(async (req, res, next) => {
@@ -598,7 +598,7 @@ app.use(async (req, res, next) => {
// Serve openvibe-shared browser assets at /shared/ — straight from node_modules/openvibe-shared,
// 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
// 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.
// 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 SHARED_BROWSER_FILES = sharedFiles.BROWSER;
const sharedServePath = sharedFiles.dir;
const sharedServePath = sharedFiles.dir;
assets.setSharedDir(sharedServePath);
assets.setSharedDir(sharedServePath);
@@ -614,13 +614,12 @@ console.log(`[Server] /shared: serving ${SHARED_BROWSER_FILES.length} browser fi
@@ -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.
// The share cards (og:image on every page), before the browser-file allowlist below.
require('./web/share-cards').register(app);
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) => {
app.use('/shared', (req, res, next) => {
const fileName = path.basename(req.path);
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');
return res.status(404).type('text/plain').send('Not found');
}
}
const filePath = path.join(sharedServePath, fileName);
const filePath = path.join(sharedServePath, fileName);
@@ -630,7 +629,7 @@ console.log(`[Server] /shared: serving ${SHARED_BROWSER_FILES.length} browser fi
@@ -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);
const hashed = Boolean(req.query.v) && req.query.v === assets.hashOf('/shared/' + fileName);
res.setHeader('Cache-Control', cache.assetHeaders(req.originalUrl, { hashed }));
res.setHeader('Cache-Control', cache.assetHeaders(req.originalUrl, { hashed }));
if (hashed) res.setHeader('CDN-Cache-Control', cache.IMMUTABLE);
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) => {
res.sendFile(filePath, (err) => {
// A listed file that cannot be read is this server's fault, and passing: never a cacheable answer
// 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).
// (its headers above said immutable; Cloudflare kept a 404 navbar through a deploy on 2026-10-17).
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -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()] };
new file mode 100644
new file mode 100644
@@ -0,0 +1,29 @@
@@ -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`);