openvibers/openvibe.codes Commits

Serve Codes' asset and page cache headers from the shared cache-policy (#2)

OP OpenVibers committed 2 Oct 2026 · 5820191e27 · 8 files, +69 −17

checks run by GitHub CI (test, test-pg) · CI ✓ 3/3 · review SHIP by DeepSeek harness (QUALITY=high)

https://github.com/OpenVibers/OpenVibe.Codes/pull/2

Changes

ddiff --git a/server/app.js b/server/app.js
iindex bafd3b6..25776c7 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -19,6 +19,7 @@ const helmet = require('helmet');
const cookieParser = require('cookie-parser');
const rateLimit = require('express-rate-limit');
const contracts = require('openvibe-contracts');
++const cache = require('openvibe-shared/cache-policy');
const configLib = require('./config');
const { openStore } = require('./db');
@@@ -145,7 +146,7 @@ async function createApp(opts = {}) {
setHeaders(res, filePath) {
const rel = path.relative(PUBLIC_DIR, filePath).split(path.sep).join('/');
const v = res.req && res.req.query && res.req.query.v;
-- res.setHeader('Cache-Control', v && v === assetVersion(rel) ? 'public, max-age=31536000, immutable' : 'public, max-age=300');
++ res.setHeader('Cache-Control', cache.assetHeaders(rel, { hashed: !!v && v === assetVersion(rel) }));
},
}));
@@@ -167,7 +168,7 @@ async function createApp(opts = {}) {
// Never log request bodies: forms here carry secrets.
log.error('[Codes]', err && err.message ? err.message.slice(0, 300) : err);
if (res.headersSent) return;
-- res.set('Cache-Control', 'private, no-store');
++ res.set('Cache-Control', cache.htmlHeaders({ private: true }));
if (req.path.startsWith('/api/')) return contracts.http.sendProblem(res, 500, 'internal.error', { detail: 'Internal error', ctx: req.ov });
if (err && err.type === 'entity.too.large') return res.status(413).type('text/plain').send('That form was too large.');
res.status(500).type('text/plain').send('Something went wrong on our side. Try again in a moment.');
ddiff --git a/server/auth/sso.js b/server/auth/sso.js
iindex 9ee598e..67d3a70 100644
---- a/server/auth/sso.js
++++ b/server/auth/sso.js
@@@ -19,6 +19,7 @@ const { staff: staffMap } = require('openvibe-contracts');
*/
const crypto = require('crypto');
const express = require('express');
++const cache = require('openvibe-shared/cache-policy');
const ACCESS_COOKIE = 'codes_at';
const REFRESH_COOKIE = 'codes_rt';
@@@ -179,12 +180,12 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Dat
const state = crypto.randomBytes(16).toString('hex');
const { verifier, challenge } = pkcePair();
res.cookie(FLOW_COOKIE, JSON.stringify({ state, verifier, next: sanitizeNext(req.query.next), silent }), flowOpts());
-- res.set('Cache-Control', 'private, no-store');
++ res.set('Cache-Control', cache.htmlHeaders({ private: true }));
res.redirect(authorizeUrl(config, { state, challenge, silent }));
});
r.get('/callback', async (req, res) => {
-- res.set('Cache-Control', 'private, no-store');
++ res.set('Cache-Control', cache.htmlHeaders({ private: true }));
let flow = null;
try { flow = JSON.parse(req.cookies[FLOW_COOKIE] || 'null'); } catch { flow = null; }
res.clearCookie(FLOW_COOKIE, { ...cookieBase(), path: '/auth' });
@@@ -222,7 +223,7 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Dat
};
r.get('/logout', (req, res, next) => {
if (sameOriginRequest(req)) return next();
-- res.set('Cache-Control', 'private, no-store');
++ res.set('Cache-Control', cache.htmlHeaders({ private: true }));
const q = req.query.next ? `?next=${encodeURIComponent(sanitizeNext(req.query.next))}` : '';
res.type('html').send(`<!doctype html><meta charset="utf-8"><title>Sign out</title><meta name="viewport" content="width=device-width,initial-scale=1">`
+ `<form method="post" action="/auth/logout${q}" style="font:16px system-ui;margin:3em auto;max-width:24em;text-align:center">`
@@@ -244,7 +245,7 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Dat
});
r.get('/me', (req, res) => {
-- res.set('Cache-Control', 'private, no-store');
++ res.set('Cache-Control', cache.htmlHeaders({ private: true }));
// No session at all (a guest: no access or refresh cookie) is signed out, not an error: the shared
// navbar asks this on every page view, and a 401 logged a console error on each (browser check,
// OpenVibe.Host). A session cookie that is present but invalid or expired still answers 401.
ddiff --git a/server/http/discovery.js b/server/http/discovery.js
iindex 9aeba2b..c368703 100644
---- a/server/http/discovery.js
++++ b/server/http/discovery.js
@@@ -15,6 +15,7 @@ const fs = require('fs');
const path = require('path');
const contracts = require('openvibe-contracts');
const seo = require('openvibe-shared/seo');
++const cache = require('openvibe-shared/cache-policy');
const { asyncRouter } = require('./router');
const SITE_NAME = 'OpenVibe.Codes';
@@@ -120,7 +121,7 @@ function createDiscoveryRoutes(ctx) {
r.get('/robots.txt', (_req, res) => {
// The same rules as before, plus the search and AI crawlers the shared kit names by name and
// the sitemap. Every previous Disallow is kept (DISALLOW).
-- res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(
++ res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(
'# openvibe.codes: the public pages are for search and AI crawlers; the portal, sign-in, staff and the API are not.\n'
+ seo.robotsTxt({ sitemaps: [abs('/sitemap.xml')], disallow: DISALLOW }));
});
@@@ -135,7 +136,7 @@ function createDiscoveryRoutes(ctx) {
];
if (firstContract) machine.push({ title: 'Contract JSON Schema', url: abs(`/docs/contracts/${firstContract}.json`), note: 'append .json to any contract page for its schema' });
if (firstService) machine.push({ title: 'Service OpenAPI', url: abs(`/docs/api/${firstService}.json`), note: 'append .json to any API explorer page for its OpenAPI document' });
-- res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(seo.llmsTxt({
++ res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.llmsTxt({
name: SITE_NAME,
summary: 'OpenVibe.Codes: the OpenVibe developer portal over OpenVibe.Network\'s API — projects, apps, scoped credentials and capability grants, reference documentation generated from the pinned contracts and SDK, and OAuth, webhook and manifest tools.',
details: 'Every page is server-rendered and readable without JavaScript, and every one is public: no account is needed to read the docs or use the OAuth, webhook and manifest tools. Projects, apps, credentials and grants belong to OpenVibe.Network; the pages that manage them need a signed-in OpenVibe account and are deliberately not listed here or in the sitemap. The reference under /docs is generated at boot from the exact openvibe-contracts and openvibe-sdk versions the platform runs, so it cannot drift from them.',
@@@ -175,7 +176,7 @@ function createDiscoveryRoutes(ctx) {
r.get('/sitemap.xml', async (_req, res) => {
const entries = await sitemapEntries();
const urls = entries.map((e) => ({ loc: abs(e.path), ...(e.lastmod ? { lastmod: e.lastmod } : {}), changefreq: e.changefreq, priority: e.priority }));
-- res.type('application/xml').set('Cache-Control', 'public, max-age=3600').send(seo.sitemapXml(urls));
++ res.type('application/xml').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.sitemapXml(urls));
});
return r;
ddiff --git a/server/http/docs.js b/server/http/docs.js
iindex 83d8506..56c673e 100644
---- a/server/http/docs.js
++++ b/server/http/docs.js
@@@ -21,6 +21,7 @@
* /docs/adr/:id ADRs as published in openvibe-contracts
*/
const { asyncRouter } = require('./router');
++const cache = require('openvibe-shared/cache-policy');
const { html, raw, table, code, badge, time, problemBox } = require('../render/html');
const { send } = require('../render/layout');
const { markdown } = require('../render/markdown');
@@@ -32,7 +33,7 @@ function createDocsRoutes(ctx) {
const openapi = require('openvibe-contracts').openapi;
const apiIndex = openapi.index();
const apiDocs = new Map(apiIndex.map((s) => [s.service, openapi.document(s.service)]));
-- const PUBLIC_CACHE = 'public, max-age=300';
++ const PUBLIC_CACHE = cache.htmlHeaders({ maxAge: 300 });
const tagNote = (tag, version) => (tag !== `v${version}` ? html` (tag ${tag})` : '');
const versions = () => html`<p class="versions">Generated at ${time(docs.generatedAt)} from
@@@ -256,7 +257,7 @@ ${(doc['x-openvibe-other-bindings'] || []).length ? html`<h2>Other bindings</h2>
sample = await f.json();
} catch (err) { failed = err.message; }
send(res, 200, {
-- index: true, cache: 'public, max-age=60', viewer: req.viewer, config, path: req.originalUrl,
++ index: true, cache: cache.htmlHeaders({ maxAge: 60 }), viewer: req.viewer, config, path: req.originalUrl,
title: 'The update system',
description: 'How every OpenVibe site shows what shipped: the network changelog feed, the data-ov-shipped markup and the openvibe-shared / openvibe-sdk helpers.',
crumbs: [{ label: 'Docs', href: '/docs' }, { label: 'Update system' }],
@@@ -321,7 +322,7 @@ ${table(['Event type', 'Payload', 'Produced by', 'Consumed by'], docs.events.map
];
});
send(res, problem ? 502 : 200, {
-- index: true, cache: 'public, max-age=30', viewer: req.viewer, config, path: req.originalUrl,
++ index: true, cache: cache.htmlHeaders({ maxAge: 30 }), viewer: req.viewer, config, path: req.originalUrl,
title: 'Services',
crumbs: [{ label: 'Docs', href: '/docs' }, { label: 'Services' }],
body: html`<h1>Service registry</h1>
@@@ -349,7 +350,7 @@ ${table(['Service', 'Status (manifest)', 'Domains'], docs.services.map((s) => [h
const api = (list || []).filter((t) => t.api && t.status !== 'unavailable');
const families = [...new Set(api.map((t) => t.family))].sort();
send(res, problem && !list ? 502 : 200, {
-- index: true, cache: 'public, max-age=60', viewer: req.viewer, config, path: req.originalUrl,
++ index: true, cache: cache.htmlHeaders({ maxAge: 60 }), viewer: req.viewer, config, path: req.originalUrl,
title: 'Tools API',
crumbs: [{ label: 'Docs', href: '/docs' }, { label: 'Tools' }],
body: html`<h1>Tools API</h1>
@@@ -383,7 +384,7 @@ ${families.map((f) => html`<h2>${f}</h2>${table(['Tool', 'Runs as', 'Access', 'I
const n = (x) => Number(x).toLocaleString('en-US');
const p = policy;
send(res, problem && !p ? 502 : 200, {
-- index: true, cache: 'public, max-age=300', viewer: req.viewer, config, path: req.originalUrl,
++ index: true, cache: PUBLIC_CACHE, viewer: req.viewer, config, path: req.originalUrl,
title: 'Billing policy',
crumbs: [{ label: 'Docs', href: '/docs' }, { label: 'Billing' }],
body: html`<h1>Billing policy</h1>
ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex 6628e72..d1f9439 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -15,6 +15,7 @@ const fs = require('fs');
const ovServe = require('openvibe-shared/serve');
const frame = require('openvibe-shared/frame');
const showcase = require('openvibe-shared/showcase');
++const cache = require('openvibe-shared/cache-policy');
const path = require('path');
const express = require('express');
const { asyncRouter } = require('./router');
@@@ -62,7 +63,7 @@ function createPageRoutes(ctx) {
const { config, docs, releases, trust, network, sso } = ctx;
const r = asyncRouter();
const form = express.urlencoded({ extended: false, limit: '32kb' });
-- const PUBLIC_CACHE = 'public, max-age=300';
++ const PUBLIC_CACHE = cache.htmlHeaders({ maxAge: 300 });
const governance = loadGovernance();
const page = (req, res, o, status = 200) => send(res, status, { viewer: req.viewer, config, path: req.originalUrl, ...o });
ddiff --git a/server/http/portal.js b/server/http/portal.js
iindex 81feaf1..ed04e54 100644
---- a/server/http/portal.js
++++ b/server/http/portal.js
@@@ -19,6 +19,7 @@
*/
const express = require('express');
const { asyncRouter } = require('./router');
++const cache = require('openvibe-shared/cache-policy');
const Busboy = require('busboy');
const { html, raw, table, code, badge, time, notice, csrfField, problemBox } = require('../render/html');
const { send } = require('../render/layout');
@@@ -738,7 +739,7 @@ function unexpected(req, res, err) {
const log = req.app.locals.ctx.log;
log.error('[Codes]', err && err.message ? err.message.slice(0, 300) : err);
if (res.headersSent) return;
-- res.set('Cache-Control', 'private, no-store');
++ res.set('Cache-Control', cache.htmlHeaders({ private: true }));
res.status(500).type('text/plain').send('Something went wrong on our side. Try again in a moment.');
}
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 3e3355b..3dae154 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -15,6 +15,7 @@ const path = require('path');
const appIcon = require('openvibe-shared/app-icon');
const frame = require('openvibe-shared/frame');
const seo = require('openvibe-shared/seo');
++const cache = require('openvibe-shared/cache-policy');
const { html, raw, esc } = require('./html');
const NETWORK_URL = 'https://openvibe.network';
@@@ -126,7 +127,7 @@ function render(v) { return require('./html').render(v); }
function send(res, status, o) {
// A page rendered for a signed-in person names them in the account bar: never shared caches.
const personal = o.viewer && o.viewer.kind === 'user';
-- if (!res.get('Cache-Control')) res.set('Cache-Control', !personal && o.cache ? o.cache : 'private, no-store');
++ if (!res.get('Cache-Control')) res.set('Cache-Control', !personal && o.cache ? o.cache : cache.htmlHeaders({ private: true }));
res.set('Vary', 'Cookie');
res.status(status).type('html').send(renderPage(o));
}
ddiff --git a/test/asset-cache.test.js b/test/asset-cache.test.js
nnew file mode 100644
iindex 0000000..4243670
---- /dev/null
++++ b/test/asset-cache.test.js
@@@ -0,0 +1,45 @@
++'use strict';
++/**
++ * The one cache rule Codes serves assets with (openvibe-shared/cache-policy, plan T11): a URL
++ * whose ?v= is the asset's current hash is immutable for a year, because those bytes can never
++ * change under the same URL; a wrong or missing ?v= may change at any moment, so it gets the
++ * short public window with a day of stale-while-revalidate. The values are pinned here against
++ * the module, not re-stated: the site never picks its own numbers.
++ */
++const assert = require('assert');
++const cache = require('openvibe-shared/cache-policy');
++const { assetVersion } = require('../server/render/layout');
++const { boot, check, done } = require('./helpers/boot');
++
++const ASSET = 'css/codes.css';
++const SHORT = 'public, max-age=300, stale-while-revalidate=86400';
++
++(async () => {
++ const t = await boot();
++ try {
++ await check('the current ?v=<assetVersion> is immutable for a year', async () => {
++ const r = await t.get(`/${ASSET}?v=${assetVersion(ASSET)}`);
++ assert.strictEqual(r.status, 200);
++ assert.strictEqual(r.headers.get('cache-control'), 'public, max-age=31536000, immutable');
++ });
++
++ await check('a wrong-but-hex ?v= is never pinned', async () => {
++ const r = await t.get(`/${ASSET}?v=deadbeefdeadbeef`);
++ assert.strictEqual(r.status, 200);
++ assert.strictEqual(r.headers.get('cache-control'), SHORT);
++ });
++
++ await check('no ?v= gets the short public window', async () => {
++ const r = await t.get(`/${ASSET}`);
++ assert.strictEqual(r.status, 200);
++ assert.strictEqual(r.headers.get('cache-control'), SHORT);
++ });
++
++ await check('those values are the module\'s, not Codes\' own', () => {
++ assert.strictEqual(cache.assetHeaders(ASSET, { hashed: true }), 'public, max-age=31536000, immutable');
++ assert.strictEqual(cache.assetHeaders(ASSET, { hashed: false }), SHORT);
++ assert.strictEqual(cache.htmlHeaders({ private: true }), 'private, no-store');
++ });
++ } finally { await t.close(); }
++ done();
++})().catch((e) => { console.error(e); process.exit(1); });