Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve refresh ux of home balance section. #2245

Merged
merged 20 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5c23a98
WIP: feat: improve refresh ux of home balance section.
richardo2016x Apr 25, 2024
82862b5
feat: cache previously loaded balance & curvePoint for address.
richardo2016x Apr 26, 2024
80f248e
fix: avoid show spining refresh button if home balance cache detected
richardo2016x Apr 26, 2024
887ee9a
feat: deprecate `homeBalanceLoadingExpiration`, timing on background …
richardo2016x Apr 28, 2024
675bb71
feat: tuning refresh delay after tx completed.
richardo2016x Apr 28, 2024
a9a5c56
feat: notify refresh balance after tx completed.
richardo2016x Apr 29, 2024
dc6d337
chore: cleanup.
richardo2016x Apr 29, 2024
d6a2c17
fix: env var.
richardo2016x Apr 29, 2024
90b715f
fix: loading style for percentage.
richardo2016x Apr 29, 2024
347b180
feat: force expire address's balance/netCurve after tx completed, sty…
richardo2016x Apr 29, 2024
6b8fd09
fix: value robust.
richardo2016x Apr 29, 2024
489ce8f
fix: type & typo.
richardo2016x Apr 29, 2024
5787a0c
feat: wider refresh-trigger area.
richardo2016x Apr 29, 2024
94a1dbf
feat: refresh on next-time open dashboard after tx completed.
richardo2016x Apr 29, 2024
70afd74
feat: tuning refresh balance timeout, clear balance cache right away …
richardo2016x Apr 29, 2024
19c99e0
fix: avoid dead reaction on click refresh button.
richardo2016x Apr 30, 2024
ac9baf3
feat: don't show change percentage if no start usd value.
richardo2016x Apr 30, 2024
6401384
feat: disable auto refresh on UI.
richardo2016x Apr 30, 2024
eac4c2b
feat: don't trigger immediate refresh after tx completed.
richardo2016x Apr 30, 2024
d07f38a
feat: only trigger expiration once on entering dashboard.
richardo2016x Apr 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "react"],
"plugins": ["@typescript-eslint", "react", "react-hooks"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
Expand All @@ -18,7 +18,10 @@
{
"endOfLine": "auto"
}
]
],
"react-hooks/exhaustive-deps": ["off", {
"additionalHooks": "(useWallet)"
}]
},
"env": {
"browser": true,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@
"eslint-plugin-import": "2.22.1",
"eslint-plugin-prettier": "3.4.0",
"eslint-plugin-react": "7.23.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-webpack-plugin": "2.5.4",
"file-loader": "6.2.0",
"fs-extra": "10.0.0",
Expand Down
125 changes: 83 additions & 42 deletions src/background/controller/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ import HdKeyring from '@rabby-wallet/eth-hd-keyring';
import CoinbaseKeyring from '@rabby-wallet/eth-coinbase-keyring/dist/coinbase-keyring';
import { customTestnetService } from '../service/customTestnet';
import { getKeyringBridge, hasBridge } from '../service/keyring/bridge';
import { http } from '../utils/http';
import { syncChainService } from '../service/syncChain';
import { matomoRequestEvent } from '@/utils/matomo-request';
import { BALANCE_LOADING_TIMES } from '@/constant/timeout';

const stashKeyrings: Record<string | number, any> = {};

Expand Down Expand Up @@ -1047,28 +1047,50 @@ export class WalletController extends BaseController {
}
};

private getTotalBalanceCached = cached(async (address) => {
const data = await openapiService.getTotalBalance(address);
preferenceService.updateAddressBalance(address, data);
return data;
// 5s
}, 5000);

private getTestnetTotalBalanceCached = cached(async (address) => {
const testnetData = await testnetOpenapiService.getTotalBalance(address);
preferenceService.updateTestnetAddressBalance(address, testnetData);
return testnetData;
}, 5000);
private getTotalBalanceCached = cached(
'getTotalBalanceCached',
async (address: string) => {
const data = await openapiService.getTotalBalance(address);
preferenceService.updateAddressBalance(address, data);
return data;
},
BALANCE_LOADING_TIMES.TIMEOUT
);

private getTestnetTotalBalanceCached = cached(
'getTestnetTotalBalanceCached',
async (address: string) => {
const testnetData = await testnetOpenapiService.getTotalBalance(address);
preferenceService.updateTestnetAddressBalance(address, testnetData);
return testnetData;
},
BALANCE_LOADING_TIMES.TIMEOUT
);

getAddressBalance = async (
address: string,
force = false,
isTestnet = false
) => {
if (isTestnet) {
return this.getTestnetTotalBalanceCached([address], address, force);
return this.getTestnetTotalBalanceCached.fn([address], address, force);
}
return this.getTotalBalanceCached([address], address, force);
return this.getTotalBalanceCached.fn([address], address, force);
};

