openvibers/openvibe.live Commits

Vibes, OpenCoins and the DM panel load where they are used, not on every page (#682)

OP OpenVibers committed 6 days ago · 4b0a72dbf3 · 5 files, +89 −11

vibes.js and opencoins.js become features.json → money: the channel, the broadcast desk and the dashboard bring it,
its inline handlers are stubs, and showModal() fetches it before a money modal on any other page (the templates for
donate, buy, cash out and add-reward are now built only when asked for; the sign-in modal used to call all four).
messenger.js and messenger.css become → messenger, loaded once someone is signed in; the navbar's Messages waits for
it. 31 KB of script and 6 KB of CSS leave most visits (11-07: "make things faster and more optimized").

Changes

ddiff --git a/public/features.json b/public/features.json
iindex 9f8c48a7..da5c2adc 100644
---- a/public/features.json
++++ b/public/features.json
@@@ -136,6 +136,7 @@
"/css/features/agent-stream.css"
],
"deps": [
++ "money",
"player",
"context",
"chatExtras",
@@@ -237,6 +238,7 @@
"/css/features/broadcast.css"
],
"deps": [
++ "money",
"chatExtras",
"guide",
"aiViewers",
@@@ -262,12 +264,38 @@
"captureScreenshot"
]
},
++ "money": {
++ "js": [
++ "/js/vibes.js",
++ "/js/opencoins.js"
++ ],
++ "stubs": [
++ "openSubscribeForCurrentStream",
++ "openvibeSubscribeModal",
++ "showSubscribeModal",
++ "toggleRewardsPanel",
++ "rewardClick",
++ "saveDashPointsConfig"
++ ]
++ },
++ "messenger": {
++ "css": [
++ "/css/messenger.css"
++ ],
++ "js": [
++ "/js/messenger.js"
++ ],
++ "stubs": [
++ "openMessengerDm"
++ ]
++ },
"dashboard": {
"fragment": "dashboard",
"css": [
"/css/features/dashboard.css"
],
"deps": [
++ "money",
"pastes",
"aiViewers"
],
ddiff --git a/public/index.html b/public/index.html
iindex b92567ef..8ea43be1 100644
---- a/public/index.html
++++ b/public/index.html
@@@ -99,7 +99,7 @@
.ov-route-error>div{flex:1;min-width:180px;display:grid;gap:2px}
.ov-route-error span{color:var(--text-muted,#8b93ad);font-size:.9rem}
@media (prefers-reduced-motion:reduce){.ov-route-skel span{animation:none}}
-- /* Widgets their scripts build before cosmetics.css / messenger.css land (both load without blocking): closed, they
++ /* Widgets their scripts build before cosmetics.css / messenger.css land (neither blocks): closed, they
stay out of the page instead of painting unstyled under the footer, and the toggle sits where it will. */
.cosmetics-modal-overlay:not(.active),.messenger-panel:not(.open){display:none}
.messenger-toggle{position:fixed;bottom:20px;left:20px;width:52px;height:52px;border:0;border-radius:50%;background:var(--accent-strong,#3472d8);color:var(--on-accent-strong,#fff)}
@@@ -121,13 +121,11 @@
for it there, and a page without chat does not download it. -->
<link rel="stylesheet" href="/css/broadcast.css" media="print" onload="this.media='all';this.onload=null">
<link rel="stylesheet" href="/css/cosmetics.css" media="print" onload="this.media='all';this.onload=null">
-- <link rel="stylesheet" href="/css/messenger.css" media="print" onload="this.media='all';this.onload=null">
<noscript>
<!-- Without JavaScript the chat never fills, but the pages that have one still show it. -->
<link rel="stylesheet" href="/css/chat-shared.css">
<link rel="stylesheet" href="/css/broadcast.css">
<link rel="stylesheet" href="/css/cosmetics.css">
-- <link rel="stylesheet" href="/css/messenger.css">
</noscript>
<!-- JSON-LD Structured Data -->
@@@ -496,10 +494,8 @@
</div>
</div>
--<script src="/js/vibes.js" defer></script>
--<script src="/js/opencoins.js" defer></script>
++<!-- vibes.js, opencoins.js, messenger.js: features.json → money, messenger (on demand). -->
<script src="/js/draggable-fab.js" defer></script>
--<script src="/js/messenger.js" defer></script>
<script src="/shared/footer.js" defer></script>
<script src="/js/footer-live.js" defer></script>
ddiff --git a/public/js/app.js b/public/js/app.js
iindex 25e09e06..323529b9 100644
---- a/public/js/app.js
++++ b/public/js/app.js
@@@ -530,7 +530,13 @@ async function loadOpenVibeAppMeta(force = false) {
}
/* ── Modal ────────────────────────────────────────────────────── */
++const MONEY_MODALS = new Set(['donate', 'buy-funds', 'cashout', 'add-reward']);
function showModal(id) {
++ // Vibes, tips and channel rewards live in features.json → money, which most pages never load: fetch it first.
++ if (MONEY_MODALS.has(id) && typeof openvibeBucksDonateModal !== 'function' && window.ov && ov.load) {
++ ov.load('money').then(() => showModal(id)).catch(() => { if (typeof toast === 'function') toast('That could not load. Check your connection and try again.', 'error'); });
++ return;
++ }
const overlay = document.getElementById('modal-overlay');
const content = document.getElementById('modal-content');
const templates = {
@@@ -548,12 +554,12 @@ function showModal(id) {
<i class="fa-solid fa-network-wired"></i> Sign in with OpenVibe
</a>
<p style="text-align:center;margin-top:12px;font-size:12px;color:var(--text-muted)">Registration is handled on openvibe.network</p>`,
-- donate: openvibeBucksDonateModal(),
-- 'buy-funds': openvibeBucksBuyModal(),
-- cashout: openvibeBucksCashoutModal(),
++ donate: () => openvibeBucksDonateModal(),
++ 'buy-funds': () => openvibeBucksBuyModal(),
++ cashout: () => openvibeBucksCashoutModal(),
'stream-key': streamKeyModal(),
'add-goal': addGoalModal(),
-- 'add-reward': addRewardModal(),
++ 'add-reward': () => addRewardModal(),
'redeem-reward': (data) => redeemRewardModal(data),
'create-managed-stream': createManagedStreamModal,
};
@@@ -762,6 +768,8 @@ function onAuthChange() {
// The navbar is the network's shared component; tell it who is here (js/ov-navbar-live.js).
if (window.LiveNav) window.LiveNav.sync(currentUser || null);
if (currentUser) loadBalance();
++ // The DM panel is for a signed-in person only (features.json → messenger).
++ if (currentUser && window.ov && ov.load) ov.load('messenger').catch(() => { /* in ov.diagnostics() */ });
try {
window.dispatchEvent(new CustomEvent('openvibe-auth-changed', {
@@@ -813,6 +821,7 @@ async function updateChannelPointsNav(streamerId) {
}
function openChannelRewards() {
if (typeof toggleRewardsPanel === 'function') toggleRewardsPanel();
++ else if (window.ov && ov.load) ov.load('money').then(() => { if (typeof toggleRewardsPanel === 'function') toggleRewardsPanel(); }).catch(() => {});
}
function isModifiedLinkClick(event) {
ddiff --git a/public/js/ov-navbar-live.js b/public/js/ov-navbar-live.js
iindex e72a4828..acf70b3b 100644
---- a/public/js/ov-navbar-live.js
++++ b/public/js/ov-navbar-live.js
@@@ -83,7 +83,7 @@
{ id: 'vibes', icon: 'fa-wallet', tone: 'green', value: '0', valueId: 'nav-balance-amount', label: 'Vibes', title: 'Vibes balance', onClick: () => call('showModal', 'buy-funds') },
],
// DMs beside the bell (Shared 4.25): the button opens the messenger panel; without it, Chat's inbox.
-- messages: { onOpen: () => { if (window.messengerWidget && window.messengerWidget.toggle) window.messengerWidget.toggle(); else location.href = 'https://openvibe.chat/messages'; }, href: 'https://openvibe.chat/messages' },
++ messages: { onOpen: () => { if (window.messengerWidget && window.messengerWidget.toggle) window.messengerWidget.toggle(); else if (window.ov && ov.load) ov.load('messenger').then(() => { if (window.messengerWidget && window.messengerWidget.toggle) window.messengerWidget.toggle(); else location.href = 'https://openvibe.chat/messages'; }).catch(() => { location.href = 'https://openvibe.chat/messages'; }); else location.href = 'https://openvibe.chat/messages'; }, href: 'https://openvibe.chat/messages' },
// Same-origin links are routes of this single-page app.
onNavigate: (href, e) => (typeof handleLinkClick === 'function' ? handleLinkClick(e, href) : true),
// No speculation rules: the router answers same-site links, so a prefetched page was never used, and the
ddiff --git a/test/money-on-demand.test.js b/test/money-on-demand.test.js
nnew file mode 100644
iindex 00000000..372c1f22
---- /dev/null
++++ b/test/money-on-demand.test.js
@@@ -0,0 +1,45 @@
++'use strict';
++/**
++ * Vibes, tips, subscriptions and channel points (vibes.js, opencoins.js) and the DM panel (messenger.js) are not on
++ * every page (11-07, the owner: "make things faster and more optimized"; 31 KB that most visits never ran):
++ * - features.json → money comes with the pages that sell, tip or reward (channel, broadcast, dashboard), its
++ * inline handlers are stubs, and showModal() fetches it for a money modal on any other page;
++ * - features.json → messenger loads once someone is signed in, and the navbar's Messages waits for it.
++ * node test/money-on-demand.test.js
++ */
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++
++const pub = (p) => fs.readFileSync(path.join(__dirname, '..', 'public', p), 'utf8');
++const html = pub('index.html');
++const reg = JSON.parse(pub('features.json')).features;
++const app = pub('js/app.js');
++const nav = pub('js/ov-navbar-live.js');
++
++// Not on every page.
++for (const f of ['vibes.js', 'opencoins.js', 'messenger.js', 'messenger.css']) {
++ assert.ok(!new RegExp(`<(script|link)[^>]+/(js|css)/${f.replace('.', '\\.')}`).test(html), `${f} is not on every page`);
++}
++
++// The two features, and who depends on money.
++assert.deepStrictEqual(reg.money.js, ['/js/vibes.js', '/js/opencoins.js']);
++assert.deepStrictEqual(reg.messenger.js, ['/js/messenger.js']);
++assert.deepStrictEqual(reg.messenger.css, ['/css/messenger.css']);
++for (const name of ['channel', 'broadcast', 'dashboard']) assert.ok((reg[name].deps || []).includes('money'), `${name} brings money`);
++// Every money function a fragment calls from an inline handler is a stub (or its page brings money).
++for (const fn of ['openSubscribeForCurrentStream', 'toggleRewardsPanel', 'saveDashPointsConfig']) {
++ assert.ok(reg.money.stubs.includes(fn), `${fn} is a stub`);
++}
++
++// showModal() builds money templates lazily and fetches money first; the sign-in modal never needs it.
++assert.ok(/donate: \(\) => openvibeBucksDonateModal\(\),/.test(app) && /'buy-funds': \(\) => openvibeBucksBuyModal\(\),/.test(app)
++ && /cashout: \(\) => openvibeBucksCashoutModal\(\),/.test(app) && /'add-reward': \(\) => addRewardModal\(\),/.test(app), 'money templates are built only when asked for');
++assert.ok(/if \(MONEY_MODALS\.has\(id\) && typeof openvibeBucksDonateModal !== 'function' && window\.ov && ov\.load\) \{\n\s*ov\.load\('money'\)\.then\(\(\) => showModal\(id\)\)/.test(app), 'a money modal fetches money first');
++assert.ok(/function openChannelRewards\(\) \{[\s\S]{0,200}ov\.load\('money'\)/.test(app), 'the rewards button fetches money');
++
++// The DM panel: on sign-in, and from the navbar's Messages.
++assert.ok(/if \(currentUser && window\.ov && ov\.load\) ov\.load\('messenger'\)/.test(app), 'messenger loads on sign-in');
++assert.ok(/messages: \{ onOpen: \(\) => \{[^\n]*ov\.load\('messenger'\)/.test(nav), 'the navbar\'s Messages loads it if needed');
++
++console.log('money on demand: vibes, opencoins and messenger load only where they are used');