openvibers/openvibe.tips Commits
Route every Cache-Control value through openvibe-shared cache-policy (#2)
Changes
@@@ -21,6 +21,7 @@ const express = require('express');const cookieParser = require('cookie-parser');const { http } = require('openvibe-contracts');const { isLoopbackDirect } = require('openvibe-shared/metrics');++const cache = require('openvibe-shared/cache-policy');const { loadConfig } = require('./config');const { openDb } = require('./db');const { createKeyProvider, createUserAuth } = require('./network');@@@ -116,7 +117,7 @@ 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' : 'no-cache');++ res.setHeader('Cache-Control', cache.assetHeaders(rel, { hashed: v && v === assetVersion(rel) }));},}));app.use(createWebRoutes({ domain, config, layout, userAuth }));@@@ -23,6 +23,7 @@*/const crypto = require('crypto');const ovServe = require('openvibe-shared/serve');++const cache = require('openvibe-shared/cache-policy');const frame = require('openvibe-shared/frame');const seo = require('openvibe-shared/seo');const express = require('express');@@@ -79,7 +80,7 @@ function createWebRoutes({ domain, config, layout, userAuth }) {r.get('/updates', withViewer, (req, res) => html(res, pageFor(req, { canonicalPath: '/updates', title: 'What shipped on OpenVibe.Tips', body: frame.updatesBody({ service: 'tips', siteName: 'OpenVibe.Tips' }) + `<script src="${ovServe.url('shipped.js')}" defer></script>` })));// Crawl and machine-readability artifacts, built from openvibe-shared/seo — the same// toolkit the other OpenVibe sites use. Public data only, never the viewer.-- r.get('/robots.txt', (req, res) => res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(seo.robotsTxt({++ r.get('/robots.txt', (req, res) => res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.robotsTxt({sitemaps: [`${config.baseUrl}/sitemap.xml`],disallow: ['/dashboard', '/receipts', '/overlay/', '/moderate/', '/auth/', '/api/', '/internal/'],allowAI: false,@@@ -87,10 +88,10 @@ function createWebRoutes({ domain, config, layout, userAuth }) {r.get('/sitemap.xml', async (req, res) => {const rows = await db.many(sql`SELECT handle, updated_at FROM creator_tip_profiles WHERE page_enabled ORDER BY handle`);const urls = [{ loc: `${config.baseUrl}/`, lastmod: BOOT_AT }, ...rows.map((p) => ({ loc: `${config.baseUrl}/${p.handle}`, lastmod: p.updated_at }))];-- res.type('application/xml').set('Cache-Control', 'public, max-age=900').send(seo.sitemapXml(urls));++ res.type('application/xml').set('Cache-Control', cache.htmlHeaders({ maxAge: 900 })).send(seo.sitemapXml(urls));});// /llms.txt (llmstxt.org): a plain-markdown map of the site for language-model crawlers.-- r.get('/llms.txt', (req, res) => res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(seo.llmsTxt({++ r.get('/llms.txt', (req, res) => res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.llmsTxt({name: 'OpenVibe.Tips',summary: 'OpenVibe.Tips: creator support — tips, goals, paid messages, TTS and media requests, and overlays.',details: 'A creator page lists the goals and a tip form that works without JavaScript; tips settle through Billing, and Tips owns the interaction, the goals and the overlay alerts. The home page lists creators whose pages are switched on. Reading any public page needs no account; tipping needs a signed-in supporter, and the dashboard, receipts, moderation, overlays, sign-in and the API are per-person and never listed here.',nnew file mode 100644@@@ -0,0 +1,34 @@++'use strict';++// Cache-Control comes from openvibe-shared/cache-policy, not from a literal per route: a versioned++// asset is immutable for a year, any other asset is a five-minute window with a day of++// stale-while-revalidate, and the machine-readable pages get the shared html policy.++const assert = require('assert');++const { boot, check, done } = require('./helpers/app');++const cache = require('openvibe-shared/cache-policy');++const { assetVersion } = require('../server/web/layout');++++(async () => {++ const t = await boot();++ const get = async (p) => { const r = await fetch(t.base + p, { redirect: 'manual' }); return { status: r.status, headers: r.headers, text: await r.text() }; };++++ await check('static assets: immutable only under their own ?v=<version>, short-lived otherwise', async () => {++ const rel = 'css/overlay.css';++ const good = await get(`/${rel}?v=${assetVersion(rel)}`);++ assert.strictEqual(good.status, 200);++ assert.strictEqual(good.headers.get('cache-control'), cache.IMMUTABLE);++ const wrong = await get(`/${rel}?v=deadbeefdeadbeef`);++ assert.strictEqual(wrong.status, 200);++ assert.strictEqual(wrong.headers.get('cache-control'), 'public, max-age=300, stale-while-revalidate=86400');++ const bare = await get(`/${rel}`);++ assert.strictEqual(bare.status, 200);++ assert.strictEqual(bare.headers.get('cache-control'), 'public, max-age=300, stale-while-revalidate=86400');++ });++++ await check('robots.txt: the shared html policy at the same window', async () => {++ const r = await get('/robots.txt');++ assert.strictEqual(r.status, 200);++ assert.strictEqual(r.headers.get('cache-control'), cache.htmlHeaders({ maxAge: 3600 }));++ });++++ await done();++})();
@@ -21,6 +21,7 @@ const express = require('express');
@@ -21,6 +21,7 @@ const express = require('express');
const cookieParser = require('cookie-parser');
const cookieParser = require('cookie-parser');
const { http } = require('openvibe-contracts');
const { http } = require('openvibe-contracts');
const { isLoopbackDirect } = require('openvibe-shared/metrics');
const { isLoopbackDirect } = require('openvibe-shared/metrics');
+const cache = require('openvibe-shared/cache-policy');
const { loadConfig } = require('./config');
const { loadConfig } = require('./config');
const { openDb } = require('./db');
const { openDb } = require('./db');
const { createKeyProvider, createUserAuth } = require('./network');
const { createKeyProvider, createUserAuth } = require('./network');
@@ -116,7 +117,7 @@ function createApp(opts = {}) {
@@ -116,7 +117,7 @@ function createApp(opts = {}) {
setHeaders(res, filePath) {
setHeaders(res, filePath) {
const rel = path.relative(PUBLIC_DIR, filePath).split(path.sep).join('/');
const rel = path.relative(PUBLIC_DIR, filePath).split(path.sep).join('/');
const v = res.req && res.req.query && res.req.query.v;
const v = res.req && res.req.query && res.req.query.v;
- res.setHeader('Cache-Control', v && v === assetVersion(rel) ? 'public, max-age=31536000, immutable' : 'no-cache');
+ res.setHeader('Cache-Control', cache.assetHeaders(rel, { hashed: v && v === assetVersion(rel) }));
},
},
}));
}));
app.use(createWebRoutes({ domain, config, layout, userAuth }));
app.use(createWebRoutes({ domain, config, layout, userAuth }));
@@ -23,6 +23,7 @@
@@ -23,6 +23,7 @@
*/
*/
const crypto = require('crypto');
const crypto = require('crypto');
const ovServe = require('openvibe-shared/serve');
const ovServe = require('openvibe-shared/serve');
+const cache = require('openvibe-shared/cache-policy');
const frame = require('openvibe-shared/frame');
const frame = require('openvibe-shared/frame');
const seo = require('openvibe-shared/seo');
const seo = require('openvibe-shared/seo');
const express = require('express');
const express = require('express');
@@ -79,7 +80,7 @@ function createWebRoutes({ domain, config, layout, userAuth }) {
@@ -79,7 +80,7 @@ function createWebRoutes({ domain, config, layout, userAuth }) {
r.get('/updates', withViewer, (req, res) => html(res, pageFor(req, { canonicalPath: '/updates', title: 'What shipped on OpenVibe.Tips', body: frame.updatesBody({ service: 'tips', siteName: 'OpenVibe.Tips' }) + `<script src="${ovServe.url('shipped.js')}" defer></script>` })));
r.get('/updates', withViewer, (req, res) => html(res, pageFor(req, { canonicalPath: '/updates', title: 'What shipped on OpenVibe.Tips', body: frame.updatesBody({ service: 'tips', siteName: 'OpenVibe.Tips' }) + `<script src="${ovServe.url('shipped.js')}" defer></script>` })));
// Crawl and machine-readability artifacts, built from openvibe-shared/seo — the same
// Crawl and machine-readability artifacts, built from openvibe-shared/seo — the same
// toolkit the other OpenVibe sites use. Public data only, never the viewer.
// toolkit the other OpenVibe sites use. Public data only, never the viewer.
- r.get('/robots.txt', (req, res) => res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(seo.robotsTxt({
+ r.get('/robots.txt', (req, res) => res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.robotsTxt({
sitemaps: [`${config.baseUrl}/sitemap.xml`],
sitemaps: [`${config.baseUrl}/sitemap.xml`],
disallow: ['/dashboard', '/receipts', '/overlay/', '/moderate/', '/auth/', '/api/', '/internal/'],
disallow: ['/dashboard', '/receipts', '/overlay/', '/moderate/', '/auth/', '/api/', '/internal/'],
allowAI: false,
allowAI: false,
@@ -87,10 +88,10 @@ function createWebRoutes({ domain, config, layout, userAuth }) {
@@ -87,10 +88,10 @@ function createWebRoutes({ domain, config, layout, userAuth }) {
r.get('/sitemap.xml', async (req, res) => {
r.get('/sitemap.xml', async (req, res) => {
const rows = await db.many(sql`SELECT handle, updated_at FROM creator_tip_profiles WHERE page_enabled ORDER BY handle`);
const rows = await db.many(sql`SELECT handle, updated_at FROM creator_tip_profiles WHERE page_enabled ORDER BY handle`);
const urls = [{ loc: `${config.baseUrl}/`, lastmod: BOOT_AT }, ...rows.map((p) => ({ loc: `${config.baseUrl}/${p.handle}`, lastmod: p.updated_at }))];
const urls = [{ loc: `${config.baseUrl}/`, lastmod: BOOT_AT }, ...rows.map((p) => ({ loc: `${config.baseUrl}/${p.handle}`, lastmod: p.updated_at }))];
- res.type('application/xml').set('Cache-Control', 'public, max-age=900').send(seo.sitemapXml(urls));
+ res.type('application/xml').set('Cache-Control', cache.htmlHeaders({ maxAge: 900 })).send(seo.sitemapXml(urls));
});
});
// /llms.txt (llmstxt.org): a plain-markdown map of the site for language-model crawlers.
// /llms.txt (llmstxt.org): a plain-markdown map of the site for language-model crawlers.
- r.get('/llms.txt', (req, res) => res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(seo.llmsTxt({
+ r.get('/llms.txt', (req, res) => res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.llmsTxt({
name: 'OpenVibe.Tips',
name: 'OpenVibe.Tips',
summary: 'OpenVibe.Tips: creator support — tips, goals, paid messages, TTS and media requests, and overlays.',
summary: 'OpenVibe.Tips: creator support — tips, goals, paid messages, TTS and media requests, and overlays.',
details: 'A creator page lists the goals and a tip form that works without JavaScript; tips settle through Billing, and Tips owns the interaction, the goals and the overlay alerts. The home page lists creators whose pages are switched on. Reading any public page needs no account; tipping needs a signed-in supporter, and the dashboard, receipts, moderation, overlays, sign-in and the API are per-person and never listed here.',
details: 'A creator page lists the goals and a tip form that works without JavaScript; tips settle through Billing, and Tips owns the interaction, the goals and the overlay alerts. The home page lists creators whose pages are switched on. Reading any public page needs no account; tipping needs a signed-in supporter, and the dashboard, receipts, moderation, overlays, sign-in and the API are per-person and never listed here.',
new file mode 100644
new file mode 100644
@@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
+'use strict';
+// Cache-Control comes from openvibe-shared/cache-policy, not from a literal per route: a versioned
+// asset is immutable for a year, any other asset is a five-minute window with a day of
+// stale-while-revalidate, and the machine-readable pages get the shared html policy.
+const assert = require('assert');
+const { boot, check, done } = require('./helpers/app');
+const cache = require('openvibe-shared/cache-policy');
+const { assetVersion } = require('../server/web/layout');
+
+(async () => {
+ const t = await boot();
+ const get = async (p) => { const r = await fetch(t.base + p, { redirect: 'manual' }); return { status: r.status, headers: r.headers, text: await r.text() }; };
+
+ await check('static assets: immutable only under their own ?v=<version>, short-lived otherwise', async () => {
+ const rel = 'css/overlay.css';
+ const good = await get(`/${rel}?v=${assetVersion(rel)}`);
+ assert.strictEqual(good.status, 200);
+ assert.strictEqual(good.headers.get('cache-control'), cache.IMMUTABLE);
+ const wrong = await get(`/${rel}?v=deadbeefdeadbeef`);
+ assert.strictEqual(wrong.status, 200);
+ assert.strictEqual(wrong.headers.get('cache-control'), 'public, max-age=300, stale-while-revalidate=86400');
+ const bare = await get(`/${rel}`);
+ assert.strictEqual(bare.status, 200);
+ assert.strictEqual(bare.headers.get('cache-control'), 'public, max-age=300, stale-while-revalidate=86400');
+ });
+
+ await check('robots.txt: the shared html policy at the same window', async () => {
+ const r = await get('/robots.txt');
+ assert.strictEqual(r.status, 200);
+ assert.strictEqual(r.headers.get('cache-control'), cache.htmlHeaders({ maxAge: 3600 }));
+ });
+
+ await done();
+})();