Files
vsix-hpl-toolbox/TODO.md

7.5 KiB

Mintegral Checker — TODO

Task Overview

Build a local VS Code webview equivalent of the Mintegral PlayTurbo checker (https://www.playturbo.com/review). Validates a Mintegral playable ad ZIP against PlayTurbo requirements. Two check categories:

  • Static — parse index.html + ZIP structure without running the ad.
  • Runtime — load the ad in a sandboxed srcdoc iframe, inject a monitoring stub, and detect whether lifecycle callbacks (gameReady, gameEnd, gameStart, gameClose, install) actually fire.

Finished

  • Pure-Node ZIP parser (EOCD → Central Directory → Local File Headers, zlib DEFLATE)
  • All 9 static checks:
    • HTML Requirements (charset, viewport, single-file)
    • CTA Method (window.install reference in scripts)
    • Game End / Game Ready / Game Start / Game Close (regex on scripts)
    • File Handling (no external URLs, base64 assets)
    • File Spec (ZIP ≤ 5 MB, single index.html)
    • Storage (no localStorage.setItem / sessionStorage.setItem)
  • Device-frame simulator (phone mockup around srcdoc iframe)
  • Event log panel showing runtime events as they fire
  • Runtime timer with 60s timeout and per-check waiting badges
  • __ping__ handshake → "Simulator connected" in event log (confirms stub runs)
  • Dual notification channels: polling __hplMtgEvents + parent.__hplMtgNotify
  • MW_INIT interceptor (wraps emitCheckData, forces _hasReview/_isPreview)
  • window.postMessage override (catches previewer:gameReady self-posts)
  • mkAccessObject.defineProperty accessor with undefined getter so AD's typeof window.gameReady === 'undefined' guard passes, setter wraps assignment
  • Capture-phase load safety net — wraps any SDK function declarations that overwrote accessors, fires before body.onload
  • Mute button UI (shows on ZIP load, hidden on reset, toggles Mute/Unmute label)
  • Diagnostic lifecycle logging (post-parse, DOMContentLoaded, load, load+500ms, callback assignment/call traces)
  • AudioContext construction proxy for mute/unmute of closure-owned engine audio

Bugs

1. Callbacks never fire (critical, diagnostics added)

Symptom: Event log shows "Simulator connected" (__ping__ fires) but no lifecycle events appear. The same ZIP passes all checks on the real PlayTurbo site.

Root cause candidates (narrowed down but not confirmed):

A. body.onload vs capture load ordering on window — When the event target is window itself, capture and bubble listeners both run at the "at target" phase in registration order. Our stub is in <head> and registers first, so it should fire before the body's onload. But this assumes no edge-case behaviour in VS Code's Chromium sandbox / srcdoc context.

B. SDK function declaration closes over a local reference — If the ad does:

var gameReady = function() { ... };
window.gameReady = gameReady;

and body.onload="gameReady()" resolves to the local var (via closure), then reassigning window.gameReady in our load-time wrapper has no effect.

C. CSP in VS Code webview blocks stub execution silently — The webview panel has an implicit CSP. The stub is injected into srcdoc; it might run in a restricted context that prevents property interception or parent access for later events (even though __ping__ works because it runs synchronously at parse time).

D. Ad calls gameReady() before load — If the ad boots via DOMContentLoaded and calls gameReady() there (rather than body.onload), our load-event safety net fires too late. The mkAccess accessor should still catch it, but only if the ad assigns window.gameReady first.

What's been added:

  • Injected diagnostic logging for lifecycle function typeof at post-parse, DOMContentLoaded, load capture before/after wrapping, and 500 ms after load.
  • Logs callback assignment/wrapping and wrapper call paths to the event log.
  • Logs dynamic document.createElement('script') calls to help trace script injection.

What's still needed if callbacks still do not fire:

  • Check if the ad bundles preview-util.js inside index.html or references it externally (would fail file-handling check anyway).
  • Compare the diagnostic timeline against the real PlayTurbo execution path.

2. Mute button does nothing (fixed in monitor stub)

Symptom: Clicking Mute/Unmute changes the label but has no audible effect.

Root cause: The current approach searches Object.keys(cw) for an AudioContext instance. AudioContexts are almost always created in closures (inside Phaser or the ad IIFE) and are never assigned as window properties — Object.keys will never find them. cw.Phaser.game.sound also likely fails because Phaser stores the game instance internally, not at window.Phaser.game.

Fix applied: Intercepts AudioContext/webkitAudioContext construction in the monitoring stub, stores captured contexts/gain nodes on window.__hplAudioContexts and window.__hplMasterGains, and routes ctx.destination through a master gain when possible. The mute button now sets captured master gain values first, then falls back to suspending/resuming captured contexts and exposed Phaser/global game sound objects.


Improvements / Nice-to-Have

  • Inject SDK alongside ad — The real PlayTurbo checker loads preview-util.js next to the ad. Bundling (or downloading once) the 265 KB SDK and injecting it into the srcdoc would let MW_INIT/emitCheckData fire natively, which is the most reliable callback path and matches what the real checker does.

  • Diagnostic mode — A "Debug" toggle that logs typeof of each lifecycle function at key moments (post-parse, DCL, load, 500ms after load) to the event log, making root-cause analysis faster.

  • Replay button — Reload only the iframe (not the full ZIP re-parse) to retest without re-picking the file.

  • Portrait / landscape toggle — Flip the device frame aspect ratio.

  • Multiple HTML entry points — Some ZIPs have playable.html instead of index.html; add a fallback search.

  • Orientation lock detection — Check for screen.orientation.lock() calls (Mintegral requires the ad to handle both orientations).

  • Version bump and changelog entry — Once callbacks are working, bump to 0.1.8 and add a changelog entry for the Mintegral Checker tool.


Known Issues / Constraints

  • VS Code WebviewPanel runs in a sandboxed Chromium process. srcdoc iframes are same-origin with the webview, so parent access works — but the webview's implicit CSP may still block certain APIs inside nested iframes.

  • The Object.defineProperty accessor approach (mkAccess) is the only way to let the AD's typeof window.X === 'undefined' guard pass while still intercepting the assignment. Any early concrete assignment (even undefined) breaks the guard in some runtimes.

  • SDK function declarations at brace-depth 0 are [[DefineOwnProperty]] calls at parse time; they overwrite our configurable accessors unconditionally. The load-time safety net is the only recovery path for that scenario.

  • The applyStaticResults function sets runtimeStatus = null for lifecycle checks that already failed statically. handleRuntimeEvent only transitions runtimeStatus === 'waiting''pass'; it silently ignores null. This means a static-fail check can't be runtime-rescued via the direct lifecycle path (though the handleSdkCheck path does handle null). Intentional, but worth documenting.