Skip to content

Commit ceea85b

Browse files
fix(ahbg): add purchase and restore controls
1 parent 2311a23 commit ceea85b

1 file changed

Lines changed: 65 additions & 0 deletions

File tree

ahbg/presentation/board.html

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,74 @@ <h2>Feed</h2>
3838
<h3>Benchmark Lab</h3>
3939
<p>Advanced scenarios, saved/replayed run comparison, and adversarial benchmark packs.</p>
4040
<button id="premium-status" disabled>Benchmark Lab: checking…</button>
41+
<button id="premium-purchase" type="button">Unlock Benchmark Lab</button>
42+
<button id="premium-restore" type="button">Restore purchase</button>
43+
<p id="premium-message" class="inspect" aria-live="polite"></p>
4144
</section>
4245
</aside>
4346
</main>
4447
<script src="board.js"></script>
48+
<script>
49+
(() => {
50+
const purchase = document.getElementById("premium-purchase");
51+
const restore = document.getElementById("premium-restore");
52+
const status = document.getElementById("premium-status");
53+
const message = document.getElementById("premium-message");
54+
const nativeBridge = typeof window.ahbg !== "undefined" ? window.ahbg : null;
55+
if (!purchase || !restore || !status || !message) return;
56+
57+
const pending = new Map();
58+
window.ahbgPremiumResult = (callbackId, payload) => {
59+
const resolver = pending.get(callbackId);
60+
if (!resolver) return;
61+
pending.delete(callbackId);
62+
try { resolver.resolve(JSON.parse(payload)); }
63+
catch (error) { resolver.reject(error); }
64+
};
65+
66+
function invoke(method) {
67+
if (!nativeBridge || typeof nativeBridge[method] !== "function") {
68+
return Promise.reject(new Error("Purchases are available in the Android app"));
69+
}
70+
return new Promise((resolve, reject) => {
71+
const callbackId = `premium_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
72+
const timer = setTimeout(() => {
73+
pending.delete(callbackId);
74+
reject(new Error("purchase bridge timeout"));
75+
}, 30000);
76+
pending.set(callbackId, {
77+
resolve: (result) => { clearTimeout(timer); resolve(result); },
78+
reject: (error) => { clearTimeout(timer); reject(error); },
79+
});
80+
nativeBridge[method](callbackId);
81+
});
82+
}
83+
84+
function applyResult(result) {
85+
status.textContent = result.unlocked
86+
? "Benchmark Lab: unlocked"
87+
: "Benchmark Lab: locked (basic play free)";
88+
message.textContent = result.message || "Purchase state updated";
89+
}
90+
91+
function run(button, method) {
92+
button.disabled = true;
93+
message.textContent = method === "purchaseBenchmarkLab" ? "Opening Google Play…" : "Restoring purchases…";
94+
invoke(method)
95+
.then(applyResult)
96+
.catch((error) => { message.textContent = String(error.message || error); })
97+
.finally(() => { button.disabled = false; });
98+
}
99+
100+
purchase.addEventListener("click", () => run(purchase, "purchaseBenchmarkLab"));
101+
restore.addEventListener("click", () => run(restore, "restoreBenchmarkLab"));
102+
103+
if (!nativeBridge) {
104+
purchase.disabled = true;
105+
restore.disabled = true;
106+
message.textContent = "Purchases are available in the Android app.";
107+
}
108+
})();
109+
</script>
45110
</body>
46111
</html>

0 commit comments

Comments
 (0)