Skip to content

Commit

Permalink
feat(network): persist user network customizations
Browse files Browse the repository at this point in the history
Add support for persisting user-modified network configurations while preserving
developer-provided defaults. This enables users to customize network settings
that persist between sessions without affecting the base configuration.

- Add `customNetworkConfigs` to persisted state
- Store base network config separately from runtime config
- Compare against base config to identify user customizations
- Update tests to verify persistence behavior
  • Loading branch information
drichar committed Jan 20, 2025
1 parent f12b448 commit a5dccfa
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 39 deletions.
9 changes: 6 additions & 3 deletions packages/use-wallet-solid/src/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const mockStore = new Store<State>({
activeWallet: null,
algodClient: new algosdk.Algodv2('', 'https://testnet-api.algonode.cloud', ''),
managerStatus: 'ready',
wallets: {}
wallets: {},
customNetworkConfigs: {}
})

// Create mock wallet manager
Expand All @@ -42,7 +43,8 @@ beforeEach(() => {
activeWallet: null,
algodClient: new algosdk.Algodv2('', 'https://testnet-api.algonode.cloud', ''),
managerStatus: 'ready',
wallets: {}
wallets: {},
customNetworkConfigs: {}
}))
})

Expand Down Expand Up @@ -538,7 +540,8 @@ describe('useWallet', () => {
activeWallet: null,
activeNetwork: NetworkId.TESTNET,
algodClient: new algosdk.Algodv2('', 'https://testnet-api.4160.nodely.dev/'),
managerStatus: 'initializing' as ManagerStatus
managerStatus: 'initializing' as ManagerStatus,
customNetworkConfigs: {}
}

mockStore = new Store<State>(defaultState)
Expand Down
3 changes: 2 additions & 1 deletion packages/use-wallet-vue/src/__tests__/useNetwork.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ const setupMocks = () => {
activeWallet: null,
algodClient: new algosdk.Algodv2('', 'https://testnet-api.algonode.cloud', ''),
managerStatus: 'ready',
wallets: {}
wallets: {},
customNetworkConfigs: {}
})

