extension: detect activatable Topstep accounts

Add a popup button that scans the Topstep dashboard's GraphQL API
(crystal.topstep.com) for passed evaluations that can still be
activated into an Express Funded account.

An eval is activatable when its status is "Passed" and its subscription
id is not shared by ANY funded account it ever spawned — regardless of
that funded account's current status, since activation is one-way. The
result and its progress are mirrored to chrome.storage so the popup
renders them independent of the message port (avoids the "message port
closed" error on this ~20s scan). Activatable accounts link to their
Topstep detail page.

Adds https://*.topstep.com/* host permission; bumps to 0.15.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-15 10:33:56 -05:00
co-authored by Claude Fable 5
parent b1dccd7dec
commit 1bf8ba8693
4 changed files with 277 additions and 7 deletions
+58
View File
@@ -45,6 +45,64 @@ document.getElementById('now').addEventListener('click', () => {
chrome.runtime.sendMessage({ type: 'capture-now' }, () => setTimeout(render, 200));
});
document.getElementById('tsCheck').addEventListener('click', () => {
// Kick off the scan and return. The worker writes progress and the result to
// storage; renderTopstepState (below) reflects whatever is there, so the
// popup can be closed and reopened mid-scan without losing the answer. The
// callback only acknowledges the request was received — it carries no result.
chrome.runtime.sendMessage({ type: 'topstep-check' }, () => void chrome.runtime.lastError);
});
/** Paint the Topstep panel from the stored scan state. */
function renderTopstepState(state) {
const btn = document.getElementById('tsCheck');
const out = document.getElementById('tsResult');
if (!state) { out.innerHTML = ''; return; }
if (state.running) {
btn.disabled = true;
btn.textContent = 'Checking…';
out.innerHTML = '<div class="muted">Scanning the Topstep dashboard…</div>';
return;
}
btn.disabled = false;
btn.textContent = 'Check activatable accounts';
if (state.error) {
out.innerHTML = `<div class="err">${esc(state.error)}</div>`;
return;
}
const r = state.result;
if (!r) { out.innerHTML = ''; return; }
if (r.signedOut) {
out.innerHTML = '<div class="err">Not signed in to Topstep. Open the dashboard, sign in, then check again.</div>';
return;
}
if (r.error) {
out.innerHTML = `<div class="err">${esc(r.error)}</div>`;
return;
}
const n = r.activatableCount || 0;
const acctUrl = (id) => `https://dashboard.topstep.com/dashboard/accounts/${encodeURIComponent(id)}`;
const list = (r.activatable || [])
.map((a) => `<li><a href="${acctUrl(a.id)}" target="_blank" rel="noopener">${esc(a.name)}</a>${a.type ? ` · ${esc(a.type)}` : ''}</li>`)
.join('');
const when = r.checkedAt ? ` · ${new Date(r.checkedAt).toLocaleTimeString()}` : '';
out.innerHTML =
`<div class="big">${n} activatable</div>` +
`<div class="muted">${r.alreadyActivated || 0} already activated · ${r.passedTotal || 0} passed total${when}</div>` +
(list ? `<ul>${list}</ul>` : '<div class="muted" style="margin-top:6px">No passed accounts waiting on activation.</div>');
}
// Render whatever the last scan left behind when the popup opens…
chrome.storage.local.get('topstepScan').then(({ topstepScan }) => renderTopstepState(topstepScan));
// …and keep up with a scan that finishes while the popup is open.
chrome.storage.onChanged.addListener((c, area) => {
if (area === 'local' && 'topstepScan' in c) renderTopstepState(c.topstepScan.newValue);
});
chrome.storage.onChanged.addListener((c, area) => { if (area === 'local' && 'state' in c) render(); });
function esc(s) {