import type { InjectedMetadataKnown } from "@polkadot/extension-inject/types"; import { VARAN_WALLET_LS_KEY } from "./constants"; import { generateRequestId } from "./helpers"; import { WalletStorage } from "./types"; type WalletStoreType = { accounts: string[]; injectedMetadataKnown?: InjectedMetadataKnown; transactions: { id: number; signedTx: string; status: 'signing' | 'success' | 'failed'; }[]; } const defaultStore: WalletStoreType = { accounts: [], injectedMetadataKnown: undefined, transactions: [], } export class WalletStore { private readonly store: WalletStorage; private readonly storeSubscribe: (cb: (ev: StorageEvent) => void) => void; private _store: WalletStoreType; constructor() { this.store = window.localStorage as WalletStorage; this._store = Object.assign(defaultStore, JSON.parse(this.store.getItem(VARAN_WALLET_LS_KEY) || '{}')); this.storeSubscribe = (cb): void => { window.addEventListener('storage', cb); }; this._setupListeners(); } private callbacks: { [K in keyof WalletStoreType]: ({ cb: (value: WalletStoreType[K]) => void, id: number; })[]; } = { accounts: [], injectedMetadataKnown: [], transactions: [], }; private _setupListeners(): void { let currentState = this._store; this.storeSubscribe(ev => { ev.stopImmediatePropagation(); if (ev.key !== VARAN_WALLET_LS_KEY) return; const data = JSON.parse(ev.newValue || '{}') as WalletStoreType; const keys = Object.keys(data) as (keyof WalletStoreType)[]; for (const key of keys) { if (key === 'injectedMetadataKnown') continue; if (key === 'accounts') { const currentAccounts = currentState[key]; const dataAccounts = data[key]; if (currentAccounts.length !== dataAccounts.length) { currentState = data; this._store = data; this.dispatch(key); continue; } let shouldDispatch = false; dataAccounts.forEach(account => { if (!currentAccounts.includes(account)) { shouldDispatch = true; } }); if (shouldDispatch) { currentState = data; this._store = data; this.dispatch(key); } continue; } if (key === 'transactions') { const currentTransactions = currentState[key]; const dataTransactions = data[key]; if (currentTransactions.length !== dataTransactions.length) { currentState = data; this._store = data; this.dispatch(key); continue; } let shouldDispatch = false; dataTransactions.forEach(transaction => { const currentTransaction = currentTransactions.find(t => t.id === transaction.id); if (currentTransaction && currentTransaction.status !== transaction.status) { shouldDispatch = true; } }); if (shouldDispatch) { currentState = data; this._store = data; this.dispatch(key); } continue; } } }); } private dispatch(key: Key): void { const callbacks = this.callbacks[key]; if (!callbacks || callbacks.length === 0) return; callbacks.forEach(c => c.cb(this._store[key])); } get(key: Key): WalletStoreType[Key] { return this._store[key] || defaultStore[key]; } set(key: Key, value: WalletStoreType[Key]): void { this._store[key] = value; this.dispatch(key); this.store.setItem(VARAN_WALLET_LS_KEY, JSON.stringify(this._store)); } remove(key: Key): void { const value = this._store[key]; if (value) { this._store[key] = defaultStore[key]; } this.store.setItem(VARAN_WALLET_LS_KEY, JSON.stringify(this._store)); } clear(): void { this.store.removeItem(VARAN_WALLET_LS_KEY); } subscribe(key: Key, cb: (value: WalletStoreType[Key]) => void): number { if (this.callbacks[key] == undefined) { this.callbacks[key] = []; } const id = generateRequestId(); this.callbacks[key].push({cb, id}); return id; } unsubscribe(key: Key, id: number): void { if (this.callbacks[key] == undefined || this.callbacks[key].length === 0) return; const filtered = this.callbacks[key].filter(c => c.id !== id); this.callbacks[key] = filtered as typeof this.callbacks[Key]; } }