forceExpireAddressBalance = (address: string, isTestnet = false) => {
if (isTestnet) {
return this.getTestnetTotalBalanceCached.forceExpire(address);
}
return this.getTotalBalanceCached.forceExpire(address);
};

isAddressBalanceExpired = (address: string, isTestnet = false) => {
if (isTestnet) {
return this.getTestnetTotalBalanceCached.isExpired(address);
}

return this.getTotalBalanceCached.isExpired(address);
};

getAddressCacheBalance = (address: string | undefined, isTestnet = false) => {
Expand All @@ -1079,12 +1101,24 @@ export class WalletController extends BaseController {
return preferenceService.getAddressBalance(address);
};

private getNetCurveCached = cached(async (address) => {
return openapiService.getNetCurve(address);
}, 5000);
private getNetCurveCached = cached(
'getNetCurveCached',
async (address) => {
return openapiService.getNetCurve(address);
},
BALANCE_LOADING_TIMES.TIMEOUT
);

getNetCurve = (address: string, force = false) => {
return this.getNetCurveCached.fn([address], address, force);
};

forceExpireNetCurve = (address: string) => {
return this.getNetCurveCached.forceExpire(address);
};

getNetCurve = (address, force = false) => {
return this.getNetCurveCached([address], address, force);
isNetCurveExpired = (address: string) => {
return this.getNetCurveCached.isExpired(address);
};

setHasOtherProvider = (val: boolean) =>
Expand Down Expand Up @@ -3620,29 +3654,35 @@ export class WalletController extends BaseController {
* disable approval management and transaction history when level is 1
* disable total balance refresh and level 1 content when level is 2
*/
getAPIConfig = cached(async () => {
interface IConfig {
data: {
level: number;
authorized: {
enable: boolean;
};
balance: {
enable: boolean;
};
history: {
enable: boolean;
getAPIConfig = cached(
'getAPIConfig',
async () => {
interface IConfig {
data: {
level: number;
authorized: {
enable: boolean;
};
balance: {
enable: boolean;
};
history: {
enable: boolean;
};
};
};
}
try {
const config = await fetch('https://static.debank.com/rabby/config.json');
const { data } = (await config.json()) as IConfig;
return data.level;
} catch (e) {
return 0;
}
}, 10000);
}
try {
const config = await fetch(
'https://static.debank.com/rabby/config.json'
);
const { data } = (await config.json()) as IConfig;
return data.level;
} catch (e) {
return 0;
}
},
10000
).fn;

rabbyPointVerifyAddress = async (params?: {
code?: string;
Expand Down Expand Up @@ -3771,4 +3811,5 @@ autoLockService.onAutoLock = async () => {
method: EVENTS.LOCK_WALLET,
});
};

export default wallet;
19 changes: 16 additions & 3 deletions src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import browser from 'webextension-polyfill';
import { ethErrors } from 'eth-rpc-errors';
import { WalletController } from 'background/controller/wallet';
import { Message } from '@/utils/message';
import { CHAINS, CHAINS_ENUM, EVENTS, KEYRING_CATEGORY_MAP } from 'consts';
import {
CHAINS,
CHAINS_ENUM,
EVENTS,
EVENTS_IN_BG,
KEYRING_CATEGORY_MAP,
} from 'consts';
import { storage } from './webapi';
import {
permissionService,
Expand Down Expand Up @@ -35,8 +41,8 @@ import createSubscription from './controller/provider/subscriptionManager';
import buildinProvider from 'background/utils/buildinProvider';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { setPopupIcon, wait } from './utils';
import { getSentryEnv } from '@/utils/env';
import { setPopupIcon } from './utils';
import { appIsDev, getSentryEnv } from '@/utils/env';
import { matomoRequestEvent } from '@/utils/matomo-request';
import { testnetOpenapiService } from './service/openapi';
import fetchAdapter from '@vespaiach/axios-fetch-adapter';
Expand Down Expand Up @@ -104,6 +110,13 @@ async function restoreAppState() {
startEnableUser();
walletController.syncMainnetChainList();

eventBus.addEventListener(EVENTS_IN_BG.ON_TX_COMPLETED, ({ address }) => {
if (!address) return;

walletController.forceExpireAddressBalance(address);
walletController.forceExpireNetCurve(address);
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'getBackgroundReady') {
sendResponse({
Expand Down
8 changes: 7 additions & 1 deletion src/background/service/transactionWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from 'background/service';
import { createPersistStore, isSameAddress } from 'background/utils';
import { notification } from 'background/webapi';
import { CHAINS, CHAINS_ENUM } from 'consts';
import { CHAINS, CHAINS_ENUM, EVENTS_IN_BG } from 'consts';
import { format, getTxScanLink } from '@/utils';
import eventBus from '@/eventBus';
import { EVENTS } from '@/constant';
Expand Down Expand Up @@ -135,6 +135,12 @@ class TransactionWatcher {
method: EVENTS.TX_COMPLETED,
params: { address, hash },
});

eventBus.emit(EVENTS_IN_BG.ON_TX_COMPLETED, {
address,
hash,
status: txReceipt.status,
});
};

// fetch pending txs status every 5s
Expand Down
1 change: 1 addition & 0 deletions src/background/utils/alarms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export const ALARMS_AUTO_LOCK = 'ALARMS_AUTO_LOCK';
export const ALARMS_RPC_CACHE = 'ALARMS_RPC_CACHE';
export const ALARMS_RELOAD_TX = 'ALARMS_RELOAD_TX';
export const ALARMS_SYNC_CHAINS = 'ALARMS_SYNC_CHAINS';
export const ALARMS_REFRESH_BALANCE = 'ALARMS_REFRESH_BALANCE';
39 changes: 33 additions & 6 deletions src/background/utils/cache.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import { IExtractFromPromise } from '@/ui/utils/type';
import { appIsDev } from '@/utils/env';

// cached any function with a key and a timer
export const cached = <T>(
fn: (...args: any[]) => Promise<T>,
timer = 3 * 60 * 1000
export const cached = <T extends (...args: any[]) => Promise<any>>(
name: string,
fn: T,
timeout = 3 * 60 * 1000
) => {
const cache: {
[key: string]: {
expire: number;
value: T;
value: IExtractFromPromise<ReturnType<T>>;
};
} = {};
return async (args: any[], key: string, force: boolean) => {

if (appIsDev) {
globalThis[`${name}_cache`] = cache;
}

const wrappedFn = async (
args: Parameters<T>,
key: string,
force: boolean
): Promise<IExtractFromPromise<ReturnType<T>>> => {
const now = Date.now();

if (!force && cache[key] && cache[key].expire > now) {
Expand All @@ -18,10 +31,24 @@ export const cached = <T>(

const res = await fn(...args);
cache[key] = {
expire: now + timer,
expire: now + timeout,
value: res,
};

return res;
};

const isExpired = (key: string) => {
return !cache[key] || cache[key].expire < Date.now();
};

const forceExpire = (key: string) => {
delete cache[key];
};

return {
fn: wrappedFn,
isExpired,
forceExpire,
};
};
5 changes: 5 additions & 0 deletions src/constant/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,11 @@ export const EVENTS = {
},
LOCK_WALLET: 'LOCK_WALLET',
RELOAD_TX: 'RELOAD_TX',
// FORCE_EXPIRE_ADDRESS_BALANCE: 'FORCE_EXPIRE_ADDRESS_BALANCE',
};

export const EVENTS_IN_BG = {
ON_TX_COMPLETED: 'ON_TX_COMPLETED',
};

export enum WALLET_BRAND_TYPES {
Expand Down
21 changes: 21 additions & 0 deletions src/constant/timeout.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
import { appIsDebugPkg, appIsDev } from '@/utils/env';

/**
* When calling signatures, it is not possible to receive event messages in time,
if the UI has not been initialized
*/
export const SIGN_TIMEOUT = 100;

export const DEFT_BALANCE_LOADING_TIMEOUT_PROD = 10 * 60 * 1e3;
export const BALANCE_LOADING_TIMES = appIsDev
? {
CHECK_INTERVAL: 2 * 1e3,
TIMEOUT: 60 * 1e3,
DELAY_AFTER_TX_COMPLETED: 1 * 60 * 1e3,
}
: appIsDebugPkg
? {
CHECK_INTERVAL: 2 * 1e3,
TIMEOUT: 90 * 1e3,
DELAY_AFTER_TX_COMPLETED: 1 * 60 * 1e3,
}
: {
CHECK_INTERVAL: 2 * 1e3,
TIMEOUT: DEFT_BALANCE_LOADING_TIMEOUT_PROD,
DELAY_AFTER_TX_COMPLETED: 5 * 60 * 1e3,
};
6 changes: 4 additions & 2 deletions src/ui/hooks/useBalanceChange.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint "react-hooks/exhaustive-deps": ["error"] */
/* eslint-enable react-hooks/exhaustive-deps */
import { useMemo } from 'react';

import { ExplainTxResponse } from '@/background/service/openapi';
Expand All @@ -17,9 +19,9 @@ export default function useBalanceChange({
}: {
balance_change?: ExplainTxResponse['balance_change'] | null;
}) {
if (!balance_change) return getDefaultValues();

return useMemo(() => {
if (!balance_change) return getDefaultValues();

const hasNFTChange =
balance_change.receive_nft_list.length > 0 ||
balance_change.send_nft_list.length > 0;
Expand Down
Loading
Loading