openvibers/openvibe.network Commits

openvibe.link: Your machines and pairing at Link's own name (#665)

OP OpenVibers committed 5 days ago · a688621e9b · 4 files, +55 −7

* openvibe.link: Your machines and pairing, at Link's own name

The page gains a Your machines section and keeps Link's name on the way in: openvibe.link/machines and
openvibe.link/pair send a visitor to the signed-in machines hub (openvibe.my/machines), which lists every paired
machine, whether it is online, what it is allowed to do, pairs a new one, and renames or revokes one. The install
steps point at openvibe.link/pair for the code. The redirect is no-store and the old node host still 301s to Link.

* openvibe.link: show which OS panels open, and say when the release read failed

* openvibe.link: offer the APK the release actually carries

The GitHub release now tells the page which assets it has. The Android row links the signed APK when it is there,
the debug build CI attaches when the signing key is absent (and says so), and the release page when neither exists,
instead of a link that 404s. The release test covers both fallbacks.

Changes

ddiff --git a/server/node/page.js b/server/node/page.js
iindex 02e67312..7727d0e2 100644
---- a/server/node/page.js
++++ b/server/node/page.js
@@@ -57,7 +57,18 @@ function installBlock(release, userAgent) {
const dl = (name) => `${latest}/${name}`;
const file = (name, label) => `<a href="${esc(dl(name))}">${esc(label || name)}</a>`;
const pkgFile = (name, label) => (pkg ? `<a href="${esc(dl(name))}">${esc(label || name)}</a>` : `<a href="${releases}">${esc(label || name)}</a>`);
-- const apk = release.version ? `${releases}/download/${encodeURIComponent(release.version)}/openvibe-link-${encodeURIComponent(release.version)}.apk` : releases;
++ // The signed APK is attached when the repo has the release key; CI attaches a debug APK when it does not. Offer
++ // whichever the release actually carries, and the release page when neither is there yet.
++ const assets = Array.isArray(release.assets) ? release.assets : null;
++ const signedApk = release.version ? `openvibe-link-${release.version}.apk` : '';
++ const debugApk = release.version ? `openvibe-link-${release.version}-debug.apk` : '';
++ const apkName = !release.version ? '' : (!assets ? signedApk : (assets.includes(signedApk) ? signedApk : (assets.includes(debugApk) ? debugApk : '')));
++ const apk = release.version
++ ? (apkName ? `${releases}/download/${encodeURIComponent(release.version)}/${encodeURIComponent(apkName)}` : `${releases}/tag/${encodeURIComponent(release.version)}`)
++ : releases;
++ const apkNote = apkName && apkName === debugApk
++ ? ' This is the debug build: the signed Android release waits on the repository\'s release key.'
++ : '';
const open = (os) => (current === os ? ' open' : '');
return `<div class="np-os-list">
<details class="np-os" data-os="windows"${open('windows')}>
@@@ -66,7 +77,7 @@ function installBlock(release, userAgent) {
<p>Download the program for this PC: ${file('openvibe-link-windows-amd64.exe', 'Windows x64 (.exe)')} or ${file('openvibe-link-windows-arm64.exe', 'Windows ARM64 (.exe)')}.</p>
<p>Windows may show SmartScreen the first time: choose <strong>More info</strong>, then <strong>Run anyway</strong>. In an <strong>Administrator</strong> PowerShell, from the folder you saved it in:</p>
${kit.snippet({ code: '.\\openvibe-link pair <CODE>\n.\\openvibe-link install', id: 'np-install-win', label: 'Copy the Windows steps' })}
--<p class="np-fine">The code comes from <a href="https://openvibe.bot/machines">openvibe.bot/machines</a> (<strong>Add a machine</strong>). <code>openvibe-node.exe</code> is the same program under its old name.</p>
++<p class="np-fine">The code comes from <a href="/pair">openvibe.link/pair</a> (the signed-in machines hub, <strong>Pair a machine</strong>). <code>openvibe-node.exe</code> is the same program under its old name.</p>
</div>
</details>
@@@ -94,8 +105,8 @@ ${kit.snippet({ code: 'sudo apt install ./openvibe-link_<version>_amd64.deb\n# o
<details class="np-os" data-os="android"${open('android')}>
<summary><span>Android</span><small>the existing APK, direct</small></summary>
<div class="np-os-body">
--<p>Download <a href="${esc(apk)}">openvibe-link${v ? `-${v}` : ''}.apk</a> on the phone, allow installing from that source when Android asks, and open <strong>OpenVibe Link</strong>.</p>
--<p>Pair from the app with a code from <a href="https://openvibe.bot/link/phone">openvibe.bot/link/phone</a>. The APK carries approvals, Activity, pairing and <strong>Send to OpenVibe</strong>; it is the direct build, not Google Play, and has no phone automation.</p>
++<p>Download <a href="${esc(apk)}">${apkName ? esc(apkName) : 'the APK from the release page'}</a> on the phone, allow installing from that source when Android asks, and open <strong>OpenVibe Link</strong>.${apkNote}</p>
++<p>Pair from the app with a code from <a href="/pair">openvibe.link/pair</a> or <a href="https://openvibe.bot/link/phone">openvibe.bot/link/phone</a>. The APK carries approvals, Activity, pairing and <strong>Send to OpenVibe</strong>; it is the direct build, not Google Play, and has no phone automation.</p>
</div>
</details>
@@@ -121,7 +132,7 @@ function releaseBlock(release) {
kit.row({ href: release.url || '#', title: 'The release on GitHub', sub: 'every asset, and the notes of that release' }),
];
return `<section class="np-sec" aria-labelledby="h-release"><h2 id="h-release">The latest release</h2>
--<p class="np-fine">${version ? `Version <strong>${esc(version)}</strong>` : 'The latest version'}${date ? `, published ${esc(date)}` : ''}. The install command above always installs that release.</p>
++<p class="np-fine">${version ? `Version <strong>${esc(version)}</strong>` : 'The download links always point at the latest release.'}${date ? `, published ${esc(date)}` : ''}. The install command above always installs that release.</p>
${kit.list(rows, { flush: true })}
${release.stale ? '<p class="np-fine np-warn">GitHub could not be reached just now, so this is the last release we read.</p>' : ''}
</section>`;
@@@ -177,9 +188,15 @@ ${CSS}
</section>
<section class="np-sec" id="install" aria-labelledby="h-install"><h2 id="h-install">Install it</h2>
++<p class="np-fine" style="margin:0 0 12px">Your system is open first; choose another to see its steps.</p>
${installBlock(release, userAgent)}
</section>
++<section class="np-sec" id="machines" aria-labelledby="h-machines"><h2 id="h-machines">Your machines</h2>
++<p class="np-fine">Sign in to see every machine you paired: whether it is online, what it is allowed to do, and the one-time code to add another. The same page renames or revokes one, and the phone pairs there too.</p>
++<p class="np-cta" style="margin-top:12px">${kit.button({ href: 'https://openvibe.my/machines', label: 'Open your machines', kind: 'primary' })} ${kit.button({ href: '/pair', label: 'Pair a machine', kind: 'quiet' })}</p>
++</section>
++
${releaseBlock(release)}
<section class="np-sec" aria-labelledby="h-never"><h2 id="h-never">What it never does</h2>
@@@ -201,7 +218,7 @@ ${require('openvibe-shared/footer').ssr({ service: 'network', variant: 'compact'
<script src="/shared/navbar.js" defer></script>
${require('../boost').SCRIPT}
<script src="/shared/kit-web.js" defer></script>
--<script>document.addEventListener('DOMContentLoaded',function(){try{if(window.OpenVibeNavbar)OpenVibeNavbar.init({service:'network',apiBase:'https://openvibe.network',links:[{label:'Link',href:'/',icon:'fa-desktop',featured:true,active:true},{label:'Network',href:'https://openvibe.network/'},{label:'My Account',href:'https://openvibe.network/my'}]});}catch(e){}});</script>
++<script>document.addEventListener('DOMContentLoaded',function(){try{if(window.OpenVibeNavbar)OpenVibeNavbar.init({service:'network',apiBase:'https://openvibe.network',links:[{label:'Link',href:'/',icon:'fa-desktop',featured:true,active:true},{label:'Machines',href:'/machines',icon:'fa-laptop'},{label:'Network',href:'https://openvibe.network/'},{label:'My Account',href:'https://openvibe.network/my'}]});}catch(e){}});</script>
</body>
</html>`;
}
@@@ -228,7 +245,9 @@ const CSS = `
.np-os summary{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:14px 16px;cursor:pointer;list-style:none}
.np-os summary::-webkit-details-marker{display:none}
.np-os summary span{font-weight:800;font-size:1rem}
--.np-os summary small{color:var(--text-muted,#7386a3);font-size:.82rem;text-align:right}
++.np-os summary small{color:var(--text-muted,#7386a3);font-size:.82rem;text-align:right;margin-left:auto}
++.np-os summary::after{content:'+';margin-left:12px;font-weight:800;color:var(--text-muted,#7386a3);font-size:1.05rem;line-height:1}
++.np-os[open] summary::after{content:'−'}
.np-os-body{padding:0 16px 16px;border-top:1px solid var(--border,#1f2d47)}
.np-os-body p{color:var(--text-secondary,#96a7c2);font-size:.92rem;line-height:1.6}
.np-os-body a{color:var(--accent,#3b82f6)}
ddiff --git a/server/node/release.js b/server/node/release.js
iindex 03d79ce6..020ec07e 100644
---- a/server/node/release.js
++++ b/server/node/release.js
@@@ -32,6 +32,7 @@ function shape(json) {
const sums = asset('SHA256SUMS');
return {
version: tag,
++ assets: assets.map((a) => String((a && a.name) || '')).filter(Boolean),
publishedAt: String(json.published_at || ''),
url: String(json.html_url || (tag ? `https://github.com/${REPO}/releases/tag/${tag}` : LATEST)),
installUrl: String((install && install.browser_download_url) || (tag ? `https://github.com/${REPO}/releases/download/${tag}/install.sh` : FALLBACK.installUrl)),
ddiff --git a/server/node/routes.js b/server/node/routes.js
iindex 2c29165d..4117bf03 100644
---- a/server/node/routes.js
++++ b/server/node/routes.js
@@@ -37,6 +37,7 @@ function llmsTxt() {
'## Install',
'',
`- [Install Link](${SITE}/install): the latest release's installer, as a script to run`,
++ `- [Your machines](${SITE}/machines): the signed-in machines hub (pair, capabilities, online state), at openvibe.my/machines`,
'',
'```sh',
page.INSTALL_LINE,
@@@ -84,6 +85,13 @@ function createNodeRoutes({ releaseId, releaseApi = release } = {}) {
// The old host is a signpost, not a second home: the same path on the new one.
if (host === LEGACY_HOST) return res.redirect(301, `${SITE}${path}${url.search}`);
++ // Your machines and pairing are the signed-in account hub's own pages (openvibe.my/machines): it lists every
++ // paired machine, whether it is online, what it is allowed to do, and pairs a new one. Link links there rather
++ // than keeping a second copy of the session on this host (sign-in stays on Network; plan 2026-11-07 §5).
++ if (path === '/machines' || path === '/pair') {
++ return res.set('Cache-Control', 'no-store').redirect(302, 'https://openvibe.my/machines');
++ }
++
if (path === '/llms.txt') {
return res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(llmsTxt());
}
ddiff --git a/test/link-home.test.js b/test/link-home.test.js
iindex 372a7805..5ebee548 100644
---- a/test/link-home.test.js
++++ b/test/link-home.test.js
@@@ -22,6 +22,7 @@ const FIXTURE = {
assets: [
{ name: 'install.sh', browser_download_url: 'https://github.com/OpenVibers/OpenVibe.Node/releases/download/v9.9.9/install.sh' },
{ name: 'SHA256SUMS', browser_download_url: 'https://github.com/OpenVibers/OpenVibe.Node/releases/download/v9.9.9/SHA256SUMS' },
++ { name: 'openvibe-link-v9.9.9.apk' },
],
};
const SHAPED = {
@@@ -89,6 +90,11 @@ const SHAPED = {
['iOS home-screen web app', 'Add to Home Screen'],
['what iOS waits on', 'Apple Developer account'],
]) assert.ok(html.includes(needle), `the install block offers ${what}`);
++ const debugOnly = page.render({ release: { ...first, assets: ['install.sh', 'openvibe-link-v9.9.9-debug.apk'] }, releaseId: 'rel_test' });
++ assert.ok(debugOnly.includes('openvibe-link-v9.9.9-debug.apk'), 'an unsigned release offers the debug APK');
++ assert.ok(debugOnly.includes('signed Android release waits'), 'and says why it is the debug build');
++ const noApk = page.render({ release: { ...first, assets: ['install.sh'] }, releaseId: 'rel_test' });
++ assert.ok(noApk.includes('releases/tag/v9.9.9'), 'a release without an APK sends the visitor to its page');
for (const [ua, os] of [
['Mozilla/5.0 (Windows NT 10.0; Win64; x64)', 'windows'],
['Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0)', 'mac'],
@@@ -102,6 +108,9 @@ const SHAPED = {
assert.ok(!one.includes(`data-os="${other}" open`), `a ${os} visitor does not open ${other}`);
}
}
++ assert.ok(html.includes('id="machines"'), 'the page has a Your machines section');
++ assert.ok(html.includes('Open your machines'), 'and links the signed-in machines hub');
++ assert.ok(html.includes('Pair a machine'), 'and the pairing entry point');
assert.ok(html.includes('What it does today'), 'the honest heading stays');
for (const [what, needle] of [
['run your agents and code', 'openvibe.codes/run'],
@@@ -127,6 +136,7 @@ const SHAPED = {
assert.ok(txt.includes('https://openvibe.link/'), 'and links the page');
assert.ok(txt.includes('curl -fsSL https://openvibe.link/install | sh'), 'and the install command');
assert.ok(txt.includes('docs/install.md'), 'and the docs');
++ assert.ok(txt.includes('https://openvibe.link/machines'), 'and the machines page');
// ── The routes: the Host header decides ──
const releaseApi = { latest: async () => SHAPED };
@@@ -181,6 +191,16 @@ const SHAPED = {
assert.strictEqual(linkInstall.headers.location, nodeInstall.headers.location, 'the same release\u2019s installer, byte for byte');
assert.strictEqual(linkInstall.headers['cache-control'], nodeInstall.headers['cache-control'], 'and the same cache contract');
++ for (const p of ['/machines', '/pair']) {
++ const hub = await get(p, 'openvibe.link');
++ assert.strictEqual(hub.status, 302, `${p} sends a signed-out visitor to the machines hub`);
++ assert.strictEqual(hub.headers.location, 'https://openvibe.my/machines', `${p} lands where pairing and the machine list live`);
++ assert.strictEqual(hub.headers['cache-control'], 'no-store', `${p} is never cached`);
++ const oldHub = await get(p, 'node.openvibe.network');
++ assert.strictEqual(oldHub.status, 301, `the old host sends ${p} to Link`);
++ assert.strictEqual(oldHub.headers.location, `https://openvibe.link${p}`, `keeping ${p}`);
++ }
++
const www = await get('/', 'www.openvibe.link');
assert.strictEqual(www.status, 301, 'www 301s');
assert.strictEqual(www.headers.location, 'https://openvibe.link/', 'to the apex');