openvibers/openvibe.live Commits
Vibes, OpenCoins and the DM panel load where they are used, not on every page (#682)
Changes
@@@ -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"],@@@ -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, theystay 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>@@@ -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) {@@@ -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 thennew file mode 100644@@@ -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');
@@ -136,6 +136,7 @@
@@ -136,6 +136,7 @@
"/css/features/agent-stream.css"
"/css/features/agent-stream.css"
],
],
"deps": [
"deps": [
+ "money",
"player",
"player",
"context",
"context",
"chatExtras",
"chatExtras",
@@ -237,6 +238,7 @@
@@ -237,6 +238,7 @@
"/css/features/broadcast.css"
"/css/features/broadcast.css"
],
],
"deps": [
"deps": [
+ "money",
"chatExtras",
"chatExtras",
"guide",
"guide",
"aiViewers",
"aiViewers",
@@ -262,12 +264,38 @@
@@ -262,12 +264,38 @@
"captureScreenshot"
"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": {
"dashboard": {
"fragment": "dashboard",
"fragment": "dashboard",
"css": [
"css": [
"/css/features/dashboard.css"
"/css/features/dashboard.css"
],
],
"deps": [
"deps": [
+ "money",
"pastes",
"pastes",
"aiViewers"
"aiViewers"
],
],
@@ -99,7 +99,7 @@
@@ -99,7 +99,7 @@
.ov-route-error>div{flex:1;min-width:180px;display:grid;gap:2px}
.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}
.ov-route-error span{color:var(--text-muted,#8b93ad);font-size:.9rem}
@media (prefers-reduced-motion:reduce){.ov-route-skel span{animation:none}}
@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. */
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}
.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)}
.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 @@
@@ -121,13 +121,11 @@
for it there, and a page without chat does not download it. -->
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/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/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>
<noscript>
<!-- Without JavaScript the chat never fills, but the pages that have one still show it. -->
<!-- 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/chat-shared.css">
<link rel="stylesheet" href="/css/broadcast.css">
<link rel="stylesheet" href="/css/broadcast.css">
<link rel="stylesheet" href="/css/cosmetics.css">
<link rel="stylesheet" href="/css/cosmetics.css">
- <link rel="stylesheet" href="/css/messenger.css">
</noscript>
</noscript>
<!-- JSON-LD Structured Data -->
<!-- JSON-LD Structured Data -->
@@ -496,10 +494,8 @@
@@ -496,10 +494,8 @@
</div>
</div>
</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/draggable-fab.js" defer></script>
-<script src="/js/messenger.js" defer></script>
<script src="/shared/footer.js" defer></script>
<script src="/shared/footer.js" defer></script>
<script src="/js/footer-live.js" defer></script>
<script src="/js/footer-live.js" defer></script>
@@ -530,7 +530,13 @@ async function loadOpenVibeAppMeta(force = false) {
@@ -530,7 +530,13 @@ async function loadOpenVibeAppMeta(force = false) {
}
}
/* ── Modal ────────────────────────────────────────────────────── */
/* ── Modal ────────────────────────────────────────────────────── */
+const MONEY_MODALS = new Set(['donate', 'buy-funds', 'cashout', 'add-reward']);
function showModal(id) {
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 overlay = document.getElementById('modal-overlay');
const content = document.getElementById('modal-content');
const content = document.getElementById('modal-content');
const templates = {
const templates = {
@@ -548,12 +554,12 @@ function showModal(id) {
@@ -548,12 +554,12 @@ function showModal(id) {
<i class="fa-solid fa-network-wired"></i> Sign in with OpenVibe
<i class="fa-solid fa-network-wired"></i> Sign in with OpenVibe
</a>
</a>
<p style="text-align:center;margin-top:12px;font-size:12px;color:var(--text-muted)">Registration is handled on openvibe.network</p>`,
<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(),
'stream-key': streamKeyModal(),
'add-goal': addGoalModal(),
'add-goal': addGoalModal(),
- 'add-reward': addRewardModal(),
+ 'add-reward': () => addRewardModal(),
'redeem-reward': (data) => redeemRewardModal(data),
'redeem-reward': (data) => redeemRewardModal(data),
'create-managed-stream': createManagedStreamModal,
'create-managed-stream': createManagedStreamModal,
};
};
@@ -762,6 +768,8 @@ function onAuthChange() {
@@ -762,6 +768,8 @@ function onAuthChange() {
// The navbar is the network's shared component; tell it who is here (js/ov-navbar-live.js).
// 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 (window.LiveNav) window.LiveNav.sync(currentUser || null);
if (currentUser) loadBalance();
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 {
try {
window.dispatchEvent(new CustomEvent('openvibe-auth-changed', {
window.dispatchEvent(new CustomEvent('openvibe-auth-changed', {
@@ -813,6 +821,7 @@ async function updateChannelPointsNav(streamerId) {
@@ -813,6 +821,7 @@ async function updateChannelPointsNav(streamerId) {
}
}
function openChannelRewards() {
function openChannelRewards() {
if (typeof toggleRewardsPanel === 'function') toggleRewardsPanel();
if (typeof toggleRewardsPanel === 'function') toggleRewardsPanel();
+ else if (window.ov && ov.load) ov.load('money').then(() => { if (typeof toggleRewardsPanel === 'function') toggleRewardsPanel(); }).catch(() => {});
}
}
function isModifiedLinkClick(event) {
function isModifiedLinkClick(event) {
@@ -83,7 +83,7 @@
@@ -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') },
{ 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.
// 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.
// Same-origin links are routes of this single-page app.
onNavigate: (href, e) => (typeof handleLinkClick === 'function' ? handleLinkClick(e, href) : true),
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
// No speculation rules: the router answers same-site links, so a prefetched page was never used, and the
new file mode 100644
new file mode 100644
@@ -0,0 +1,45 @@
@@ -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');