openvibers/openvibe.ai Commits

A missing page answers the site's 404 page in the Frame (#248)

OP OpenVibers committed 12 days ago · 6875b66fb9 · 3 files, +42 −9

A browser that asks for a path AI does not serve gets the site's own 404 page (the OpenVibe Frame, a noindex page, one line naming the host and links home, to /stats and to the developer guide). API clients keep the problem document: /api/*, /v1/* and a request whose Accept is not text/html still get application/problem+json.

Changes

ddiff --git a/server/app.js b/server/app.js
iindex 09c0a68..5d9302e 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -237,7 +237,8 @@ function createApp({ config, db, registry, pool, quotas, cache, runs, auth, keys
// 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.path.startsWith('/v1/') && req.accepts(['application/json', 'text/html']) === 'text/html') {
-- return res.status(404).type('html').set('Content-Security-Policy', home.HOME_CSP).set('Cache-Control', 'no-store').send(home.renderNotFound());
++ return res.status(404).type('html').set('Content-Security-Policy', home.HOME_CSP).set('Cache-Control', 'no-store')
++ .set('X-Robots-Tag', 'noindex').send(home.renderNotFound({ siteUrl: config.baseUrl }));
}
return http.sendProblem(res, 404, 'ai.not_found', { detail: `no route ${req.method} ${req.path}`, ctx: req.ov });
});
ddiff --git a/server/home.js b/server/home.js
iindex f581c9d..428d4bf 100644
---- a/server/home.js
++++ b/server/home.js
@@@ -414,12 +414,35 @@ function llmsTxt({ siteUrl }) {
].join('\n');
}
--/** 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: 'ai' }), '</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="/stats">Prices and latency</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://ai.openvibe.services' } = {}) {
++ const host = String(siteUrl).replace(/^https?:\/\//, '').replace(/\/.*$/, '');
++ return shell.page({
++ name: SITE_NAME, service: 'ai', 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: 'ai', apiBase: NETWORK_URL, links: NAV_LINKS, history: { type: 'page', title: SITE_NAME } },
++ footer: FOOTER, home: '/', navLinks: NAV_LINKS,
++ head: [
++ appIcon.headTags({ site: 'ai' }),
++ `<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="/stats">Prices and latency</a>
++<a class="sc-btn" href="${GUIDE}">Developer guide</a>
++</div>
++</main>`,
++ });
}
module.exports = { renderHome, renderUpdates, renderStats, llmsTxt, llmsFullTxt, PUBLIC_PAGES, HOME_CSP, SITE_NAME, renderNotFound, FAQ, DESCRIPTION };
ddiff --git a/test/home.test.js b/test/home.test.js
iindex 3aaca46..8e91a06 100644
---- a/test/home.test.js
++++ b/test/home.test.js
@@@ -183,13 +183,22 @@ t.test('the CSPs let Cloudflare Web Analytics load and report (it injects its be
assert.strictEqual(stats.headers.get('content-security-policy'), HOME_CSP);
});
--t.test('a browser gets a page for a path nothing serves; an API client the problem document; /favicon.ico is the icon', async () => {
++t.test('a browser that asks for a missing page gets the site 404 page; an API client the problem document', async () => {
const page = await request(h.base, 'GET', '/no-such-page', { headers: { Accept: BROWSER } });
assert.strictEqual(page.status, 404);
-- assert.ok(page.text.includes('<html lang="en">') && page.text.includes('<title>Not found') && page.text.includes('rel="icon"'));
++ assert.match(page.headers.get('content-type'), /^text\/html/);
++ assert.ok(page.text.includes('<h1>Page not found</h1>'));
++ assert.ok(page.text.includes('There is no page at this address on ai.openvibe.services. The link may be old or mistyped.'));
++ assert.ok(page.text.includes('id="navbar-mount"'), 'the page is in the site Frame');
++ assert.ok(page.text.includes('<meta name="robots" content="noindex, follow">'));
++ assert.strictEqual(page.headers.get('x-robots-tag'), 'noindex');
++ assert.ok(page.text.includes('class="sc-btn sc-primary" href="/"'), 'home is the first way back');
const api = await request(h.base, 'GET', '/api/v1/no-such-thing', { headers: { Accept: BROWSER } });
assert.strictEqual(api.status, 404);
assert.match(api.headers.get('content-type'), /json/);
++ const notHtml = await request(h.base, 'GET', '/no-such-page', { headers: { Accept: 'application/json' } });
++ assert.strictEqual(notHtml.status, 404);
++ assert.match(notHtml.headers.get('content-type'), /json/);
const icon = await request(h.base, 'GET', '/favicon.ico');
assert.strictEqual(icon.status, 200);
assert.match(icon.headers.get('content-type'), /^image\/svg\+xml/);