openvibers/openvibe.run Commits

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

OP OpenVibers committed 12 days ago · b13e1bcecc · 3 files, +47 −3

A browser that asks for a path Run does not serve gets the site's own 404 page (the OpenVibe Frame, noindex, one line naming the host, links home, to how it works and to the API). An API path, an internal path or a request whose Accept is not text/html now gets the problem document.

Changes

ddiff --git a/server/app.js b/server/app.js
iindex acf6672..ab4d0c2 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -163,8 +163,11 @@ function createApp(opts = {}) {
if (opts.accountData) app.post('/internal/events', opts.accountData.consumer({ secrets: [config.events.secret].filter((s) => s.length >= 32), send: opts.accountSend || null }));
app.use((req, res) => {
-- if (req.path.startsWith('/api/') || req.path.startsWith('/internal/')) return http.sendProblem(res, 404, 'not_found', { ctx: req.ov });
-- return res.status(404).type('text/plain').send('Not found\n');
++ if (req.path.startsWith('/api/') || req.path.startsWith('/internal/') || req.accepts(['application/json', 'text/html']) !== 'text/html') {
++ return http.sendProblem(res, 404, 'not_found', { ctx: req.ov });
++ }
++ 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({ config }));
});
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
ddiff --git a/server/web/home.js b/server/web/home.js
iindex 452f31c..876690d 100644
---- a/server/web/home.js
++++ b/server/web/home.js
@@@ -112,6 +112,33 @@ function renderUpdates({ config }) {
});
}
++/** 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. */
++function renderNotFound({ config }) {
++ const host = String(config.baseUrl).replace(/^https?:\/\//, '').replace(/\/.*$/, '');
++ const links = [{ label: 'How it works', href: '/#how' }, { label: 'API', href: '/#api' }];
++ return shell.page({
++ name: SITE_NAME, service: 'run', 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: 'run', apiBase: NETWORK_URL, links, history: { type: 'page', title: SITE_NAME } },
++ footer: FOOTER, home: '/', navLinks: links,
++ head: [appIcon.headTags({ site: 'run' }), '<link rel="manifest" href="/manifest.webmanifest">',
++ `<link rel="apple-touch-icon" href="${require('openvibe-shared/app-icons').url('run', 192)}">`,
++ `<link rel="stylesheet" href="${ovServe.url(showcase.STYLESHEET)}">`, UPDATES_FONT].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="/#how">How it works</a>
++<a class="sc-btn" href="/#api">API</a>
++</div>
++</main>`,
++ });
++}
++
const SDK_EXAMPLE = `const { createClient } = require('openvibe-sdk/core');
const { createRunClient } = require('openvibe-sdk/run');
@@@ -212,4 +239,4 @@ function body() {
});
}
--module.exports = { renderHome, renderUpdates, HOME_CSP, SITE_NAME, DESCRIPTION, FAQ };
++module.exports = { renderHome, renderUpdates, renderNotFound, HOME_CSP, SITE_NAME, DESCRIPTION, FAQ };
ddiff --git a/test/site.test.js b/test/site.test.js
iindex 24bcb85..b5fd81b 100644
---- a/test/site.test.js
++++ b/test/site.test.js
@@@ -58,6 +58,20 @@ const { boot, check, done } = require('./helpers/app');
assert.strictEqual(r.status, 404);
assert.match(r.headers.get('content-type'), /problem\+json/);
});
++ await check('a browser that asks for a missing page gets the site 404 page in the Frame', async () => {
++ const r = await t.call('GET', '/no-such-page-xyz', { token: null, headers: { Accept: 'text/html' } });
++ assert.strictEqual(r.status, 404);
++ assert.match(r.headers.get('content-type'), /^text\/html/);
++ assert.ok(r.text.includes('<h1>Page not found</h1>'), r.text.slice(0, 300));
++ assert.ok(r.text.includes('There is no page at this address on run.test. The link may be old or mistyped.'));
++ assert.ok(r.text.includes('id="navbar-mount"'), 'the page is in the site Frame');
++ assert.ok(r.text.includes('<meta name="robots" content="noindex, follow">'));
++ assert.strictEqual(r.headers.get('x-robots-tag'), 'noindex');
++ assert.ok(r.text.includes('class="sc-btn sc-primary" href="/"'), 'home is the first way back');
++ const api = await t.call('GET', '/no-such-page-xyz', { token: null, headers: { Accept: 'application/json' } });
++ assert.strictEqual(api.status, 404);
++ assert.match(api.headers.get('content-type'), /problem\+json/);
++ });
} finally {
await t.close();
}