A Vest firm's credential is the wallet of the login it trades. It no longer has to be typed or clicked in: when the extension relays a Vest tab's session and a Vest firm here has no credential, that wallet is saved as the credential (lib/vest-wallet.ts), the client starts, and the backup is pushed. - A saved wallet is never replaced. When the tab is signed in to another login, the firm now says which one and how to switch: sign in as the saved wallet, or remove the credential. - With two logins relayed at once nothing binds itself; the one-click form decides. - Extension 0.17.1 re-posts the session every minute even when it hasn't changed. The dashboard holds it in memory only, so a restart there was unauthenticated until the tab's next renewal (up to ~15 min); now it is back within a minute, and a firm created later binds within a minute too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
2.5 KiB
TypeScript
63 lines
2.5 KiB
TypeScript
import { NextRequest } from 'next/server';
|
|
import { putVestSession, listVestSessions } from '@/lib/vest-session';
|
|
import { autoBindVestWallet } from '@/lib/vest-wallet';
|
|
import { getTradableFirms } from '@/lib/firm-catalog';
|
|
import { getClients } from '@/lib/clients';
|
|
import { corsJson, corsPreflight } from '../../autobuyer/cors';
|
|
|
|
/** The wallets this machine's Vest firms trade (their saved credential), lower-case. */
|
|
function wantedWallets(): string[] {
|
|
const wallets = getTradableFirms()
|
|
.filter((f) => f.platform === 'vest')
|
|
.map((f) => f.apiKey.trim().toLowerCase());
|
|
return [...new Set(wallets)];
|
|
}
|
|
|
|
/**
|
|
* The extension's Vest relay (lib/vest-session.ts): POST the access token the
|
|
* signed-in Vest tab just renewed — { accessToken, expiresAt, primaryAddr,
|
|
* userId } — and it is held in memory for this machine's Vest client. A Vest
|
|
* firm here with no credential yet is bound to that login's wallet
|
|
* (lib/vest-wallet.ts). The extension posts again every minute, so a restart
|
|
* here is back on the session within a minute. GET
|
|
* lists what has been relayed (never the tokens) and which wallets this
|
|
* machine wants, so the extension knows whether to keep a Vest tab open and
|
|
* the dashboard can offer the address as the firm's credential.
|
|
*
|
|
* Same LAN-only, any-origin CORS as the AutoBuyer routes: the extension's
|
|
* service worker calls it.
|
|
*/
|
|
export async function POST(req: NextRequest) {
|
|
let body: Record<string, unknown>;
|
|
try {
|
|
body = await req.json() as Record<string, unknown>;
|
|
} catch {
|
|
return corsJson({ error: 'JSON body required' }, { status: 400 });
|
|
}
|
|
const refused = putVestSession({
|
|
accessToken: body.accessToken,
|
|
expiresAt: body.expiresAt,
|
|
primaryAddr: body.primaryAddr,
|
|
userId: body.userId,
|
|
});
|
|
if (refused) return corsJson({ ok: false, reason: refused }, { status: 400 });
|
|
|
|
const bound = autoBindVestWallet();
|
|
const wanted = wantedWallets();
|
|
const addr = String(body.primaryAddr).toLowerCase();
|
|
// A client waiting on this wallet's session syncs now, not at its next poll.
|
|
for (const client of getClients().values()) {
|
|
const vest = client as { onVestSession?: (wallet: string) => void };
|
|
vest.onVestSession?.(addr);
|
|
}
|
|
return corsJson({ ok: true, used: wanted.includes(addr), wanted, bound });
|
|
}
|
|
|
|
export async function GET() {
|
|
return corsJson({ sessions: listVestSessions(), wanted: wantedWallets() });
|
|
}
|
|
|
|
export async function OPTIONS() {
|
|
return corsPreflight();
|
|
}
|