mockWalletManager = new WalletManager({
Expand Down
3 changes: 2 additions & 1 deletion packages/use-wallet-vue/src/__tests__/useWallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ const setupMocks = () => {
activeWallet: null,
algodClient: new algosdk.Algodv2('', 'https://testnet-api.algonode.cloud', ''),
managerStatus: 'ready',
wallets: {}
wallets: {},
customNetworkConfigs: {}
})

mockWalletManager = new WalletManager({
Expand Down
251 changes: 240 additions & 11 deletions packages/use-wallet/src/__tests__/manager.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Store } from '@tanstack/store'
import algosdk from 'algosdk'
import { logger } from 'src/logger'
import { NetworkConfigBuilder } from 'src/network'
import { createNetworkConfig, DEFAULT_NETWORKS, NetworkConfigBuilder } from 'src/network'
import { LOCAL_STORAGE_KEY, PersistedState, State, DEFAULT_STATE } from 'src/store'
import { WalletManager } from 'src/manager'
import { StorageAdapter } from 'src/storage'
Expand Down Expand Up @@ -458,7 +458,8 @@ describe('WalletManager', () => {
activeWallet: WalletId.KIBISIS,
activeNetwork: 'betanet',
algodClient: new algosdk.Algodv2('', 'https://betanet-api.4160.nodely.dev/'),
managerStatus: 'ready'
managerStatus: 'ready',
customNetworkConfigs: {}
}
})

Expand Down Expand Up @@ -503,22 +504,246 @@ describe('WalletManager', () => {

describe('savePersistedState', () => {
it('saves state to local storage', async () => {
const stateToSave: PersistedState = {
const manager = new WalletManager({
wallets: [WalletId.DEFLY, WalletId.KIBISIS]
})
await manager.setActiveNetwork('mainnet')

const expectedState: PersistedState = {
wallets: {},
activeWallet: null,
activeNetwork: 'mainnet'
activeNetwork: 'mainnet',
customNetworkConfigs: {}
}

expect(vi.mocked(StorageAdapter.setItem)).toHaveBeenCalledWith(
LOCAL_STORAGE_KEY,
JSON.stringify(expectedState)
)
})

it('persists custom network configurations', () => {
const manager = new WalletManager({
wallets: [WalletId.DEFLY, WalletId.KIBISIS]
wallets: [WalletId.DEFLY]
})
await manager.setActiveNetwork('mainnet')

const customAlgod = {
token: 'custom-token',
baseServer: 'https://custom-server.com',
headers: { 'X-API-Key': 'custom-key' },
port: '443'
}

manager.updateNetworkAlgod('mainnet', customAlgod)

// Verify the persisted state includes the custom network config
const expectedState: PersistedState = {
wallets: {},
activeWallet: null,
activeNetwork: 'testnet',
customNetworkConfigs: {
mainnet: {
algod: customAlgod
}
}
}

expect(vi.mocked(StorageAdapter.setItem)).toHaveBeenLastCalledWith(
LOCAL_STORAGE_KEY,
JSON.stringify(expectedState)
)
})

it('only persists modified network configurations', () => {
const manager = new WalletManager({
wallets: [WalletId.DEFLY]
})

// Get the default config for comparison
const defaultConfig = createNetworkConfig()

// Update only one property
manager.updateNetworkAlgod('mainnet', {
token: 'custom-token'
})

// The persisted state should only include the modified property
const expectedState: PersistedState = {
wallets: {},
activeWallet: null,
activeNetwork: 'testnet',
customNetworkConfigs: {
mainnet: {
algod: {
...defaultConfig.mainnet.algod,
token: 'custom-token'
}
}
}
}

expect(vi.mocked(StorageAdapter.setItem)).toHaveBeenCalledWith(
LOCAL_STORAGE_KEY,
JSON.stringify(stateToSave)
JSON.stringify(expectedState)
)
})

it('removes network from customNetworkConfigs when reset to default', () => {
const manager = new WalletManager({
wallets: [WalletId.DEFLY]
})

// Get the default config
const defaultConfig = createNetworkConfig()

// First modify the network
manager.updateNetworkAlgod('mainnet', {
token: 'custom-token'
})

// Then reset it back to default
manager.updateNetworkAlgod('mainnet', defaultConfig.mainnet.algod)

// The persisted state should not include mainnet in customNetworkConfigs
const expectedState: PersistedState = {
wallets: {},
activeWallet: null,
activeNetwork: 'testnet',
customNetworkConfigs: {}
}

expect(vi.mocked(StorageAdapter.setItem)).toHaveBeenLastCalledWith(
LOCAL_STORAGE_KEY,
JSON.stringify(expectedState)
)
})
})

describe('network configuration persistence', () => {
it('loads persisted network configurations on initialization', () => {
mockInitialState = {
wallets: {},
activeWallet: null,
activeNetwork: 'testnet',
algodClient: new algosdk.Algodv2('', 'https://testnet-api.4160.nodely.dev'),
managerStatus: 'ready',
customNetworkConfigs: {
mainnet: {
algod: {
token: 'persisted-token',
baseServer: 'https://mainnet-api.4160.nodely.dev',
headers: {},
port: ''
},
isTestnet: false,
genesisHash: 'wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=',
genesisId: 'mainnet-v1.0',
caipChainId: 'algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k'
}
}
}

const manager = new WalletManager({
wallets: [WalletId.DEFLY]
})

// Verify the custom config was loaded
expect(manager.networks.mainnet.algod).toEqual(
mockInitialState.customNetworkConfigs.mainnet.algod
)
})

it('merges persisted configurations with provided configurations', () => {
mockInitialState = {
wallets: {},
activeWallet: null,
activeNetwork: 'testnet',
algodClient: new algosdk.Algodv2('', 'https://testnet-api.4160.nodely.dev/'),
managerStatus: 'ready',
customNetworkConfigs: {
mainnet: {
algod: {
token: 'persisted-token',
baseServer: 'https://persisted-server.com',
headers: {},
port: ''
},
isTestnet: false,
genesisHash: 'wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=',
genesisId: 'mainnet-v1.0',
caipChainId: 'algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k'
}
}
}

// Provide configuration in constructor with different baseServer
const providedNetworks = {
mainnet: {
...DEFAULT_NETWORKS.mainnet,
algod: {
...DEFAULT_NETWORKS.mainnet.algod,
baseServer: 'https://provided-server.com'
}
},
testnet: DEFAULT_NETWORKS.testnet,
betanet: DEFAULT_NETWORKS.betanet,
fnet: DEFAULT_NETWORKS.fnet,
localnet: DEFAULT_NETWORKS.localnet
}

const manager = new WalletManager({
wallets: [WalletId.DEFLY],
networks: providedNetworks
})

// Verify the configs were merged correctly, with persisted taking precedence
expect(manager.networks.mainnet.algod).toEqual({
token: 'persisted-token',
baseServer: 'https://persisted-server.com',
headers: {},
port: ''
})
})

it('does not persist developer-provided configurations', () => {
// Provide custom configuration in constructor
const providedNetworks = {
mainnet: {
algod: {
...DEFAULT_NETWORKS.mainnet.algod,
baseServer: 'https://custom-server.com'
}
}
}

const manager = new WalletManager({
networks: providedNetworks,
defaultNetwork: 'mainnet'
})

// Get the last persisted state
const lastCall = vi.mocked(StorageAdapter.setItem).mock.lastCall
const persistedState = JSON.parse(lastCall?.[1] || '{}')

// Verify that the developer's custom configuration wasn't persisted
expect(persistedState.customNetworkConfigs).toEqual({})

// Now update the network config through the manager
manager.updateNetworkAlgod('mainnet', {
baseServer: 'https://user-server.com'
})

// Get the updated persisted state
const updatedCall = vi.mocked(StorageAdapter.setItem).mock.lastCall
const updatedState = JSON.parse(updatedCall?.[1] || '{}')

// Verify that only the user's modification was persisted
expect(updatedState.customNetworkConfigs.mainnet.algod).toEqual({
token: '',
baseServer: 'https://user-server.com',
headers: {}
})
})
})

describe('activeWallet', () => {
Expand All @@ -541,7 +766,8 @@ describe('WalletManager', () => {
activeWallet: WalletId.KIBISIS,
activeNetwork: 'betanet',
algodClient: new algosdk.Algodv2('', 'https://betanet-api.4160.nodely.dev/'),
managerStatus: 'ready'
managerStatus: 'ready',
customNetworkConfigs: {}
}
})

Expand Down Expand Up @@ -735,7 +961,8 @@ describe('WalletManager', () => {
activeWallet: null,
activeNetwork: 'mainnet',
algodClient: new algosdk.Algodv2('', 'https://mainnet-api.4160.nodely.dev'),
managerStatus: 'ready'
managerStatus: 'ready',
customNetworkConfigs: {}
}

const manager = new WalletManager({
Expand All @@ -753,7 +980,8 @@ describe('WalletManager', () => {
activeWallet: null,
activeNetwork: 'mainnet',
algodClient: new algosdk.Algodv2('', 'https://mainnet-api.4160.nodely.dev'),
managerStatus: 'ready'
managerStatus: 'ready',
customNetworkConfigs: {}
}

const manager = new WalletManager({
Expand Down Expand Up @@ -786,7 +1014,8 @@ describe('WalletManager', () => {
activeWallet: WalletId.PERA,
activeNetwork: 'mainnet',
algodClient: new algosdk.Algodv2('', 'https://mainnet-api.4160.nodely.dev'),
managerStatus: 'ready'
managerStatus: 'ready',
customNetworkConfigs: {}
}

const manager = new WalletManager({
Expand Down
6 changes: 4 additions & 2 deletions packages/use-wallet/src/__tests__/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,8 @@ describe('Type Guards', () => {
const defaultState: PersistedState = {
wallets: {},
activeWallet: null,
activeNetwork: 'testnet'
activeNetwork: 'testnet',
customNetworkConfigs: {}
}
expect(isValidPersistedState(defaultState)).toBe(true)

Expand Down Expand Up @@ -601,7 +602,8 @@ describe('Type Guards', () => {
}
},
activeWallet: WalletId.DEFLY,
activeNetwork: 'testnet'
activeNetwork: 'testnet',
customNetworkConfigs: {}
}
expect(isValidPersistedState(state)).toBe(true)
})
Expand Down
Loading

0 comments on commit a5dccfa

Please sign in to comment.