openvibers/openvibe.run Commits
Run: /updates — what shipped on openvibe.run (the shared update log) (#47)
Changes
@@@ -114,12 +114,18 @@ function createApp(opts = {}) {res.setHeader('Cache-Control', 'public, max-age=300');res.type('html').send(home.renderHome({ config }));});++ // What shipped on this site: the shared update log, as on every OpenVibe site.++ app.get('/updates', (req, res) => {++ res.setHeader('Content-Security-Policy', home.HOME_CSP);++ res.setHeader('Cache-Control', 'public, max-age=300');++ res.type('html').send(home.renderUpdates({ config }));++ });{ const legal = require('openvibe-shared/legal');app.get(legal.PATHS, (req, res, next) => { res.setHeader('Content-Security-Policy', home.HOME_CSP); next(); }, legal.handler({ id: 'run', service: 'run', host: 'openvibe.run', name: home.SITE_NAME, profile: 'hosting' })); }app.get('/llms.txt', (req, res) => res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(discovery.llmsTxt(config)));app.get('/llms-full.txt', (req, res) => res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(discovery.llmsFull(config)));app.get('/robots.txt', (req, res) => res.type('text/plain').send(['User-agent: *', 'Allow: /$', 'Disallow: /api/', 'Disallow: /internal/', `Sitemap: ${config.baseUrl}/sitemap.xml`, ''].join('\n')));-- app.get('/sitemap.xml', (req, res) => res.type('application/xml').send(require('openvibe-shared/seo').sitemapXml(['/', '/terms', '/privacy', '/dmca'].map((p) => ({ loc: `${config.baseUrl}${p}` })))));++ app.get('/sitemap.xml', (req, res) => res.type('application/xml').send(require('openvibe-shared/seo').sitemapXml(discovery.PAGES.map((p) => ({ loc: `${config.baseUrl}${p}` })))));app.get('/favicon.ico', (req, res) => res.type('image/svg+xml').set('Cache-Control', 'public, max-age=86400').send(appIcon.favicon({ site: 'run' })));app.get('/manifest.webmanifest', (req, res) => res.type('application/manifest+json').send(JSON.stringify(appIcon.manifest({ site: 'run', name: home.SITE_NAME, shortName: 'Run', description: home.DESCRIPTION, startUrl: '/' }))));@@@ -11,12 +11,13 @@ const REPO = 'https://github.com/OpenVibers/OpenVibe.Run';const NODE_REPO = 'https://github.com/OpenVibers/OpenVibe.Node';// The public pages, in the order the sitemap lists them.--const PAGES = ['/', '/terms', '/privacy', '/dmca'];++const PAGES = ['/', '/updates', '/terms', '/privacy', '/dmca'];function sections(base) {return [{ title: 'Pages', links: [{ title: 'OpenVibe.Run', url: `${base}/`, note: 'what a job is, how it runs, and the API in brief' },++ { title: 'What shipped', url: `${base}/updates`, note: 'every change deployed, newest first' },{ title: 'Terms', url: `${base}/terms` },{ title: 'Privacy', url: `${base}/privacy` },{ title: 'DMCA', url: `${base}/dmca` },@@@ -10,6 +10,7 @@* HOME_CSP its Content-Security-Policy (the Frame's scripts and calls to the Network)*/const shell = require('openvibe-shared/shell');++const frame = require('openvibe-shared/frame');const showcase = require('openvibe-shared/showcase');const appIcon = require('openvibe-shared/app-icon');const ovServe = require('openvibe-shared/serve');@@@ -54,13 +55,30 @@ function renderHome({ config }) {title: `${SITE_NAME}: run a job on a node that can`,siteName: SITE_NAME, description: DESCRIPTION, summary: DESCRIPTION,canonical: `${config.baseUrl}/`, robots: 'index, follow',-- navbar: nav, footer: { service: 'run', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME }, home: '/',++ navbar: nav, footer: FOOTER, home: '/',navLinks: nav.links,head: [appIcon.headTags({ site: 'run' }), '<link rel="manifest" href="/manifest.webmanifest">', `<link rel="stylesheet" href="${ovServe.url(showcase.STYLESHEET)}">`].join('\n'),body: `<div id="navbar-mount"></div>\n<main id="main">${body()}</main>`,});}++const FOOTER = { service: 'run', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };++++/** /updates: what shipped on this site, the network's shared update log (openvibe-shared/frame updatesBody). */++function renderUpdates({ config }) {++ const links = [{ label: 'How it works', href: '/#how' }, { label: 'API', href: '/#api' }];++ return shell.page({++ name: SITE_NAME, service: 'run', lang: 'en',++ title: `What shipped on ${SITE_NAME}`,++ siteName: SITE_NAME, description: `Every change deployed to ${SITE_NAME}, newest first.`,++ canonical: `${config.baseUrl}/updates`, robots: 'index, 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">'].join('\n'),++ body: `<div id="navbar-mount"></div>\n<main id="main" class="page">\n${frame.updatesBody({ service: 'run', siteName: SITE_NAME })}\n</main>\n<script src="${ovServe.url('shipped.js')}" defer></script>`,++ });++}++function body() {return showcase.hero({eyebrow: 'OpenVibe.Run · jobs on nodes · alpha',@@@ -102,4 +120,4 @@ function body() {</section>`;}--module.exports = { renderHome, HOME_CSP, SITE_NAME, DESCRIPTION };++module.exports = { renderHome, renderUpdates, HOME_CSP, SITE_NAME, DESCRIPTION };@@@ -59,7 +59,7 @@ const KEY = require('crypto').randomBytes(16).toString('hex');assert.strictEqual(sent.length, 1);assert.strictEqual(sent[0].key, KEY);assert.strictEqual(sent[0].keyLocation, `https://run.test/${KEY}.txt`);-- assert.deepStrictEqual(sent[0].urlList.slice(0, 2), ['https://run.test/', 'https://run.test/terms']);++ assert.deepStrictEqual(sent[0].urlList.slice(0, 3), ['https://run.test/', 'https://run.test/updates', 'https://run.test/terms']);assert.ok(sent[0].urlList.every((u) => !/\/api\/|\/internal\//.test(u)));});} finally {@@@ -37,6 +37,15 @@ const { boot, check, done } = require('./helpers/app');const manifest = JSON.parse((await t.call('GET', '/manifest.webmanifest', { token: null })).text);assert.strictEqual(manifest.name, 'OpenVibe.Run');});++ await check('/updates: the shared update log in the Frame, one heading, in the sitemap', async () => {++ const r = await t.call('GET', '/updates', { token: null });++ assert.strictEqual(r.status, 200);++ assert.ok(r.text.includes('<h1>What shipped on OpenVibe.Run</h1>') && r.text.includes('data-ov-shipped="log" data-service="run"'));++ assert.strictEqual((r.text.match(/<h1[\s>]/g) || []).length, 1);++ assert.match(r.text, /<script src="\/shared\/shipped\.js\?v=[0-9a-f]+" defer><\/script>/);++ assert.match(r.headers.get('content-security-policy'), /connect-src 'self' https:\/\/openvibe\.network/);++ assert.match((await t.call('GET', '/sitemap.xml', { token: null })).text, /<loc>[^<]*\/updates<\/loc>/);++ });await check('an unknown API path is still a problem+json 404', async () => {const r = await t.call('GET', '/api/v1/nothing-here', { token: null });assert.strictEqual(r.status, 404);
@@ -114,12 +114,18 @@ function createApp(opts = {}) {
@@ -114,12 +114,18 @@ function createApp(opts = {}) {
res.setHeader('Cache-Control', 'public, max-age=300');
res.setHeader('Cache-Control', 'public, max-age=300');
res.type('html').send(home.renderHome({ config }));
res.type('html').send(home.renderHome({ config }));
});
});
+ // What shipped on this site: the shared update log, as on every OpenVibe site.
+ app.get('/updates', (req, res) => {
+ res.setHeader('Content-Security-Policy', home.HOME_CSP);
+ res.setHeader('Cache-Control', 'public, max-age=300');
+ res.type('html').send(home.renderUpdates({ config }));
+ });
{ const legal = require('openvibe-shared/legal');
{ const legal = require('openvibe-shared/legal');
app.get(legal.PATHS, (req, res, next) => { res.setHeader('Content-Security-Policy', home.HOME_CSP); next(); }, legal.handler({ id: 'run', service: 'run', host: 'openvibe.run', name: home.SITE_NAME, profile: 'hosting' })); }
app.get(legal.PATHS, (req, res, next) => { res.setHeader('Content-Security-Policy', home.HOME_CSP); next(); }, legal.handler({ id: 'run', service: 'run', host: 'openvibe.run', name: home.SITE_NAME, profile: 'hosting' })); }
app.get('/llms.txt', (req, res) => res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(discovery.llmsTxt(config)));
app.get('/llms.txt', (req, res) => res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(discovery.llmsTxt(config)));
app.get('/llms-full.txt', (req, res) => res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(discovery.llmsFull(config)));
app.get('/llms-full.txt', (req, res) => res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(discovery.llmsFull(config)));
app.get('/robots.txt', (req, res) => res.type('text/plain').send(['User-agent: *', 'Allow: /$', 'Disallow: /api/', 'Disallow: /internal/', `Sitemap: ${config.baseUrl}/sitemap.xml`, ''].join('\n')));
app.get('/robots.txt', (req, res) => res.type('text/plain').send(['User-agent: *', 'Allow: /$', 'Disallow: /api/', 'Disallow: /internal/', `Sitemap: ${config.baseUrl}/sitemap.xml`, ''].join('\n')));
- app.get('/sitemap.xml', (req, res) => res.type('application/xml').send(require('openvibe-shared/seo').sitemapXml(['/', '/terms', '/privacy', '/dmca'].map((p) => ({ loc: `${config.baseUrl}${p}` })))));
+ app.get('/sitemap.xml', (req, res) => res.type('application/xml').send(require('openvibe-shared/seo').sitemapXml(discovery.PAGES.map((p) => ({ loc: `${config.baseUrl}${p}` })))));
app.get('/favicon.ico', (req, res) => res.type('image/svg+xml').set('Cache-Control', 'public, max-age=86400').send(appIcon.favicon({ site: 'run' })));
app.get('/favicon.ico', (req, res) => res.type('image/svg+xml').set('Cache-Control', 'public, max-age=86400').send(appIcon.favicon({ site: 'run' })));
app.get('/manifest.webmanifest', (req, res) => res.type('application/manifest+json').send(JSON.stringify(appIcon.manifest({ site: 'run', name: home.SITE_NAME, shortName: 'Run', description: home.DESCRIPTION, startUrl: '/' }))));
app.get('/manifest.webmanifest', (req, res) => res.type('application/manifest+json').send(JSON.stringify(appIcon.manifest({ site: 'run', name: home.SITE_NAME, shortName: 'Run', description: home.DESCRIPTION, startUrl: '/' }))));
@@ -11,12 +11,13 @@ const REPO = 'https://github.com/OpenVibers/OpenVibe.Run';
@@ -11,12 +11,13 @@ const REPO = 'https://github.com/OpenVibers/OpenVibe.Run';
const NODE_REPO = 'https://github.com/OpenVibers/OpenVibe.Node';
const NODE_REPO = 'https://github.com/OpenVibers/OpenVibe.Node';
// The public pages, in the order the sitemap lists them.
// The public pages, in the order the sitemap lists them.
-const PAGES = ['/', '/terms', '/privacy', '/dmca'];
+const PAGES = ['/', '/updates', '/terms', '/privacy', '/dmca'];
function sections(base) {
function sections(base) {
return [
return [
{ title: 'Pages', links: [
{ title: 'Pages', links: [
{ title: 'OpenVibe.Run', url: `${base}/`, note: 'what a job is, how it runs, and the API in brief' },
{ title: 'OpenVibe.Run', url: `${base}/`, note: 'what a job is, how it runs, and the API in brief' },
+ { title: 'What shipped', url: `${base}/updates`, note: 'every change deployed, newest first' },
{ title: 'Terms', url: `${base}/terms` },
{ title: 'Terms', url: `${base}/terms` },
{ title: 'Privacy', url: `${base}/privacy` },
{ title: 'Privacy', url: `${base}/privacy` },
{ title: 'DMCA', url: `${base}/dmca` },
{ title: 'DMCA', url: `${base}/dmca` },
@@ -10,6 +10,7 @@
@@ -10,6 +10,7 @@
* HOME_CSP its Content-Security-Policy (the Frame's scripts and calls to the Network)
* HOME_CSP its Content-Security-Policy (the Frame's scripts and calls to the Network)
*/
*/
const shell = require('openvibe-shared/shell');
const shell = require('openvibe-shared/shell');
+const frame = require('openvibe-shared/frame');
const showcase = require('openvibe-shared/showcase');
const showcase = require('openvibe-shared/showcase');
const appIcon = require('openvibe-shared/app-icon');
const appIcon = require('openvibe-shared/app-icon');
const ovServe = require('openvibe-shared/serve');
const ovServe = require('openvibe-shared/serve');
@@ -54,13 +55,30 @@ function renderHome({ config }) {
@@ -54,13 +55,30 @@ function renderHome({ config }) {
title: `${SITE_NAME}: run a job on a node that can`,
title: `${SITE_NAME}: run a job on a node that can`,
siteName: SITE_NAME, description: DESCRIPTION, summary: DESCRIPTION,
siteName: SITE_NAME, description: DESCRIPTION, summary: DESCRIPTION,
canonical: `${config.baseUrl}/`, robots: 'index, follow',
canonical: `${config.baseUrl}/`, robots: 'index, follow',
- navbar: nav, footer: { service: 'run', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME }, home: '/',
+ navbar: nav, footer: FOOTER, home: '/',
navLinks: nav.links,
navLinks: nav.links,
head: [appIcon.headTags({ site: 'run' }), '<link rel="manifest" href="/manifest.webmanifest">', `<link rel="stylesheet" href="${ovServe.url(showcase.STYLESHEET)}">`].join('\n'),
head: [appIcon.headTags({ site: 'run' }), '<link rel="manifest" href="/manifest.webmanifest">', `<link rel="stylesheet" href="${ovServe.url(showcase.STYLESHEET)}">`].join('\n'),
body: `<div id="navbar-mount"></div>\n<main id="main">${body()}</main>`,
body: `<div id="navbar-mount"></div>\n<main id="main">${body()}</main>`,
});
});
}
}
+const FOOTER = { service: 'run', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };
+
+/** /updates: what shipped on this site, the network's shared update log (openvibe-shared/frame updatesBody). */
+function renderUpdates({ config }) {
+ const links = [{ label: 'How it works', href: '/#how' }, { label: 'API', href: '/#api' }];
+ return shell.page({
+ name: SITE_NAME, service: 'run', lang: 'en',
+ title: `What shipped on ${SITE_NAME}`,
+ siteName: SITE_NAME, description: `Every change deployed to ${SITE_NAME}, newest first.`,
+ canonical: `${config.baseUrl}/updates`, robots: 'index, 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">'].join('\n'),
+ body: `<div id="navbar-mount"></div>\n<main id="main" class="page">\n${frame.updatesBody({ service: 'run', siteName: SITE_NAME })}\n</main>\n<script src="${ovServe.url('shipped.js')}" defer></script>`,
+ });
+}
+
function body() {
function body() {
return showcase.hero({
return showcase.hero({
eyebrow: 'OpenVibe.Run · jobs on nodes · alpha',
eyebrow: 'OpenVibe.Run · jobs on nodes · alpha',
@@ -102,4 +120,4 @@ function body() {
@@ -102,4 +120,4 @@ function body() {
</section>`;
</section>`;
}
}
-module.exports = { renderHome, HOME_CSP, SITE_NAME, DESCRIPTION };
+module.exports = { renderHome, renderUpdates, HOME_CSP, SITE_NAME, DESCRIPTION };
@@ -59,7 +59,7 @@ const KEY = require('crypto').randomBytes(16).toString('hex');
@@ -59,7 +59,7 @@ const KEY = require('crypto').randomBytes(16).toString('hex');
assert.strictEqual(sent.length, 1);
assert.strictEqual(sent.length, 1);
assert.strictEqual(sent[0].key, KEY);
assert.strictEqual(sent[0].key, KEY);
assert.strictEqual(sent[0].keyLocation, `https://run.test/${KEY}.txt`);
assert.strictEqual(sent[0].keyLocation, `https://run.test/${KEY}.txt`);
- assert.deepStrictEqual(sent[0].urlList.slice(0, 2), ['https://run.test/', 'https://run.test/terms']);
+ assert.deepStrictEqual(sent[0].urlList.slice(0, 3), ['https://run.test/', 'https://run.test/updates', 'https://run.test/terms']);
assert.ok(sent[0].urlList.every((u) => !/\/api\/|\/internal\//.test(u)));
assert.ok(sent[0].urlList.every((u) => !/\/api\/|\/internal\//.test(u)));
});
});
} finally {
} finally {
@@ -37,6 +37,15 @@ const { boot, check, done } = require('./helpers/app');
@@ -37,6 +37,15 @@ const { boot, check, done } = require('./helpers/app');
const manifest = JSON.parse((await t.call('GET', '/manifest.webmanifest', { token: null })).text);
const manifest = JSON.parse((await t.call('GET', '/manifest.webmanifest', { token: null })).text);
assert.strictEqual(manifest.name, 'OpenVibe.Run');
assert.strictEqual(manifest.name, 'OpenVibe.Run');
});
});
+ await check('/updates: the shared update log in the Frame, one heading, in the sitemap', async () => {
+ const r = await t.call('GET', '/updates', { token: null });
+ assert.strictEqual(r.status, 200);
+ assert.ok(r.text.includes('<h1>What shipped on OpenVibe.Run</h1>') && r.text.includes('data-ov-shipped="log" data-service="run"'));
+ assert.strictEqual((r.text.match(/<h1[\s>]/g) || []).length, 1);
+ assert.match(r.text, /<script src="\/shared\/shipped\.js\?v=[0-9a-f]+" defer><\/script>/);
+ assert.match(r.headers.get('content-security-policy'), /connect-src 'self' https:\/\/openvibe\.network/);
+ assert.match((await t.call('GET', '/sitemap.xml', { token: null })).text, /<loc>[^<]*\/updates<\/loc>/);
+ });
await check('an unknown API path is still a problem+json 404', async () => {
await check('an unknown API path is still a problem+json 404', async () => {
const r = await t.call('GET', '/api/v1/nothing-here', { token: null });
const r = await t.call('GET', '/api/v1/nothing-here', { token: null });
assert.strictEqual(r.status, 404);
assert.strictEqual(r.status, 404);