openvibers/openvibe.run Commits
A missing page answers the site's 404 page in the Frame (#147)
Changes
@@@ -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-varsapp.use((err, req, res, next) => {@@@ -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 };@@@ -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();}
@@ -163,8 +163,11 @@ function createApp(opts = {}) {
@@ -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 }));
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) => {
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
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
app.use((err, req, res, next) => {
@@ -112,6 +112,33 @@ function renderUpdates({ config }) {
@@ -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 SDK_EXAMPLE = `const { createClient } = require('openvibe-sdk/core');
const { createRunClient } = require('openvibe-sdk/run');
const { createRunClient } = require('openvibe-sdk/run');
@@ -212,4 +239,4 @@ function body() {
@@ -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 };
@@ -58,6 +58,20 @@ const { boot, check, done } = require('./helpers/app');
@@ -58,6 +58,20 @@ const { boot, check, done } = require('./helpers/app');
assert.strictEqual(r.status, 404);
assert.strictEqual(r.status, 404);
assert.match(r.headers.get('content-type'), /problem\+json/);
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 {
} finally {
await t.close();
await t.close();
}
}