// Full-screen ad viewer: the advertiser URL fills the tab, a countdown runs in // the top bar (paused whenever this tab loses focus), and once the dwell is // done the server hands out a human check. Solve it and the view credits. // The dwell floor is enforced on the SERVER clock; this UI cannot cheat it. (() => { const $ = id => document.getElementById(id); const token = location.pathname.split('/').pop(); let left = 5, timer = null, credited = false, asking = false; const setMsg = t => { $('vMsg').textContent = t; }; async function j(url, body) { const r = await fetch(url, body ? { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) } : undefined); return r.json(); } function tick() { if (credited || asking) return; if (document.visibilityState !== 'visible' || !document.hasFocus()) { $('vTimer').classList.add('paused'); $('vTimer').textContent = 'paused'; setMsg('Come back to this tab to keep the countdown moving.'); return; } $('vTimer').classList.remove('paused'); left = Math.max(0, left - 0.25); $('vTimer').textContent = Math.ceil(left) + 's left'; if (left <= 0) { clearInterval(timer); askChallenge(); } } async function askChallenge() { asking = true; $('vTimer').textContent = 'check'; $('vTimer').classList.add('done'); setMsg('One quick check to count the view:'); let c = await j('/api/my/viewchallenge?token=' + token); if (c.early) { // server clock says not quite yet: wait it out and re-ask await new Promise(r => setTimeout(r, (c.wait || 1) * 1000 + 300)); c = await j('/api/my/viewchallenge?token=' + token); } if (c.error) return fail(c.error); renderChallenge(c); } function renderChallenge(c) { $('vPrompt').textContent = 'Click the ' + c.prompt + ':'; const w = $('vOpts'); w.innerHTML = ''; c.options.forEach((em, i) => { const b = document.createElement('button'); b.type = 'button'; b.textContent = em; b.addEventListener('click', () => answer(i)); w.appendChild(b); }); $('vCheck').classList.add('on'); } async function answer(i) { const r = await j('/api/my/adview', { token, answer: i }); if (r.error) { if (r.retry) { setMsg('Not that one — try again.'); const c = await j('/api/my/viewchallenge?token=' + token); if (!c.error) return renderChallenge(c); return fail(c.error); } return fail(r.error); } credited = true; $('vCheck').classList.remove('on'); $('vTimer').textContent = '✓ credited'; const st = r.status || r; setMsg('View ' + st.views + ' of ' + st.target + ' counted for today.' + (st.views >= st.target && !st.claimed ? ' Head back and claim your credits.' : '')); $('vDone').classList.add('on'); try { localStorage.setItem('iap-view-done', String(Date.now())); } catch (e) {} } function fail(msg) { credited = true; // stop the loop; this view is over either way if (timer) clearInterval(timer); $('vCheck').classList.remove('on'); $('vTimer').textContent = '—'; setMsg(msg); $('vDone').classList.add('on'); } $('vClose').addEventListener('click', () => { window.close(); setMsg('You can close this tab now.'); }); (async () => { const info = await j('/api/my/viewinfo?token=' + token); if (info.error) { $('vFrame').remove(); const d = document.createElement('div'); d.className = 'vfail'; d.textContent = info.error; document.querySelector('.vw').appendChild(d); $('vDone').classList.add('on'); $('vTimer').textContent = '—'; setMsg(''); return; } left = info.dwell || 5; $('vFrame').src = info.targetUrl; setMsg('Watching: ' + (info.adName || 'member ad') + ' — stay on this tab.'); $('vTimer').textContent = left + 's left'; timer = setInterval(tick, 250); })(); })();