Vest: one refused account token fails only its account, and the relay sends the current session's token
Every open Vest tab renews the login on its own, and each renewal moves the session on: a token from before still reads, but Vest refuses to mint an account's token with it (401, code 1002). The extension relayed the first tab's token, so mints failed most of the time with two tabs open, and each refusal dropped the whole session — every later account in a history pass failed "no Vest session", and new accounts waited out 5-minute retries for up to an hour (2026-10-28/29). The client now fails only that account's call on a refused mint, keeps the session and the cached account tokens, and logs the refusal now and then with the login token's life left. An active account with no history is read again every minute even while a whole pass's retry waits; a pass reads those first and inactive accounts last, whose failures no longer set the retry. Exits, lone take-profits and flattens wait up to 90s for the session and the account's token instead of failing at once; entries still go now or not at all. Closed accounts' history is read through the login first. Extension 0.18.0: the hook records each token's session id and takes sign-in tokens too; the relay reads every Vest tab and Vest's own current-session record, and sends the token that session holds, else the newest, saying in the popup when none is current. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
bf7e147507
commit
0eb56b3949
+61
-7
@@ -1204,6 +1204,13 @@ async function tick() {
|
||||
// Vest app renewed for itself (~15 minutes each); this lifts it and posts it to
|
||||
// the dashboard whenever it changes. Nothing here renews or signs anything: the
|
||||
// session stays the app's.
|
||||
//
|
||||
// With several Vest tabs open, each renews on its own, and each renewal moves
|
||||
// the login on to a new session: the other tabs' tokens still read, but Vest
|
||||
// refuses to mint an account's token with them (401, code 1002) — the bot's
|
||||
// history reads and orders fail until the tab it relays renews again. So the
|
||||
// token relayed is the one Vest's current session holds (pickVestSession),
|
||||
// whichever tab it is in, not the first tab's.
|
||||
|
||||
const VEST_PATTERN = 'https://next.vestmarkets.com/*';
|
||||
const VEST_URL = 'https://next.vestmarkets.com/portfolio';
|
||||
@@ -1220,10 +1227,47 @@ let vestLastPostAt = 0;
|
||||
let vestOpenedAt = 0;
|
||||
|
||||
/** Runs in the Vest tab's MAIN world: the session vest-relay.js holds, while
|
||||
* it has life left. Self-contained. */
|
||||
* it has life left, and the current session the Vest app keeps for all its
|
||||
* tabs. Self-contained. Version 2's token whenever it holds one: it sees
|
||||
* every login renewal version 1 does once it's in, so a version-1 token it
|
||||
* lacks is an account's (the 0.17.0/0.17.1 hooks kept those too). */
|
||||
function pageVestSession() {
|
||||
const s = window.__afVestSession;
|
||||
return { hooked: window.__afVestHook === true, session: s && s.expiresAt > Date.now() + 30000 ? s : null };
|
||||
const live = (s) => (s && typeof s.accessToken === 'string' && s.expiresAt > Date.now() + 30000 ? s : null);
|
||||
const session = live(window.__afVestLogin) ?? live(window.__afVestSession);
|
||||
let current = null;
|
||||
try {
|
||||
const raw = JSON.parse(localStorage.getItem('vest-auth-session') || 'null');
|
||||
if (raw && typeof raw === 'object') {
|
||||
current = {
|
||||
refreshTokenId: typeof raw.refreshTokenId === 'string' && raw.refreshTokenId ? raw.refreshTokenId : null,
|
||||
accessTokenExpiresAt: Number.isFinite(raw.accessTokenExpiresAt) ? raw.accessTokenExpiresAt : null,
|
||||
};
|
||||
}
|
||||
} catch { /* no session kept */ }
|
||||
return { hooked: window.__afVestHook === 2, session, current };
|
||||
}
|
||||
|
||||
/** Whether a tab's token is the one the Vest app's current session holds:
|
||||
* the same session id — the app's own check before it mints — else, for a
|
||||
* token caught by a version-1 hook (no id), the same expiry, which the app
|
||||
* writes into the session with the token. */
|
||||
function isCurrentVestSession(session, current) {
|
||||
if (!session || !current) return false;
|
||||
if (session.refreshTokenId && current.refreshTokenId) return session.refreshTokenId === current.refreshTokenId;
|
||||
return current.accessTokenExpiresAt !== null && session.expiresAt === current.accessTokenExpiresAt;
|
||||
}
|
||||
|
||||
/** Out of every Vest tab's pageVestSession, the session to relay: the
|
||||
* current one, else — no tab holds it (one renewed before its hook was in)
|
||||
* — the latest to expire, which still reads. `current` is null when no tab
|
||||
* shows the app's session record to judge by. */
|
||||
function pickVestSession(pages) {
|
||||
const live = pages.filter((p) => p && p.session);
|
||||
if (live.length === 0) return null;
|
||||
const current = live.find((p) => isCurrentVestSession(p.session, p.current));
|
||||
if (current) return { session: current.session, current: true };
|
||||
const newest = live.reduce((x, y) => (y.session.expiresAt > x.session.expiresAt ? y : x));
|
||||
return { session: newest.session, current: pages.some((p) => p && p.current) ? false : null };
|
||||
}
|
||||
|
||||
/** The wallets the dashboard's Vest firms trade; [] from an older dashboard. */
|
||||
@@ -1259,7 +1303,8 @@ async function relayVestSession(cfg) {
|
||||
return;
|
||||
}
|
||||
|
||||
let found = null;
|
||||
// Every tab: the current session may be in any of them.
|
||||
const pages = [];
|
||||
for (const tab of tabs) {
|
||||
// A discarded tab stops renewing the session; keep Chrome from it.
|
||||
if (tab.autoDiscardable !== false) chrome.tabs.update(tab.id, { autoDiscardable: false }).catch(() => {});
|
||||
@@ -1272,12 +1317,14 @@ async function relayVestSession(cfg) {
|
||||
// next renewal is caught.
|
||||
await chrome.scripting.executeScript({ target: { tabId: tab.id }, world: 'MAIN', files: ['vest-relay.js'] }).catch(() => {});
|
||||
}
|
||||
if (out?.session) { found = out.session; break; }
|
||||
if (out) pages.push(out);
|
||||
}
|
||||
if (!found) {
|
||||
const picked = pickVestSession(pages);
|
||||
if (!picked) {
|
||||
await setState({ vest: 'Vest tab open — waiting for its session (sign in there if it shows Log In)' });
|
||||
return;
|
||||
}
|
||||
const found = picked.session;
|
||||
|
||||
const fingerprint = `${found.expiresAt}:${found.accessToken.length}:${found.accessToken.slice(-16)}`;
|
||||
if (fingerprint === vestLastRelayed && now - vestLastPostAt < VEST_REPOST_MS) return;
|
||||
@@ -1297,7 +1344,14 @@ async function relayVestSession(cfg) {
|
||||
const mins = Math.max(0, Math.round((found.expiresAt - Date.now()) / 60000));
|
||||
const note = Array.isArray(body.bound) && body.bound.length > 0 ? ` · bound to ${body.bound.join(', ')}`
|
||||
: body.used ? '' : ' (no Vest firm here uses this wallet)';
|
||||
await setState({ vest: `Relayed ${short}${note} · good ${mins}m` });
|
||||
// A tab that renewed before its hook was in holds the current session
|
||||
// unseen: until it renews again, orders and history reads can fail —
|
||||
// reloading it hooks it.
|
||||
const tabsNote = picked.current === false
|
||||
? (tabs.length === 1 ? " · its token isn't Vest's current session — reload the Vest tab"
|
||||
: ` · ${tabs.length} Vest tabs and none holds the current session — reload them, or keep one open`)
|
||||
: tabs.length > 1 ? ` · ${tabs.length} Vest tabs${picked.current ? ', relaying the current one' : ''}` : '';
|
||||
await setState({ vest: `Relayed ${short}${note} · good ${mins}m${tabsNote}` });
|
||||
}
|
||||
|
||||
// ── 2FA: the person's authenticator code for the firm site you are on ───────
|
||||
|
||||
Reference in New Issue
Block a user