const FIELDS = ['apiBase', 'pollSeconds', 'elementSelector'];
const DEFAULTS = {
apiBase: 'http://localhost:3000',
pollSeconds: 3,
elementSelector: '',
};
async function load() {
const cfg = { ...DEFAULTS, ...(await chrome.storage.local.get(FIELDS)) };
for (const f of FIELDS) document.getElementById(f).value = cfg[f];
render();
}
async function render() {
const { state = {} } = await chrome.storage.local.get('state');
// There is nothing to switch on: either it can reach the dashboard or it
// cannot. `waiting` below says why a tick captured nothing.
const color = state.connected ? '#22c55e' : '#ef4444';
const label = state.connected ? 'Capturing' : 'Disconnected';
const cap = state.lastCapture;
const detail = state.waiting
? `
${esc(state.waiting)}
`
: cap
? `${esc(cap.title || cap.url)}
${(cap.bytes / 1024).toFixed(1)} KB · ${new Date(cap.at).toLocaleTimeString()}
`
: '';
document.getElementById('status').innerHTML =
`${label}` +
detail +
(state.error ? `${esc(state.error)}
` : '');
}
document.getElementById('save').addEventListener('click', async () => {
const patch = {};
for (const f of FIELDS) patch[f] = document.getElementById(f).value.trim();
patch.pollSeconds = Number(patch.pollSeconds) || DEFAULTS.pollSeconds;
patch.apiBase = patch.apiBase || DEFAULTS.apiBase;
await chrome.storage.local.set(patch);
setTimeout(render, 400);
});
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 = 'Scanning the Topstep dashboard…
';
return;
}
btn.disabled = false;
btn.textContent = 'Check activatable accounts';
if (state.error) {
out.innerHTML = `${esc(state.error)}
`;
return;
}
const r = state.result;
if (!r) { out.innerHTML = ''; return; }
if (r.signedOut) {
out.innerHTML = 'Not signed in to Topstep. Open the dashboard, sign in, then check again.
';
return;
}
if (r.error) {
out.innerHTML = `${esc(r.error)}
`;
return;
}
const n = r.activatableCount || 0;
const acctUrl = (id) => `https://dashboard.topstep.com/dashboard/accounts/${encodeURIComponent(id)}`;
const list = (r.activatable || [])
.map((a) => `${esc(a.name)}${a.type ? ` · ${esc(a.type)}` : ''}`)
.join('');
const when = r.checkedAt ? ` · ${new Date(r.checkedAt).toLocaleTimeString()}` : '';
out.innerHTML =
`${n} activatable
` +
`${r.alreadyActivated || 0} already activated · ${r.passedTotal || 0} passed total${when}
` +
(list ? `` : 'No passed accounts waiting on activation.
');
}
// 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) {
return String(s).replace(/[&<>"]/g, (ch) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[ch]));
}
load();