openvibers/openvibe.chat Commits

A live presence link opens the channel, not a section under it (#491)

OP OpenVibers committed 4 days ago · 698abf76d3 · 3 files, +41 −2

Changes

ddiff --git a/public/web/chat.js b/public/web/chat.js
iindex 734aab6..e32b9cf 100644
---- a/public/web/chat.js
++++ b/public/web/chat.js
@@@ -67,9 +67,19 @@
};
function presenceState(p) { if (!p) return null; var s = p.live ? 'live' : String(p.state || ''); return PRESENCE[s] ? s : null; }
function presenceLine(p) { var s = presenceState(p); return s ? PRESENCE[s].label + (p.status_text ? ' \u00b7 ' + String(p.status_text).slice(0, 80) : '') : ''; }
++ // A channel link never carries a section: openvibe.live/@name#about is the channel page (ask 285).
++ function channelHref(href) {
++ if (typeof href !== 'string') return href;
++ var u; try { u = new URL(href, 'https://openvibe.live'); } catch (_) { return href; }
++ if (u.origin !== 'https://openvibe.live' || !/^\/@[A-Za-z0-9_]{3,24}\/?$/.test(u.pathname)) return href;
++ var clean = '/@' + u.pathname.slice(2).replace(/\/$/, '');
++ return /^https:\/\//i.test(href) ? 'https://openvibe.live' + clean : clean;
++ }
function presenceHref(p) {
var href = p && (p.channel_url || p.url);
-- return typeof href === 'string' && (/^https:\/\//i.test(href) || /^\/(?!\/)/.test(href)) ? href : null;
++ if (typeof href !== 'string') return null;
++ href = channelHref(href);
++ return (/^https:\/\//i.test(href) || /^\/(?!\/)/.test(href)) ? href : null;
}
function setPresenceLine(box, p, username) {
if (!box) return;
ddiff --git a/server/web/ui.js b/server/web/ui.js
iindex ab36360..c01d425 100644
---- a/server/web/ui.js
++++ b/server/web/ui.js
@@@ -66,12 +66,26 @@ function presenceOrder(presence) {
const state = presenceState(presence);
return state === 'online' || state === 'live' ? 0 : state === 'away' || state === 'dnd' ? 1 : 2;
}
++/**
++ * A channel link never carries a section: openvibe.live/@name#about is the channel page, and the address a live
++ * presence link hands over is /@name (ask 285). A relative /@name stays relative; any other link is untouched.
++ */
++function channelHref(href) {
++ if (typeof href !== 'string') return href;
++ let u;
++ try { u = new URL(href, 'https://openvibe.live'); } catch { return href; }
++ if (u.origin !== 'https://openvibe.live' || !/^\/@[A-Za-z0-9_]{3,24}\/?$/.test(u.pathname)) return href;
++ const clean = `/@${u.pathname.slice(2).replace(/\/$/, '')}`;
++ return /^https:\/\//i.test(href) ? `https://openvibe.live${clean}` : clean;
++}
++
/** "Online", "Live now" and — when there is one — " · their status line". Nothing for offline. */
function presenceLine(presence, { liveHref = null } = {}) {
const state = presenceState(presence);
if (!state) return '';
const label = esc(PRESENCE[state].label);
-- const href = typeof liveHref === 'string' && (/^https:\/\//i.test(liveHref) || /^\/(?!\/)/.test(liveHref)) ? liveHref : null;
++ const cleaned = channelHref(liveHref);
++ const href = typeof cleaned === 'string' && (/^https:\/\//i.test(cleaned) || /^\/(?!\/)/.test(cleaned)) ? cleaned : null;
const shown = state === 'live' && href ? `<a href="${esc(href)}">${label}</a>` : label;
return `${shown}${presence.status_text ? ` · ${esc(String(presence.status_text).slice(0, 80))}` : ''}`;
}
ddiff --git a/test/ui.test.js b/test/ui.test.js
iindex 2ce3ee6..d8dc98b 100644
---- a/test/ui.test.js
++++ b/test/ui.test.js
@@@ -41,6 +41,10 @@ t('the state line and the member-list order', () => {
assert.strictEqual(ui.presenceLine({ state: 'dnd' }), 'Do not disturb');
assert.strictEqual(ui.presenceLine({ state: 'online', live: true }), 'Live now');
assert.strictEqual(ui.presenceLine({ state: 'online', live: true }, { liveHref: 'https://openvibe.live/@alice' }), '<a href="https://openvibe.live/@alice">Live now</a>');
++ assert.strictEqual(ui.presenceLine({ state: 'online', live: true }, { liveHref: 'https://openvibe.live/@alice#about' }), '<a href="https://openvibe.live/@alice">Live now</a>', 'a section is not the channel');
++ assert.strictEqual(ui.presenceLine({ state: 'online', live: true }, { liveHref: 'https://openvibe.live/@alice?section=videos' }), '<a href="https://openvibe.live/@alice">Live now</a>', 'nor a section query');
++ assert.strictEqual(ui.presenceLine({ state: 'online', live: true }, { liveHref: '/@alice#about' }), '<a href="/@alice">Live now</a>', 'a relative channel link stays relative');
++ assert.strictEqual(ui.presenceLine({ state: 'online', live: true }, { liveHref: 'https://openvibe.live/vod/5#t=10' }), '<a href="https://openvibe.live/vod/5#t=10">Live now</a>', 'other Live pages keep their fragment');
assert.strictEqual(ui.presenceLine({ state: 'online' }, { liveHref: 'javascript:alert(1)' }), 'Online');
assert.strictEqual(ui.presenceLine({ state: 'offline' }), '');
assert.strictEqual(ui.presenceLine({ state: 'invisible' }), '');
@@@ -50,6 +54,17 @@ t('the state line and the member-list order', () => {
);
});
++t('the client draws the same section-free live channel link', () => {
++ const chat = fs.readFileSync(path.join(__dirname, '..', 'public', 'web', 'chat.js'), 'utf8');
++ const fn = /function channelHref\(href\) \{[\s\S]*?\n \}/.exec(chat);
++ assert.ok(fn, 'channelHref is in public/web/chat.js');
++ const run = new Function('URL', `${fn[0]}; return channelHref;`)(URL);
++ assert.strictEqual(run('https://openvibe.live/@alice#about'), 'https://openvibe.live/@alice');
++ assert.strictEqual(run('https://openvibe.live/@alice?section=videos'), 'https://openvibe.live/@alice');
++ assert.strictEqual(run('/@alice#clips'), '/@alice');
++ assert.strictEqual(run('https://openvibe.live/vod/5#t=10'), 'https://openvibe.live/vod/5#t=10');
++});
++
t('the dot CSS copies Shared\'s .ovp-dot', () => {
const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'web', 'chat.css'), 'utf8');
for (const rule of [