openvibers/openvibe.network Commits

Profiles live on openvibe.network/@name; openvibe.my keeps managing them (#682)

OP OpenVibers committed 5 days ago · 2b9f75a332 · 7 files, +29 −46

The owner, 11-08: "make profiles available on OpenVibe.Network, e.g. /@Maticus … OpenVibe.My is for users' stuff …
the @ profiles should be Network". One full profile at openvibe.network/@name (canonical there), with pronouns, the
red Live now dot, and the links as full-width buttons on a phone; openvibe.my/@name 301s to it. The sitemap, IndexNow,
the API's profile_url and My's "Your public page" link all say openvibe.network; editing stays on openvibe.my. The
separate openvibe.my person page is gone.

Changes

ddiff --git a/public/js/my/profile.js b/public/js/my/profile.js
iindex 3f461ef0..4ca8d157 100644
---- a/public/js/my/profile.js
++++ b/public/js/my/profile.js
@@@ -223,7 +223,7 @@ function renderProfile(user) {
let places = {}; try { places = typeof user.public_page_places === 'string' ? JSON.parse(user.public_page_places) : (user.public_page_places || {}); } catch {}
for (const key of ['live', 'media', 'space', 'community']) document.getElementById(`public-place-${key}`).checked = places[key] !== false;
const pageUrl = document.getElementById('public-page-url');
-- if (user.username && !user.is_anon) { pageUrl.href = `https://openvibe.my/@${encodeURIComponent(user.username)}`; pageUrl.textContent = `openvibe.my/@${user.username}`; }
++ if (user.username && !user.is_anon) { pageUrl.href = `https://openvibe.network/@${encodeURIComponent(user.username)}`; pageUrl.textContent = `openvibe.network/@${user.username}`; }
for (const id of ['public-page-pronouns', 'public-page-bio', 'public-page-links', 'public-place-live', 'public-place-media', 'public-place-space', 'public-place-community', 'public-page-preview-toggle', 'public-page-save']) document.getElementById(id).disabled = unavailable;
if (unavailable) document.getElementById('public-page-msg').textContent = 'Sign in to edit your public page.';
}
ddiff --git a/public/my.html b/public/my.html
iindex 57819d91..81d8bea6 100644
---- a/public/my.html
++++ b/public/my.html
@@@ -213,7 +213,7 @@
</div>
<div class="card" id="public-page-card">
<h3><span class="icon"><i class="fa-solid fa-address-card"></i></span> Your public page</h3>
-- <p class="muted">A link-in-bio page at <a id="public-page-url" href="https://openvibe.my/" target="_blank" rel="noopener">openvibe.my/@you</a>. Only information already public while signed out can appear here.</p>
++ <p class="muted">A link-in-bio page at <a id="public-page-url" href="https://openvibe.network/" target="_blank" rel="noopener">openvibe.network/@you</a>. Only information already public while signed out can appear here.</p>
<label for="public-page-pronouns">Pronouns <small>(40 characters)</small></label><input id="public-page-pronouns" maxlength="40" autocomplete="off">
<label for="public-page-bio">Bio <small>(280 characters)</small></label><textarea id="public-page-bio" maxlength="280" rows="3"></textarea>
<label for="public-page-links">Links <small>(up to 8; one per line: label | https://…)</small></label><textarea id="public-page-links" rows="5" placeholder="My website | https://example.com"></textarea>
ddiff --git a/server/auth/routes.js b/server/auth/routes.js
iindex 34d86cc6..e3b81d24 100644
---- a/server/auth/routes.js
++++ b/server/auth/routes.js
@@@ -496,8 +496,7 @@ router.put('/profile', requireAuth, async (req, res) => {
if (!Array.isArray(links)) links = [];
const indexable = Number(user.profile_public) && (!!String(user.public_page_bio || '').trim() || links.length > 0);
if (changed && indexable) {
-- const origin = String(getConfig(req).my?.baseUrl || 'https://openvibe.my').replace(/\/+$/, '');
-- req.app.locals.publicPageIndexNow?.pingSoon(`${origin}/@${encodeURIComponent(user.username)}`);
++ req.app.locals.publicPageIndexNow?.pingSoon(`https://openvibe.network/@${encodeURIComponent(user.username)}`);
}
}
res.json({ user: sanitizeUser(user) });
ddiff --git a/server/home/render.js b/server/home/render.js
iindex c2e6a1e9..8408b29b 100644
---- a/server/home/render.js
++++ b/server/home/render.js
@@@ -498,7 +498,7 @@ function llmsTxt() {
// The JSON an agent came for is a main section, not something to skip (AGT-5 review).
{ title: 'Machine-readable', links: [
{ title: 'Open stats (JSON): every site\'s numbers', url: 'https://openvibe.network/api/v1/stats' },
-- { title: 'Public person page (JSON)', url: 'https://openvibe.network/api/v1/people/@handle/page', note: 'the signed-out profile at https://openvibe.my/@handle, including its public OpenVibe places' },
++ { title: 'Public person page (JSON)', url: 'https://openvibe.network/api/v1/people/@handle/page', note: 'the signed-out profile at https://openvibe.network/@handle, including its public OpenVibe places' },
{ title: 'OpenVibe Connect MCP', url: 'https://connect.openvibe.network/mcp', note: 'includes network.person_page.read for a public person page' },
{ title: 'Tool catalog (JSON)', url: 'https://openvibe.tools/api/catalog.json' },
{ title: 'Tool domains (JSON)', url: 'https://openvibe.network/api/domains' },
ddiff --git a/server/profile/public.js b/server/profile/public.js
iindex b2c4f6e4..54fabf47 100644
---- a/server/profile/public.js
++++ b/server/profile/public.js
@@@ -25,7 +25,6 @@ const { siteForHost, fullName } = require('../frame/sites');
const esc = seo.esc;
const SITE = 'https://openvibe.network';
--const MY_SITE = 'https://openvibe.my';
const SPACE_SITE = 'https://openvibe.space';
const COMMUNITY_SITE = 'https://openvibe.community';
const INVENTORY_SITE = 'https://openvibe.my/inventory';
@@@ -296,7 +295,7 @@ function createPublicProfiles({ db, selfToken = () => null, inventoryUrl = 'http
try { links = typeof u.public_page_links === 'string' ? JSON.parse(u.public_page_links) : (u.public_page_links || []); } catch {}
try { choices = { ...choices, ...(typeof u.public_page_places === 'string' ? JSON.parse(u.public_page_places) : (u.public_page_places || {})) }; } catch {}
const places = Number(u.profile_public) ? await placesOf(u.username, choices) : { spaces: [], community: [], videos: [] };
-- return { username: u.username, display_name: u.display_name || u.username, pronouns: Number(u.profile_public) ? String(u.public_page_pronouns || '').slice(0, 40) : '', avatar_url: avatarUrl(u.username), profile_url: `${MY_SITE}/@${encodeURIComponent(u.username)}`, bio: Number(u.profile_public) ? String(u.public_page_bio || '').slice(0, 280) : '', links: Number(u.profile_public) && Array.isArray(links) ? links.slice(0, 8) : [], places };
++ return { username: u.username, display_name: u.display_name || u.username, pronouns: Number(u.profile_public) ? String(u.public_page_pronouns || '').slice(0, 40) : '', avatar_url: avatarUrl(u.username), profile_url: `${SITE}/@${encodeURIComponent(u.username)}`, bio: Number(u.profile_public) ? String(u.public_page_bio || '').slice(0, 280) : '', links: Number(u.profile_public) && Array.isArray(links) ? links.slice(0, 8) : [], places };
}
/** profile@1: what every site may show about a person. */
@@@ -328,6 +327,9 @@ function createPublicProfiles({ db, selfToken = () => null, inventoryUrl = 'http
/** GET /@:username (server/index.js mounts it with its own rate limit). */
async function pageHandler(req, res, next) {
try {
++ if (String(req.hostname || '').toLowerCase() === 'openvibe.my') {
++ return res.redirect(301, `${SITE}/@${encodeURIComponent(String(req.params.username || ''))}`);
++ }
const found = await find(req.params.username);
if (!found) return next(); // the shared 404 page
if (found.moved) return res.redirect(301, `/@${encodeURIComponent(found.moved)}`);
@@@ -336,8 +338,9 @@ function createPublicProfiles({ db, selfToken = () => null, inventoryUrl = 'http
const [inv, page] = await Promise.all([Number(u.profile_public) ? wearsOf(u.subject_id) : null, pageOf(u)]);
res.set('Content-Type', 'text/html; charset=utf-8');
res.set('Cache-Control', cache.htmlHeaders({ maxAge: 60 }));
-- const origin = String(req.hostname || '').toLowerCase() === 'openvibe.my' ? 'https://openvibe.my' : SITE;
-- res.send(origin === MY_SITE ? renderPersonPage(u, page, origin) : renderPage(u, inv, origin, page.places));
++ // A person's public profile lives on openvibe.network (the owner, 11-08: "OpenVibe.My is for users' stuff,
++ // the profiles should be on .Network"). openvibe.my is the person's own hub: /@name there moves here.
++ res.send(renderPage(u, inv, SITE, page.places, page.pronouns));
} catch (err) { next(err); }
}
@@@ -450,32 +453,7 @@ ${sorted.map((g) => {
<p class="pf-note"><a href="${esc(all)}">${inv.more ? 'Every item' : 'This inventory'} on OpenVibe.Inventory</a> · <a href="${INVENTORY_SITE}/items">Every item there is</a></p></section>`;
}
--function renderPersonPage(u, page, origin = MY_SITE) {
-- const name = page.display_name || page.username;
-- const url = `${origin}/@${encodeURIComponent(page.username)}`;
-- const indexable = !!(page.bio || page.links.length);
-- const ld = seo.jsonLdTag({
-- '@context': 'https://schema.org', '@type': 'ProfilePage', url, name: `${name} (@${page.username}) · OpenVibe`,
-- mainEntity: { '@type': 'Person', name, alternateName: `@${page.username}`, image: page.avatar_url, url, ...(page.bio ? { description: page.bio } : {}) },
-- });
-- const links = page.links.map((l) => {
-- try { const parsed = new URL(l.url); return parsed.protocol === 'https:' && !parsed.username && !parsed.password ? `<li><a href="${esc(parsed.href)}" rel="me noopener noreferrer">${esc(l.label)}</a></li>` : ''; } catch { return ''; }
-- }).join('');
-- const places = publicPlacesHtml(page.places);
-- return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
--<title>${esc(name)} (@${esc(page.username)}) · OpenVibe</title><meta name="description" content="${esc(page.bio || `${name} on OpenVibe.`)}">
--<link rel="canonical" href="${esc(url)}">${indexable ? '' : '<meta name="robots" content="noindex">'}
--<meta property="og:type" content="profile"><meta property="og:title" content="${esc(`${name} (@${page.username})`)}"><meta property="og:url" content="${esc(url)}"><meta property="og:image" content="${esc(page.avatar_url)}"><meta property="profile:username" content="${esc(page.username)}">
--${ld}<style>
--:root{color-scheme:light dark}*{box-sizing:border-box}body{margin:0;background:var(--bg-primary,#f5f6fa);color:var(--text-primary,#171923);font:16px/1.55 system-ui,sans-serif}.person-page{width:min(100% - 32px,600px);margin:clamp(24px,7vw,80px) auto}.person-card{padding:clamp(22px,5vw,40px);border:1px solid var(--border,#d8dbe4);border-radius:24px;background:var(--bg-card,var(--bg-secondary,#fff));box-shadow:0 18px 54px #0002}.person-head{display:flex;flex-direction:column;align-items:center;gap:14px;text-align:center}.person-avatar{width:104px;height:104px;border-radius:50%;object-fit:cover;background:var(--bg-secondary,#eee)}h1{font-size:clamp(1.7rem,5vw,2.5rem);line-height:1.1;margin:0;overflow-wrap:anywhere}.person-handle{margin:6px 0 0;color:var(--text-secondary,#5c6170)}.person-pronouns{display:inline-block;margin:8px 0 0;padding:2px 10px;border:1px solid var(--border,#d8dbe4);border-radius:999px;font-size:.85rem;color:var(--text-secondary,#5c6170)}.person-bio{margin:18px auto 0;max-width:46ch;text-align:center;white-space:pre-wrap;overflow-wrap:anywhere}.person-links{display:grid;gap:10px;list-style:none;margin:24px 0 0;padding:0}.person-links a{display:flex;min-height:52px;align-items:center;justify-content:center;padding:10px 18px;border:1px solid var(--border,#d8dbe4);border-radius:14px;background:var(--bg-secondary,#f6f7fa);color:inherit;font-weight:650;text-align:center;text-decoration:none;overflow-wrap:anywhere;transition:border-color .15s,transform .15s}.person-links a:hover{border-color:var(--accent,#6d5dfc)}.person-links a:active{transform:scale(.99)}.person-places{margin-top:30px}.person-places h2{margin:0 0 10px;font-size:.78rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--text-secondary,#5c6170)}.pf-places{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(230px,100%),1fr));gap:12px;list-style:none;padding:0;margin:0}.pf-places li{min-width:0}.pf-places a{display:flex;flex-direction:column;justify-content:center;gap:5px;min-height:72px;padding:12px 16px;border:1px solid var(--border,#d8dbe4);border-radius:14px;background:var(--bg-secondary,#f6f7fa);color:inherit;text-decoration:none;overflow-wrap:anywhere}.pf-places a span{font-size:.82rem;color:var(--text-secondary,#5c6170)}.pf-places a .pf-live{display:inline-flex;align-items:center;gap:6px;color:var(--live-red,#e5484d);font-weight:700}.pf-live::before{content:'';width:8px;height:8px;border-radius:50%;background:currentColor}@media(max-width:520px){.person-avatar{width:88px;height:88px}}@media(prefers-color-scheme:dark){body{background:var(--bg-primary,#101218);color:var(--text-primary,#f5f6fa)}.person-card{border-color:var(--border,#30333d);background:var(--bg-card,var(--bg-secondary,#171a22))}.person-avatar{background:var(--bg-secondary,#222)}.person-handle,.person-pronouns,.pf-places a span{color:var(--text-secondary,#a8adba)}.person-links a,.pf-places a{border-color:var(--border,#30333d)}.person-links a,.pf-places a{background:var(--bg-secondary,#20232d)}}
--</style></head><body><div id="navbar-mount"></div><main class="person-page"><article class="person-card" itemscope itemtype="https://schema.org/Person">
--<div class="person-head"><img class="person-avatar" src="${esc(page.avatar_url)}" alt="" width="104" height="104" itemprop="image"><div><h1 itemprop="name">${esc(name)}</h1><p class="person-handle">@${esc(page.username)}</p>${page.pronouns ? `<p class="person-pronouns">${esc(page.pronouns)}</p>` : ''}</div></div>
--${page.bio ? `<p class="person-bio" itemprop="description">${esc(page.bio)}</p>` : ''}${links ? `<nav aria-label="${esc(name)}'s links"><ul class="person-links">${links}</ul></nav>` : ''}
--${places ? `<div class="person-places">${places}</div>` : ''}</article></main>${require('openvibe-shared/footer').ssr({ service: 'network', variant: 'compact' })}
--<script src="/shared/navbar.js" defer></script><script src="/js/account-safety.js" defer></script><script>document.addEventListener('DOMContentLoaded',function(){try{if(window.OpenVibeNavbar)OpenVibeNavbar.init({service:'network',apiBase:location.origin});}catch(e){}});</script></body></html>`;
--}
--
--function renderPage(u, inv, origin = SITE, places = { spaces: [], community: [], videos: [] }) {
++function renderPage(u, inv, origin = SITE, places = { spaces: [], community: [], videos: [] }, pronouns = '') {
const canonicalUrl = `${origin}/@${encodeURIComponent(u.username)}`;
const name = u.display_name || u.username;
const isPublic = !!inv;
@@@ -546,7 +524,7 @@ a{color:var(--accent)}
.pf-who{flex:1;min-width:200px;padding-top:56px}
.pf-who h1{margin:0;font-size:clamp(1.5rem,4vw,2.1rem);line-height:1.15;overflow-wrap:anywhere}
.pf-name-badge{width:.85em;height:.85em;margin-right:.25em;vertical-align:-.08em;border-radius:6px;object-fit:contain}
--.pf-handle{margin:.2em 0 0;color:var(--text-secondary)}
++.pf-handle{margin:.2em 0 0;color:var(--text-secondary)}.pf-pronouns{display:inline-block;margin-left:6px;padding:1px 9px;border:1px solid var(--border);border-radius:999px;font-size:.82em;vertical-align:1px}.pf-places a .pf-live{display:inline-flex;align-items:center;gap:6px;color:var(--live-red,#e5484d);font-weight:700}.pf-live::before{content:'';width:8px;height:8px;border-radius:50%;background:currentColor}
.pf-bio{margin:0;padding:0 24px 18px;color:var(--text-primary);line-height:1.6;white-space:pre-line;overflow-wrap:anywhere;max-width:72ch}
.pf-stats{list-style:none;display:flex;flex-wrap:wrap;gap:8px;margin:0;padding:0 24px 22px}
.pf-stats li{display:flex;flex-direction:column;gap:2px;padding:8px 14px;border:1px solid var(--border);border-radius:12px;background:var(--bg-card);min-width:110px}
@@@ -586,7 +564,8 @@ a{color:var(--accent)}
.pf-ach li{overflow-wrap:anywhere}.pf-ach small{color:var(--text-muted);font-size:.75rem}
.pf-empty{color:var(--text-secondary);padding:18px;border:1px dashed var(--border);border-radius:12px;margin:0}
.pf-note{color:var(--text-secondary);font-size:.85rem;margin:12px 0 0}
--.pf-links{display:flex;flex-wrap:wrap;gap:10px;list-style:none;margin:12px 0;padding:0}.pf-links a{display:inline-flex;min-height:44px;align-items:center;padding:8px 14px;border:1px solid var(--border);border-radius:999px;color:var(--text-primary);text-decoration:none}
++.pf-links{display:flex;flex-wrap:wrap;gap:10px;list-style:none;margin:0 0 18px;padding:0 24px}.pf-links a{display:inline-flex;min-height:44px;align-items:center;padding:8px 14px;border:1px solid var(--border);border-radius:999px;color:var(--text-primary);text-decoration:none}
++@media(max-width:600px){.pf-links{display:grid;padding:0 24px}.pf-links a{justify-content:center;min-height:50px;border-radius:14px;font-weight:650}}
.pf-places{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(230px,100%),1fr));gap:12px;list-style:none;padding:0;margin:0}.pf-places li{min-width:0}.pf-places a{display:flex;flex-direction:column;justify-content:center;gap:5px;min-height:72px;padding:12px 16px;border:1px solid var(--border);border-radius:14px;background:var(--bg-secondary);color:var(--text-primary);text-decoration:none;overflow-wrap:anywhere}.pf-places a span{font-size:.82rem;color:var(--text-secondary)}
@media (max-width:560px){.pf-head{padding:0 16px 16px;gap:14px}.pf-avatar{width:92px;height:92px}.pf-hat{font-size:32px;top:-18px}.pf-who{padding-top:0;min-width:100%}.pf-bio,.pf-stats,.pf-private{padding-left:16px;padding-right:16px}.pf-grid{grid-template-columns:repeat(auto-fill,minmax(112px,1fr))}.pf-badge small{display:none}.pf-badge{padding:6px 12px 6px 9px}}
</style>
@@@ -599,7 +578,7 @@ ${require('openvibe-shared/frame').noscriptNav({ name: 'OpenVibe.Network', links
<div class="pf-banner" aria-hidden="true"></div>
<div class="pf-head">
<div class="pf-pic"><img class="pf-avatar" src="${esc(avatarUrl(u.username))}" alt="" width="112" height="112">${hatGlyph ? `<span class="pf-hat" title="${esc(hat.name)}" aria-hidden="true">${esc(hatGlyph)}</span>` : ''}</div>
--<div class="pf-who"><h1>${badgeId ? `<img class="pf-name-badge" src="https://openvibe.media/o/${badgeId}" alt="" title="${esc(badge.name)}" width="32" height="32">` : ''}${fxClass ? `<span class="ov-fx ${fxClass}" title="${esc(fx.name)}">${esc(name)}</span>` : esc(name)}</h1><p class="pf-handle">@${esc(u.username)}</p></div>
++<div class="pf-who"><h1>${badgeId ? `<img class="pf-name-badge" src="https://openvibe.media/o/${badgeId}" alt="" title="${esc(badge.name)}" width="32" height="32">` : ''}${fxClass ? `<span class="ov-fx ${fxClass}" title="${esc(fx.name)}">${esc(name)}</span>` : esc(name)}</h1><p class="pf-handle">@${esc(u.username)}${isPublic && pronouns ? ` <span class="pf-pronouns">${esc(pronouns)}</span>` : ''}</p></div>
</div>
${bio ? `<p class="pf-bio">${esc(bio)}</p>` : ''}
${isPublic && Array.isArray(publicLinks) && publicLinks.length ? `<nav aria-label="${esc(name)}'s links"><ul class="pf-links">${publicLinks.slice(0, 8).map((link) => { let href = ''; try { const parsed = new URL(link.url); if (parsed.protocol === 'https:' && !parsed.username && !parsed.password) href = parsed.href; } catch {} return href ? `<li><a rel="me noopener noreferrer" href="${esc(href)}">${esc(String(link.label || '').slice(0, 40))}</a></li>` : ''; }).join('')}</ul></nav>` : ''}
@@@ -616,4 +595,4 @@ ${require('openvibe-shared/footer').ssr({ service: 'network', variant: 'compact'
</html>`;
}
--module.exports = { createPublicProfiles, renderPage, renderPersonPage, monthOf, SUBJECT_RE, PUBLIC_PLACE_TIMEOUT_MS, PUBLIC_PLACE_TTL_MS };
++module.exports = { createPublicProfiles, renderPage, monthOf, SUBJECT_RE, PUBLIC_PLACE_TIMEOUT_MS, PUBLIC_PLACE_TTL_MS };
ddiff --git a/server/seo/routes.js b/server/seo/routes.js
iindex 5db27089..f71ec36f 100644
---- a/server/seo/routes.js
++++ b/server/seo/routes.js
@@@ -32,7 +32,7 @@ function sitemapXml({ release = null, profiles = [] } = {}) {
const day = String(lastmod).slice(0, 10);
return seo.sitemapXml([
...PAGES.map((page) => ({ loc: `${HOST}${page.path}`, lastmod: day, changefreq: page.changefreq, priority: page.priority })),
-- ...profiles.map((username) => ({ loc: `https://openvibe.my/@${encodeURIComponent(username)}`, lastmod: day, changefreq: 'weekly', priority: 0.5 })),
++ ...profiles.map((username) => ({ loc: `https://openvibe.network/@${encodeURIComponent(username)}`, lastmod: day, changefreq: 'weekly', priority: 0.5 })),
]);
}
ddiff --git a/test/public-profile.test.js b/test/public-profile.test.js
iindex 2ac9c407..82a4a6a0 100644
---- a/test/public-profile.test.js
++++ b/test/public-profile.test.js
@@@ -11,7 +11,7 @@ const fs = require('fs');
const path = require('path');
const express = require('express');
const { getDb } = require('../server/db/database');
--const { createPublicProfiles, renderPersonPage, monthOf, PUBLIC_PLACE_TIMEOUT_MS, PUBLIC_PLACE_TTL_MS } = require('../server/profile/public');
++const { createPublicProfiles, monthOf, PUBLIC_PLACE_TIMEOUT_MS, PUBLIC_PLACE_TTL_MS } = require('../server/profile/public');
const publicCors = require('../server/public-cors');
const A = 'usr_01JZ0000000000000000000AAA';
@@@ -106,7 +106,7 @@ const INSTANCES = [
assert.ok(r.text.includes('Member since') && r.text.includes('October 2026'));
assert.ok(/<span class="pf-stat-k">Rarest<\/span><span class="pf-stat-v">Legendary<\/span>/.test(r.text));
assert.ok(r.text.includes('<span class="pf-count">4</span>'), 'four items');
-- assert.ok(r.text.includes('"@type":"ProfilePage"') && r.text.includes('<link rel="canonical" href="https://openvibe.network/@Ana">'));
++ assert.ok(r.text.includes('"@type":"ProfilePage"') && r.text.includes('<link rel="canonical" href="https://openvibe.network/@Ana">'), 'profiles live on openvibe.network');
assert.ok(r.text.includes('--pf-color:#ff0066'));
assert.ok(r.text.includes('https://openvibe.my/inventory/itd_01JZ00000000000000000001R1'), 'items link to their Inventory page');
assert.ok(!/name="robots" content="noindex"/.test(r.text));
@@@ -158,7 +158,7 @@ const INSTANCES = [
assert.strictEqual(r.status, 200);
assert.deepStrictEqual(JSON.parse(r.text).person.links, [{ label: 'My site', url: 'https://example.com/' }]);
assert.strictEqual(JSON.parse(r.text).person.bio, 'Hello there');
-- assert.strictEqual(JSON.parse(r.text).person.profile_url, 'https://openvibe.my/@Ana');
++ assert.strictEqual(JSON.parse(r.text).person.profile_url, 'https://openvibe.network/@Ana');
assert.strictEqual(JSON.parse(r.text).person.places.live.is_live, true);
assert.deepStrictEqual(JSON.parse(r.text).person.places.videos.map((v) => v.title), ['First video']);
assert.deepStrictEqual(JSON.parse(r.text).person.places.videos.map((v) => v.url), ['https://openvibe.video/watch?v=tdLVs5wad4A'], 'a video links to its watch page');
@@@ -167,9 +167,14 @@ const INSTANCES = [
const person = JSON.parse(r.text).person;
r = await get('/api/v1/people/@ana/page');
assert.deepStrictEqual([r.status, r.headers.get('location')], [301, '/api/v1/people/@Ana/page']);
-- const newPage = renderPersonPage({ username: 'Ana' }, person, 'https://openvibe.my');
-- assert.ok(newPage.includes('<link rel="canonical" href="https://openvibe.my/@Ana">') && newPage.includes('First video') && newPage.includes('My site'));
-- assert.ok(!newPage.includes('pf-grid') && !newPage.includes('inventory'), 'the new page shows public places without private account inventory');
++ // openvibe.my is the person's own hub; a profile address there moves to openvibe.network (the owner, 11-08).
++ // fetch cannot set Host, so this one request goes out raw.
++ const onMy = await new Promise((resolve, reject) => {
++ const u = new URL(b);
++ require('http').get({ host: u.hostname, port: u.port, path: '/@Ana', headers: { Host: 'openvibe.my' } }, (res) => { res.resume(); resolve({ status: res.statusCode, location: res.headers.location }); }).on('error', reject);
++ });
++ assert.deepStrictEqual([onMy.status, onMy.location], [301, 'https://openvibe.network/@Ana']);
++ assert.strictEqual(person.username, 'Ana');
// A hidden profile: name and picture, nothing else, not indexed.
r = await get('/@hidden');