'use strict'; // ---- iPad and Screen Time (admin): which web addresses the app needs, the ones devices were seen reaching, and a check you run on the device itself. const stOk = u => /^https?:\/\//i.test(u || '') ? '/api/img?u=' + encodeURIComponent(u) : (u || ''); // a picture from another site, passed through the hub so the browser only talks to the hub async function screenTimeSheet() { let d; try { d = await jfetch('/api/screentime'); } catch (e) { return toast(e.message); } const row = (x, extra = '') => `
The simple way. On the iPad: Settings → Screen Time → Content & Privacy Restrictions → Content Restrictions → Web Content. Choose Limit Adult Websites, then under Always Allow tap Add Website and enter ${esc(hub)}. Nothing else needs adding: the hub carries the music, books, podcasts and pictures itself.
Only if you use Allowed Websites Only do you need to add every address below.
Seen while people used the app (a podcast cover, a weather extension, a site you added). Add only what you want available.
No other addresses have been seen yet. Use the app for a day on the iPad and any others will show up here.
'}“Check this device” tries each address from the device you are on, and shows which ones Screen Time (or the network) is blocking. Run it on the iPad.
${d.seen.length ? '' : ''}`); el.querySelector('.sheet').classList.add('wide'); el.querySelectorAll('[data-c]').forEach(b => b.onclick = () => { navigator.clipboard.writeText(b.dataset.c).then(() => toast('Copied ' + b.dataset.c), () => toast(b.dataset.c)); }); const all = [...d.required, ...d.seen, ...d.optional].map(x => x.host); el.querySelector('#stcopy').onclick = () => navigator.clipboard.writeText(all.join('\n')).then(() => toast('Copied ' + all.length + ' addresses'), () => toast('Could not copy')); el.querySelector('#stchk').onclick = async e => { const b = e.currentTarget; b.disabled = true; b.textContent = 'Checking…'; await Promise.all(all.map(async h => { const mark = el.querySelector(`.stres[data-h="${CSS.escape(h)}"]`); if (!mark) return; try { await fetch(h === location.hostname ? '/manifest.webmanifest' : 'https://' + h + '/', { mode: 'no-cors', cache: 'no-store', signal: AbortSignal.timeout(8000) }); mark.textContent = ' ✓'; mark.style.color = 'var(--accent)'; mark.title = 'Reachable'; } catch { mark.textContent = ' ✗'; mark.style.color = 'var(--danger)'; mark.title = 'Blocked or not reachable from this device'; } })); b.disabled = false; b.textContent = 'Check again'; }; if (el.querySelector('#stclr')) el.querySelector('#stclr').onclick = async () => { try { await jfetch('/api/screentime', { method: 'DELETE' }); closeSheet(); toast('Cleared'); } catch (x) { toast(x.message); } }; }