package main import ( "archive/zip" _ "embed" "encoding/json" "fmt" "html" "io" "net/http" "os" "path/filepath" "regexp" "strings" "sync" ) //go:embed assets/mintegral/preview-util.js var mintegralPreviewUtil string const ( mintegralZipLimit = 5 * 1024 * 1024 mintegralHTMLLimit = 5 * 1024 * 1024 ) type mintegralCheck struct { ID string `json:"id"` Status string `json:"status"` Detail string `json:"detail"` } type mintegralZipEntry struct { Name string CompressedSize uint64 UncompressedSize uint64 Data []byte } type mintegralRuntimePayload struct { FileName string `json:"fileName"` ZipSize int64 `json:"zipSize"` HTML string `json:"html"` Checks []mintegralCheck `json:"checks"` } var mintegralRuntime = struct { sync.RWMutex payload *mintegralRuntimePayload }{} func MintegralPage(w http.ResponseWriter, r *http.Request) { body := `

Mintegral Checker

Validates a Mintegral playable ad ZIP against PlayTurbo requirements. Runtime checks update live as you interact with the playable.

Input

Playable ZIP
or drop a Mintegral ZIP here
(no file selected)
` w.Header().Set("Content-Type", "text/html; charset=utf-8") _, _ = w.Write([]byte(Page("/mintegral", "Mintegral Checker", body))) } func MintegralPick(w http.ResponseWriter, r *http.Request) { paths := PickFiles("Select Mintegral ZIP", []FileFilter{{Name: "ZIP", Extensions: []string{"zip"}}}, false, "") path := "" if len(paths) > 0 { path = paths[0] } writeJSON(w, map[string]any{"path": path}) } func MintegralLoad(w http.ResponseWriter, r *http.Request) { var req struct { Path string `json:"path"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, map[string]any{"error": err.Error()}) return } payload, err := loadMintegralZip(req.Path) if err != nil { writeJSON(w, map[string]any{"error": err.Error()}) return } mintegralRuntime.Lock() mintegralRuntime.payload = payload mintegralRuntime.Unlock() writeJSON(w, map[string]any{ "fileName": payload.FileName, "zipSize": payload.ZipSize, "runtimeUrl": "/mintegral/runtime", }) } func MintegralRuntimePage(w http.ResponseWriter, r *http.Request) { mintegralRuntime.RLock() payload := mintegralRuntime.payload mintegralRuntime.RUnlock() if payload == nil { http.Error(w, "No Mintegral ZIP loaded.", http.StatusNotFound) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Cache-Control", "no-store") _, _ = w.Write([]byte(mintegralRuntimeHTML(payload))) } func MintegralAd(w http.ResponseWriter, r *http.Request) { mintegralRuntime.RLock() payload := mintegralRuntime.payload mintegralRuntime.RUnlock() if payload == nil { http.Error(w, "No Mintegral ZIP loaded.", http.StatusNotFound) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Cache-Control", "no-store") _, _ = w.Write([]byte(payload.HTML)) } func MintegralPreviewUtil(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/javascript; charset=utf-8") w.Header().Set("Cache-Control", "no-store") _, _ = w.Write([]byte(mintegralPreviewUtil)) } func MintegralMonitor(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/javascript; charset=utf-8") w.Header().Set("Cache-Control", "no-store") _, _ = w.Write([]byte(mintegralMonitorScript)) } func loadMintegralZip(zipPath string) (*mintegralRuntimePayload, error) { if zipPath == "" { return nil, fmt.Errorf("Pick a ZIP first.") } info, err := os.Stat(zipPath) if err != nil { return nil, err } if info.IsDir() { return nil, fmt.Errorf("Expected a ZIP file, got a folder.") } entries, err := readMintegralZip(zipPath) if err != nil { return nil, err } var index *mintegralZipEntry for i := range entries { if entries[i].Name == "index.html" { index = &entries[i] break } } htmlText := "" if index != nil { htmlText = string(index.Data) } checks := runMintegralStaticChecks(htmlText, entries, info.Size()) return &mintegralRuntimePayload{ FileName: filepath.Base(zipPath), ZipSize: info.Size(), HTML: injectMintegralMonitoring(htmlText), Checks: checks, }, nil } func readMintegralZip(zipPath string) ([]mintegralZipEntry, error) { zr, err := zip.OpenReader(zipPath) if err != nil { return nil, err } defer zr.Close() entries := make([]mintegralZipEntry, 0, len(zr.File)) for _, f := range zr.File { if strings.HasSuffix(f.Name, "/") { continue } rc, err := f.Open() if err != nil { continue } data, err := io.ReadAll(rc) _ = rc.Close() if err != nil { data = nil } entries = append(entries, mintegralZipEntry{ Name: filepath.ToSlash(f.Name), CompressedSize: f.CompressedSize64, UncompressedSize: f.UncompressedSize64, Data: data, }) } return entries, nil } func injectMintegralMonitoring(htmlText string) string { stub := ` ` if regexp.MustCompile(`(?i)]*>`).MatchString(htmlText) { return regexp.MustCompile(`(?i)]*>`).ReplaceAllStringFunc(htmlText, func(m string) string { return m + "\n" + stub }) } return stub + "\n" + htmlText } func runMintegralStaticChecks(htmlText string, entries []mintegralZipEntry, zipSize int64) []mintegralCheck { scripts := mintegralExtractScripts(htmlText) return []mintegralCheck{ mintegralCheckHTML(htmlText), mintegralCheckCTA(scripts), mintegralCheckGameEnd(scripts), mintegralCheckGameReady(htmlText, scripts), mintegralCheckGameStart(scripts), mintegralCheckGameClose(scripts), mintegralCheckFileHandling(htmlText), mintegralCheckFileSpec(entries, zipSize), mintegralCheckStorage(scripts), } } func mintegralExtractScripts(htmlText string) string { re := regexp.MustCompile(`(?is)]*>(.*?)`) matches := re.FindAllStringSubmatch(htmlText, -1) parts := make([]string, 0, len(matches)) for _, m := range matches { parts = append(parts, m[1]) } return strings.Join(parts, "\n") } func mintegralCheckHTML(htmlText string) mintegralCheck { issues := []string{} if !regexp.MustCompile(`(?i)^\s*`).MatchString(htmlText) { issues = append(issues, "Missing ") } if !regexp.MustCompile(`(?i)]*\bcharset\s*=`).MatchString(htmlText) { issues = append(issues, "Missing charset meta tag") } if !regexp.MustCompile(`(?i)]*\bname\s*=\s*["']viewport["']`).MatchString(htmlText) { issues = append(issues, "Missing viewport meta tag") } if regexp.MustCompile(`(?i)]*\btype\s*=\s*["']module["']`).MatchString(htmlText) { issues = append(issues, `type="module" on script tag`) } if regexp.MustCompile(`(?i)]*\bcrossorigin\b`).MatchString(htmlText) { issues = append(issues, "crossorigin attribute on script tag") } if !regexp.MustCompile(`(?i)`).MatchString(htmlText) { issues = append(issues, "Missing closing tag") } if len(issues) == 0 { return mintegralCheck{ID: "html-requirements", Status: "pass", Detail: "Valid HTML structure"} } return mintegralCheck{ID: "html-requirements", Status: "fail", Detail: strings.Join(issues, "; ")} } func mintegralCheckCTA(scripts string) mintegralCheck { if regexp.MustCompile(`\bwindow\.install\b|\btypeof\s+window\.install`).MatchString(scripts) { return mintegralCheck{ID: "cta-method", Status: "pass", Detail: "window.install() referenced in scripts"} } return mintegralCheck{ID: "cta-method", Status: "fail", Detail: "window.install() not found - Mintegral CTA requires window.install()"} } func mintegralCheckGameEnd(scripts string) mintegralCheck { if regexp.MustCompile(`\bgameEnd\b`).MatchString(scripts) { return mintegralCheck{ID: "game-end", Status: "pass", Detail: "gameEnd() found in scripts"} } return mintegralCheck{ID: "game-end", Status: "fail", Detail: "gameEnd() not found"} } func mintegralCheckGameReady(htmlText, scripts string) mintegralCheck { inScript := regexp.MustCompile(`\bgameReady\b`).MatchString(scripts) inOnload := regexp.MustCompile(`(?i)\bonload\s*=\s*["'][^"']*\bgameReady\b`).MatchString(htmlText) if !inScript && !inOnload { return mintegralCheck{ID: "game-ready", Status: "fail", Detail: "gameReady() not found"} } if !inOnload { return mintegralCheck{ID: "game-ready", Status: "warn", Detail: `gameReady() found but not wired to body onload - Mintegral requires onload="gameReady()"`} } return mintegralCheck{ID: "game-ready", Status: "pass", Detail: "gameReady() wired to body onload"} } func mintegralCheckGameStart(scripts string) mintegralCheck { if regexp.MustCompile(`\bgameStart\b`).MatchString(scripts) { return mintegralCheck{ID: "game-start", Status: "pass", Detail: "gameStart() found in scripts"} } return mintegralCheck{ID: "game-start", Status: "fail", Detail: "gameStart() not found"} } func mintegralCheckGameClose(scripts string) mintegralCheck { if regexp.MustCompile(`\bgameClose\b`).MatchString(scripts) { return mintegralCheck{ID: "game-close", Status: "pass", Detail: "gameClose() found in scripts"} } return mintegralCheck{ID: "game-close", Status: "fail", Detail: "gameClose() not found"} } func mintegralCheckFileHandling(htmlText string) mintegralCheck { re := regexp.MustCompile(`(?i)(?:src|href|action)\s*=\s*["'](https?://[^"']+)["']|url\s*\(\s*["']?(https?://[^)"'\s]+)`) matches := re.FindAllStringSubmatch(htmlText, -1) seen := map[string]bool{} found := []string{} for _, m := range matches { u := m[1] if u == "" { u = m[2] } if u != "" && !seen[u] { seen[u] = true found = append(found, u) } } if len(found) == 0 { return mintegralCheck{ID: "file-handling", Status: "pass", Detail: "All assets are inline - no external URLs detected"} } detail := fmt.Sprintf("%d external URL(s): %s", len(found), strings.Join(found[:minInt(2, len(found))], ", ")) if len(found) > 2 { detail += fmt.Sprintf(" (+%d more)", len(found)-2) } return mintegralCheck{ID: "file-handling", Status: "fail", Detail: detail} } func mintegralCheckFileSpec(entries []mintegralZipEntry, zipSize int64) mintegralCheck { issues := []string{} var index *mintegralZipEntry for i := range entries { if entries[i].Name == "index.html" { index = &entries[i] break } } if index == nil { nested := "" for _, e := range entries { if regexp.MustCompile(`(?i)/index\.html$`).MatchString(e.Name) { nested = e.Name break } } if nested != "" { issues = append(issues, fmt.Sprintf("index.html found at '%s' - must be at ZIP root", nested)) } else { issues = append(issues, "index.html not found in ZIP") } } if zipSize > mintegralZipLimit { issues = append(issues, fmt.Sprintf("ZIP size %.2f MB exceeds 5 MB limit", float64(zipSize)/1024/1024)) } if index != nil && index.UncompressedSize > mintegralHTMLLimit { issues = append(issues, fmt.Sprintf("index.html %.2f MB uncompressed exceeds 5 MB", float64(index.UncompressedSize)/1024/1024)) } if len(issues) > 0 { return mintegralCheck{ID: "file-spec", Status: "fail", Detail: strings.Join(issues, "; ")} } extras := []string{} for _, e := range entries { if e.Name != "index.html" { extras = append(extras, e.Name) } } sizeStr := fmt.Sprintf("index.html %.0f KB, ZIP %.0f KB", float64(index.UncompressedSize)/1024, float64(zipSize)/1024) if len(extras) > 0 { return mintegralCheck{ID: "file-spec", Status: "warn", Detail: fmt.Sprintf("%s; %d extra file(s) in ZIP (%s)", sizeStr, len(extras), strings.Join(extras, ", "))} } return mintegralCheck{ID: "file-spec", Status: "pass", Detail: sizeStr} } func mintegralCheckStorage(scripts string) mintegralCheck { issues := []string{} if regexp.MustCompile(`\blocalStorage\s*\.\s*setItem\s*\(`).MatchString(scripts) { issues = append(issues, "localStorage.setItem") } if regexp.MustCompile(`\bsessionStorage\s*\.\s*setItem\s*\(`).MatchString(scripts) { issues = append(issues, "sessionStorage.setItem") } if regexp.MustCompile(`\bdocument\.cookie\s*=`).MatchString(scripts) { issues = append(issues, "document.cookie write") } if len(issues) == 0 { return mintegralCheck{ID: "storage", Status: "pass", Detail: "No persistent storage writes detected"} } return mintegralCheck{ID: "storage", Status: "fail", Detail: "Forbidden storage write(s): " + strings.Join(issues, ", ")} } func minInt(a, b int) int { if a < b { return a } return b } func mintegralRuntimeHTML(payload *mintegralRuntimePayload) string { boot, _ := json.Marshal(map[string]any{ "fileName": payload.FileName, "zipSize": payload.ZipSize, "checks": payload.Checks, }) return ` Mintegral Runtime Checker

Playable

Checks

Starting...
60s
      ` } const mintegralRuntimeJS = ` const CHECKS=[ {id:'html-requirements',num:1,label:'HTML Requirements',runtimeEvent:null},{id:'cta-method',num:2,label:'CTA Call Method',runtimeEvent:'install'},{id:'game-end',num:3,label:'Game End Call Method',runtimeEvent:'gameEnd'},{id:'game-ready',num:4,label:'Game Ready Call Method',runtimeEvent:'gameReady'},{id:'game-start',num:5,label:'Game Start Call Method',runtimeEvent:'gameStart'},{id:'game-close',num:6,label:'Game Close Call Method',runtimeEvent:'gameClose'},{id:'file-handling',num:7,label:'File Handling Method',runtimeEvent:null},{id:'file-spec',num:8,label:'File Spec',runtimeEvent:null},{id:'storage',num:9,label:'Storage Requirements',runtimeEvent:null},{id:'code-exception',num:10,label:'Code Exception',runtimeEvent:'exception'}]; const EVENT_TO_CHECK={};CHECKS.forEach(c=>{if(c.runtimeEvent)EVENT_TO_CHECK[c.runtimeEvent]=c.id;}); const SDK_TYPE_TO_CHECK={game_ready:'game-ready',game_end:'game-end',game_start:'game-start',game_close:'game-close',install:'cta-method',outer_chain:'file-handling',base64:'file-handling',code:'code-exception'}; const state={checks:{},seconds:0,muted:false,connected:false,timer:null,timeout:60,codeCleanAfter:5,hasSentNoRetry:false}; CHECKS.forEach(c=>state.checks[c.id]={status:'pending',detail:'',runtimeStatus:c.runtimeEvent?'waiting':null,runtimeDetail:''}); BOOT.checks.forEach(r=>{const ch=state.checks[r.id];if(ch){ch.status=r.status;ch.detail=r.detail;}}); state.checks['code-exception'].status='pass';state.checks['code-exception'].runtimeStatus='monitoring'; ['cta-method','game-end','game-ready','game-start','game-close'].forEach(id=>{if(state.checks[id].status==='fail')state.checks[id].runtimeStatus=null;}); const checksEl=document.getElementById('checksTab'),eventLog=document.getElementById('eventsTab'),adFrame=document.getElementById('adFrame'),muteBtn=document.getElementById('muteBtn'); document.querySelectorAll('.tab').forEach(btn=>btn.addEventListener('click',()=>{document.querySelectorAll('.tab').forEach(b=>b.classList.remove('active'));document.querySelectorAll('.tab-content').forEach(p=>p.classList.remove('active'));btn.classList.add('active');document.getElementById(btn.dataset.tab).classList.add('active');})); function resetChecks(){if(state.timer)clearInterval(state.timer);state.seconds=0;state.connected=false;state.hasSentNoRetry=false;CHECKS.forEach(c=>state.checks[c.id]={status:'pending',detail:'',runtimeStatus:c.runtimeEvent?'waiting':null,runtimeDetail:''});BOOT.checks.forEach(r=>{const ch=state.checks[r.id];if(ch){ch.status=r.status;ch.detail=r.detail;}});state.checks['code-exception'].status='pass';state.checks['code-exception'].runtimeStatus='monitoring';['cta-method','game-end','game-ready','game-start','game-close'].forEach(id=>{if(state.checks[id].status==='fail')state.checks[id].runtimeStatus=null;});eventLog.innerHTML='';document.getElementById('bridge').textContent='Starting...';document.getElementById('timer').textContent=state.timeout+'s';render();state.timer=startRuntimeTimer();} function esc(s){return String(s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));} function addLog(list,name,cls,msg){const d=new Date(),li=document.createElement('li');li.innerHTML=''+String(d.getMinutes()).padStart(2,'0')+':'+String(d.getSeconds()).padStart(2,'0')+''+esc(name)+''+esc(msg||'')+'';list.appendChild(li);list.scrollTop=list.scrollHeight;} function render(){checksEl.innerHTML=CHECKS.map(c=>{const ch=state.checks[c.id];let s=ch.status,d=ch.detail;if(ch.runtimeStatus==='waiting'&&ch.status==='pass'){s='waiting';d=ch.detail+' - awaiting runtime confirmation';}else if(ch.runtimeStatus==='monitoring'){s='waiting';d='Monitoring for exceptions...';}else if(ch.runtimeStatus==='pass'){s='pass';d=(ch.detail||'')+(ch.runtimeDetail?' - '+ch.runtimeDetail:'');}else if(ch.runtimeStatus==='fail'){s='fail';d=ch.runtimeDetail||ch.detail;}else if(ch.runtimeStatus==='timeout'&&ch.status==='pass'){s='warn';d=ch.detail+' - not fired within '+state.timeout+'s';}else if(ch.runtimeStatus==='clean'){s='pass';d='No exceptions detected';}return '
    • '+c.num+'
      '+esc(c.label)+' '+s.toUpperCase()+'
      '+esc(d||'')+'
    • ';}).join('');} function sdkCheck(type,value,msg){const id=SDK_TYPE_TO_CHECK[type],ch=state.checks[id];if(!ch)return;const pass=value===1||value===true;if(type==='code'){ch.status='fail';ch.runtimeStatus='fail';ch.runtimeDetail=msg||'JS error detected';addLog(eventLog,'code error','fail',ch.runtimeDetail);}else if(type==='outer_chain'){ch.runtimeStatus=pass?'pass':'fail';ch.runtimeDetail=pass?'No external resources at runtime':'External resources detected at runtime';addLog(eventLog,type,pass?'pass':'fail',ch.runtimeDetail);}else if(type==='base64'){if(!pass){ch.status='fail';ch.runtimeStatus='fail';ch.runtimeDetail='Non-JS/HTML assets must be base64';addLog(eventLog,type,'warn',ch.runtimeDetail);}}else{if(pass&&(ch.runtimeStatus==='waiting'||ch.runtimeStatus===null)){ch.runtimeStatus='pass';ch.runtimeDetail='Confirmed by SDK at '+state.seconds+'s';addLog(eventLog,type,'pass','');}else if(!pass&&(ch.runtimeStatus==='waiting'||ch.runtimeStatus===null)){ch.runtimeStatus='fail';ch.runtimeDetail=type+' not defined';addLog(eventLog,type,'warn',ch.runtimeDetail);}}render();done();} function inferAll(text){const s=String(text||''),out=[];if(/(^|[^a-z])gameReady([^a-z]|$)|previewer:gameReady|game_ready|\bDISPLAYED\b|\bLOADED\b/i.test(s))out.push('gameReady');if(/(^|[^a-z])gameStart([^a-z]|$)|previewer:gameStart|game_start|\bCHALLENGE_STARTED\b/i.test(s))out.push('gameStart');if(/(^|[^a-z])gameEnd([^a-z]|$)|previewer:gameEnd|game_end|\bCHALLENGE_SOLVED\b|\bENDCARD_SHOWN\b/i.test(s))out.push('gameEnd');if(/(^|[^a-z])gameClose([^a-z]|$)|previewer:gameClose|game_close/i.test(s))out.push('gameClose');if(/(^|[^a-z])install([^a-z]|$)|previewer:install|\bCTA_CLICKED\b/i.test(s))out.push('install');return out;} function handle(msg){const name=msg.name,extra=msg.extra||{};if(name==='__ping__'){state.connected=true;document.getElementById('bridge').textContent='Monitor connected';addLog(eventLog,'Monitor connected','pass','');return;}if(name==='sdkCheck'){sdkCheck(extra.type,extra.value,extra.msg||'');return;}if(name==='console'){inferAll(extra.text||'').forEach(ev=>handle({name:ev,extra:{source:'console'}}));return;}if(name==='diagnostic'){addLog(eventLog,extra.label||'diagnostic','warn',extra.detail||'');return;}if(name==='audioContext'){addLog(eventLog,'Audio captured','pass',extra.routed?'master gain ready':'context only');return;}if(name==='exception'){const ch=state.checks['code-exception'];ch.status='fail';ch.runtimeStatus='fail';ch.runtimeDetail=extra.message||'Unhandled exception';addLog(eventLog,'exception','fail',ch.runtimeDetail);render();done();return;}const id=EVENT_TO_CHECK[name];if(id){const ch=state.checks[id];if(ch.runtimeStatus==='waiting'||ch.runtimeStatus===null){ch.runtimeStatus='pass';ch.runtimeDetail=(extra.source==='console'?'Seen in console at ':'Fired at ')+state.seconds+'s';addLog(eventLog,name,'pass',extra.source==='console'?'from console':'');render();done();}}} window.__hplMtgNotify=(name,extra)=>handle({name,extra:extra||{}}); window.addEventListener('message',e=>{const m=e.data;if(!m)return;if(m.type==='lifecycle')handle(m);else if(typeof m.type==='string'&&m.type.startsWith('previewer:')){const map={'previewer:gameReady':'gameReady','previewer:gameStart':'gameStart','previewer:install':'install','previewer:gameEnd':'gameEnd','previewer:gameClose':'gameClose'};if(map[m.type])handle({name:map[m.type],extra:{}});if(m.type==='previewer:review'&&m.data&&Array.isArray(m.data.reviewResult)){if(!state.hasSentNoRetry){state.hasSentNoRetry=true;try{adFrame.contentWindow.postMessage({type:'previewer:review',data:{reviewResult:{no_game_retry:true}}},'*');}catch(e){}}m.data.reviewResult.forEach(r=>sdkCheck(r.type,r.value,r.msg||''));}}}); function applyMute(){try{const cw=adFrame.contentWindow;if(!cw)return;cw.__hplMuted=state.muted;if(typeof cw.__hplApplyMutedState==='function')cw.__hplApplyMutedState();(cw.__hplMasterGains||[]).forEach(g=>{try{if(g&&g.gain)g.gain.value=state.muted?0:1;}catch(e){}});(cw.__hplMediaElements||[]).forEach(el=>{try{el.muted=state.muted;el.volume=state.muted?0:1;}catch(e){}});}catch(e){}} function renderMuteButton(){muteBtn.innerHTML=state.muted?'🔈':'🔇';muteBtn.title=state.muted?'Unmute':'Mute';muteBtn.setAttribute('aria-label',state.muted?'Unmute':'Mute');muteBtn.setAttribute('aria-pressed',state.muted?'true':'false');} muteBtn.onclick=()=>{state.muted=!state.muted;renderMuteButton();applyMute();addLog(eventLog,state.muted?'Muted':'Unmuted','pass','');}; document.getElementById('reloadBtn').onclick=()=>{resetChecks();document.getElementById('bridge').textContent='Reloading...';adFrame.src='/mintegral/ad?preview=true&loading=0&review=1&t='+Date.now();}; ['pointerdown','click','touchstart','keydown'].forEach(evt=>window.addEventListener(evt,()=>setTimeout(applyMute,0),true)); function markCodeClean(){const ch=state.checks['code-exception'];if(ch&&ch.runtimeStatus==='monitoring'){ch.runtimeStatus='clean';ch.runtimeDetail='No exceptions detected';addLog(eventLog,'code clean','pass',ch.runtimeDetail);render();}} function done(){const othersDone=CHECKS.every(c=>c.id==='code-exception'||!c.runtimeEvent||state.checks[c.id].runtimeStatus!=='waiting');if(othersDone)markCodeClean();if(CHECKS.every(c=>!c.runtimeEvent||!['waiting','monitoring'].includes(state.checks[c.id].runtimeStatus))){clearInterval(state.timer);document.getElementById('timer').textContent='Runtime complete';}} function startRuntimeTimer(){return setInterval(()=>{state.seconds++;document.getElementById('timer').textContent=Math.max(0,state.timeout-state.seconds)+'s';if(state.seconds===2&&!state.connected){document.getElementById('bridge').textContent='Monitor not connected';addLog(eventLog,'Monitor not connected','fail','Check browser DevTools console for blocked inline script/CSP errors.');}if(state.seconds>=state.codeCleanAfter)markCodeClean();done();if(state.seconds>=state.timeout){clearInterval(state.timer);CHECKS.forEach(c=>{const ch=state.checks[c.id];if(ch.runtimeStatus==='waiting')ch.runtimeStatus='timeout';if(ch.runtimeStatus==='monitoring'){ch.runtimeStatus='clean';ch.runtimeDetail='No exceptions detected';}});render();document.getElementById('timer').textContent='Runtime complete';}},1000);} state.timer=startRuntimeTimer();renderMuteButton();render(); ` const mintegralMonitorScript = ` (function(){ window.__hplMtgEvents=window.__hplMtgEvents||[]; function notify(n,extra){window.__hplMtgEvents.push({name:n,extra:extra||{}});try{if(typeof parent.__hplMtgNotify==='function')parent.__hplMtgNotify(n,extra||{});}catch(e){}try{parent.postMessage({type:'lifecycle',name:n,extra:extra||{}},'*');}catch(e){}} notify('__ping__'); function applyMutedState(){var muted=!!window.__hplMuted;try{(window.__hplMasterGains||[]).forEach(function(g){try{if(g&&g.gain)g.gain.value=muted?0:1;}catch(e){}});(window.__hplMediaElements||[]).forEach(function(el){try{el.muted=muted;el.volume=muted?0:1;}catch(e){}});}catch(e){}} window.__hplApplyMutedState=applyMutedState; try{(function(){var OrigAC=window.AudioContext||window.webkitAudioContext;if(!OrigAC||OrigAC.__hplProxy)return;window.__hplAudioContexts=[];window.__hplMasterGains=[];window.__hplAudioDestinations=[];function ACProxy(opts){var ctx=arguments.length?new OrigAC(opts):new OrigAC();var dest=ctx.destination,gain=null;try{gain=ctx.createGain();gain.gain.value=window.__hplMuted?0:1;gain.connect(dest);if(window.AudioNode&&window.AudioNode.prototype&&!window.AudioNode.prototype.connect.__hpl){var origConnect=window.AudioNode.prototype.connect;var connectProxy=function(target){var args=Array.prototype.slice.call(arguments);try{var idx=window.__hplAudioDestinations.indexOf(args[0]);if(idx>=0&&window.__hplMasterGains[idx])args[0]=window.__hplMasterGains[idx];}catch(e){}return origConnect.apply(this,args);};connectProxy.__hpl=true;window.AudioNode.prototype.connect=connectProxy;}}catch(e){}window.__hplAudioContexts.push(ctx);if(gain)window.__hplMasterGains.push(gain);if(gain)window.__hplAudioDestinations.push(dest);notify('audioContext',{state:ctx.state||'',routed:!!gain});return ctx;}ACProxy.prototype=OrigAC.prototype;Object.setPrototypeOf&&Object.setPrototypeOf(ACProxy,OrigAC);ACProxy.__hplProxy=true;window.AudioContext=ACProxy;window.webkitAudioContext=ACProxy;})();}catch(e){} try{window.__hplMediaElements=[];var mediaPlay=window.HTMLMediaElement&&window.HTMLMediaElement.prototype&&window.HTMLMediaElement.prototype.play;if(mediaPlay&&!mediaPlay.__hpl){var playProxy=function(){window.__hplMediaElements.push(this);applyMutedState();notify('audioContext',{state:'media-play',routed:true});return mediaPlay.apply(this,arguments);};playProxy.__hpl=true;window.HTMLMediaElement.prototype.play=playProxy;}}catch(e){} function patchMwInit(obj){if(!obj||obj.__hplMwPatched)return;obj.__hplMwPatched=true;obj._hasReview=function(){return true;};obj._isPreview=function(){return true;};var _oe=obj.emitCheckData;if(typeof _oe==='function'){obj.emitCheckData=function(d){_oe.call(obj,d);if(d&&d.type)notify('sdkCheck',{type:d.type,value:d.value,msg:d.msg||''});};}} var _mwInit=window.MW_INIT||null;patchMwInit(_mwInit);try{Object.defineProperty(window,'MW_INIT',{configurable:true,enumerable:true,get:function(){return _mwInit;},set:function(obj){_mwInit=obj;patchMwInit(obj);}});}catch(e){} var _owp=window.postMessage;window.postMessage=function(data,origin,transfer){if(data&&typeof data.type==='string'){var pm={'previewer:gameReady':'gameReady','previewer:gameStart':'gameStart','previewer:gameEnd':'gameEnd','previewer:gameClose':'gameClose','previewer:install':'install','previewer:gameRetry':'gameRetry'};if(pm[data.type])notify(pm[data.type]);}return _owp.apply(this,arguments);}; function mkAccess(name,onCall){var existing,_v;try{existing=window[name];Object.defineProperty(window,name,{configurable:true,enumerable:true,get:function(){return _v;},set:function(fn){var orig=fn;if(typeof orig==='function'){var w=function(){onCall();return orig.apply(this,arguments);};w.__hpl=true;_v=w;}else{_v=orig;}}});if(typeof existing==='function')window[name]=existing;}catch(e){}} var _lh={gameReady:function(){notify('gameReady');setTimeout(function(){notify('sdkCheck',{type:'game_start',value:typeof window.gameStart==='function'?1:0});notify('sdkCheck',{type:'game_close',value:typeof window.gameClose==='function'?1:0});},100);},gameEnd:function(){notify('sdkCheck',{type:'game_end',value:1});},gameStart:function(){notify('sdkCheck',{type:'game_start',value:1});},gameClose:function(){notify('sdkCheck',{type:'game_close',value:1});},install:function(){notify('sdkCheck',{type:'install',value:1});}}; mkAccess('gameReady',_lh.gameReady);mkAccess('gameEnd',_lh.gameEnd);mkAccess('gameStart',_lh.gameStart);mkAccess('gameClose',_lh.gameClose);if(typeof window.install!=='function'){var _installW=function(){_lh.install();};_installW.__hpl=true;window.install=_installW;} window.addEventListener('load',function(){Object.keys(_lh).forEach(function(name){var fn=window[name];if(typeof fn==='function'&&!fn.__hpl){var orig=fn,h=_lh[name];var w=function(){h();return orig.apply(this,arguments);};w.__hpl=true;try{window[name]=w;}catch(e){}}else if(typeof fn!=='function'){var ww=function(){_lh[name]();};ww.__hpl=true;try{window[name]=ww;}catch(e){}}});},true); window.onerror=function(msg,src,line){notify('exception',{message:String(msg),line:line||0,source:src||''});return false;}; window.addEventListener('unhandledrejection',function(e){notify('exception',{message:String(e&&e.reason||'Unhandled promise rejection')});}); })(); ` var _ = html.EscapeString