openvibers/openvibe.shared Commits
4.106.3: a site on its own open.<tld> domain is named for it (#273)
Changes
@@@ -4,6 +4,14 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semvebreaking change to any exported module, browser global or served file name is a new major.A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).++## 4.106.3 — 2026-11-02++++- **A site on its own open.<tld> domain is named for it.** open.shoes read "OpenVibe.Shoes" in the bar and its name++ linked to openvibe.shoes (the owner's screenshot). The navbar now reads `<sub>.open.<tld>` hosts as Open.<Tld>, and a++ config can say `brand: { core: 'Open' }` itself off the domain (localhost, previews). The brand carries its front door++ as `apex` (open.shoes, openre.stream, openvibe.<tld>), and the name's links go there. The footer's fallback and the++ installed app's name (`icons/sites.json`, from the manifest's first domain) say Open.Shoes too.++## 4.106.2 — 2026-11-02- **The site's name reads whole on a signed-in phone.** On openvibe.live a phone showed "Li…" beside the balances, the@@@ -2,7 +2,7 @@> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.--**Status:** alpha, v4.106.2 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.106.3 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployedconsumer installs a tagged release and none keeps a vendored copy([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` namesthe tag it pins.@@@ -4,7 +4,7 @@"code": true,"wave": "W2","package": "openvibe-shared",-- "version": "4.106.2",++ "version": "4.106.3","plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)","deployed": false,"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",@@@ -188,6 +188,7 @@let m = h.match(/^(?:(.+)\.)?openvibe\.([a-z]+)$/);if (m) { const sub = m[1] && m[1] !== 'www' ? m[1] : null; return [sub ? (sub.length <= 4 ? sub.toUpperCase() : cap(sub)) : null, 'OpenVibe', TLD_LABELS[m[2]] || cap(m[2])].filter(Boolean).join('.'); }if ((m = h.match(/^(?:(.+)\.)?openre\.stream$/))) return 'OpenRestream';++ if ((m = h.match(/^(?:(.+)\.)?open\.([a-z]+)$/))) return `Open.${TLD_LABELS[m[2]] || cap(m[2])}`;const found = NETWORK.find(n => n.id === service);if (service && TLD_LABELS[service]) return `OpenVibe.${TLD_LABELS[service]}`;if (found && found.name !== 'Account & themes' && found.name !== 'The whole network →') return `${cap(service)}.OpenVibe.Tools`;@@@ -200,7 +200,7 @@"icon": "network"},"shoes": {-- "name": "OpenVibe.Shoes",++ "name": "Open.Shoes","description": "Every shoe, every market, the right price","icon": "network"},@@@ -675,7 +675,7 @@}// ─── Brand from hostname ───────────────────────────────────-- // Every property is <sub?>.openvibe.<tld> (plus openre.stream). The navbar spells the++ // Every property is <sub?>.openvibe.<tld> (plus openre.stream and open.<tld>). The navbar spells the// whole name — Pastes.OpenVibe.Tools, not "Paste.OpenVibe" — because the subdomain and// the TLD are what tell a visitor where they are in the network.const TLD_LABELS = {@@@ -728,6 +728,8 @@let m = host.match(/^(?:(.+)\.)?openvibe\.([a-z]+)$/);if (m) { sub = m[1] && m[1] !== 'www' ? m[1] : null; tld = m[2]; }else if ((m = host.match(/^(?:(.+)\.)?openre\.stream$/))) { sub = m[1] && m[1] !== 'www' ? m[1] : null; core = 'OpenRe'; tld = 'stream'; joined = true; }++ // A site on its own open.<tld> domain (open.shoes) is named for it: Open.Shoes, not OpenVibe.Shoes.++ else if ((m = host.match(/^(?:(.+)\.)?open\.([a-z]+)$/))) { sub = m[1] && m[1] !== 'www' ? m[1] : null; core = 'Open'; tld = m[2]; }// Off-network hosts (localhost, previews): fall back to the service id.if (!tld) { tld = SERVICE_TLD[_config.service] || (SERVICE_SUB[_config.service] ? 'tools' : 'network'); sub = SERVICE_SUB[_config.service] || null; }// Legacy brandName ("Paste.OpenVibe", "OpenVibe.Live") still steers the segments.@@@ -739,18 +741,21 @@}if (b.sub !== undefined) sub = b.sub ? String(b.sub).toLowerCase() : null;if (b.tld) tld = String(b.tld).toLowerCase();++ if (b.core && !joined) core = String(b.core);const subText = b.subLabel || (sub ? subLabel(sub) : null);const tldText = b.tldLabel || (joined ? tld : TLD_LABELS[tld] || titleCase(tld));const site = joined ? `${core}${tldText}` : `${core}.${tldText}`;const name = b.name || [subText, site].filter(Boolean).join('.');const icon = b.icon || _config.brandIcon || null;const variant = b.variant || (joined ? 'stream' : tld);-- return { sub, subText, core, tld, tldText, joined, site, name, short: subText || site, icon, variant, tag: b.tag || null, href: b.href || '/' };++ // The site's own front door: openre.stream, open.shoes, openvibe.<tld>.++ const apex = b.apex || (joined ? 'https://openre.stream/' : `https://${core.toLowerCase()}.${tld}/`);++ return { sub, subText, core, tld, tldText, joined, site, name, short: subText || site, icon, variant, tag: b.tag || null, href: b.href || '/', apex };}/** Where each brand segment goes: sub → this tool, OpenVibe → the network, TLD → the site's apex. */function brandLinks(brand) {-- const apex = brand.joined ? 'https://openre.stream/' : `https://openvibe.${brand.tld}/`;++ const apex = brand.apex || (brand.joined ? 'https://openre.stream/' : `https://openvibe.${brand.tld}/`);if (!brand.subText) return { sub: brand.href, core: brand.href, tld: brand.href, apex };return { sub: brand.href, core: brand.joined ? apex : 'https://openvibe.network/', tld: apex, apex };}@@@ -769,7 +774,7 @@const dot = '<span class="b-dot">.</span>';const join = brand.joined ? '' : dot;const text = brand.subText-- ? seg('b-sub', brand.subText, to.sub, brand.name) + dot + seg('b-core', brand.core, to.core, brand.joined ? brand.site : 'OpenVibe Network') + join + seg('b-tld', brand.tldText, to.tld, `All of ${brand.site}`)++ ? seg('b-sub', brand.subText, to.sub, brand.name) + dot + seg('b-core', brand.core, to.core, brand.core === 'OpenVibe' ? 'OpenVibe Network' : brand.site) + join + seg('b-tld', brand.tldText, to.tld, `All of ${brand.site}`): seg('b-core', brand.core, to.core, brand.name) + join + seg('b-tld', brand.tldText, to.tld, brand.name);const mark = brand.icon? `<i class="fa-solid ${escapeAttr(brand.icon)}"></i>`@@@ -1,12 +1,12 @@{"name": "openvibe-shared",-- "version": "4.106.2",++ "version": "4.106.3","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-shared",-- "version": "4.106.2",++ "version": "4.106.3","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.106.2",++ "version": "4.106.3","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -33,12 +33,14 @@ function siteOf(id) {if (d) return { id, name: d.name, tagline: d.tagline };const m = JSON.parse(fs.readFileSync(path.join(SERVICES, `${id}.json`), 'utf8'));if (!m.site) throw new Error(`${id}: no site block`);-- return { id, name: m.site.name, tagline: m.site.tagline || '' };++ return { id, name: m.site.name, tagline: m.site.tagline || '', domains: m.domains || [] };}--/** The product's full name: "OpenVibe." + the site name, unless it already carries its own ("OpenRestream"). */--function productName(name) {-- return /^Open(Re)(.+)$/.test(name) ? name : `OpenVibe.${name}`;++/** The product's full name: "OpenVibe." + the site name, unless it already carries its own ("OpenRestream") or lives on++ * its own open.<tld> domain (open.shoes → "Open.Shoes"). */++function productName(name, domains = []) {++ if (/^Open(Re)(.+)$/.test(name)) return name;++ return /^open\.[a-z]+$/.test(domains[0] || '') ? `Open.${name}` : `OpenVibe.${name}`;}(async () => {@@@ -49,7 +51,7 @@ function productName(name) {for (const id of serviceIds()) {const site = siteOf(id);const variant = social.iconSite(id);-- sites[id] = { name: productName(site.name), description: site.tagline, icon: variant };++ sites[id] = { name: productName(site.name, site.domains), description: site.tagline, icon: variant };variants.add(variant);}for (const variant of [...variants].sort()) {@@@ -102,6 +102,10 @@ function pngSize(buf) {const openre = await (await fetch(base + serve.manifestUrl('openre'))).json();assert.strictEqual(openre.name, 'OpenRestream');assert.strictEqual(openre.short_name, 'OpenRestream');++ // A site on its own open.<tld> domain installs under that name (11-02: open.shoes was "OpenVibe.Shoes").++ const shoes = await (await fetch(base + serve.manifestUrl('shoes'))).json();++ assert.strictEqual(shoes.name, 'Open.Shoes');++ assert.strictEqual(shoes.short_name, 'Open.Shoes');assert.strictEqual((await fetch(base + '/shared/manifest/nope.webmanifest')).status, 404);assert.strictEqual((await fetch(base + '/shared/manifest/wiki.png')).status, 404);@@@ -108,6 +108,33 @@ function load(hostname, opts = {}) {navbar.init({ service: 'network' });assert.strictEqual(navbar.brand().name, 'OpenVibe.VIP');}++{++ // A site on its own open.<tld> domain is named for it (11-02: open.shoes read "OpenVibe.Shoes"), and its++ // segments lead to open.shoes, not openvibe.shoes. Shoes' own config (tld + label) keeps that.++ const { navbar } = load('open.shoes');++ navbar.init({ service: 'shoes', brand: { tld: 'shoes', tldLabel: 'Shoes' } });++ const b = navbar.brand();++ assert.strictEqual(b.name, 'Open.Shoes');++ assert.strictEqual(b.core, 'Open');++ assert.strictEqual(b.apex, 'https://open.shoes/');++ assert.strictEqual(b.variant, 'shoes');++}++{++ // Off the domain (localhost, previews) the config can say so itself.++ const { navbar } = load('localhost');++ navbar.init({ service: 'shoes', brand: { core: 'Open', tld: 'shoes', tldLabel: 'Shoes' } });++ assert.strictEqual(navbar.brand().name, 'Open.Shoes');++ assert.strictEqual(navbar.brand().apex, 'https://open.shoes/');++}++{++ // openvibe.<tld> and openre.stream keep their front doors.++ let { navbar } = load('openvibe.live');++ navbar.init({ service: 'live' });++ assert.strictEqual(navbar.brand().apex, 'https://openvibe.live/');++ ({ navbar } = load('openre.stream'));++ navbar.init({ service: 'openre' });++ assert.strictEqual(navbar.brand().apex, 'https://openre.stream/');++}// ── off-network hosts fall back to the service id ────────────{const { navbar } = load('localhost');
@@ -4,6 +4,14 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
@@ -4,6 +4,14 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
breaking change to any exported module, browser global or served file name is a new major.
breaking change to any exported module, browser global or served file name is a new major.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
+## 4.106.3 — 2026-11-02
+
+- **A site on its own open.<tld> domain is named for it.** open.shoes read "OpenVibe.Shoes" in the bar and its name
+ linked to openvibe.shoes (the owner's screenshot). The navbar now reads `<sub>.open.<tld>` hosts as Open.<Tld>, and a
+ config can say `brand: { core: 'Open' }` itself off the domain (localhost, previews). The brand carries its front door
+ as `apex` (open.shoes, openre.stream, openvibe.<tld>), and the name's links go there. The footer's fallback and the
+ installed app's name (`icons/sites.json`, from the manifest's first domain) say Open.Shoes too.
+
## 4.106.2 — 2026-11-02
## 4.106.2 — 2026-11-02
- **The site's name reads whole on a signed-in phone.** On openvibe.live a phone showed "Li…" beside the balances, the
- **The site's name reads whole on a signed-in phone.** On openvibe.live a phone showed "Li…" beside the balances, the
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
-**Status:** alpha, v4.106.2 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.106.3 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
consumer installs a tagged release and none keeps a vendored copy
consumer installs a tagged release and none keeps a vendored copy
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
the tag it pins.
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
"package": "openvibe-shared",
"package": "openvibe-shared",
- "version": "4.106.2",
+ "version": "4.106.3",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
@@ -188,6 +188,7 @@
@@ -188,6 +188,7 @@
let m = h.match(/^(?:(.+)\.)?openvibe\.([a-z]+)$/);
let m = h.match(/^(?:(.+)\.)?openvibe\.([a-z]+)$/);
if (m) { const sub = m[1] && m[1] !== 'www' ? m[1] : null; return [sub ? (sub.length <= 4 ? sub.toUpperCase() : cap(sub)) : null, 'OpenVibe', TLD_LABELS[m[2]] || cap(m[2])].filter(Boolean).join('.'); }
if (m) { const sub = m[1] && m[1] !== 'www' ? m[1] : null; return [sub ? (sub.length <= 4 ? sub.toUpperCase() : cap(sub)) : null, 'OpenVibe', TLD_LABELS[m[2]] || cap(m[2])].filter(Boolean).join('.'); }
if ((m = h.match(/^(?:(.+)\.)?openre\.stream$/))) return 'OpenRestream';
if ((m = h.match(/^(?:(.+)\.)?openre\.stream$/))) return 'OpenRestream';
+ if ((m = h.match(/^(?:(.+)\.)?open\.([a-z]+)$/))) return `Open.${TLD_LABELS[m[2]] || cap(m[2])}`;
const found = NETWORK.find(n => n.id === service);
const found = NETWORK.find(n => n.id === service);
if (service && TLD_LABELS[service]) return `OpenVibe.${TLD_LABELS[service]}`;
if (service && TLD_LABELS[service]) return `OpenVibe.${TLD_LABELS[service]}`;
if (found && found.name !== 'Account & themes' && found.name !== 'The whole network →') return `${cap(service)}.OpenVibe.Tools`;
if (found && found.name !== 'Account & themes' && found.name !== 'The whole network →') return `${cap(service)}.OpenVibe.Tools`;
@@ -200,7 +200,7 @@
@@ -200,7 +200,7 @@
"icon": "network"
"icon": "network"
},
},
"shoes": {
"shoes": {
- "name": "OpenVibe.Shoes",
+ "name": "Open.Shoes",
"description": "Every shoe, every market, the right price",
"description": "Every shoe, every market, the right price",
"icon": "network"
"icon": "network"
},
},
@@ -675,7 +675,7 @@
@@ -675,7 +675,7 @@
}
}
// ─── Brand from hostname ───────────────────────────────────
// ─── Brand from hostname ───────────────────────────────────
- // Every property is <sub?>.openvibe.<tld> (plus openre.stream). The navbar spells the
+ // Every property is <sub?>.openvibe.<tld> (plus openre.stream and open.<tld>). The navbar spells the
// whole name — Pastes.OpenVibe.Tools, not "Paste.OpenVibe" — because the subdomain and
// whole name — Pastes.OpenVibe.Tools, not "Paste.OpenVibe" — because the subdomain and
// the TLD are what tell a visitor where they are in the network.
// the TLD are what tell a visitor where they are in the network.
const TLD_LABELS = {
const TLD_LABELS = {
@@ -728,6 +728,8 @@
@@ -728,6 +728,8 @@
let m = host.match(/^(?:(.+)\.)?openvibe\.([a-z]+)$/);
let m = host.match(/^(?:(.+)\.)?openvibe\.([a-z]+)$/);
if (m) { sub = m[1] && m[1] !== 'www' ? m[1] : null; tld = m[2]; }
if (m) { sub = m[1] && m[1] !== 'www' ? m[1] : null; tld = m[2]; }
else if ((m = host.match(/^(?:(.+)\.)?openre\.stream$/))) { sub = m[1] && m[1] !== 'www' ? m[1] : null; core = 'OpenRe'; tld = 'stream'; joined = true; }
else if ((m = host.match(/^(?:(.+)\.)?openre\.stream$/))) { sub = m[1] && m[1] !== 'www' ? m[1] : null; core = 'OpenRe'; tld = 'stream'; joined = true; }
+ // A site on its own open.<tld> domain (open.shoes) is named for it: Open.Shoes, not OpenVibe.Shoes.
+ else if ((m = host.match(/^(?:(.+)\.)?open\.([a-z]+)$/))) { sub = m[1] && m[1] !== 'www' ? m[1] : null; core = 'Open'; tld = m[2]; }
// Off-network hosts (localhost, previews): fall back to the service id.
// Off-network hosts (localhost, previews): fall back to the service id.
if (!tld) { tld = SERVICE_TLD[_config.service] || (SERVICE_SUB[_config.service] ? 'tools' : 'network'); sub = SERVICE_SUB[_config.service] || null; }
if (!tld) { tld = SERVICE_TLD[_config.service] || (SERVICE_SUB[_config.service] ? 'tools' : 'network'); sub = SERVICE_SUB[_config.service] || null; }
// Legacy brandName ("Paste.OpenVibe", "OpenVibe.Live") still steers the segments.
// Legacy brandName ("Paste.OpenVibe", "OpenVibe.Live") still steers the segments.
@@ -739,18 +741,21 @@
@@ -739,18 +741,21 @@
}
}
if (b.sub !== undefined) sub = b.sub ? String(b.sub).toLowerCase() : null;
if (b.sub !== undefined) sub = b.sub ? String(b.sub).toLowerCase() : null;
if (b.tld) tld = String(b.tld).toLowerCase();
if (b.tld) tld = String(b.tld).toLowerCase();
+ if (b.core && !joined) core = String(b.core);
const subText = b.subLabel || (sub ? subLabel(sub) : null);
const subText = b.subLabel || (sub ? subLabel(sub) : null);
const tldText = b.tldLabel || (joined ? tld : TLD_LABELS[tld] || titleCase(tld));
const tldText = b.tldLabel || (joined ? tld : TLD_LABELS[tld] || titleCase(tld));
const site = joined ? `${core}${tldText}` : `${core}.${tldText}`;
const site = joined ? `${core}${tldText}` : `${core}.${tldText}`;
const name = b.name || [subText, site].filter(Boolean).join('.');
const name = b.name || [subText, site].filter(Boolean).join('.');
const icon = b.icon || _config.brandIcon || null;
const icon = b.icon || _config.brandIcon || null;
const variant = b.variant || (joined ? 'stream' : tld);
const variant = b.variant || (joined ? 'stream' : tld);
- return { sub, subText, core, tld, tldText, joined, site, name, short: subText || site, icon, variant, tag: b.tag || null, href: b.href || '/' };
+ // The site's own front door: openre.stream, open.shoes, openvibe.<tld>.
+ const apex = b.apex || (joined ? 'https://openre.stream/' : `https://${core.toLowerCase()}.${tld}/`);
+ return { sub, subText, core, tld, tldText, joined, site, name, short: subText || site, icon, variant, tag: b.tag || null, href: b.href || '/', apex };
}
}
/** Where each brand segment goes: sub → this tool, OpenVibe → the network, TLD → the site's apex. */
/** Where each brand segment goes: sub → this tool, OpenVibe → the network, TLD → the site's apex. */
function brandLinks(brand) {
function brandLinks(brand) {
- const apex = brand.joined ? 'https://openre.stream/' : `https://openvibe.${brand.tld}/`;
+ const apex = brand.apex || (brand.joined ? 'https://openre.stream/' : `https://openvibe.${brand.tld}/`);
if (!brand.subText) return { sub: brand.href, core: brand.href, tld: brand.href, apex };
if (!brand.subText) return { sub: brand.href, core: brand.href, tld: brand.href, apex };
return { sub: brand.href, core: brand.joined ? apex : 'https://openvibe.network/', tld: apex, apex };
return { sub: brand.href, core: brand.joined ? apex : 'https://openvibe.network/', tld: apex, apex };
}
}
@@ -769,7 +774,7 @@
@@ -769,7 +774,7 @@
const dot = '<span class="b-dot">.</span>';
const dot = '<span class="b-dot">.</span>';
const join = brand.joined ? '' : dot;
const join = brand.joined ? '' : dot;
const text = brand.subText
const text = brand.subText
- ? seg('b-sub', brand.subText, to.sub, brand.name) + dot + seg('b-core', brand.core, to.core, brand.joined ? brand.site : 'OpenVibe Network') + join + seg('b-tld', brand.tldText, to.tld, `All of ${brand.site}`)
+ ? seg('b-sub', brand.subText, to.sub, brand.name) + dot + seg('b-core', brand.core, to.core, brand.core === 'OpenVibe' ? 'OpenVibe Network' : brand.site) + join + seg('b-tld', brand.tldText, to.tld, `All of ${brand.site}`)
: seg('b-core', brand.core, to.core, brand.name) + join + seg('b-tld', brand.tldText, to.tld, brand.name);
: seg('b-core', brand.core, to.core, brand.name) + join + seg('b-tld', brand.tldText, to.tld, brand.name);
const mark = brand.icon
const mark = brand.icon
? `<i class="fa-solid ${escapeAttr(brand.icon)}"></i>`
? `<i class="fa-solid ${escapeAttr(brand.icon)}"></i>`
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.106.2",
+ "version": "4.106.3",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.106.2",
+ "version": "4.106.3",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.106.2",
+ "version": "4.106.3",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
@@ -33,12 +33,14 @@ function siteOf(id) {
@@ -33,12 +33,14 @@ function siteOf(id) {
if (d) return { id, name: d.name, tagline: d.tagline };
if (d) return { id, name: d.name, tagline: d.tagline };
const m = JSON.parse(fs.readFileSync(path.join(SERVICES, `${id}.json`), 'utf8'));
const m = JSON.parse(fs.readFileSync(path.join(SERVICES, `${id}.json`), 'utf8'));
if (!m.site) throw new Error(`${id}: no site block`);
if (!m.site) throw new Error(`${id}: no site block`);
- return { id, name: m.site.name, tagline: m.site.tagline || '' };
+ return { id, name: m.site.name, tagline: m.site.tagline || '', domains: m.domains || [] };
}
}
-/** The product's full name: "OpenVibe." + the site name, unless it already carries its own ("OpenRestream"). */
-function productName(name) {
- return /^Open(Re)(.+)$/.test(name) ? name : `OpenVibe.${name}`;
+/** The product's full name: "OpenVibe." + the site name, unless it already carries its own ("OpenRestream") or lives on
+ * its own open.<tld> domain (open.shoes → "Open.Shoes"). */
+function productName(name, domains = []) {
+ if (/^Open(Re)(.+)$/.test(name)) return name;
+ return /^open\.[a-z]+$/.test(domains[0] || '') ? `Open.${name}` : `OpenVibe.${name}`;
}
}
(async () => {
(async () => {
@@ -49,7 +51,7 @@ function productName(name) {
@@ -49,7 +51,7 @@ function productName(name) {
for (const id of serviceIds()) {
for (const id of serviceIds()) {
const site = siteOf(id);
const site = siteOf(id);
const variant = social.iconSite(id);
const variant = social.iconSite(id);
- sites[id] = { name: productName(site.name), description: site.tagline, icon: variant };
+ sites[id] = { name: productName(site.name, site.domains), description: site.tagline, icon: variant };
variants.add(variant);
variants.add(variant);
}
}
for (const variant of [...variants].sort()) {
for (const variant of [...variants].sort()) {
@@ -102,6 +102,10 @@ function pngSize(buf) {
@@ -102,6 +102,10 @@ function pngSize(buf) {
const openre = await (await fetch(base + serve.manifestUrl('openre'))).json();
const openre = await (await fetch(base + serve.manifestUrl('openre'))).json();
assert.strictEqual(openre.name, 'OpenRestream');
assert.strictEqual(openre.name, 'OpenRestream');
assert.strictEqual(openre.short_name, 'OpenRestream');
assert.strictEqual(openre.short_name, 'OpenRestream');
+ // A site on its own open.<tld> domain installs under that name (11-02: open.shoes was "OpenVibe.Shoes").
+ const shoes = await (await fetch(base + serve.manifestUrl('shoes'))).json();
+ assert.strictEqual(shoes.name, 'Open.Shoes');
+ assert.strictEqual(shoes.short_name, 'Open.Shoes');
assert.strictEqual((await fetch(base + '/shared/manifest/nope.webmanifest')).status, 404);
assert.strictEqual((await fetch(base + '/shared/manifest/nope.webmanifest')).status, 404);
assert.strictEqual((await fetch(base + '/shared/manifest/wiki.png')).status, 404);
assert.strictEqual((await fetch(base + '/shared/manifest/wiki.png')).status, 404);
@@ -108,6 +108,33 @@ function load(hostname, opts = {}) {
@@ -108,6 +108,33 @@ function load(hostname, opts = {}) {
navbar.init({ service: 'network' });
navbar.init({ service: 'network' });
assert.strictEqual(navbar.brand().name, 'OpenVibe.VIP');
assert.strictEqual(navbar.brand().name, 'OpenVibe.VIP');
}
}
+{
+ // A site on its own open.<tld> domain is named for it (11-02: open.shoes read "OpenVibe.Shoes"), and its
+ // segments lead to open.shoes, not openvibe.shoes. Shoes' own config (tld + label) keeps that.
+ const { navbar } = load('open.shoes');
+ navbar.init({ service: 'shoes', brand: { tld: 'shoes', tldLabel: 'Shoes' } });
+ const b = navbar.brand();
+ assert.strictEqual(b.name, 'Open.Shoes');
+ assert.strictEqual(b.core, 'Open');
+ assert.strictEqual(b.apex, 'https://open.shoes/');
+ assert.strictEqual(b.variant, 'shoes');
+}
+{
+ // Off the domain (localhost, previews) the config can say so itself.
+ const { navbar } = load('localhost');
+ navbar.init({ service: 'shoes', brand: { core: 'Open', tld: 'shoes', tldLabel: 'Shoes' } });
+ assert.strictEqual(navbar.brand().name, 'Open.Shoes');
+ assert.strictEqual(navbar.brand().apex, 'https://open.shoes/');
+}
+{
+ // openvibe.<tld> and openre.stream keep their front doors.
+ let { navbar } = load('openvibe.live');
+ navbar.init({ service: 'live' });
+ assert.strictEqual(navbar.brand().apex, 'https://openvibe.live/');
+ ({ navbar } = load('openre.stream'));
+ navbar.init({ service: 'openre' });
+ assert.strictEqual(navbar.brand().apex, 'https://openre.stream/');
+}
// ── off-network hosts fall back to the service id ────────────
// ── off-network hosts fall back to the service id ────────────
{
{
const { navbar } = load('localhost');
const { navbar } = load('localhost');