diff --git a/Roadmap.md b/Roadmap.md index 8887884..5d78776 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -22,7 +22,7 @@ -- -5. [ ] Wallet +5. [X] Wallet 1. [X] Overview screen 1. [X] Balance 2. [X] Network/API status @@ -34,39 +34,39 @@ 1. [X] List 4. [X] Manage accounts 1. [X] Create new - 2. [ ] Rename - 3. [ ] Delete + 2. [X] Rename + 3. [X] Delete 4. [X] View only accounts (see Y) 5. [X] Manage keys 1. [X] Create new 2. [X] Import secret key - 3. [ ] Import from Ledger - 4. [ ] Delete (?) + 3. [X] Import from Ledger + 4. [X] Delete (?) 5. [X] Export secret key - 6. [ ] Send transaction + 6. [X] Send transaction 1. [X] Single signature // Just sign with selected account - 2. [ ] Multiple Signatures - 1. [ ] Sign with selected account - 2. [ ] Sign with another account in the wallet - 3. [ ] Parse transaction and signatures (that sent by other party) and sign - 3. [ ] Support all TX kinds + 2. [X] Multiple Signatures + 1. [X] Sign with selected account + 2. [X] Sign with another account in the wallet + 3. [X] Parse transaction and signatures (that sent by other party) and sign + 3. [X] Support all TX kinds 1. [X] SingleSig.Spawn 2. [X] SingleSig.Spend - 3. [ ] MultiSig.Spawn - 4. [ ] MultiSig.Spend - 5. [ ] Vesting.Drain - 6. [ ] Vault.Spawn - 7. [ ] Sign message - 8. [ ] Verify signed message + 3. [X] MultiSig.Spawn + 4. [X] MultiSig.Spend + 5. [X] Vesting.Drain + 6. [X] Vault.Spawn + 7. [X] Sign message + 8. [X] Verify signed message 6. [ ] Settings 1. [X] Network settings - RPC - Remote node status 1. [X] Add network - 2. [ ] Edit/view network (?) - 3. [ ] Delete network + 2. [X] Edit/view network (?) + 3. [X] Delete network 2. [ ] Change password 7. [X] Backup wallet @@ -98,10 +98,10 @@ Features - [X] Auto-fetch transactions and rewards periodically -- [ ] Add SMH/Smidge Inputs +- [X] Add SMH/Smidge Inputs - [ ] Add QR Code scanner - [ ] Load more transactions / rewards (now it is limited to latest 1000) -- [ ] Session stickiness \ No newline at end of file +- [X] Session stickiness \ No newline at end of file diff --git a/src/api/fetch.ts b/src/api/fetch.ts new file mode 100644 index 0000000..5ed0701 --- /dev/null +++ b/src/api/fetch.ts @@ -0,0 +1,48 @@ +const SESSION_HEADER_NAME = 'x-sm-affinity'; +const SESSION_IDS_STORAGE_KEY = 'sm-affinity'; + +const loadSessionIds = () => { + try { + return JSON.parse(localStorage.getItem(SESSION_IDS_STORAGE_KEY) || '{}'); + } catch (_) { + return {}; + } +}; + +const getSessionId = (url: string) => { + const u = new URL(url); + const keys = loadSessionIds(); + if (!keys || !keys[u.hostname]) { + return null; + } + return keys[u.hostname]; +}; + +const setSessionId = (url: string, sessionId: string) => { + const u = new URL(url); + const keys = loadSessionIds(); + keys[u.hostname] = sessionId; + localStorage.setItem(SESSION_IDS_STORAGE_KEY, JSON.stringify(keys)); + return keys; +}; + +export default (url: string, options?: RequestInit) => { + const sessionHeader = getSessionId(url); + const reqOptions = sessionHeader + ? { + ...options, + headers: { + ...options?.headers, + [SESSION_HEADER_NAME]: sessionHeader, + }, + } + : options; + + return fetch(url, reqOptions).then((r) => { + const sessionId = r.headers.get(SESSION_HEADER_NAME); + if (sessionId) { + setSessionId(url, sessionId); + } + return r; + }); +}; diff --git a/src/api/requests/getFetchAll.ts b/src/api/getFetchAll.ts similarity index 100% rename from src/api/requests/getFetchAll.ts rename to src/api/getFetchAll.ts diff --git a/src/api/requests/balance.ts b/src/api/requests/balance.ts index 9ff0feb..e9ded01 100644 --- a/src/api/requests/balance.ts +++ b/src/api/requests/balance.ts @@ -1,5 +1,6 @@ import { AccountStatesWithAddress } from '../../types/account'; import { Bech32Address } from '../../types/common'; +import fetch from '../fetch'; import { BalanceResponseSchema } from '../schemas/account'; import { parseResponse } from '../schemas/error'; diff --git a/src/api/requests/netinfo.ts b/src/api/requests/netinfo.ts index 22c3bc0..7fcb1ab 100644 --- a/src/api/requests/netinfo.ts +++ b/src/api/requests/netinfo.ts @@ -1,16 +1,17 @@ import parse from 'parse-duration'; import { fromBase64 } from '../../utils/base64'; -import fetchJSON from '../../utils/fetchJSON'; import { toHexString } from '../../utils/hexString'; +import fetch from '../fetch'; import { parseResponse } from '../schemas/error'; import { NetworkInfoResponseSchema } from '../schemas/network'; import { NodeStatusSchema, NodeSyncStatus } from '../schemas/node'; export const fetchNetworkInfo = (rpc: string) => - fetchJSON(`${rpc}/spacemesh.v2alpha1.NetworkService/Info`, { + fetch(`${rpc}/spacemesh.v2alpha1.NetworkService/Info`, { method: 'POST', }) + .then((r) => r.json()) .then(parseResponse(NetworkInfoResponseSchema)) .then((res) => ({ ...res, @@ -20,7 +21,10 @@ export const fetchNetworkInfo = (rpc: string) => })); export const fetchNodeStatus = (rpc: string) => - fetchJSON(`${rpc}/spacemesh.v2alpha1.NodeService/Status`, { method: 'POST' }) + fetch(`${rpc}/spacemesh.v2alpha1.NodeService/Status`, { + method: 'POST', + }) + .then((r) => r.json()) .then(parseResponse(NodeStatusSchema)) .then((status) => ({ connectedPeers: parseInt(status.connectedPeers, 10), diff --git a/src/api/requests/rewards.ts b/src/api/requests/rewards.ts index dd217f9..754990c 100644 --- a/src/api/requests/rewards.ts +++ b/src/api/requests/rewards.ts @@ -2,11 +2,11 @@ import { Bech32Address } from '../../types/common'; import { Reward } from '../../types/reward'; import { fromBase64 } from '../../utils/base64'; import { toHexString } from '../../utils/hexString'; +import fetch from '../fetch'; +import getFetchAll from '../getFetchAll'; import { parseResponse } from '../schemas/error'; import { RewardsListSchema } from '../schemas/rewards'; -import getFetchAll from './getFetchAll'; - export const fetchRewardsChunk = ( rpc: string, address: Bech32Address, diff --git a/src/api/requests/tx.ts b/src/api/requests/tx.ts index e31d021..7905518 100644 --- a/src/api/requests/tx.ts +++ b/src/api/requests/tx.ts @@ -10,6 +10,8 @@ import { getTemplateMethod, getTemplateNameByAddress, } from '../../utils/templates'; +import fetch from '../fetch'; +import getFetchAll from '../getFetchAll'; import { parseResponse } from '../schemas/error'; import { EstimateGasResponseSchema, @@ -21,8 +23,6 @@ import { WithExtraData, } from '../schemas/tx'; -import getFetchAll from './getFetchAll'; - const getTxState = ( resultStatus: TransactionResultStatus | undefined, txState: TransactionState | undefined diff --git a/src/utils/fetchJSON.ts b/src/utils/fetchJSON.ts deleted file mode 100644 index 3f0ea34..0000000 --- a/src/utils/fetchJSON.ts +++ /dev/null @@ -1,10 +0,0 @@ -function fetchJSON(url: string, options?: RequestInit) { - return fetch(url, options).then((r) => { - if (r.ok) { - return r.json(); - } - throw new Error(`Cannot fetch JSON data from ${url}. Got ${r.status}`); - }); -} - -export default fetchJSON;