Merge branch 'session-path' into 'master'
Add proper auto-reconnect with @wizardconnect/react See merge request riftenlabs/lib/wizardconnect!12
This commit is contained in:
commit
930f09a167
9 changed files with 277 additions and 9 deletions
|
|
@ -21,6 +21,7 @@ test:
|
||||||
extends: .node-common
|
extends: .node-common
|
||||||
script:
|
script:
|
||||||
- npm install
|
- npm install
|
||||||
|
- npm run build
|
||||||
- npm run test
|
- npm run test
|
||||||
|
|
||||||
build:
|
build:
|
||||||
|
|
|
||||||
34
docs/dapp.md
34
docs/dapp.md
|
|
@ -37,6 +37,12 @@ class DappConnectionManager extends EventEmitter {
|
||||||
* Caller is responsible for calling dappRelay.cleanup() afterwards. */
|
* Caller is responsible for calling dappRelay.cleanup() afterwards. */
|
||||||
sendDisconnect(message?: string): Promise<void>
|
sendDisconnect(message?: string): Promise<void>
|
||||||
|
|
||||||
|
/** Get raw PathXpub[] received in wallet_ready (for caching). */
|
||||||
|
getSessionPaths(): PathXpub[]
|
||||||
|
|
||||||
|
/** Restore cached xpub paths — enables getPubkey() without wallet_ready. */
|
||||||
|
restoreSessionPaths(paths: PathXpub[]): void
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
on("walletready", (msg: WalletReadyMessage) => void)
|
on("walletready", (msg: WalletReadyMessage) => void)
|
||||||
on("messagesent", (msg: ProtocolMessage) => void)
|
on("messagesent", (msg: ProtocolMessage) => void)
|
||||||
|
|
@ -72,6 +78,12 @@ removeFromChangeQueue(index: bigint): void
|
||||||
|
|
||||||
// Get the stored xpub node (after wallet_ready)
|
// Get the stored xpub node (after wallet_ready)
|
||||||
getXpubNode(childIndex: number): HdPublicNodeValid | undefined
|
getXpubNode(childIndex: number): HdPublicNodeValid | undefined
|
||||||
|
|
||||||
|
// Get raw PathXpub[] from wallet_ready (for caching)
|
||||||
|
getSessionPaths(): PathXpub[]
|
||||||
|
|
||||||
|
// Restore cached PathXpub[] — populates pubkeyState so getPubkey works without wallet_ready
|
||||||
|
restoreSessionPaths(paths: PathXpub[]): void
|
||||||
```
|
```
|
||||||
|
|
||||||
Child index values: `0` = receive, `1` = change, `7` = defi (Cauldron). These are
|
Child index values: `0` = receive, `1` = change, `7` = defi (Cauldron). These are
|
||||||
|
|
@ -172,6 +184,28 @@ The `disconnect` event fires in two cases:
|
||||||
`supported_protocols`. The dapp automatically sends a `ProtocolMismatch` disconnect to the
|
`supported_protocols`. The dapp automatically sends a `ProtocolMismatch` disconnect to the
|
||||||
wallet before emitting the event.
|
wallet before emitting the event.
|
||||||
|
|
||||||
|
### Session path persistence
|
||||||
|
|
||||||
|
After `wallet_ready`, the manager stores the raw `PathXpub[]` from the wallet. Use
|
||||||
|
`getSessionPaths()` to retrieve them (e.g. for caching in localStorage). On a subsequent
|
||||||
|
page load, restore them with `restoreSessionPaths()` so `getPubkey()` works immediately
|
||||||
|
without waiting for the wallet to reconnect:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// After wallet_ready — save for later
|
||||||
|
const paths = dappMgr.getSessionPaths();
|
||||||
|
localStorage.setItem("myapp-paths", JSON.stringify(paths));
|
||||||
|
|
||||||
|
// On page load — restore before wallet reconnects
|
||||||
|
const cached = JSON.parse(localStorage.getItem("myapp-paths") ?? "null");
|
||||||
|
if (cached) {
|
||||||
|
dappMgr.restoreSessionPaths(cached);
|
||||||
|
// getPubkey() now works without wallet_ready
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Throws if any xpub string is invalid (corrupt cached data should be cleared).
|
||||||
|
|
||||||
### Reconnection
|
### Reconnection
|
||||||
|
|
||||||
`updateConnection()` is called on every relay status change. When `status.status === "connected"`,
|
`updateConnection()` is called on every relay status change. When `status.status === "connected"`,
|
||||||
|
|
|
||||||
|
|
@ -130,9 +130,12 @@ level H (30% recovery), which allows a center logo overlay without breaking the
|
||||||
|
|
||||||
## Session persistence
|
## Session persistence
|
||||||
|
|
||||||
By default, `useWizardConnect` persists session credentials (private key, shared secret, and
|
By default, `useWizardConnect` persists session credentials (private key, shared secret,
|
||||||
wallet public key) to localStorage under the `wizardconnect-session` key. This enables
|
wallet public key, and xpub paths) to localStorage under the `wizardconnect-session` key.
|
||||||
auto-reconnect when the user refreshes the page.
|
This enables auto-reconnect when the user refreshes the page.
|
||||||
|
|
||||||
|
When xpub paths are cached, they are restored via `restoreSessionPaths()` on auto-reconnect
|
||||||
|
so that `getPubkey()` works immediately — before the wallet sends a new `wallet_ready`.
|
||||||
|
|
||||||
The persistence key and behavior can be customized:
|
The persistence key and behavior can be customized:
|
||||||
|
|
||||||
|
|
|
||||||
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -2487,9 +2487,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/flatted": {
|
"node_modules/flatted": {
|
||||||
"version": "3.4.1",
|
"version": "3.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||||
"integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==",
|
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ export function initiateDappRelay(
|
||||||
const cleanup = initiateRelay(
|
const cleanup = initiateRelay(
|
||||||
wrappedCallback,
|
wrappedCallback,
|
||||||
dappPrivateKey,
|
dappPrivateKey,
|
||||||
new Uint8Array(33),
|
walletPublicKeyNostr ?? new Uint8Array(33),
|
||||||
{
|
{
|
||||||
explicitRelayUrls: relayUrls,
|
explicitRelayUrls: relayUrls,
|
||||||
reconnectInterval: options.reconnectInterval,
|
reconnectInterval: options.reconnectInterval,
|
||||||
|
|
|
||||||
108
packages/dapp/src/dapp-connection-manager.test.ts
Normal file
108
packages/dapp/src/dapp-connection-manager.test.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright (C) 2026 Whiterun LLC,
|
||||||
|
// This software is licensed under the GNU Lesser General Public License (LGPL), version 3.0 or later.
|
||||||
|
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/lgpl-3.0.html
|
||||||
|
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { encodeHdPublicKey } from "@bitauth/libauth";
|
||||||
|
import type { PathXpub } from "@wizardconnect/core";
|
||||||
|
import { DappConnectionManager } from "./dapp-connection-manager.js";
|
||||||
|
|
||||||
|
// Build a valid xpub string from a deterministic test node.
|
||||||
|
// Uses the secp256k1 generator point (a known valid public key).
|
||||||
|
function makeTestXpub(): string {
|
||||||
|
const node = {
|
||||||
|
chainCode: new Uint8Array(32).fill(0xbb),
|
||||||
|
// secp256k1 generator point (compressed) — always valid
|
||||||
|
publicKey: Uint8Array.from(
|
||||||
|
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
||||||
|
.match(/.{2}/g)!
|
||||||
|
.map((h) => parseInt(h, 16)),
|
||||||
|
),
|
||||||
|
childIndex: 0,
|
||||||
|
depth: 3,
|
||||||
|
parentFingerprint: new Uint8Array(4),
|
||||||
|
};
|
||||||
|
const result = encodeHdPublicKey({ node, network: "mainnet" });
|
||||||
|
if (typeof result === "string")
|
||||||
|
throw new Error("Failed to encode test xpub: " + result);
|
||||||
|
return result.hdPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("DappConnectionManager", () => {
|
||||||
|
describe("getSessionPaths", () => {
|
||||||
|
it("returns empty array before wallet_ready", () => {
|
||||||
|
const mgr = new DappConnectionManager();
|
||||||
|
expect(mgr.getSessionPaths()).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("restoreSessionPaths", () => {
|
||||||
|
it("restores xpubs so getPubkey works", () => {
|
||||||
|
const mgr = new DappConnectionManager();
|
||||||
|
const xpub = makeTestXpub();
|
||||||
|
const paths: PathXpub[] = [{ name: "receive", xpub }];
|
||||||
|
|
||||||
|
mgr.restoreSessionPaths(paths);
|
||||||
|
|
||||||
|
expect(mgr.hasPath(0)).toBe(true);
|
||||||
|
const pk = mgr.getPubkey(0, 0n);
|
||||||
|
expect(pk).toBeInstanceOf(Uint8Array);
|
||||||
|
expect(pk!.length).toBe(33);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("makes paths available via getSessionPaths", () => {
|
||||||
|
const mgr = new DappConnectionManager();
|
||||||
|
const xpub = makeTestXpub();
|
||||||
|
const paths: PathXpub[] = [
|
||||||
|
{ name: "receive", xpub },
|
||||||
|
{ name: "change", xpub },
|
||||||
|
];
|
||||||
|
|
||||||
|
mgr.restoreSessionPaths(paths);
|
||||||
|
|
||||||
|
const stored = mgr.getSessionPaths();
|
||||||
|
expect(stored).toHaveLength(2);
|
||||||
|
expect(stored[0].name).toBe("receive");
|
||||||
|
expect(stored[1].name).toBe("change");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns a copy, not a reference", () => {
|
||||||
|
const mgr = new DappConnectionManager();
|
||||||
|
const xpub = makeTestXpub();
|
||||||
|
mgr.restoreSessionPaths([{ name: "receive", xpub }]);
|
||||||
|
|
||||||
|
const paths = mgr.getSessionPaths();
|
||||||
|
paths.push({ name: "change", xpub });
|
||||||
|
|
||||||
|
expect(mgr.getSessionPaths()).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws on invalid xpub strings", () => {
|
||||||
|
const mgr = new DappConnectionManager();
|
||||||
|
const paths: PathXpub[] = [{ name: "receive", xpub: "not-a-valid-xpub" }];
|
||||||
|
|
||||||
|
expect(() => mgr.restoreSessionPaths(paths)).toThrow("Invalid xpub");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can derive multiple child indices after restore", () => {
|
||||||
|
const mgr = new DappConnectionManager();
|
||||||
|
const xpub = makeTestXpub();
|
||||||
|
mgr.restoreSessionPaths([{ name: "receive", xpub }]);
|
||||||
|
|
||||||
|
for (const idx of [0n, 1n, 5n, 10n]) {
|
||||||
|
const pk = mgr.getPubkey(0, idx);
|
||||||
|
expect(pk, `index ${idx}`).toBeInstanceOf(Uint8Array);
|
||||||
|
expect(pk!.length, `index ${idx}`).toBe(33);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not affect paths not in the restore set", () => {
|
||||||
|
const mgr = new DappConnectionManager();
|
||||||
|
const xpub = makeTestXpub();
|
||||||
|
mgr.restoreSessionPaths([{ name: "receive", xpub }]);
|
||||||
|
|
||||||
|
expect(mgr.hasPath(0)).toBe(true); // receive = 0
|
||||||
|
expect(mgr.hasPath(1)).toBe(false); // change = 1
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -22,6 +22,7 @@ import {
|
||||||
childIndexOfPathName,
|
childIndexOfPathName,
|
||||||
isHdwalletv1Session,
|
isHdwalletv1Session,
|
||||||
} from "@wizardconnect/core";
|
} from "@wizardconnect/core";
|
||||||
|
import type { PathXpub } from "@wizardconnect/core";
|
||||||
import { DappPubkeyStateManager } from "./pubkey-state-manager.js";
|
import { DappPubkeyStateManager } from "./pubkey-state-manager.js";
|
||||||
|
|
||||||
export interface DappConnectionManagerEvents {
|
export interface DappConnectionManagerEvents {
|
||||||
|
|
@ -61,6 +62,7 @@ export class DappConnectionManager extends EventEmitter<DappConnectionManagerEve
|
||||||
private readonly supportedProtocols: string[] = [PROTOCOL_NAME];
|
private readonly supportedProtocols: string[] = [PROTOCOL_NAME];
|
||||||
|
|
||||||
private walletDiscovered = false;
|
private walletDiscovered = false;
|
||||||
|
private sessionPaths: PathXpub[] = [];
|
||||||
private pendingSignatureRequests = new Map<
|
private pendingSignatureRequests = new Map<
|
||||||
number,
|
number,
|
||||||
{
|
{
|
||||||
|
|
@ -199,6 +201,33 @@ export class DappConnectionManager extends EventEmitter<DappConnectionManagerEve
|
||||||
return this.pubkeyState.getXpubNode(childIndex);
|
return this.pubkeyState.getXpubNode(childIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the raw PathXpub entries received from the wallet.
|
||||||
|
* Available after wallet_ready is received.
|
||||||
|
*/
|
||||||
|
getSessionPaths(): PathXpub[] {
|
||||||
|
return [...this.sessionPaths];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore session paths from a previous session (e.g. from localStorage).
|
||||||
|
* Decodes xpub strings and populates pubkeyState so getPubkey() works
|
||||||
|
* without waiting for wallet_ready.
|
||||||
|
*/
|
||||||
|
restoreSessionPaths(paths: PathXpub[]): void {
|
||||||
|
this.sessionPaths = [...paths];
|
||||||
|
for (const pathInfo of paths) {
|
||||||
|
const decoded = decodeHdPublicKey(pathInfo.xpub);
|
||||||
|
if (typeof decoded === "string") {
|
||||||
|
throw new Error(
|
||||||
|
`[wizardconnect/dapp] Invalid xpub for path "${pathInfo.name}": ${decoded}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const ci = childIndexOfPathName(pathInfo.name as PathName);
|
||||||
|
this.pubkeyState.setXpubNode(ci, decoded.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Private protocol handling -------------------------------------------
|
// --- Private protocol handling -------------------------------------------
|
||||||
|
|
||||||
private onConnected(): void {
|
private onConnected(): void {
|
||||||
|
|
@ -294,7 +323,8 @@ export class DappConnectionManager extends EventEmitter<DappConnectionManagerEve
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store xpub nodes — no eager derivation; consumer drives it
|
// Store raw paths for getSessionPaths() and xpub nodes for derivation
|
||||||
|
this.sessionPaths = [...sessionData.paths];
|
||||||
for (const pathInfo of sessionData.paths) {
|
for (const pathInfo of sessionData.paths) {
|
||||||
const decoded = decodeHdPublicKey(pathInfo.xpub);
|
const decoded = decodeHdPublicKey(pathInfo.xpub);
|
||||||
if (typeof decoded === "string") {
|
if (typeof decoded === "string") {
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ vi.mock("@wizardconnect/dapp", () => {
|
||||||
walletIcon: null,
|
walletIcon: null,
|
||||||
updateConnection: vi.fn(),
|
updateConnection: vi.fn(),
|
||||||
sendDisconnect: vi.fn(() => Promise.resolve()),
|
sendDisconnect: vi.fn(() => Promise.resolve()),
|
||||||
|
getSessionPaths: vi.fn(() => []),
|
||||||
|
restoreSessionPaths: vi.fn(),
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
@ -280,6 +282,54 @@ describe("useWizardConnect", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("restores cached xpub paths on auto-reconnect", async () => {
|
||||||
|
const testPaths = [
|
||||||
|
{ name: "receive" as const, xpub: "xpub6test1" },
|
||||||
|
{ name: "change" as const, xpub: "xpub6test2" },
|
||||||
|
];
|
||||||
|
localStorage.setItem(
|
||||||
|
SESSION_KEY,
|
||||||
|
JSON.stringify({
|
||||||
|
privateKey: "d".repeat(64),
|
||||||
|
secret: "e".repeat(16),
|
||||||
|
walletPublicKey: "f".repeat(64),
|
||||||
|
paths: testPaths,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { DappConnectionManager } = vi.mocked(
|
||||||
|
await import("@wizardconnect/dapp"),
|
||||||
|
);
|
||||||
|
|
||||||
|
await renderHook({ persistSession: true });
|
||||||
|
await new Promise<void>((r) => setTimeout(r, 0));
|
||||||
|
|
||||||
|
// Manager should have been created and restoreSessionPaths called
|
||||||
|
const mgrInstance = DappConnectionManager.mock.results[0]?.value;
|
||||||
|
expect(mgrInstance.restoreSessionPaths).toHaveBeenCalledWith(testPaths);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not call restoreSessionPaths when no cached paths", async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
SESSION_KEY,
|
||||||
|
JSON.stringify({
|
||||||
|
privateKey: "d".repeat(64),
|
||||||
|
secret: "e".repeat(16),
|
||||||
|
walletPublicKey: "f".repeat(64),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { DappConnectionManager } = vi.mocked(
|
||||||
|
await import("@wizardconnect/dapp"),
|
||||||
|
);
|
||||||
|
|
||||||
|
await renderHook({ persistSession: true });
|
||||||
|
await new Promise<void>((r) => setTimeout(r, 0));
|
||||||
|
|
||||||
|
const mgrInstance = DappConnectionManager.mock.results[0]?.value;
|
||||||
|
expect(mgrInstance.restoreSessionPaths).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("passes relayUrls to initiateDappRelay", async () => {
|
it("passes relayUrls to initiateDappRelay", async () => {
|
||||||
const { result } = await renderHook({
|
const { result } = await renderHook({
|
||||||
relayUrls: ["wss://custom-relay:443"],
|
relayUrls: ["wss://custom-relay:443"],
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
initiateDappRelay,
|
initiateDappRelay,
|
||||||
type DappRelayResult,
|
type DappRelayResult,
|
||||||
type RelayUpdatePayload,
|
type RelayUpdatePayload,
|
||||||
|
type PathXpub,
|
||||||
binToHex,
|
binToHex,
|
||||||
} from "@wizardconnect/core";
|
} from "@wizardconnect/core";
|
||||||
import { DappConnectionManager } from "@wizardconnect/dapp";
|
import { DappConnectionManager } from "@wizardconnect/dapp";
|
||||||
|
|
@ -22,6 +23,8 @@ interface StoredSession {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
walletPublicKey?: string;
|
walletPublicKey?: string;
|
||||||
|
/** Raw xpub paths from wallet_ready, persisted for offline pubkey derivation. */
|
||||||
|
paths?: PathXpub[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSession(key: string): StoredSession | null {
|
function loadSession(key: string): StoredSession | null {
|
||||||
|
|
@ -97,6 +100,18 @@ export function useWizardConnect(
|
||||||
setWalletName(mgr.walletName);
|
setWalletName(mgr.walletName);
|
||||||
setWalletIcon(mgr.walletIcon);
|
setWalletIcon(mgr.walletIcon);
|
||||||
setState("connected");
|
setState("connected");
|
||||||
|
|
||||||
|
// Persist xpub paths so getPubkey works on next page load
|
||||||
|
if (persistSession) {
|
||||||
|
const paths = mgr.getSessionPaths();
|
||||||
|
if (paths.length > 0) {
|
||||||
|
const stored = loadSession(sessionKey);
|
||||||
|
if (stored) {
|
||||||
|
stored.paths = paths;
|
||||||
|
saveSession(sessionKey, stored);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mgr.on("disconnect", () => {
|
mgr.on("disconnect", () => {
|
||||||
|
|
@ -121,7 +136,10 @@ export function useWizardConnect(
|
||||||
setQrUri(relay.qrUri);
|
setQrUri(relay.qrUri);
|
||||||
|
|
||||||
if (persistSession) {
|
if (persistSession) {
|
||||||
|
// Merge with existing session to preserve walletPublicKey and paths
|
||||||
|
const existing = loadSession(sessionKey);
|
||||||
saveSession(sessionKey, {
|
saveSession(sessionKey, {
|
||||||
|
...existing,
|
||||||
privateKey: relay.credentials.privateKey,
|
privateKey: relay.credentials.privateKey,
|
||||||
secret: relay.credentials.secret,
|
secret: relay.credentials.secret,
|
||||||
});
|
});
|
||||||
|
|
@ -184,13 +202,37 @@ export function useWizardConnect(
|
||||||
autoReconnectAttempted.current = true;
|
autoReconnectAttempted.current = true;
|
||||||
|
|
||||||
const stored = loadSession(sessionKey);
|
const stored = loadSession(sessionKey);
|
||||||
|
console.log(
|
||||||
|
"[wizardconnect/react] auto-reconnect: stored session:",
|
||||||
|
stored
|
||||||
|
? `walletPublicKey=${!!stored.walletPublicKey}, paths=${stored.paths?.length ?? 0}`
|
||||||
|
: "null",
|
||||||
|
);
|
||||||
if (!stored || !stored.walletPublicKey) return;
|
if (!stored || !stored.walletPublicKey) return;
|
||||||
|
|
||||||
startRelay({
|
const started = startRelay({
|
||||||
privateKey: stored.privateKey,
|
privateKey: stored.privateKey,
|
||||||
secret: stored.secret,
|
secret: stored.secret,
|
||||||
walletPublicKey: stored.walletPublicKey,
|
walletPublicKey: stored.walletPublicKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Restore cached xpub paths so getPubkey works before wallet_ready
|
||||||
|
if (started && stored.paths?.length && managerRef.current) {
|
||||||
|
try {
|
||||||
|
managerRef.current.restoreSessionPaths(stored.paths);
|
||||||
|
} catch (e) {
|
||||||
|
// Corrupt cached paths — clear them but don't block reconnect
|
||||||
|
console.warn(
|
||||||
|
"[wizardconnect/react] Failed to restore cached xpub paths:",
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
const refreshed = loadSession(sessionKey);
|
||||||
|
if (refreshed) {
|
||||||
|
delete refreshed.paths;
|
||||||
|
saveSession(sessionKey, refreshed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}, [persistSession, sessionKey, startRelay]);
|
}, [persistSession, sessionKey, startRelay]);
|
||||||
|
|
||||||
// Cleanup on unmount
|
// Cleanup on unmount
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue