openvibers/openvibe.events Commits
A missing page answers the site's 404 page in the Frame (#194)
Changes
@@@ -185,7 +185,8 @@ function createApp({ config, store, auth, keys, worker, realtime, metrics, dnsLo// A browser asking for a page that is not here gets a page; an API client the problem document.app.use((req, res) => {if (req.method === 'GET' && !req.path.startsWith('/api/') && req.accepts(['application/json', 'text/html']) === 'text/html') {-- return res.status(404).type('html').set('Content-Security-Policy', HOME_CSP).set('Cache-Control', 'no-store').send(renderNotFound());++ return res.status(404).type('html').set('Content-Security-Policy', HOME_CSP).set('Cache-Control', 'no-store')++ .set('X-Robots-Tag', 'noindex').send(renderNotFound({ siteUrl: config.siteUrl }));}return http.sendProblem(res, 404, 'events.not_found', { detail: `no route ${req.method} ${req.path}`, ctx: req.ov });});@@@ -494,12 +494,35 @@ ${sections({ apiUrl, stats })}});}--/** The page a browser gets for a path nothing serves: a real document (language, title, the app icon), not JSON. */--function renderNotFound() {-- return ['<!doctype html>', '<html lang="en">', '<head>', '<meta charset="utf-8">', '<meta name="viewport" content="width=device-width, initial-scale=1">',-- `<title>Not found · ${SITE_NAME}</title>`, '<meta name="robots" content="noindex">', appIcon.headTags({ site: 'events' }), '</head>',-- '<body style="font-family:system-ui,sans-serif;margin:3rem auto;max-width:36rem;padding:0 1rem;line-height:1.5">',-- '<h1>Not found</h1>', `<p>Nothing is here. <a href="/">${SITE_NAME}</a> · <a href="${NETWORK_URL}">OpenVibe</a></p>`, '</body>', '</html>'].join('\n');++/**++ * The page a browser gets for a path nothing serves: the site's own Frame, one way back, and no clue++ * about what was asked for. An API client keeps the problem document (server/app.js).++ */++function renderNotFound({ siteUrl = 'https://openvibe.events' } = {}) {++ const host = String(siteUrl).replace(/^https?:\/\//, '').replace(/\/.*$/, '');++ const links = [{ label: 'Guide', href: `${SOURCE}#readme` }];++ return shell.page({++ name: SITE_NAME, service: 'events', lang: 'en',++ title: `Page not found · ${SITE_NAME}`,++ siteName: SITE_NAME,++ description: `There is no page at this address on ${host}.`,++ robots: 'noindex, follow',++ navbar: { service: 'events', apiBase: NETWORK_URL, links, history: { type: 'page', title: SITE_NAME } },++ footer: FOOTER, home: '/', navLinks: links,++ head: [++ appIcon.headTags({ site: 'events' }),++ `<link rel="stylesheet" href="${ovServe.url(showcase.STYLESHEET)}">`,++ ].join('\n'),++ body: `<div id="navbar-mount"></div>++<main id="main" class="page">++<h1>Page not found</h1>++<p>There is no page at this address on ${esc(host)}. The link may be old or mistyped.</p>++<div class="sc-actions">++<a class="sc-btn sc-primary" href="/">${SITE_NAME} home</a>++<a class="sc-btn" href="${SOURCE}#readme">Developer guide</a>++</div>++</main>`,++ });}module.exports = { renderHome, renderUpdates, renderNotFound, HOME_CSP, DESCRIPTION, sections };@@@ -112,11 +112,16 @@ t('the home CSP lets Cloudflare Web Analytics load and report (it injects its beassert.match(HOME_CSP, /connect-src [^;]*https:\/\/cloudflareinsights\.com/);});--t('a browser gets a page for a path nothing serves (lang, title, icon); an API client the problem document', async () => {++t('a browser that asks for a missing page gets the site 404 page; an API client the problem document', async () => {const page = await raw(h.base, '/no-such-page', { Accept: 'text/html,*/*;q=0.8' });assert.strictEqual(page.status, 404);assert.match(page.headers['content-type'], /^text\/html/);-- assert.ok(page.body.includes('<html lang="en">') && page.body.includes('<title>Not found') && page.body.includes('rel="icon"'), page.body.slice(0, 300));++ assert.ok(page.body.includes('<h1>Page not found</h1>'), page.body.slice(0, 300));++ assert.ok(page.body.includes('There is no page at this address on openvibe.events. The link may be old or mistyped.'));++ assert.ok(page.body.includes('id="navbar-mount"'), 'the page is in the site Frame');++ assert.ok(page.body.includes('<meta name="robots" content="noindex, follow">'));++ assert.strictEqual(page.headers['x-robots-tag'], 'noindex');++ assert.ok(page.body.includes('class="sc-btn sc-primary" href="/"'), 'home is the first way back');const api = await raw(h.base, '/no-such-page', { Accept: 'application/json' });assert.strictEqual(api.status, 404);assert.match(api.headers['content-type'], /problem\+json|json/);
@@ -185,7 +185,8 @@ function createApp({ config, store, auth, keys, worker, realtime, metrics, dnsLo
@@ -185,7 +185,8 @@ function createApp({ config, store, auth, keys, worker, realtime, metrics, dnsLo
// A browser asking for a page that is not here gets a page; an API client the problem document.
// A browser asking for a page that is not here gets a page; an API client the problem document.
app.use((req, res) => {
app.use((req, res) => {
if (req.method === 'GET' && !req.path.startsWith('/api/') && req.accepts(['application/json', 'text/html']) === 'text/html') {
if (req.method === 'GET' && !req.path.startsWith('/api/') && req.accepts(['application/json', 'text/html']) === 'text/html') {
- return res.status(404).type('html').set('Content-Security-Policy', HOME_CSP).set('Cache-Control', 'no-store').send(renderNotFound());
+ return res.status(404).type('html').set('Content-Security-Policy', HOME_CSP).set('Cache-Control', 'no-store')
+ .set('X-Robots-Tag', 'noindex').send(renderNotFound({ siteUrl: config.siteUrl }));
}
}
return http.sendProblem(res, 404, 'events.not_found', { detail: `no route ${req.method} ${req.path}`, ctx: req.ov });
return http.sendProblem(res, 404, 'events.not_found', { detail: `no route ${req.method} ${req.path}`, ctx: req.ov });
});
});
@@ -494,12 +494,35 @@ ${sections({ apiUrl, stats })}
@@ -494,12 +494,35 @@ ${sections({ apiUrl, stats })}
});
});
}
}
-/** The page a browser gets for a path nothing serves: a real document (language, title, the app icon), not JSON. */
-function renderNotFound() {
- return ['<!doctype html>', '<html lang="en">', '<head>', '<meta charset="utf-8">', '<meta name="viewport" content="width=device-width, initial-scale=1">',
- `<title>Not found · ${SITE_NAME}</title>`, '<meta name="robots" content="noindex">', appIcon.headTags({ site: 'events' }), '</head>',
- '<body style="font-family:system-ui,sans-serif;margin:3rem auto;max-width:36rem;padding:0 1rem;line-height:1.5">',
- '<h1>Not found</h1>', `<p>Nothing is here. <a href="/">${SITE_NAME}</a> · <a href="${NETWORK_URL}">OpenVibe</a></p>`, '</body>', '</html>'].join('\n');
+/**
+ * The page a browser gets for a path nothing serves: the site's own Frame, one way back, and no clue
+ * about what was asked for. An API client keeps the problem document (server/app.js).
+ */
+function renderNotFound({ siteUrl = 'https://openvibe.events' } = {}) {
+ const host = String(siteUrl).replace(/^https?:\/\//, '').replace(/\/.*$/, '');
+ const links = [{ label: 'Guide', href: `${SOURCE}#readme` }];
+ return shell.page({
+ name: SITE_NAME, service: 'events', lang: 'en',
+ title: `Page not found · ${SITE_NAME}`,
+ siteName: SITE_NAME,
+ description: `There is no page at this address on ${host}.`,
+ robots: 'noindex, follow',
+ navbar: { service: 'events', apiBase: NETWORK_URL, links, history: { type: 'page', title: SITE_NAME } },
+ footer: FOOTER, home: '/', navLinks: links,
+ head: [
+ appIcon.headTags({ site: 'events' }),
+ `<link rel="stylesheet" href="${ovServe.url(showcase.STYLESHEET)}">`,
+ ].join('\n'),
+ body: `<div id="navbar-mount"></div>
+<main id="main" class="page">
+<h1>Page not found</h1>
+<p>There is no page at this address on ${esc(host)}. The link may be old or mistyped.</p>
+<div class="sc-actions">
+<a class="sc-btn sc-primary" href="/">${SITE_NAME} home</a>
+<a class="sc-btn" href="${SOURCE}#readme">Developer guide</a>
+</div>
+</main>`,
+ });
}
}
module.exports = { renderHome, renderUpdates, renderNotFound, HOME_CSP, DESCRIPTION, sections };
module.exports = { renderHome, renderUpdates, renderNotFound, HOME_CSP, DESCRIPTION, sections };
@@ -112,11 +112,16 @@ t('the home CSP lets Cloudflare Web Analytics load and report (it injects its be
@@ -112,11 +112,16 @@ t('the home CSP lets Cloudflare Web Analytics load and report (it injects its be
assert.match(HOME_CSP, /connect-src [^;]*https:\/\/cloudflareinsights\.com/);
assert.match(HOME_CSP, /connect-src [^;]*https:\/\/cloudflareinsights\.com/);
});
});
-t('a browser gets a page for a path nothing serves (lang, title, icon); an API client the problem document', async () => {
+t('a browser that asks for a missing page gets the site 404 page; an API client the problem document', async () => {
const page = await raw(h.base, '/no-such-page', { Accept: 'text/html,*/*;q=0.8' });
const page = await raw(h.base, '/no-such-page', { Accept: 'text/html,*/*;q=0.8' });
assert.strictEqual(page.status, 404);
assert.strictEqual(page.status, 404);
assert.match(page.headers['content-type'], /^text\/html/);
assert.match(page.headers['content-type'], /^text\/html/);
- assert.ok(page.body.includes('<html lang="en">') && page.body.includes('<title>Not found') && page.body.includes('rel="icon"'), page.body.slice(0, 300));
+ assert.ok(page.body.includes('<h1>Page not found</h1>'), page.body.slice(0, 300));
+ assert.ok(page.body.includes('There is no page at this address on openvibe.events. The link may be old or mistyped.'));
+ assert.ok(page.body.includes('id="navbar-mount"'), 'the page is in the site Frame');
+ assert.ok(page.body.includes('<meta name="robots" content="noindex, follow">'));
+ assert.strictEqual(page.headers['x-robots-tag'], 'noindex');
+ assert.ok(page.body.includes('class="sc-btn sc-primary" href="/"'), 'home is the first way back');
const api = await raw(h.base, '/no-such-page', { Accept: 'application/json' });
const api = await raw(h.base, '/no-such-page', { Accept: 'application/json' });
assert.strictEqual(api.status, 404);
assert.strictEqual(api.status, 404);
assert.match(api.headers['content-type'], /problem\+json|json/);
assert.match(api.headers['content-type'], /problem\+json|json/);