Vest: relay the login's token, never one account's
The Vest app renews each trading account's own token through the same POST /v3/auth/refresh as the login's (with that account's refresh token in the body). The extension relayed those too, and the dashboard kept whichever token lived longest — so while someone traded in an account on Vest, Me synced with a token that sees that account alone and dropped the other 24 from the list. Extension 0.17.2 skips account renewals. The dashboard also refuses a token that sees one account and no primary (or names that account in its claims), keeps the last account list, and falls back to the login's token it replaced. An account gone from Vest's list no longer leaves a stale position holding the lane. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
95cc3bd23f
commit
a0e7c5b7c2
@@ -334,6 +334,40 @@ export function parseHistoryPosition(raw: unknown): VestTrade | null {
|
||||
};
|
||||
}
|
||||
|
||||
/** Every string value in a JWT's payload, lower-case ([] when it isn't
|
||||
* one). Used only to compare against account ids — never logged. */
|
||||
export function jwtClaimStrings(token: string): string[] {
|
||||
const part = token.split('.')[1];
|
||||
if (!part) return [];
|
||||
let payload: unknown;
|
||||
try {
|
||||
payload = JSON.parse(Buffer.from(part.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8'));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
const out: string[] = [];
|
||||
const walk = (v: unknown, depth: number): void => {
|
||||
if (typeof v === 'string') out.push(v.toLowerCase());
|
||||
else if (v && typeof v === 'object' && depth < 4) for (const x of Object.values(v)) walk(x, depth + 1);
|
||||
};
|
||||
walk(payload, 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a token the extension relayed is one Vest account's own token, not
|
||||
* the login's. The Vest app renews both through POST /v3/auth/refresh — the
|
||||
* login's every ~15 minutes, an account's while someone trades in it — and
|
||||
* an account's token sees only that account: /v3/capital/accounts/active
|
||||
* lists it alone, and /v3/accounts has no primary (wallet) account. The
|
||||
* login sees every account and its primary one.
|
||||
*/
|
||||
export function accountScopedToken(o: { capitalIds: string[]; hasPrimary: boolean; claims: string[] }): boolean {
|
||||
if (o.capitalIds.length > 1) return false;
|
||||
if (!o.hasPrimary) return true;
|
||||
return o.capitalIds.some((id) => o.claims.includes(id.toLowerCase()));
|
||||
}
|
||||
|
||||
/** Exit legs still waiting to trigger. */
|
||||
export function awaitingLegs(p: VestPosition): VestLeg[] {
|
||||
return [...p.stopLosses, ...p.takeProfits].filter((l) => l.status === 'awaiting');
|
||||
|
||||
Reference in New Issue
Block a user