openvibers/openvibe.shared cache-policy.js

cache-policy.js

main 3.2 KB · 63 lines · javascript

1'use strict';
2/**
3 * openvibe-shared/cache-policy — the one cache rule every OpenVibe site uses, so no site has to
4 * remember it and no site invents its own numbers: a content-addressed asset is immutable for a
5 * year, everything else is a short public window with a short stale-while-revalidate, and a page
6 * that is personalised for the person reading it is never cached by anybody.
7 *
8 * const cache = require('openvibe-shared/cache-policy');
9 * res.setHeader('Cache-Control', cache.assetHeaders(req.url)); // ?v=<hash> → immutable
10 * res.setHeader('Cache-Control', cache.htmlHeaders()); // public, max-age=120, swr 3600
11 * app.use(cache.applyHtml({ private: req.path === '/account' }));
12 *
13 * A URL is content-addressed when it carries `?v=<hash>` (what serve.url() hands out) or a hashed
14 * filename (`app.6f2a91c3.js`, `styles-a1b2c3d4e5.css`). Immutable is only ever right for those:
15 * the bytes cannot change under the same URL. Everything else may change at any moment, so it gets
16 * five minutes plus five more of stale-while-revalidate — a visitor is served the old copy for a
17 * second while the new one is fetched, and nobody waits on the network twice. 4.227.0: it was a day,
18 * and the first visit to each site after a release ran the old navbar.js (sites load it without ?v=),
19 * so the owner's phone showed an old menu hours after the new one shipped (11-12). After ten quiet
20 * minutes a browser asks again: a 304 for an unchanged file.
21 */
22const IMMUTABLE = 'public, max-age=31536000, immutable';
23const ASSET_MAX_AGE = 300;
24const ASSET_SWR = 300;
25
26/** True when `path` is content-addressed: `?v=<hash>` or a hash in the filename. */
27function isHashed(path) {
28 const s = String(path == null ? '' : path);
29 if (/(?:[?&]|&amp;)v=[0-9a-f]{8,64}\b/.test(s)) return true;
30 const file = s.split(/[?#]/)[0];
31 return /[.\-_][0-9a-f]{8,64}(?=[.\-_/]|$)/.test(file);
32}
33
34/**
35 * The Cache-Control for a static asset. `hashed` defaults to the URL's own content-address; pass
36 * it explicitly when the caller knows (a build manifest, a filename it generated itself).
37 */
38function assetHeaders(path, { hashed = isHashed(path), maxAge = ASSET_MAX_AGE, swr = ASSET_SWR } = {}) {
39 if (hashed) return IMMUTABLE;
40 return `public, max-age=${maxAge}, stale-while-revalidate=${swr}`;
41}
42
43/**
44 * The Cache-Control for an HTML page. `private: true` (the default when a page is personalised)
45 * is `private, no-store`: no shared cache, no browser cache, nothing left on a shared machine.
46 */
47function htmlHeaders({ private: isPrivate = false, maxAge = 120, swr = 3600 } = {}) {
48 if (isPrivate) return 'private, no-store';
49 return `public, max-age=${maxAge}, stale-while-revalidate=${swr}`;
50}
51
52/** Express/connect middleware: sets the HTML policy on every GET/HEAD response that has no policy yet. */
53function applyHtml(opts = {}) {
54 const value = htmlHeaders(opts);
55 return function openvibeCachePolicy(req, res, next) {
56 if (req.method !== 'GET' && req.method !== 'HEAD') return next();
57 if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', value);
58 return next();
59 };
60}
61
62module.exports = { IMMUTABLE, isHashed, assetHeaders, htmlHeaders, applyHtml };
63