Auth now goes through an actual connected Solana wallet instead of a locally-generated ed25519 keypair. Phantom's window.solana provider is only reachable from a page's own JS world, not an isolated-world content script, so this adds a second world:'MAIN' content script (wallet-bridge.content.ts + wallet-bridge/inject.ts) that talks to window.solana directly and relays to the isolated world via window CustomEvents (wallet-bridge/relay.ts), matched by request id. wallet-connect.ts orchestrates: try a silent onlyIfTrusted connect on load; if that fails, show an on-page banner (wallet-bridge/banner.ts) whose click handler is what actually calls connect() -- Phantom requires a real user gesture for the approval popup on a first-ever connect, which a click relayed from the extension popup wouldn't count as by the time it reaches the wallet. background/wallet-auth.ts runs the REST auth flow (nonce -> ask the tab's content script to sign it -> verify -> store session token) once a wallet reports connected. ws-client.ts no longer force-retries with a known-bad token on auth failure; it calls onAuthExpired instead (which clears the token and prompts a silent wallet reconnect) and exposes reconnectNow() so background.ts can short-circuit the backoff wait once a fresh token exists. identity.ts and its tweetnacl dependency are gone -- no more stub signer. Untested against real Phantom (no browser automation available this session) -- flagged in CLAUDE.md as needing manual verification, along with a note that world:'MAIN' needs Firefox 128+. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YXiHuScXrjxBh7yFGAPq3B
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
import { browser } from 'wxt/browser';
|
|
import type { NexaMessage } from '@/shared/messaging';
|
|
import { callWallet } from './wallet-bridge/relay';
|
|
import { hideConnectBanner, setConnectBannerError, showConnectBanner } from './wallet-bridge/banner';
|
|
|
|
interface ConnectResult {
|
|
walletAddress: string;
|
|
}
|
|
|
|
interface SignResult {
|
|
signature: string;
|
|
}
|
|
|
|
async function reportConnected(walletAddress: string): Promise<void> {
|
|
hideConnectBanner();
|
|
await browser.runtime
|
|
.sendMessage({ type: 'nexa:wallet-connected', walletAddress } satisfies NexaMessage)
|
|
.catch(() => undefined);
|
|
}
|
|
|
|
/** Real user gesture (banner button click) — required for Phantom to show its connect approval popup on a first-ever connect. */
|
|
async function connectWithGesture(): Promise<void> {
|
|
try {
|
|
const { walletAddress } = await callWallet<ConnectResult>('connect', {});
|
|
await reportConnected(walletAddress);
|
|
} catch (err) {
|
|
setConnectBannerError(err instanceof Error ? err.message : String(err));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* `onlyIfTrusted` succeeds without any user interaction if this origin was
|
|
* already approved in a previous session — the normal case after the first
|
|
* connect. Falls back to the on-page banner (a real click) only when it isn't.
|
|
*/
|
|
async function attemptSilentConnect(): Promise<void> {
|
|
try {
|
|
const { walletAddress } = await callWallet<ConnectResult>('connect', { onlyIfTrusted: true });
|
|
await reportConnected(walletAddress);
|
|
} catch {
|
|
showConnectBanner(() => void connectWithGesture());
|
|
}
|
|
}
|
|
|
|
/** Wires wallet connect/sign into the page. Independent of any site adapter — runs regardless of whether a buy-button adapter matched. */
|
|
export function initWalletConnect(): void {
|
|
void attemptSilentConnect();
|
|
|
|
browser.runtime.onMessage.addListener((message: NexaMessage, _sender, sendResponse) => {
|
|
if (message?.type === 'nexa:wallet-sign-request') {
|
|
callWallet<SignResult>('signMessage', { message: message.nonce })
|
|
.then((result) => sendResponse(result))
|
|
.catch((err) => sendResponse({ error: err instanceof Error ? err.message : String(err) }));
|
|
return true;
|
|
}
|
|
|
|
if (message?.type === 'nexa:request-wallet-connect') {
|
|
void attemptSilentConnect();
|
|
return false;
|
|
}
|
|
|
|
return undefined;
|
|
});
|
|
}
|