/* AutoFirmer — Vest session hook. * * Runs in the page's MAIN world on next.vestmarkets.com, at document_start * (manifest content script) or injected later into a tab that was already open * (background.js relayVestSession). Installs once per page. * * Vest's app renews its own ~15-minute access token by itself: at load, then * whenever one nears expiry (POST /v3/auth/refresh, or /v3/auth/register at * sign-in). Those answers carry the token, its expiry, the login's wallet and * the session it belongs to (refreshTokenId). This hook reads them off the * app's own XHRs and keeps the latest on `window.__afVestLogin`, where * background.js collects it and relays it to the local dashboard, whose Vest * client trades with it. * * Every tab renews on its own, and each renewal moves the login on to a new * session (localStorage `vest-auth-session`): a token from before still reads, * but Vest refuses to mint an account's token with it (401, code 1002). The * session id lets background.js relay the tab whose token is the current one. * * It sends nothing anywhere, changes no request and never renews anything * itself: the session stays the app's, refreshed by the app with its own * device key, so the tab is never logged out by the bot. * * The app also renews each trading account's own token through the same * /v3/auth/refresh, sending that account's refresh token in the body (the * login's rides in a cookie) and getting it back renewed. Those tokens see * one account only, so they are skipped: relaying one would leave the * dashboard with a single account. A sign-in's answer carries the login's * own refresh token, and is taken. * * Version 2. A page hooked by version 1 (`__afVestHook === true`, keeping * `__afVestSession`, no session id, no sign-ins) takes this one beside it. */ (() => { if (window.__afVestHook === 2) return; window.__afVestHook = 2; const AUTH = /^https:\/\/api-gateway\.hz\.vestmarkets\.com\/v3\/auth\/(refresh|register)(\?|$)/; const SIGN_IN = /\/v3\/auth\/register(\?|$)/; /** A request body that carries a refresh token: an account's renewal. */ function accountRenewal(reqBody) { return typeof reqBody === 'string' && /"refreshToken"\s*:\s*"[^"]/.test(reqBody); } function take(url, status, text, accountRequest) { if (accountRequest || status < 200 || status >= 300 || !AUTH.test(url)) return; let body; try { body = JSON.parse(text); } catch { return; } if (!body || typeof body.accessToken !== 'string' || !body.accessToken) return; // A renewal answering with a refresh token is an account's. if (!SIGN_IN.test(url) && typeof body.refreshToken === 'string' && body.refreshToken) return; const expiresAt = Number(body.accessExpiresAtMs); if (!Number.isFinite(expiresAt)) return; window.__afVestLogin = { accessToken: body.accessToken, expiresAt, primaryAddr: typeof body.primaryAddr === 'string' ? body.primaryAddr : '', userId: typeof body.userId === 'string' ? body.userId : '', refreshTokenId: typeof body.refreshTokenId === 'string' && body.refreshTokenId ? body.refreshTokenId : null, at: Date.now(), }; } const open = XMLHttpRequest.prototype.open; const send = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url) { try { this.__afVestUrl = String(url); } catch { /* ignore */ } return open.apply(this, arguments); }; XMLHttpRequest.prototype.send = function (reqBody) { if (this.__afVestUrl && AUTH.test(this.__afVestUrl)) { let acct = false; try { acct = accountRenewal(reqBody); } catch { /* ignore */ } this.addEventListener('loadend', () => { try { if (this.responseType === '' || this.responseType === 'text') take(this.__afVestUrl, this.status, this.responseText, acct); else if (this.responseType === 'json') take(this.__afVestUrl, this.status, JSON.stringify(this.response), acct); } catch { /* never break the app */ } }); } return send.apply(this, arguments); }; const fetch0 = window.fetch; window.fetch = async function (input, init) { const res = await fetch0.apply(this, arguments); try { const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input && input.url; const acct = accountRenewal(init && init.body); if (url && AUTH.test(url)) res.clone().text().then((t) => take(url, res.status, t, acct)).catch(() => {}); } catch { /* never break the app */ } return res; }; })();