'use strict'; // ---- Audiobooks: embedded site. Sign-in details saved in Settings (this browser only) can be copied with one tap, // because a page can't type into another site's login form. const abCreds = () => store.get('ab_creds', { u: '', p: '' }); // Podcast Admin (Verse by Verse): the site refuses to be framed (X-Frame-Options), so this is a launcher. function viewVerse() { const url = cfg.verse, host = new URL(url).host; $('#view').innerHTML = `

Podcast Admin

${esc(host + new URL(url).pathname)}

This site doesn't allow being shown inside another app, so it opens in its own tab.

`; } // Frames that must keep running (audio) live outside #view: created once, then only shown or hidden. function showFrame(key, url, title, allow) { const wrap = $('#audiowrap'); let f = wrap.querySelector(`iframe[data-k="${key}"]`); if (!f) { f = document.createElement('iframe'); f.dataset.k = key; f.src = url; f.title = title; f.allow = allow; wrap.appendChild(f); } wrap.querySelectorAll('iframe').forEach(x => x.classList.toggle('act', x === f)); wrap.className = 'on'; document.body.classList.add('onaudio'); } // Podcast: opens in a frame when it has a secure (https) address; a plain-http address cannot be framed by a secure page. // Podcast: a plain-http address on the home network. A secure page cannot frame http content, and the address is // private to the LAN, so this opens it directly in the browser (which works on the home network or over a VPN). function viewPodcast() { const url = cfg.podcast, u = new URL(url), canFrame = u.protocol === 'https:'; $('#view').innerHTML = `

Podcast

${esc(u.host)}

Open in new tab
${canFrame ? '

If the page below stays blank, the app does not allow being shown inside another app. Use Open in new tab.

' : '

This is a plain http address, which a secure page cannot show inside itself. It opens in its own tab, and works at home or on your VPN.

'}
`; if (canFrame) showFrame('podcast', url, 'Podcast', 'autoplay; fullscreen; picture-in-picture; clipboard-write'); } // The frame is created once and kept alive, so playback carries on while you use other tabs. // It loads /api/audiobooks/enter, where the server adds the site's secret sign-in link itself (the key never reaches this page). const ENTER = '/api/audiobooks/enter'; function viewAudio() { const c = abCreds(), old = $('#audiowrap iframe[data-k="audio"]'); $('#view').innerHTML = `
Audiobooks${old ? '' : 'Opening…'} ${c.u ? `` : ''} ${icon('ext')}
`; const setSt = t => { const e = $('#abst'); if (e) e.textContent = t; }; const open = fresh => { setSt('Opening…'); let f = $('#audiowrap iframe[data-k="audio"]'); if (f) f.remove(); showFrame('audio', ENTER + (fresh ? '?t=' + Date.now() : ''), 'Audiobooks', 'autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media'); f = $('#audiowrap iframe[data-k="audio"]'); f.referrerPolicy = 'no-referrer'; let done = false; f.addEventListener('load', () => { done = true; setSt(''); }); setTimeout(() => { if (!done) setSt('Still loading… if it stays blank, tap the arrow to open it in its own tab'); }, 10000); }; if (!old) open(false); else { showFrame('audio', '', 'Audiobooks', ''); setSt(''); } $('#ar').onclick = () => open(true); $('#af').onclick = () => { const f = $('#audiowrap iframe[data-k="audio"]'); if (f && f.requestFullscreen) f.requestFullscreen().catch(() => toast('Full screen is not available here')); else toast('Full screen is not available here'); }; const cu = $('#cu'); if (cu) cu.onclick = async () => { try { await navigator.clipboard.writeText(c.u); toast('Username copied. Password is in Settings.'); } catch { toast('Could not copy'); } }; }