'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 = '') => `
${esc(x.host)}${esc(x.why || ((x.what || []).join(', ') + ' · seen ' + x.n + (x.n === 1 ? ' time' : ' times')))}${extra}
`; const hub = d.required[0].host; const el = openSheet('iPad and Screen Time', 'Let the hub through Screen Time without adding each site one by one', `

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.

Addresses the app needs

${d.required.map(x => row(x)).join('')}
${d.seen.length ? `

Others your devices reached

Seen while people used the app (a podcast cover, a weather extension, a site you added). Add only what you want available.

${d.seen.map(x => row(x)).join('')}
` : '

No other addresses have been seen yet. Use the app for a day on the iPad and any others will show up here.

'}

Only sometimes

${d.optional.map(x => row(x)).join('')}

“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); } }; }