๐Ÿ“„ 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