Files
copilot/src/entrypoints/background.ts
T
selimaj-devandclaude b93a9c6f10 Support Phantom account switching and add sign-out
Wires Phantom's own accountChanged/disconnect provider events
(inject.ts) so switching accounts or disconnecting directly in
Phantom's UI is detected, not just our own connect/sign calls --
relayed as unsolicited postMessage events (relay.ts's onWalletEvent)
since they aren't a response to any request we made.

Adds a "Sign out" button in the popup (nexa:sign-out) that clears the
stored session, force-closes the WS connection via a new
ws-client.ts disconnect() (distinct from reconnectNow() -- it also
suppresses auto-reconnect until a new wallet connects), and asks the
content script to call provider.disconnect(), which revokes
Phantom's trust for the origin so the next silent connect correctly
fails until the user reconnects.

Fixes a real bug this surfaced: the existing "skip re-auth if a
session token exists" check in background.ts only checked for *any*
token, so switching Phantom accounts would have silently kept
authenticating as the old wallet. Session storage now tracks which
wallet it belongs to (backend-client.ts's storeSession(token,
walletAddress)) so the handler can tell "already signed in" apart
from "signed in as a different wallet than the one that just
connected."

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YXiHuScXrjxBh7yFGAPq3B
2026-09-07 13:48:05 +02:00

71 lines
2.9 KiB
TypeScript

import { clearSessionToken, getSessionToken, getStoredSession } from '@/background/backend-client';
import { getConnectionStatus, setConnectionStatus } from '@/background/connection-status';
import { applyLockState, getLockState } from '@/background/lock-state';
import { handleWalletConnected, requestWalletDisconnect, requestWalletReconnect } from '@/background/wallet-auth';
import { connectWsClient } from '@/background/ws-client';
import type { NexaMessage } from '@/shared/messaging';
export default defineBackground(() => {
const ws = connectWsClient({
getToken: getSessionToken,
onLockState: (state) => void applyLockState(state),
onStatusChange: (status) => void setConnectionStatus(status),
onAuthExpired: () => {
void clearSessionToken().then(() => requestWalletReconnect());
},
});
async function signOut(): Promise<void> {
await clearSessionToken();
ws.disconnect();
await requestWalletDisconnect();
}
browser.runtime.onMessage.addListener((message: NexaMessage, sender, sendResponse) => {
switch (message?.type) {
case 'nexa:get-lock-state':
getLockState().then(sendResponse);
return true; // keep the message channel open for the async response
case 'nexa:get-connection-status':
sendResponse(getConnectionStatus());
return false;
case 'nexa:open-popup':
// Best-effort: not all browsers/contexts allow programmatic popup
// opening outside a direct user gesture on the action icon.
browser.action.openPopup().catch(() => undefined);
return false;
case 'nexa:wallet-connected':
// A content script reports this on every page load (it always tries
// a silent onlyIfTrusted connect first), AND whenever the user
// switches accounts in Phantom's own UI. Only re-authenticate (which
// means asking Phantom to sign a fresh nonce — a popup every time,
// unlike a silent connect) when this isn't the wallet we're already
// signed in as; a bare "do we have a token" check can't tell those
// apart from an account switch.
getStoredSession()
.then((session) => {
if (session?.walletAddress === message.walletAddress) return;
return handleWalletConnected(sender.tab?.id, message.walletAddress).then(() => ws.reconnectNow());
})
.catch((err) => console.debug('[nexa/background] wallet auth failed:', err)); // ws-client's own retry loop keeps trying regardless
return false;
case 'nexa:wallet-disconnected':
// Disconnected directly in Phantom's UI (not via our own sign-out
// flow, which already clears/disconnects itself) — treat the same way.
void clearSessionToken().then(() => ws.disconnect());
return false;
case 'nexa:sign-out':
void signOut();
return false;
default:
return undefined;
}
});
});