๐ src/web3/vue/composables/useWeb3Session.ts
D-OPEN SOVEREIGN
Documentation & Insights
Reactive flat account surface (spec ยง13.2). Replaces the nested
`walletState: Ref<GlobalWalletState>` map โ a thin reactive wrapper over
sessionStore, not an owner of state; every call site reads the same singleton.1import { computed, type ComputedRef, ref, type Ref } from 'vue';
2import { sessionStore } from '../../session/store';
3import { syncAccount as syncAccountFn } from '../../session/sync';
4import type { Web3SessionAccount } from '../../session/types';
5import type { WalletConnector, WalletType } from '../../client/types';
6
7// sessionStore is a plain (non-Vue-reactive) singleton. This local counter is
8// bumped whenever the store changes โ via sessionStore.subscribe(), wired once
9// at module load (spec ยง13.2 G2: "wires sessionStore event listeners internally
10// via an install() call triggered at module load") โ so every computed below
11// recomputes regardless of which code path (mutations.ts, sync.ts, this
12// composable) triggered the change.
13const revision = ref(0);
14sessionStore.subscribe(() => {
15 revision.value++;
16});
17
18const isConnecting = ref(false);
19
20export interface UseWeb3SessionReturn {
21 accounts: ComputedRef<Web3SessionAccount[]>;
22 registeredAccounts: ComputedRef<Web3SessionAccount[]>;
23 activeAccount: ComputedRef<Web3SessionAccount | null>;
24 isConnecting: Ref<boolean>;
25
26 discoverAccounts(
27 connector: WalletConnector,
28 options?: { forceRefresh?: boolean }
29 ): Promise<Web3SessionAccount[]>;
30
31 syncAccount(address: string, chainId: number, wallet: WalletType, forceRefresh?: boolean): Promise<Web3SessionAccount>;
32
33 setActive(account: Web3SessionAccount): void;
34
35 accountsForChain(chainId: number): ComputedRef<Web3SessionAccount[]>;
36 registeredAccountsForChain(chainId: number): ComputedRef<Web3SessionAccount[]>;
37
38 lastSyncCounters(address: string, chainId: number): { lastPatchIndex: number; lastRestorationIndex: number };
39 setLastSavedPatchIndex(address: string, chainId: number, index: number): void;
40 setLastDownloadedSaveIndex(address: string, chainId: number, index: number): void;
41}
42
43/**
44 * Reactive flat account surface (spec ยง13.2). Replaces the nested
45 * `walletState: Ref<GlobalWalletState>` map โ a thin reactive wrapper over
46 * sessionStore, not an owner of state; every call site reads the same singleton.
47 */
48export function useWeb3Session(): UseWeb3SessionReturn {
49 // sessionStore mutates in place (array.push, Object.assign) โ returning the
50 // store's own references here would make a recomputed `.value` `===` the
51 // previous one, which makes Vue's computed skip notifying dependents even
52 // though `revision` changed. Slicing/spreading forces a fresh reference on
53 // every recompute so the reactivity actually propagates.
54 const accounts = computed(() => {
55 revision.value;
56 return sessionStore.getState().known.slice();
57 });
58
59 const registeredAccounts = computed(() => accounts.value.filter((a) => a.hasAccount));
60
61 const activeAccount = computed(() => {
62 revision.value;
63 const active = sessionStore.getState().active;
64 return active ? { ...active } : null;
65 });
66
67 async function discoverAccounts(
68 connector: WalletConnector,
69 options?: { forceRefresh?: boolean }
70 ): Promise<Web3SessionAccount[]> {
71 isConnecting.value = true;
72 try {
73 const chainId = await connector.getChainId();
74 const addresses = await connector.getAccounts();
75 const found: Web3SessionAccount[] = [];
76 for (const address of addresses) {
77 const account = await syncAccountFn(address, chainId, connector.type, 'production', options?.forceRefresh);
78 if (account.hasAccount) found.push(account);
79 }
80 return found;
81 } finally {
82 isConnecting.value = false;
83 }
84 }
85
86 async function syncAccountWrapped(
87 address: string,
88 chainId: number,
89 wallet: WalletType,
90 forceRefresh = false
91 ): Promise<Web3SessionAccount> {
92 return syncAccountFn(address, chainId, wallet, 'production', forceRefresh);
93 }
94
95 function setActive(account: Web3SessionAccount): void {
96 sessionStore.setActive(account);
97 }
98
99 function accountsForChain(chainId: number): ComputedRef<Web3SessionAccount[]> {
100 return computed(() => accounts.value.filter((a) => a.chainId === chainId));
101 }
102
103 function registeredAccountsForChain(chainId: number): ComputedRef<Web3SessionAccount[]> {
104 return computed(() => registeredAccounts.value.filter((a) => a.chainId === chainId));
105 }
106
107 function setLastSavedPatchIndex(address: string, chainId: number, index: number): void {
108 sessionStore.setLastSavedPatchIndex(address, chainId, index);
109 }
110
111 function setLastDownloadedSaveIndex(address: string, chainId: number, index: number): void {
112 sessionStore.setLastRestorationIndex(address, chainId, index);
113 }
114
115 return {
116 accounts,
117 registeredAccounts,
118 activeAccount,
119 isConnecting,
120 discoverAccounts,
121 syncAccount: syncAccountWrapped,
122 setActive,
123 accountsForChain,
124 registeredAccountsForChain,
125 lastSyncCounters: (address, chainId) => sessionStore.lastSyncCounters(address, chainId),
126 setLastSavedPatchIndex,
127 setLastDownloadedSaveIndex,
128 };
129}
130