openvibers/openvibe.shared Commits
4.154.0: Float your agent's screen; Link shares are not first-party (#344)
Changes
@@@ -1,5 +1,16 @@# Changelog++## 4.154.0 — 2026-11-08++++- **Float your agent's screen** (the owner: overlays "usable for agents, Vibe chat, Codes"). The Dock's Agents tab++ gains **Float** beside Pause, Curtain, Mute and End: the agent's live screen (Live's agent embed, with its own++ renewing view ticket) moves into a floating card that follows the person around this site and on to other OpenVibe++ sites, and the Dock closes while they watch.++- **Link shares are not first-party** (ahead of Connect's shares moving to `<label>--<user>.openvibe.link`): a share++ is a person's own app, so `*--*.openvibe.link` and `*--*.openvibe.website` are never a card's source or a card's++ destination; only openvibe.link itself and connect.openvibe.link are.++- Tests: `test/overlay-chrome.test.js` (share hosts refused, Link's apex and Connect allowed).++## 4.153.0 — 2026-11-08- **Signed in to the network, signed in here — on phones too** (the owner: "Why am I not auto signed into some domains@@@ -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.153.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.154.0 (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.153.0",++ "version": "4.154.0","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)",@@@ -1,5 +1,5 @@// ═══════════════════════════════════════════════════════════════--// OpenVibe — the Dock's agent view (Dock plan 2026-10-30, K-D3; Agent Streams S1), Shared 4.120.0++// OpenVibe — the Dock's agent view (Dock plan 2026-10-30, K-D3; Agent Streams S1), Shared 4.120.0; Float 4.154.0//// The person's own agent desk stream at the top of the Dock's Agents tab. dock.js loads this file the first time that// tab opens:@@@ -31,6 +31,7 @@sound: '<path d="M4 9.5h4L13 5v14l-5-4.5H4z"/><path d="M17 9.5a4 4 0 0 1 0 5"/>',stop: '<rect x="6.5" y="6.5" width="11" height="11" rx="2.5"/>',open: '<path d="M14 4h6v6M20 4l-9 9M19 14v5a1.5 1.5 0 0 1-1.5 1.5h-12A1.5 1.5 0 0 1 4 19V7a1.5 1.5 0 0 1 1.5-1.5H10"/>',++ float: '<rect x="3.5" y="5" width="17" height="14" rx="2.5"/><rect x="11.5" y="11" width="7" height="6" rx="1.2" fill="currentColor" stroke="none"/>',};// The style goes into the document the view lives in: the page, or the Dock's own window when it is popped out.@@@ -200,6 +201,16 @@b.innerHTML = `${svg(G.stop)}End`;acts.append(b);}++ // 4.154.0: Float puts the agent's screen in a floating card (overlay.js) that stays on screen as the person++ // moves around this site and on to other OpenVibe sites, so the Dock can close while they watch.++ if (embed && !ended) {++ const b = h('button', null);++ b.type = 'button';++ b.dataset.act = 'float';++ b.innerHTML = `${svg(G.float)}Float`;++ b.title = 'Keep watching in a small window that follows you';++ acts.append(b);++ }if (agent.page_url && /^https:\/\/openvibe\.live\//.test(agent.page_url)) {const a = h('a');a.href = agent.page_url;@@@ -258,8 +269,19 @@else if (b.dataset.act === 'curtain') control('curtain', b);else if (b.dataset.act === 'mute') control('mute', b, { muted: !(agent && agent.voice_muted) });else if (b.dataset.act === 'end') control('end', b);++ else if (b.dataset.act === 'float') floatIt(b);});++ function floatIt(btn) {++ const embed = agent && typeof agent.embed_url === 'string' && EMBED.test(agent.embed_url) ? agent.embed_url : null;++ const nav = root.OpenVibeNavbar;++ if (!embed || !nav || typeof nav.overlay !== 'function') { btn.title = 'Floating is not available on this page.'; return; }++ const pageUrl = agent.page_url && /^https:\/\/openvibe\.live\//.test(agent.page_url) ? agent.page_url : null;++ nav.overlay({ id: 'agent', src: embed, title: (agent.task && agent.task.title) || agent.title || 'Your agent', live: agent.state === 'live', href: pageUrl || undefined })++ .then((card) => { if (card && root.OpenVibeDock && typeof root.OpenVibeDock.close === 'function') root.OpenVibeDock.close(); })++ .catch(() => { btn.title = 'That did not work. Try again.'; });++ }++refresh();return { refresh, destroy() { gone = true; clearTimeout(timer); if (lastFrame) URL.revokeObjectURL(lastFrame); container.innerHTML = ''; container.classList.remove('ovda'); } };}@@@ -100,7 +100,7 @@}// Each sibling's content hash (scripts/sibling-revs.js writes it), so a release gives each a new URL.-- const DOCK_REV = { 'dock-vibe.js': 'fa6d48d86c71', 'action-center.js': '125c20e01289', 'dock-timeline.js': 'ad21e7bb4306', 'dock-agent.js': '49938c0551cc', 'dock-timer.js': 'f5d9fee28824', 'dock-share.js': 'e83b2e27b3ef', 'dock-card.js': '2643c20b0016' };++ const DOCK_REV = { 'dock-vibe.js': 'fa6d48d86c71', 'action-center.js': '125c20e01289', 'dock-timeline.js': 'ad21e7bb4306', 'dock-agent.js': '8026ded2b4e5', 'dock-timer.js': 'f5d9fee28824', 'dock-share.js': 'e83b2e27b3ef', 'dock-card.js': '2643c20b0016' };function load(src, test) {return new Promise((ok) => {if (test()) return ok(true);@@@ -539,7 +539,7 @@const NAV_ICONS_REV = 'd9a4febbbad7'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)// END generated// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.-- const SIBLING_REV = { 'ov-mark.js': '1e91c9c0f347', 'ov-glyphs.js': '1a0d8ce6909f', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '068ce1b1c4d5', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': 'fa4ded2928f4', 'ov-share.css': 'cbd18e6ce991', 'search.js': 'c036db4ac93a', 'dock.js': 'ef5c198edf47', 'presence.js': '1b675c37d888', 'panels.js': '7d8267ca8035', 'notification-ui.js': 'a2d37237ba0b', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': 'd3e93fe9313d' };++ const SIBLING_REV = { 'ov-mark.js': '1e91c9c0f347', 'ov-glyphs.js': '1a0d8ce6909f', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '068ce1b1c4d5', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': 'fa4ded2928f4', 'ov-share.css': 'cbd18e6ce991', 'search.js': 'c036db4ac93a', 'dock.js': '4dacf614797b', 'presence.js': '1b675c37d888', 'panels.js': '7d8267ca8035', 'notification-ui.js': 'a2d37237ba0b', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': '25960cf0c075' };// The navbar's icons are filled paths (no Font Awesome CSS needed): the ones above ship here, the rest// (nav-icons.js) load after first paint when a link, chip or menu row names one, into an empty <svg> of their// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.@@@ -30,7 +30,8 @@if (!root || root.OVOverlay || typeof document === 'undefined') return;// Every source a card may frame, and every site a card may follow the person to.-- const FIRST_PARTY = /^(?:[a-z0-9-]+\.)*openvibe\.(?:live|chat|actor|codes|network|games|video|design|report|my|link|computer|tools|services|space|community|news|wiki|blog|mail|email|events|help|quest|rent|work|food|host|deals|coupons|trade|tips|vip|zone|run|search|today|surf|meme|vision|exchange|fun|cam|casino|tours|shoes|connect|media|pics|download|domains|studio|party|locker|watch|bot|inventory)$/;++ // Link shares are user-run apps; only its apex and Connect control panel are first-party.++ const FIRST_PARTY = /^(?:(?:connect\.)?openvibe\.link|(?:[a-z0-9-]+\.)*openvibe\.(?:live|chat|actor|codes|network|games|video|design|report|my|computer|tools|services|space|community|news|wiki|blog|mail|email|events|help|quest|rent|work|food|host|deals|coupons|trade|tips|vip|zone|run|search|today|surf|meme|vision|exchange|fun|cam|casino|tours|shoes|connect|media|pics|download|domains|studio|party|locker|watch|bot|inventory))$/;const KEY = 'ov-overlay';const MAX_CARDS = 3;const cards = new Map(); // id → card@@@ -45,6 +46,7 @@function safeSrc(u) {try {const url = new URL(String(u || ''), root.location.href);++ if (/--[^.]+\.openvibe\.(?:link|website)$/.test(url.hostname)) return null;if (url.protocol !== 'https:' && !(url.origin === root.location.origin && /^https?:$/.test(url.protocol))) return null;if (url.origin !== root.location.origin && !FIRST_PARTY.test(url.hostname)) return null;return url.href;@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.153.0",++ "version": "4.154.0","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -371,7 +371,7 @@ async function drag(p, from, to) {assert.strictEqual(src, view().embed_url, 'the embed Codes answered, with the ticket in its address');assert.strictEqual(await p.evaluate("document.querySelector('.ovda-line').textContent"), 'Fix the flaky upload test', 'the task, one line');assert.strictEqual(await p.evaluate("document.querySelector('.ovda-meta').textContent"), 'Live · 2 watching · worked 12 min');-- assert.deepStrictEqual(await p.evaluate("[...document.querySelectorAll('.ovda-acts button')].map((b) => b.dataset.act)"), ['pause', 'curtain', 'mute', 'end'], 'the owner\'s four controls (S5)');++ assert.deepStrictEqual(await p.evaluate("[...document.querySelectorAll('.ovda-acts button')].map((b) => b.dataset.act)"), ['pause', 'curtain', 'mute', 'end', 'float'], 'the owner\'s four controls (S5), and Float while the screen plays (4.154.0)');assert.strictEqual(await p.evaluate("document.querySelector('.ovda').classList.contains('on')"), true, 'the view is shown above the list');// Curtain now: Live is asked, and the line says so.await p.evaluate("document.querySelector('.ovda-acts button[data-act=curtain]').click()");@@@ -62,7 +62,7 @@ const RECT = `(() => { const c = document.querySelector('.ovo-card'); if (!c) re// Its chat opens as a sheet above the app bar on a phone.await p.evaluate(`document.querySelector('[data-ovo="side"]').click(), true`);-- await wait(400);++ assert.ok(await until(p, `getComputedStyle(document.querySelector('.ovo-side')).transform === 'none'`), 'the sheet has finished opening');const side = await p.evaluate(`(() => { const s = document.querySelector('.ovo-side'); const r = s.getBoundingClientRect(); return { open: s.classList.contains('is-open'), bottom: Math.round(r.bottom), src: s.querySelector('iframe').getAttribute('src') }; })()`);assert.deepStrictEqual(side, { open: true, bottom: 844 - 58 - 10, src: `${base}/frame-chat` }, 'the chat sheet, above the app bar');await p.evaluate(`document.querySelector('[data-ovo="side"]').click(), true`);@@@ -96,6 +96,11 @@ const RECT = `(() => { const c = document.querySelector('.ovo-card'); if (!c) reawait wait(200);assert.strictEqual(await e.evaluate(`document.querySelectorAll('.ovo-card').length`), 0, 'a foreign source is refused');assert.strictEqual(await e.evaluate(`OVOverlay.open({ src: 'javascript:alert(1)' })`), null);++ for (const host of ['portal--alex.openvibe.link', 'portal--alex.openvibe.website', 'nested.portal--alex.openvibe.link', 'unknown.openvibe.link']) {++ assert.strictEqual(await e.evaluate(`OVOverlay.safeSrc('https://${host}/')`), null, host);++ assert.strictEqual(await p.evaluate(`OVOverlay.handoffUrl('https://${host}/')`), `https://${host}/`, 'no card carried to a share');++ }++ for (const host of ['openvibe.link', 'connect.openvibe.link']) assert.ok(await e.evaluate(`OVOverlay.safeSrc('https://${host}/')`), host);assert.strictEqual(await e.evaluate(`OVOverlay.open({ src: 'http://openvibe.live/embed/x' })`), null, 'plain http to another site is refused');assert.ok(await e.evaluate(`!!OVOverlay.safeSrc('https://openvibe.live/embed/maticus')`), "Live's embed is first-party");console.log('overlay-chrome: ok');
@@ -1,5 +1,16 @@
@@ -1,5 +1,16 @@
# Changelog
# Changelog
+## 4.154.0 — 2026-11-08
+
+- **Float your agent's screen** (the owner: overlays "usable for agents, Vibe chat, Codes"). The Dock's Agents tab
+ gains **Float** beside Pause, Curtain, Mute and End: the agent's live screen (Live's agent embed, with its own
+ renewing view ticket) moves into a floating card that follows the person around this site and on to other OpenVibe
+ sites, and the Dock closes while they watch.
+- **Link shares are not first-party** (ahead of Connect's shares moving to `<label>--<user>.openvibe.link`): a share
+ is a person's own app, so `*--*.openvibe.link` and `*--*.openvibe.website` are never a card's source or a card's
+ destination; only openvibe.link itself and connect.openvibe.link are.
+- Tests: `test/overlay-chrome.test.js` (share hosts refused, Link's apex and Connect allowed).
+
## 4.153.0 — 2026-11-08
## 4.153.0 — 2026-11-08
- **Signed in to the network, signed in here — on phones too** (the owner: "Why am I not auto signed into some domains
- **Signed in to the network, signed in here — on phones too** (the owner: "Why am I not auto signed into some domains
@@ -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.153.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.154.0 (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.153.0",
+ "version": "4.154.0",
"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)",
@@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
// ═══════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════
-// OpenVibe — the Dock's agent view (Dock plan 2026-10-30, K-D3; Agent Streams S1), Shared 4.120.0
+// OpenVibe — the Dock's agent view (Dock plan 2026-10-30, K-D3; Agent Streams S1), Shared 4.120.0; Float 4.154.0
//
//
// The person's own agent desk stream at the top of the Dock's Agents tab. dock.js loads this file the first time that
// The person's own agent desk stream at the top of the Dock's Agents tab. dock.js loads this file the first time that
// tab opens:
// tab opens:
@@ -31,6 +31,7 @@
@@ -31,6 +31,7 @@
sound: '<path d="M4 9.5h4L13 5v14l-5-4.5H4z"/><path d="M17 9.5a4 4 0 0 1 0 5"/>',
sound: '<path d="M4 9.5h4L13 5v14l-5-4.5H4z"/><path d="M17 9.5a4 4 0 0 1 0 5"/>',
stop: '<rect x="6.5" y="6.5" width="11" height="11" rx="2.5"/>',
stop: '<rect x="6.5" y="6.5" width="11" height="11" rx="2.5"/>',
open: '<path d="M14 4h6v6M20 4l-9 9M19 14v5a1.5 1.5 0 0 1-1.5 1.5h-12A1.5 1.5 0 0 1 4 19V7a1.5 1.5 0 0 1 1.5-1.5H10"/>',
open: '<path d="M14 4h6v6M20 4l-9 9M19 14v5a1.5 1.5 0 0 1-1.5 1.5h-12A1.5 1.5 0 0 1 4 19V7a1.5 1.5 0 0 1 1.5-1.5H10"/>',
+ float: '<rect x="3.5" y="5" width="17" height="14" rx="2.5"/><rect x="11.5" y="11" width="7" height="6" rx="1.2" fill="currentColor" stroke="none"/>',
};
};
// The style goes into the document the view lives in: the page, or the Dock's own window when it is popped out.
// The style goes into the document the view lives in: the page, or the Dock's own window when it is popped out.
@@ -200,6 +201,16 @@
@@ -200,6 +201,16 @@
b.innerHTML = `${svg(G.stop)}End`;
b.innerHTML = `${svg(G.stop)}End`;
acts.append(b);
acts.append(b);
}
}
+ // 4.154.0: Float puts the agent's screen in a floating card (overlay.js) that stays on screen as the person
+ // moves around this site and on to other OpenVibe sites, so the Dock can close while they watch.
+ if (embed && !ended) {
+ const b = h('button', null);
+ b.type = 'button';
+ b.dataset.act = 'float';
+ b.innerHTML = `${svg(G.float)}Float`;
+ b.title = 'Keep watching in a small window that follows you';
+ acts.append(b);
+ }
if (agent.page_url && /^https:\/\/openvibe\.live\//.test(agent.page_url)) {
if (agent.page_url && /^https:\/\/openvibe\.live\//.test(agent.page_url)) {
const a = h('a');
const a = h('a');
a.href = agent.page_url;
a.href = agent.page_url;
@@ -258,8 +269,19 @@
@@ -258,8 +269,19 @@
else if (b.dataset.act === 'curtain') control('curtain', b);
else if (b.dataset.act === 'curtain') control('curtain', b);
else if (b.dataset.act === 'mute') control('mute', b, { muted: !(agent && agent.voice_muted) });
else if (b.dataset.act === 'mute') control('mute', b, { muted: !(agent && agent.voice_muted) });
else if (b.dataset.act === 'end') control('end', b);
else if (b.dataset.act === 'end') control('end', b);
+ else if (b.dataset.act === 'float') floatIt(b);
});
});
+ function floatIt(btn) {
+ const embed = agent && typeof agent.embed_url === 'string' && EMBED.test(agent.embed_url) ? agent.embed_url : null;
+ const nav = root.OpenVibeNavbar;
+ if (!embed || !nav || typeof nav.overlay !== 'function') { btn.title = 'Floating is not available on this page.'; return; }
+ const pageUrl = agent.page_url && /^https:\/\/openvibe\.live\//.test(agent.page_url) ? agent.page_url : null;
+ nav.overlay({ id: 'agent', src: embed, title: (agent.task && agent.task.title) || agent.title || 'Your agent', live: agent.state === 'live', href: pageUrl || undefined })
+ .then((card) => { if (card && root.OpenVibeDock && typeof root.OpenVibeDock.close === 'function') root.OpenVibeDock.close(); })
+ .catch(() => { btn.title = 'That did not work. Try again.'; });
+ }
+
refresh();
refresh();
return { refresh, destroy() { gone = true; clearTimeout(timer); if (lastFrame) URL.revokeObjectURL(lastFrame); container.innerHTML = ''; container.classList.remove('ovda'); } };
return { refresh, destroy() { gone = true; clearTimeout(timer); if (lastFrame) URL.revokeObjectURL(lastFrame); container.innerHTML = ''; container.classList.remove('ovda'); } };
}
}
@@ -100,7 +100,7 @@
@@ -100,7 +100,7 @@
}
}
// Each sibling's content hash (scripts/sibling-revs.js writes it), so a release gives each a new URL.
// Each sibling's content hash (scripts/sibling-revs.js writes it), so a release gives each a new URL.
- const DOCK_REV = { 'dock-vibe.js': 'fa6d48d86c71', 'action-center.js': '125c20e01289', 'dock-timeline.js': 'ad21e7bb4306', 'dock-agent.js': '49938c0551cc', 'dock-timer.js': 'f5d9fee28824', 'dock-share.js': 'e83b2e27b3ef', 'dock-card.js': '2643c20b0016' };
+ const DOCK_REV = { 'dock-vibe.js': 'fa6d48d86c71', 'action-center.js': '125c20e01289', 'dock-timeline.js': 'ad21e7bb4306', 'dock-agent.js': '8026ded2b4e5', 'dock-timer.js': 'f5d9fee28824', 'dock-share.js': 'e83b2e27b3ef', 'dock-card.js': '2643c20b0016' };
function load(src, test) {
function load(src, test) {
return new Promise((ok) => {
return new Promise((ok) => {
if (test()) return ok(true);
if (test()) return ok(true);
@@ -539,7 +539,7 @@
@@ -539,7 +539,7 @@
const NAV_ICONS_REV = 'd9a4febbbad7'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
const NAV_ICONS_REV = 'd9a4febbbad7'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
// END generated
// END generated
// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.
// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.
- const SIBLING_REV = { 'ov-mark.js': '1e91c9c0f347', 'ov-glyphs.js': '1a0d8ce6909f', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '068ce1b1c4d5', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': 'fa4ded2928f4', 'ov-share.css': 'cbd18e6ce991', 'search.js': 'c036db4ac93a', 'dock.js': 'ef5c198edf47', 'presence.js': '1b675c37d888', 'panels.js': '7d8267ca8035', 'notification-ui.js': 'a2d37237ba0b', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': 'd3e93fe9313d' };
+ const SIBLING_REV = { 'ov-mark.js': '1e91c9c0f347', 'ov-glyphs.js': '1a0d8ce6909f', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '068ce1b1c4d5', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': 'fa4ded2928f4', 'ov-share.css': 'cbd18e6ce991', 'search.js': 'c036db4ac93a', 'dock.js': '4dacf614797b', 'presence.js': '1b675c37d888', 'panels.js': '7d8267ca8035', 'notification-ui.js': 'a2d37237ba0b', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': '25960cf0c075' };
// The navbar's icons are filled paths (no Font Awesome CSS needed): the ones above ship here, the rest
// The navbar's icons are filled paths (no Font Awesome CSS needed): the ones above ship here, the rest
// (nav-icons.js) load after first paint when a link, chip or menu row names one, into an empty <svg> of their
// (nav-icons.js) load after first paint when a link, chip or menu row names one, into an empty <svg> of their
// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.
// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.
@@ -30,7 +30,8 @@
@@ -30,7 +30,8 @@
if (!root || root.OVOverlay || typeof document === 'undefined') return;
if (!root || root.OVOverlay || typeof document === 'undefined') return;
// Every source a card may frame, and every site a card may follow the person to.
// Every source a card may frame, and every site a card may follow the person to.
- const FIRST_PARTY = /^(?:[a-z0-9-]+\.)*openvibe\.(?:live|chat|actor|codes|network|games|video|design|report|my|link|computer|tools|services|space|community|news|wiki|blog|mail|email|events|help|quest|rent|work|food|host|deals|coupons|trade|tips|vip|zone|run|search|today|surf|meme|vision|exchange|fun|cam|casino|tours|shoes|connect|media|pics|download|domains|studio|party|locker|watch|bot|inventory)$/;
+ // Link shares are user-run apps; only its apex and Connect control panel are first-party.
+ const FIRST_PARTY = /^(?:(?:connect\.)?openvibe\.link|(?:[a-z0-9-]+\.)*openvibe\.(?:live|chat|actor|codes|network|games|video|design|report|my|computer|tools|services|space|community|news|wiki|blog|mail|email|events|help|quest|rent|work|food|host|deals|coupons|trade|tips|vip|zone|run|search|today|surf|meme|vision|exchange|fun|cam|casino|tours|shoes|connect|media|pics|download|domains|studio|party|locker|watch|bot|inventory))$/;
const KEY = 'ov-overlay';
const KEY = 'ov-overlay';
const MAX_CARDS = 3;
const MAX_CARDS = 3;
const cards = new Map(); // id → card
const cards = new Map(); // id → card
@@ -45,6 +46,7 @@
@@ -45,6 +46,7 @@
function safeSrc(u) {
function safeSrc(u) {
try {
try {
const url = new URL(String(u || ''), root.location.href);
const url = new URL(String(u || ''), root.location.href);
+ if (/--[^.]+\.openvibe\.(?:link|website)$/.test(url.hostname)) return null;
if (url.protocol !== 'https:' && !(url.origin === root.location.origin && /^https?:$/.test(url.protocol))) return null;
if (url.protocol !== 'https:' && !(url.origin === root.location.origin && /^https?:$/.test(url.protocol))) return null;
if (url.origin !== root.location.origin && !FIRST_PARTY.test(url.hostname)) return null;
if (url.origin !== root.location.origin && !FIRST_PARTY.test(url.hostname)) return null;
return url.href;
return url.href;
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.153.0",
+ "version": "4.154.0",
"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": {
@@ -371,7 +371,7 @@ async function drag(p, from, to) {
@@ -371,7 +371,7 @@ async function drag(p, from, to) {
assert.strictEqual(src, view().embed_url, 'the embed Codes answered, with the ticket in its address');
assert.strictEqual(src, view().embed_url, 'the embed Codes answered, with the ticket in its address');
assert.strictEqual(await p.evaluate("document.querySelector('.ovda-line').textContent"), 'Fix the flaky upload test', 'the task, one line');
assert.strictEqual(await p.evaluate("document.querySelector('.ovda-line').textContent"), 'Fix the flaky upload test', 'the task, one line');
assert.strictEqual(await p.evaluate("document.querySelector('.ovda-meta').textContent"), 'Live · 2 watching · worked 12 min');
assert.strictEqual(await p.evaluate("document.querySelector('.ovda-meta').textContent"), 'Live · 2 watching · worked 12 min');
- assert.deepStrictEqual(await p.evaluate("[...document.querySelectorAll('.ovda-acts button')].map((b) => b.dataset.act)"), ['pause', 'curtain', 'mute', 'end'], 'the owner\'s four controls (S5)');
+ assert.deepStrictEqual(await p.evaluate("[...document.querySelectorAll('.ovda-acts button')].map((b) => b.dataset.act)"), ['pause', 'curtain', 'mute', 'end', 'float'], 'the owner\'s four controls (S5), and Float while the screen plays (4.154.0)');
assert.strictEqual(await p.evaluate("document.querySelector('.ovda').classList.contains('on')"), true, 'the view is shown above the list');
assert.strictEqual(await p.evaluate("document.querySelector('.ovda').classList.contains('on')"), true, 'the view is shown above the list');
// Curtain now: Live is asked, and the line says so.
// Curtain now: Live is asked, and the line says so.
await p.evaluate("document.querySelector('.ovda-acts button[data-act=curtain]').click()");
await p.evaluate("document.querySelector('.ovda-acts button[data-act=curtain]').click()");
@@ -62,7 +62,7 @@ const RECT = `(() => { const c = document.querySelector('.ovo-card'); if (!c) re
@@ -62,7 +62,7 @@ const RECT = `(() => { const c = document.querySelector('.ovo-card'); if (!c) re
// Its chat opens as a sheet above the app bar on a phone.
// Its chat opens as a sheet above the app bar on a phone.
await p.evaluate(`document.querySelector('[data-ovo="side"]').click(), true`);
await p.evaluate(`document.querySelector('[data-ovo="side"]').click(), true`);
- await wait(400);
+ assert.ok(await until(p, `getComputedStyle(document.querySelector('.ovo-side')).transform === 'none'`), 'the sheet has finished opening');
const side = await p.evaluate(`(() => { const s = document.querySelector('.ovo-side'); const r = s.getBoundingClientRect(); return { open: s.classList.contains('is-open'), bottom: Math.round(r.bottom), src: s.querySelector('iframe').getAttribute('src') }; })()`);
const side = await p.evaluate(`(() => { const s = document.querySelector('.ovo-side'); const r = s.getBoundingClientRect(); return { open: s.classList.contains('is-open'), bottom: Math.round(r.bottom), src: s.querySelector('iframe').getAttribute('src') }; })()`);
assert.deepStrictEqual(side, { open: true, bottom: 844 - 58 - 10, src: `${base}/frame-chat` }, 'the chat sheet, above the app bar');
assert.deepStrictEqual(side, { open: true, bottom: 844 - 58 - 10, src: `${base}/frame-chat` }, 'the chat sheet, above the app bar');
await p.evaluate(`document.querySelector('[data-ovo="side"]').click(), true`);
await p.evaluate(`document.querySelector('[data-ovo="side"]').click(), true`);
@@ -96,6 +96,11 @@ const RECT = `(() => { const c = document.querySelector('.ovo-card'); if (!c) re
@@ -96,6 +96,11 @@ const RECT = `(() => { const c = document.querySelector('.ovo-card'); if (!c) re
await wait(200);
await wait(200);
assert.strictEqual(await e.evaluate(`document.querySelectorAll('.ovo-card').length`), 0, 'a foreign source is refused');
assert.strictEqual(await e.evaluate(`document.querySelectorAll('.ovo-card').length`), 0, 'a foreign source is refused');
assert.strictEqual(await e.evaluate(`OVOverlay.open({ src: 'javascript:alert(1)' })`), null);
assert.strictEqual(await e.evaluate(`OVOverlay.open({ src: 'javascript:alert(1)' })`), null);
+ for (const host of ['portal--alex.openvibe.link', 'portal--alex.openvibe.website', 'nested.portal--alex.openvibe.link', 'unknown.openvibe.link']) {
+ assert.strictEqual(await e.evaluate(`OVOverlay.safeSrc('https://${host}/')`), null, host);
+ assert.strictEqual(await p.evaluate(`OVOverlay.handoffUrl('https://${host}/')`), `https://${host}/`, 'no card carried to a share');
+ }
+ for (const host of ['openvibe.link', 'connect.openvibe.link']) assert.ok(await e.evaluate(`OVOverlay.safeSrc('https://${host}/')`), host);
assert.strictEqual(await e.evaluate(`OVOverlay.open({ src: 'http://openvibe.live/embed/x' })`), null, 'plain http to another site is refused');
assert.strictEqual(await e.evaluate(`OVOverlay.open({ src: 'http://openvibe.live/embed/x' })`), null, 'plain http to another site is refused');
assert.ok(await e.evaluate(`!!OVOverlay.safeSrc('https://openvibe.live/embed/maticus')`), "Live's embed is first-party");
assert.ok(await e.evaluate(`!!OVOverlay.safeSrc('https://openvibe.live/embed/maticus')`), "Live's embed is first-party");
console.log('overlay-chrome: ok');
console.log('overlay-chrome: ok');