Skip to content

Commit 110925a

Browse files
committed
feat(*): add isReady state to track manager initialization
Add `managerStatus` to track wallet manager initialization state and expose `isReady` getter in React, Vue, and Solid implementations. This allows consumers to know when the wallet manager has completed initialization and is ready for use. - Add `managerStatus` to `State` type and `defaultState` - Add `status` and `isReady` getters to `WalletManager` class - Remove `isReconnecting` from React implementation (see PR #330) - Add `isReady` to all framework implementations - Add tests for `isReady` behavior
1 parent 4e138ba commit 110925a

File tree

11 files changed

+280
-65
lines changed

11 files changed

+280
-65
lines changed

packages/use-wallet-react/src/__tests__/index.test.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,8 @@ describe('useWallet', () => {
364364
activeWalletAccounts,
365365
activeWalletAddresses,
366366
activeAccount,
367-
activeAddress
367+
activeAddress,
368+
isReady
368369
} = useWallet()
369370

370371
return (
@@ -374,6 +375,7 @@ describe('useWallet', () => {
374375
<li key={wallet.id}>{wallet.metadata.name}</li>
375376
))}
376377
</ul>
378+
<div data-testid="is-ready">Is Ready: {JSON.stringify(isReady)}</div>
377379
<div data-testid="active-network">Active Network: {JSON.stringify(activeNetwork)}</div>
378380
<div data-testid="active-wallet">Active Wallet: {JSON.stringify(activeWallet)}</div>
379381
<div data-testid="active-wallet-accounts">
@@ -400,6 +402,7 @@ describe('useWallet', () => {
400402
expect(listItems[index]).toHaveTextContent(wallet.metadata.name)
401403
})
402404

405+
expect(getByTestId('is-ready')).toHaveTextContent('false')
403406
expect(getByTestId('active-network')).toHaveTextContent(JSON.stringify(NetworkId.TESTNET))
404407
expect(getByTestId('active-wallet')).toHaveTextContent(JSON.stringify(null))
405408
expect(getByTestId('active-wallet-accounts')).toHaveTextContent(JSON.stringify(null))
@@ -411,6 +414,7 @@ describe('useWallet', () => {
411414
act(() => {
412415
mockStore.setState((state) => ({
413416
...state,
417+
managerStatus: 'ready',
414418
wallets: {
415419
[WalletId.DEFLY]: {
416420
accounts: [
@@ -429,6 +433,7 @@ describe('useWallet', () => {
429433
}))
430434
})
431435

436+
expect(getByTestId('is-ready')).toHaveTextContent('true')
432437
expect(getByTestId('active-network')).toHaveTextContent(JSON.stringify(NetworkId.TESTNET))
433438
expect(getByTestId('active-wallet')).toHaveTextContent(JSON.stringify(WalletId.DEFLY))
434439
expect(getByTestId('active-wallet-accounts')).toHaveTextContent(
@@ -474,4 +479,35 @@ describe('useWallet', () => {
474479
new algosdk.Algodv2(token, baseServer, port, headers)
475480
)
476481
})
482+
483+
it('initializes with isReady false and updates after resumeSessions', async () => {
484+
const { result } = renderHook(() => useWallet(), { wrapper })
485+
486+
// Initially should not be ready
487+
expect(result.current.isReady).toBe(false)
488+
489+
// Wait for resumeSessions to complete
490+
await act(async () => {
491+
await new Promise((resolve) => setTimeout(resolve, 0))
492+
})
493+
494+
// Should be ready after resumeSessions
495+
expect(result.current.isReady).toBe(true)
496+
})
497+
498+
it('updates isReady when manager status changes', () => {
499+
const { result } = renderHook(() => useWallet(), { wrapper })
500+
501+
expect(result.current.isReady).toBe(false)
502+
503+
// Simulate manager status change
504+
act(() => {
505+
mockStore.setState((state) => ({
506+
...state,
507+
managerStatus: 'ready'
508+
}))
509+
})
510+
511+
expect(result.current.isReady).toBe(true)
512+
})
477513
})

packages/use-wallet-react/src/index.tsx

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ export * from '@txnlab/use-wallet'
77

88
interface IWalletContext {
99
manager: WalletManager
10-
isReconnecting: boolean
1110
algodClient: algosdk.Algodv2
1211
setAlgodClient: React.Dispatch<React.SetStateAction<algosdk.Algodv2>>
1312
}
@@ -34,7 +33,10 @@ export const useWallet = () => {
3433
throw new Error('useWallet must be used within the WalletProvider')
3534
}
3635

37-
const { manager, isReconnecting, algodClient, setAlgodClient } = context
36+
const { manager, algodClient, setAlgodClient } = context
37+
38+
const managerStatus = useStore(manager.store, (state) => state.managerStatus)
39+
const isReady = managerStatus === 'ready'
3840

3941
const activeNetwork = useStore(manager.store, (state) => state.activeNetwork)
4042

@@ -109,7 +111,7 @@ export const useWallet = () => {
109111

110112
return {
111113
wallets,
112-
isReconnecting,
114+
isReady,
113115
algodClient,
114116
activeNetwork,
115117
activeWallet,
@@ -131,7 +133,6 @@ interface WalletProviderProps {
131133

132134
export const WalletProvider = ({ manager, children }: WalletProviderProps): JSX.Element => {
133135
const [algodClient, setAlgodClient] = React.useState(manager.algodClient)
134-
const [isReconnecting, setIsReconnecting] = React.useState(true)
135136

136137
React.useEffect(() => {
137138
manager.algodClient = algodClient
@@ -140,24 +141,14 @@ export const WalletProvider = ({ manager, children }: WalletProviderProps): JSX.
140141
const resumedRef = React.useRef(false)
141142

142143
React.useEffect(() => {
143-
const resumeSessions = async () => {
144-
try {
145-
await manager.resumeSessions()
146-
} catch (error) {
147-
console.error('Error resuming sessions:', error)
148-
} finally {
149-
setIsReconnecting(false)
150-
}
151-
}
152-
153144
if (!resumedRef.current) {
154-
resumeSessions()
145+
manager.resumeSessions()
155146
resumedRef.current = true
156147
}
157148
}, [manager])
158149

159150
return (
160-
<WalletContext.Provider value={{ manager, isReconnecting, algodClient, setAlgodClient }}>
151+
<WalletContext.Provider value={{ manager, algodClient, setAlgodClient }}>
161152
{children}
162153
</WalletContext.Provider>
163154
)

packages/use-wallet-solid/src/__tests__/index.test.tsx

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
WalletManager,
99
WalletId,
1010
type State,
11-
type WalletAccount
11+
type WalletAccount,
12+
type ManagerStatus
1213
} from '@txnlab/use-wallet'
1314
import { For, Show, createSignal } from 'solid-js'
1415
import { Wallet, WalletProvider, useWallet, useWalletManager } from '../index'
@@ -69,7 +70,8 @@ const TestComponent = () => {
6970
isWalletConnected,
7071
walletStore,
7172
wallets,
72-
algodClient
73+
algodClient,
74+
isReady
7375
} = useWallet()
7476

7577
const [magicEmail, setMagicEmail] = createSignal('')
@@ -83,6 +85,7 @@ const TestComponent = () => {
8385

8486
return (
8587
<div>
88+
<div data-testid="is-ready">{JSON.stringify(isReady())}</div>
8689
<div data-testid="active-account">{JSON.stringify(activeAccount())}</div>
8790
<div data-testid="active-address">{JSON.stringify(activeAddress())}</div>
8891
<div data-testid="active-network">{activeNetwork()}</div>
@@ -191,7 +194,8 @@ describe('useWallet', () => {
191194
wallets: {},
192195
activeWallet: null,
193196
activeNetwork: NetworkId.TESTNET,
194-
algodClient: new algosdk.Algodv2('', 'https://testnet-api.4160.nodely.dev/')
197+
algodClient: new algosdk.Algodv2('', 'https://testnet-api.4160.nodely.dev/'),
198+
managerStatus: 'initializing' as ManagerStatus
195199
}
196200

197201
mockStore = new Store<State>(defaultState)
@@ -532,6 +536,58 @@ describe('useWallet', () => {
532536

533537
expect(screen.getByTestId('active-network')).toHaveTextContent(NetworkId.MAINNET)
534538
})
539+
540+
it('initializes with isReady false and updates after resumeSessions', async () => {
541+
render(() => (
542+
<WalletProvider manager={mockWalletManager}>
543+
<TestComponent />
544+
</WalletProvider>
545+
))
546+
547+
// Initially should not be ready
548+
expect(screen.getByTestId('is-ready')).toHaveTextContent('false')
549+
550+
// Simulate manager status change
551+
mockStore.setState((state) => ({
552+
...state,
553+
managerStatus: 'ready'
554+
}))
555+
556+
// Should be ready after status change
557+
await waitFor(() => {
558+
expect(screen.getByTestId('is-ready')).toHaveTextContent('true')
559+
})
560+
})
561+
562+
it('updates isReady when manager status changes', async () => {
563+
render(() => (
564+
<WalletProvider manager={mockWalletManager}>
565+
<TestComponent />
566+
</WalletProvider>
567+
))
568+
569+
expect(screen.getByTestId('is-ready')).toHaveTextContent('false')
570+
571+
// Simulate manager status change
572+
mockStore.setState((state) => ({
573+
...state,
574+
managerStatus: 'ready'
575+
}))
576+
577+
await waitFor(() => {
578+
expect(screen.getByTestId('is-ready')).toHaveTextContent('true')
579+
})
580+
581+
// Simulate manager status change back to initializing (though this shouldn't happen in practice)
582+
mockStore.setState((state) => ({
583+
...state,
584+
managerStatus: 'initializing'
585+
}))
586+
587+
await waitFor(() => {
588+
expect(screen.getByTestId('is-ready')).toHaveTextContent('false')
589+
})
590+
})
535591
})
536592

537593
describe('WalletProvider', () => {

packages/use-wallet-solid/src/index.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,27 +57,22 @@ export interface Wallet {
5757
export function useWallet() {
5858
const manager = createMemo(() => useWalletManager())
5959

60-
const algodClient = useStore(manager().store, (state) => state.algodClient)
60+
const managerStatus = useStore(manager().store, (state) => state.managerStatus)
61+
const isReady = createMemo(() => managerStatus() === 'ready')
6162

63+
const algodClient = useStore(manager().store, (state) => state.algodClient)
6264
const walletStore = useStore(manager().store, (state) => state.wallets)
63-
6465
const walletState = (walletId: WalletId): WalletState | null => walletStore()[walletId] || null
65-
6666
const activeWalletId = useStore(manager().store, (state) => state.activeWallet)
67-
6867
const activeWallet = () => manager().getWallet(activeWalletId() as WalletId) || null
69-
7068
const activeWalletState = () => walletState(activeWalletId() as WalletId)
71-
7269
const activeWalletAccounts = () => activeWalletState()?.accounts ?? null
7370

7471
const activeWalletAddresses = () =>
7572
activeWalletAccounts()?.map((account) => account.address) ?? null
7673

7774
const activeAccount = () => activeWalletState()?.activeAccount ?? null
78-
7975
const activeAddress = () => activeAccount()?.address ?? null
80-
8176
const isWalletActive = (walletId: WalletId) => walletId === activeWalletId()
8277
const isWalletConnected = (walletId: WalletId) =>
8378
!!walletState(walletId)?.accounts.length || false
@@ -141,6 +136,7 @@ export function useWallet() {
141136
setActiveNetwork,
142137
signTransactions,
143138
transactionSigner,
144-
wallets: manager().wallets
139+
wallets: manager().wallets,
140+
isReady
145141
}
146142
}

packages/use-wallet-vue/src/__tests__/useWallet.test.ts

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,11 +314,12 @@ describe('useWallet', () => {
314314
})
315315

316316
it('integrates correctly with Vue component', async () => {
317-
const { wallets, activeWallet, activeAddress, activeNetwork } = useWallet()
317+
const { wallets, activeWallet, activeAddress, activeNetwork, isReady } = useWallet()
318318

319319
const TestComponent = {
320320
template: `
321321
<div>
322+
<div data-testid="is-ready">{{ isReady }}</div>
322323
<ul>
323324
<li v-for="wallet in wallets" :key="wallet.id" data-testid="wallet">
324325
{{ wallet.metadata.name }}
@@ -334,7 +335,8 @@ describe('useWallet', () => {
334335
wallets,
335336
activeWallet,
336337
activeAddress,
337-
activeNetwork
338+
activeNetwork,
339+
isReady
338340
}
339341
}
340342
}
@@ -388,4 +390,59 @@ describe('useWallet', () => {
388390
expect(wrapper.get('[data-testid="activeWallet"]').text()).toBe(WalletId.DEFLY)
389391
expect(wrapper.get('[data-testid="activeAddress"]').text()).toBe('address1')
390392
})
393+
394+
it('initializes with isReady false and updates when manager status changes', async () => {
395+
const { isReady } = useWallet()
396+
397+
// Initially should not be ready
398+
expect(isReady.value).toBe(false)
399+
400+
mockStore.setState((state) => ({
401+
...state,
402+
managerStatus: 'ready'
403+
}))
404+
405+
await nextTick()
406+
407+
expect(isReady.value).toBe(true)
408+
409+
// Change back to initializing (though this shouldn't happen in practice)
410+
mockStore.setState((state) => ({
411+
...state,
412+
managerStatus: 'initializing'
413+
}))
414+
415+
await nextTick()
416+
417+
expect(isReady.value).toBe(false)
418+
})
419+
420+
it('integrates isReady with Vue component', async () => {
421+
const TestComponent = {
422+
template: `
423+
<div>
424+
<div data-testid="is-ready">{{ isReady }}</div>
425+
</div>
426+
`,
427+
setup() {
428+
const { isReady } = useWallet()
429+
return { isReady }
430+
}
431+
}
432+
433+
const wrapper = mount(TestComponent)
434+
435+
// Initially not ready
436+
expect(wrapper.get('[data-testid="is-ready"]').text()).toBe('false')
437+
438+
mockStore.setState((state) => ({
439+
...state,
440+
managerStatus: 'ready'
441+
}))
442+
443+
await nextTick()
444+
445+
// Should show ready after status change
446+
expect(wrapper.get('[data-testid="is-ready"]').text()).toBe('true')
447+
})
391448
})

packages/use-wallet-vue/src/useWallet.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export function useWallet() {
3535
throw new Error('Algod client or setter not properly installed')
3636
}
3737

38+
const managerStatus = useStore(manager.store, (state) => state.managerStatus)
39+
const isReady = computed(() => managerStatus.value === 'ready')
40+
3841
const activeNetwork = useStore(manager.store, (state) => state.activeNetwork)
3942
const setActiveNetwork = async (networkId: NetworkId): Promise<void> => {
4043
if (networkId === activeNetwork.value) {
@@ -124,6 +127,7 @@ export function useWallet() {
124127

125128
return {
126129
wallets,
130+
isReady,
127131
algodClient: computed(() => {
128132
if (!algodClient.value) {
129133
throw new Error('Algod client is undefined')

0 commit comments

Comments
 (0)