openvibers/openvibe.wiki Commits

Static assets are immutable only when ?v= is the served file's hash (roadmap WS-P task 10): any ?v= used to be cached for a year, so after a deploy or a rollback an older page's URL could pin the wrong bytes; other ?v= values and plain URLs now get a 5-minute cache, like Blog, Codes and the others

OP OpenVibers committed 26 Sept 2026 · 255de21754 · 3 files, +34 −2


Changes

ddiff --git a/server/app.js b/server/app.js
iindex 50a4ecd..e4a99fc 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -93,7 +93,13 @@ function createApp({ config, svc, viewers, platform, keys, db, log = console, ra
app.use('/shared', require('openvibe-shared/serve').handler());
app.use(express.static(PUBLIC_DIR, {
index: false, redirect: false,
-- setHeaders(res) { res.setHeader('Cache-Control', res.req && res.req.query && res.req.query.v ? 'public, max-age=31536000, immutable' : 'public, max-age=3600'); },
++ // Immutable only when ?v= is the hash of the bytes served: an older page's URL after a deploy (or a
++ // rollback) gets the current file with a short cache, never pinned for a year (WS-P task 10).
++ setHeaders(res, filePath) {
++ const v = res.req && res.req.query && res.req.query.v;
++ const rel = path.relative(PUBLIC_DIR, filePath).split(path.sep).join('/');
++ res.setHeader('Cache-Control', v && v === require('./render/layout').assetVersion(rel) ? 'public, max-age=31536000, immutable' : 'public, max-age=300');
++ },
}));
app.use(createMachine({ svc, config }));
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex f8bab81..297f7e5 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -29,6 +29,8 @@ function asset(rel) {
}
return `/${rel}?v=${hashes.get(rel)}`;
}
++/** The ?v= this process renders for a public/ file (the static route caches only that one as immutable). */
++function assetVersion(rel) { asset(rel); return hashes.get(rel); }
function navConfig(o, config) {
return {
@@@ -100,4 +102,4 @@ document.addEventListener('DOMContentLoaded', function () {
</html>`;
}
--module.exports = { renderPage, asset, SITE_NAME, DEFAULT_DESCRIPTION, NETWORK_URL };
++module.exports = { renderPage, asset, SITE_NAME, DEFAULT_DESCRIPTION, NETWORK_URL, assetVersion };
ddiff --git a/test/asset-cache.test.js b/test/asset-cache.test.js
nnew file mode 100644
iindex 0000000..f94a6db
---- /dev/null
++++ b/test/asset-cache.test.js
@@@ -0,0 +1,24 @@
++'use strict';
++// Static assets (WS-P task 10): a ?v= URL is cached for a year only when ?v= is the hash of the bytes
++// served. An older page's URL after a deploy or a rollback, or any other ?v=, gets the current file
++// with a short cache, so no cache can pin the wrong bytes under that URL.
++const assert = require('assert');
++const { boot } = require('./helpers');
++const { assetVersion } = require('../server/render/layout');
++
++(async () => {
++ const h = await boot();
++ try {
++ const v = assetVersion('css/wiki.css');
++ assert.match(v, /^[0-9a-f]{10}$/);
++ let r = await fetch(`${h.base}/css/wiki.css?v=${v}`);
++ assert.strictEqual(r.status, 200);
++ assert.strictEqual(r.headers.get('cache-control'), 'public, max-age=31536000, immutable', 'the current hash is immutable');
++ r = await fetch(`${h.base}/css/wiki.css?v=0123456789`);
++ assert.strictEqual(r.status, 200);
++ assert.strictEqual(r.headers.get('cache-control'), 'public, max-age=300', 'another ?v= is never pinned');
++ r = await fetch(`${h.base}/css/wiki.css`);
++ assert.strictEqual(r.headers.get('cache-control'), 'public, max-age=300');
++ console.log('asset cache: all checks passed');
++ } finally { await h.stop(); }
++})().catch((e) => { console.error(e); process.exit(1); });