fix(wc): pairing diagnostics + 45s visible-timeout
Aegis (and anything built on @wizardconnect/dapp 0.2.x) can pair on its
own side and never advance ours — the dapp library's handleWalletReady
silently swallows invalid session payloads at dapp-connection-manager.js:436:
if (!isHdwalletv1Session(sessionData)) {
console.error("[wizardconnect/dapp] Invalid hdwalletv1 session data:", sessionData);
return; // ← no 'walletready', no 'disconnect' event — just a stuck chip
}
When this fires, nothing in our chip updates. Previously the user watched
"connecting…" forever with no clue why.
Two additions:
Diagnostic ring buffer (last 40 relay messages):
manager.on('messagereceived') / ('messagesent') / ('reconnecting') →
push {t, dir, action, raw} into wc.diag, exposed as window.wcDiag.
After a failed pairing attempt the user can paste
JSON.stringify(window.wcDiag, (_,v) => typeof v === 'bigint' ? v.toString() : v, 2)
from the browser console, which — combined with the console.error the
dapp lib prints — tells us precisely which of the three silent-failure
modes hit: session-shape rejected, protocol mismatch, or no inbound at
all (relay dead).
Visible 45-second timeout:
If snap.phase is still 'awaiting-scan' 45s after connect(), snap flips
to 'error' with a message pointing the user at window.wcDiag. Any
inbound message resets the timer (so a slow-reconnect but alive wallet
doesn't false-trigger). cleanup() + walletready both clear the timer.
Zero behavior change in the happy path — the diagnostic listeners are
purely observational, and the timeout only fires when the dapp lib's
silent-return bug would have left us hung.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
a7e501087b
commit
e66a8d6c8a
1 changed files with 38 additions and 0 deletions
|
|
@ -31,6 +31,11 @@ export class WcClient extends EventEmitter<WcEvents> {
|
|||
private manager: DappConnectionManager | null = null;
|
||||
private relay: DappRelayResult | null = null;
|
||||
private snap: SessionSnapshot = { ...initialSnapshot };
|
||||
private pairingTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
/** Diagnostic log of raw relay messages — exposed as window.wcDiag so a user
|
||||
* pairing with a non-stock wallet can show us what it sent. Capped at the
|
||||
* last 40 messages so a long-lived session doesn't leak memory. */
|
||||
readonly diag: Array<{ t: number; dir: 'in' | 'out'; action?: string; raw?: unknown }> = [];
|
||||
readonly network: Network;
|
||||
readonly dappName: string;
|
||||
readonly dappIcon?: string;
|
||||
|
|
@ -61,6 +66,25 @@ export class WcClient extends EventEmitter<WcEvents> {
|
|||
manager.on('walletready', (msg: WalletReadyMessage) => this.onWalletReady(msg));
|
||||
manager.on('disconnect', (_reason, message) => this.onDisconnect(message ?? 'wallet disconnected'));
|
||||
|
||||
// Diagnostic listeners — surface any raw relay traffic so we can see why
|
||||
// a wallet pair-and-silence event happened. The dapp lib bails SILENTLY
|
||||
// in handleWalletReady() when a wallet sends a session shape its validator
|
||||
// doesn't accept (dapp-connection-manager.js:436) — the only breadcrumb
|
||||
// is a console.error we might miss. Mirror everything here into `diag`.
|
||||
const push = (dir: 'in' | 'out', action: string | undefined, raw: unknown) => {
|
||||
this.diag.push({ t: Date.now(), dir, action, raw });
|
||||
if (this.diag.length > 40) this.diag.shift();
|
||||
};
|
||||
manager.on('messagereceived', (msg: unknown) => {
|
||||
const action = (msg as { action?: string } | null)?.action;
|
||||
push('in', action, msg);
|
||||
// Reset the pairing timeout on ANY inbound message — the wallet is
|
||||
// alive, we just might not have reached `paired` yet.
|
||||
if (this.pairingTimer) { clearTimeout(this.pairingTimer); this.pairingTimer = null; }
|
||||
});
|
||||
manager.on('messagesent', (msg: unknown) => push('out', (msg as { action?: string } | null)?.action, msg));
|
||||
manager.on('reconnecting', () => push('in', 'relay-reconnecting', null));
|
||||
|
||||
try {
|
||||
const relay = initiateDappRelay((client, status) => manager.updateConnection(client, status));
|
||||
this.relay = relay;
|
||||
|
|
@ -68,7 +92,19 @@ export class WcClient extends EventEmitter<WcEvents> {
|
|||
this.update({ uri: relay.uri, qrUri: relay.qrUri });
|
||||
} catch (e) {
|
||||
this.onError(e instanceof Error ? e.message : String(e));
|
||||
return { ...this.snap };
|
||||
}
|
||||
|
||||
// Visible timeout — if nothing (not even a message) arrives in 45 seconds
|
||||
// of the user scanning the QR, surface a real error so they don't stare
|
||||
// at a frozen chip. 45s = long enough for Aegis + a slow relay reconnect;
|
||||
// short enough that a genuinely-dead relay doesn't leave the user guessing.
|
||||
this.pairingTimer = setTimeout(() => {
|
||||
if (this.snap.phase === 'awaiting-scan') {
|
||||
this.onError('wallet did not respond — scan the QR again, check that Aegis has internet, or copy the diagnostic from window.wcDiag');
|
||||
}
|
||||
}, 45_000);
|
||||
|
||||
return { ...this.snap };
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +118,7 @@ export class WcClient extends EventEmitter<WcEvents> {
|
|||
}
|
||||
try {
|
||||
const cashaddr = cashaddrFromPubkey(pubkey, this.network);
|
||||
if (this.pairingTimer) { clearTimeout(this.pairingTimer); this.pairingTimer = null; }
|
||||
this.update({ phase: 'paired', walletName: msg.wallet_name ?? 'wallet', cashaddr, error: null });
|
||||
} catch (e) {
|
||||
this.onError('cashaddr derivation failed: ' + (e instanceof Error ? e.message : String(e)));
|
||||
|
|
@ -130,6 +167,7 @@ export class WcClient extends EventEmitter<WcEvents> {
|
|||
}
|
||||
|
||||
private cleanup(): void {
|
||||
if (this.pairingTimer) { clearTimeout(this.pairingTimer); this.pairingTimer = null; }
|
||||
try { this.manager?.sendDisconnect('bye').catch(() => {}); } catch { /* noop */ }
|
||||
try { this.relay?.cleanup(); } catch { /* noop */ }
|
||||
this.manager = null;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue