// 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(); const framed = window.self !== window.top; // shown inside the dashboard's in-page ad overlay 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; // In the in-page overlay the iframe often doesn't hold focus even though it's // fully on screen, so gate on tab visibility only when framed. if (document.visibilityState !== 'visible' || (!framed && !document.hasFocus())) { $('vTimer').classList.add('paused'); $('vTimer').textContent = 'paused'; setMsg(framed ? 'Keep this ad on screen to finish the countdown.' : '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) {} try { if (framed) window.parent.postMessage({ t: 'iap-view-done' }, location.origin); } 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(); // window.close() is blocked in mobile and in-app (wallet dApp) browsers. If // the tab is still here a moment later, take them back to the dashboard so // they are never stuck on a tab they can't close. setTimeout(() => { if (!window.closed) { setMsg('Taking you back to your dashboard…'); location.href = '/my#earn'; } }, 400); }); // When shown inside the dashboard's in-page overlay there is no tab to close: // hide "Close tab" and turn "Back to dashboard" into a close-the-overlay signal. if (framed) { const cb = $('vClose'); if (cb) cb.style.display = 'none'; const back = document.querySelector('#vDone a[href="/my#earn"]'); if (back) back.addEventListener('click', e => { e.preventDefault(); try { window.parent.postMessage({ t: 'iap-view-close' }, location.origin); } catch (x) {} }); } (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); })(); })();