openvibers/openvibe.connect Commits
openvibe-shared v4.108.0 (was v4.107.4) (#32)
Changes
@@@ -213,5 +213,5 @@ AGPL-3.0-only. The client (`public/ov-connect.js`) is part of this repository un<!-- versions:start -->- openvibe-contracts: v0.279.0- openvibe-sdk: v0.82.0--- openvibe-shared: v4.107.4++- openvibe-shared: v4.108.0<!-- versions:end -->@@@ -9,11 +9,11 @@"domain": "connect.openvibe.website","contracts": "openvibe-contracts v0.279.0","sdk": "openvibe-sdk v0.82.0",-- "shared": "openvibe-shared v4.107.4",++ "shared": "openvibe-shared v4.108.0","packages": ["openvibe-contracts v0.279.0","openvibe-sdk v0.82.0",-- "openvibe-shared v4.107.4"++ "openvibe-shared v4.108.0"],"summary": "OpenVibe Connect puts one port on a person's computer on the web at its own HTTPS address, behind OpenVibe sign-in. A one-file client (Node 22, no dependencies) opens one outgoing WebSocket through Cloudflare and carries requests to localhost: HTTP with streaming both ways, server-sent events and WebSocket upgrades. Every visitor signs in and must be allowed by the share's policy (only the owner, named people, or anyone signed in); the share host keeps its own host-only signed cookie. Nothing that passes through is stored.","works": [@@@ -17,7 +17,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.279.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0","pg": "^8.23.1","ws": "^8.18.0"},@@@ -1202,9 +1202,9 @@}},"node_modules/openvibe-shared": {-- "version": "4.107.4",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",-- "integrity": "sha512-tThN0m3KMncEHVcBOvE+ysHjsZTlVIDLG1P+/i2Mulmxfc/AUpebAXhpWWrut50OChZ8o+HeePxsSrPI8mUQOQ==",++ "version": "4.108.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",++ "integrity": "sha512-NPzwjxpzGIpFpIm4uw3+VwHfsxbJ2+cD9yuhRzSwolSwqv2kCmwaVtHJ0DA9gKO3K8PCBfIP3UpKzpuSbYEJ+g==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -27,7 +27,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.279.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0","pg": "^8.23.1","ws": "^8.18.0"},@@@ -28,6 +28,7 @@ const { ApiKeyError } = require('../api-keys');// Connect's README, its full public documentation, for /llms-full.txt (read once; empty if it is not shipped).const README = (() => { try { return require('fs').readFileSync(require('path').join(__dirname, '..', '..', 'README.md'), 'utf8').trim(); } catch { return ''; } })();const { OAuthError } = require('../oauth');++const seo = require('openvibe-shared/seo');function createPageRoutes(ctx) {const { config, shares, tunnels, gate, proxy, machines, hooks, apiKeys } = ctx;@@@ -305,7 +306,6 @@ function createPageRoutes(ctx) {}});-- const site = String(config.baseUrl).replace(/\/+$/, '');r.get('/robots.txt', (req, res) => {res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(`User-agent: *\nAllow: /$\nDisallow: /shares\nDisallow: /grant\nDisallow: /auth\nDisallow: /api\n\nSitemap: ${site}/sitemap.xml\n`);});@@@ -314,24 +314,34 @@ function createPageRoutes(ctx) {res.type('application/xml').set('Cache-Control', 'public, max-age=3600').send(`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n <url><loc>${site}/</loc></url>\n</urlset>\n`);});++ const site = String(config.baseUrl).replace(/\/+$/, '');++ const abs = (p) => `${site}${p}`;++// What llms.txt says (SEO-33: /llms-full.txt adds the README, Connect's full documentation, after it).-- const LLMS_LINES = [-- '# OpenVibe Connect',-- '',-- '> Share one port on your computer at https://<name>--<username>.openvibe.website, behind OpenVibe sign-in.',-- '',-- '- Client: one file, Node 22+: curl -fsSLO https://connect.openvibe.website/ov-connect.js; node ov-connect.js --key ovc_… --port 8080',-- '- Who can open a share: only the owner, the owner and named OpenVibe usernames, or anyone signed in to OpenVibe. No public shares yet.',-- '- The app behind a share receives X-OpenVibe-User and X-OpenVibe-Subject; Host is localhost:<port>.',-- '- API (a person\'s Network token as a Bearer): GET/POST /api/v1/shares, GET/PATCH/DELETE /api/v1/shares/{id}, POST /api/v1/shares/{id}/key',-- '- Source: https://github.com/OpenVibers/OpenVibe.Connect (AGPL-3.0)',-- '',-- ];++ const NAME = 'OpenVibe Connect';++ const SUMMARY = 'Share one port on your computer at https://<name>--<username>.openvibe.website, behind OpenVibe sign-in.';++ const DETAILS = [++ 'Client: one file, Node 22+: curl -fsSLO https://connect.openvibe.website/ov-connect.js; node ov-connect.js --key ovc_… --port 8080.',++ 'Who can open a share: only the owner, the owner and named OpenVibe usernames, or anyone signed in to OpenVibe. No public shares yet.',++ 'The app behind a share receives X-OpenVibe-User and X-OpenVibe-Subject; Host is localhost:<port>.',++ 'API (a person\'s Network token as a Bearer): GET/POST /api/v1/shares, GET/PATCH/DELETE /api/v1/shares/{id}, POST /api/v1/shares/{id}/key.',++ 'Source: https://github.com/OpenVibers/OpenVibe.Connect (AGPL-3.0).',++ ].join(' ');++ const LLMS_SECTIONS = [{ title: 'Start here', links: [{ title: NAME, url: abs('/'), note: 'your shares and the form to make one' }] }];++ const LLMS_OPTIONAL = [++ { title: 'Client download', url: abs('/ov-connect.js'), note: 'one file, Node 22+' },++ { title: 'Sitemap', url: abs('/sitemap.xml') },++ { title: 'Full text for language models', url: abs('/llms-full.txt') },++ ];++ const LLMS_HEADER = seo.llmsTxt({++ name: NAME, summary: SUMMARY, details: DETAILS, markdown: true, base: config.baseUrl,++ sections: LLMS_SECTIONS, optional: LLMS_OPTIONAL,++ });r.get('/llms.txt', (req, res) => {-- res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(LLMS_LINES.join('\n'));++ res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(LLMS_HEADER);});r.get('/llms-full.txt', (req, res) => {-- res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(`${LLMS_LINES.join('\n')}\n\n## The documentation (README)\n\n${README}\n`);++ res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(`${LLMS_HEADER}\n\n## The documentation (README)\n\n${README}\n`);});return r;@@@ -103,7 +103,10 @@ const { check, done } = require('./helpers/check');const map = await t.get('/sitemap.xml');assert.strictEqual(map.status, 200, 'the sitemap agent.json names answers');assert.match(map.text, /<urlset xmlns="http:\/\/www\.sitemaps\.org\/schemas\/sitemap\/0\.9">\n <url><loc>https?:\/\/[^<]+\/<\/loc><\/url>\n<\/urlset>/);-- assert.match((await t.get('/llms.txt')).text, /OpenVibe Connect/);++ const llms = await t.get('/llms.txt');++ assert.match(llms.text, /OpenVibe Connect/);++ assert.match(llms.text, /\n## Optional\n/);++ assert.match(llms.text, /\(https:\/\/connect\.openvibe\.website\/index\.md\)/);// SEO-2: the agent manifest is served (11-03: the mount had landed inside the share-host middleware, after its// return, so it never ran while the source check passed).const agent = await t.get('/.well-known/agent.json');
@@ -213,5 +213,5 @@ AGPL-3.0-only. The client (`public/ov-connect.js`) is part of this repository un
@@ -213,5 +213,5 @@ AGPL-3.0-only. The client (`public/ov-connect.js`) is part of this repository un
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.279.0
- openvibe-contracts: v0.279.0
- openvibe-sdk: v0.82.0
- openvibe-sdk: v0.82.0
-- openvibe-shared: v4.107.4
+- openvibe-shared: v4.108.0
<!-- versions:end -->
<!-- versions:end -->
@@ -9,11 +9,11 @@
@@ -9,11 +9,11 @@
"domain": "connect.openvibe.website",
"domain": "connect.openvibe.website",
"contracts": "openvibe-contracts v0.279.0",
"contracts": "openvibe-contracts v0.279.0",
"sdk": "openvibe-sdk v0.82.0",
"sdk": "openvibe-sdk v0.82.0",
- "shared": "openvibe-shared v4.107.4",
+ "shared": "openvibe-shared v4.108.0",
"packages": [
"packages": [
"openvibe-contracts v0.279.0",
"openvibe-contracts v0.279.0",
"openvibe-sdk v0.82.0",
"openvibe-sdk v0.82.0",
- "openvibe-shared v4.107.4"
+ "openvibe-shared v4.108.0"
],
],
"summary": "OpenVibe Connect puts one port on a person's computer on the web at its own HTTPS address, behind OpenVibe sign-in. A one-file client (Node 22, no dependencies) opens one outgoing WebSocket through Cloudflare and carries requests to localhost: HTTP with streaming both ways, server-sent events and WebSocket upgrades. Every visitor signs in and must be allowed by the share's policy (only the owner, named people, or anyone signed in); the share host keeps its own host-only signed cookie. Nothing that passes through is stored.",
"summary": "OpenVibe Connect puts one port on a person's computer on the web at its own HTTPS address, behind OpenVibe sign-in. A one-file client (Node 22, no dependencies) opens one outgoing WebSocket through Cloudflare and carries requests to localhost: HTTP with streaming both ways, server-sent events and WebSocket upgrades. Every visitor signs in and must be allowed by the share's policy (only the owner, named people, or anyone signed in); the share host keeps its own host-only signed cookie. Nothing that passes through is stored.",
"works": [
"works": [
@@ -17,7 +17,7 @@
@@ -17,7 +17,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.279.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.279.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",
"pg": "^8.23.1",
"pg": "^8.23.1",
"ws": "^8.18.0"
"ws": "^8.18.0"
},
},
@@ -1202,9 +1202,9 @@
@@ -1202,9 +1202,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "4.107.4",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",
- "integrity": "sha512-tThN0m3KMncEHVcBOvE+ysHjsZTlVIDLG1P+/i2Mulmxfc/AUpebAXhpWWrut50OChZ8o+HeePxsSrPI8mUQOQ==",
+ "version": "4.108.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",
+ "integrity": "sha512-NPzwjxpzGIpFpIm4uw3+VwHfsxbJ2+cD9yuhRzSwolSwqv2kCmwaVtHJ0DA9gKO3K8PCBfIP3UpKzpuSbYEJ+g==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -27,7 +27,7 @@
@@ -27,7 +27,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.279.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.279.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",
"pg": "^8.23.1",
"pg": "^8.23.1",
"ws": "^8.18.0"
"ws": "^8.18.0"
},
},
@@ -28,6 +28,7 @@ const { ApiKeyError } = require('../api-keys');
@@ -28,6 +28,7 @@ const { ApiKeyError } = require('../api-keys');
// Connect's README, its full public documentation, for /llms-full.txt (read once; empty if it is not shipped).
// Connect's README, its full public documentation, for /llms-full.txt (read once; empty if it is not shipped).
const README = (() => { try { return require('fs').readFileSync(require('path').join(__dirname, '..', '..', 'README.md'), 'utf8').trim(); } catch { return ''; } })();
const README = (() => { try { return require('fs').readFileSync(require('path').join(__dirname, '..', '..', 'README.md'), 'utf8').trim(); } catch { return ''; } })();
const { OAuthError } = require('../oauth');
const { OAuthError } = require('../oauth');
+const seo = require('openvibe-shared/seo');
function createPageRoutes(ctx) {
function createPageRoutes(ctx) {
const { config, shares, tunnels, gate, proxy, machines, hooks, apiKeys } = ctx;
const { config, shares, tunnels, gate, proxy, machines, hooks, apiKeys } = ctx;
@@ -305,7 +306,6 @@ function createPageRoutes(ctx) {
@@ -305,7 +306,6 @@ function createPageRoutes(ctx) {
}
}
});
});
- const site = String(config.baseUrl).replace(/\/+$/, '');
r.get('/robots.txt', (req, res) => {
r.get('/robots.txt', (req, res) => {
res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(`User-agent: *\nAllow: /$\nDisallow: /shares\nDisallow: /grant\nDisallow: /auth\nDisallow: /api\n\nSitemap: ${site}/sitemap.xml\n`);
res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(`User-agent: *\nAllow: /$\nDisallow: /shares\nDisallow: /grant\nDisallow: /auth\nDisallow: /api\n\nSitemap: ${site}/sitemap.xml\n`);
});
});
@@ -314,24 +314,34 @@ function createPageRoutes(ctx) {
@@ -314,24 +314,34 @@ function createPageRoutes(ctx) {
res.type('application/xml').set('Cache-Control', 'public, max-age=3600')
res.type('application/xml').set('Cache-Control', 'public, max-age=3600')
.send(`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n <url><loc>${site}/</loc></url>\n</urlset>\n`);
.send(`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n <url><loc>${site}/</loc></url>\n</urlset>\n`);
});
});
+ const site = String(config.baseUrl).replace(/\/+$/, '');
+ const abs = (p) => `${site}${p}`;
+
// What llms.txt says (SEO-33: /llms-full.txt adds the README, Connect's full documentation, after it).
// What llms.txt says (SEO-33: /llms-full.txt adds the README, Connect's full documentation, after it).
- const LLMS_LINES = [
- '# OpenVibe Connect',
- '',
- '> Share one port on your computer at https://<name>--<username>.openvibe.website, behind OpenVibe sign-in.',
- '',
- '- Client: one file, Node 22+: curl -fsSLO https://connect.openvibe.website/ov-connect.js; node ov-connect.js --key ovc_… --port 8080',
- '- Who can open a share: only the owner, the owner and named OpenVibe usernames, or anyone signed in to OpenVibe. No public shares yet.',
- '- The app behind a share receives X-OpenVibe-User and X-OpenVibe-Subject; Host is localhost:<port>.',
- '- API (a person\'s Network token as a Bearer): GET/POST /api/v1/shares, GET/PATCH/DELETE /api/v1/shares/{id}, POST /api/v1/shares/{id}/key',
- '- Source: https://github.com/OpenVibers/OpenVibe.Connect (AGPL-3.0)',
- '',
- ];
+ const NAME = 'OpenVibe Connect';
+ const SUMMARY = 'Share one port on your computer at https://<name>--<username>.openvibe.website, behind OpenVibe sign-in.';
+ const DETAILS = [
+ 'Client: one file, Node 22+: curl -fsSLO https://connect.openvibe.website/ov-connect.js; node ov-connect.js --key ovc_… --port 8080.',
+ 'Who can open a share: only the owner, the owner and named OpenVibe usernames, or anyone signed in to OpenVibe. No public shares yet.',
+ 'The app behind a share receives X-OpenVibe-User and X-OpenVibe-Subject; Host is localhost:<port>.',
+ 'API (a person\'s Network token as a Bearer): GET/POST /api/v1/shares, GET/PATCH/DELETE /api/v1/shares/{id}, POST /api/v1/shares/{id}/key.',
+ 'Source: https://github.com/OpenVibers/OpenVibe.Connect (AGPL-3.0).',
+ ].join(' ');
+ const LLMS_SECTIONS = [{ title: 'Start here', links: [{ title: NAME, url: abs('/'), note: 'your shares and the form to make one' }] }];
+ const LLMS_OPTIONAL = [
+ { title: 'Client download', url: abs('/ov-connect.js'), note: 'one file, Node 22+' },
+ { title: 'Sitemap', url: abs('/sitemap.xml') },
+ { title: 'Full text for language models', url: abs('/llms-full.txt') },
+ ];
+ const LLMS_HEADER = seo.llmsTxt({
+ name: NAME, summary: SUMMARY, details: DETAILS, markdown: true, base: config.baseUrl,
+ sections: LLMS_SECTIONS, optional: LLMS_OPTIONAL,
+ });
r.get('/llms.txt', (req, res) => {
r.get('/llms.txt', (req, res) => {
- res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(LLMS_LINES.join('\n'));
+ res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(LLMS_HEADER);
});
});
r.get('/llms-full.txt', (req, res) => {
r.get('/llms-full.txt', (req, res) => {
- res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(`${LLMS_LINES.join('\n')}\n\n## The documentation (README)\n\n${README}\n`);
+ res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(`${LLMS_HEADER}\n\n## The documentation (README)\n\n${README}\n`);
});
});
return r;
return r;
@@ -103,7 +103,10 @@ const { check, done } = require('./helpers/check');
@@ -103,7 +103,10 @@ const { check, done } = require('./helpers/check');
const map = await t.get('/sitemap.xml');
const map = await t.get('/sitemap.xml');
assert.strictEqual(map.status, 200, 'the sitemap agent.json names answers');
assert.strictEqual(map.status, 200, 'the sitemap agent.json names answers');
assert.match(map.text, /<urlset xmlns="http:\/\/www\.sitemaps\.org\/schemas\/sitemap\/0\.9">\n <url><loc>https?:\/\/[^<]+\/<\/loc><\/url>\n<\/urlset>/);
assert.match(map.text, /<urlset xmlns="http:\/\/www\.sitemaps\.org\/schemas\/sitemap\/0\.9">\n <url><loc>https?:\/\/[^<]+\/<\/loc><\/url>\n<\/urlset>/);
- assert.match((await t.get('/llms.txt')).text, /OpenVibe Connect/);
+ const llms = await t.get('/llms.txt');
+ assert.match(llms.text, /OpenVibe Connect/);
+ assert.match(llms.text, /\n## Optional\n/);
+ assert.match(llms.text, /\(https:\/\/connect\.openvibe\.website\/index\.md\)/);
// SEO-2: the agent manifest is served (11-03: the mount had landed inside the share-host middleware, after its
// SEO-2: the agent manifest is served (11-03: the mount had landed inside the share-host middleware, after its
// return, so it never ran while the source check passed).
// return, so it never ran while the source check passed).
const agent = await t.get('/.well-known/agent.json');
const agent = await t.get('/.well-known/agent.json');