Add remote config functionality (#784)

Co-authored-by: Felicio <felicio@users.noreply.github.com>
This commit is contained in:
marcelines
2025-08-28 19:22:23 +09:00
committed by GitHub
co-authored by Felicio
parent c51e8b422c
commit 997b4a138d
14 changed files with 127 additions and 27 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@status-im/wallet': patch
'wallet': patch
'api': patch
---
remote config env
-2
View File
@@ -16,9 +16,7 @@
# https://vercel.com/docs/cli/env#exporting-development-environment-variables
INFURA_API_KEY=""
ALCHEMY_API_KEY=""
ALCHEMY_API_KEYS=""
COINGECKO_API_KEY=""
CRYPTOCOMPARE_API_KEY=""
CRYPTOCOMPARE_API_KEYS=""
MERCURYO_SECRET_KEY=""
+2 -2
View File
@@ -18,9 +18,9 @@ export const envSchema = z.object({
.optional(),
INFURA_API_KEY: z.string(),
MERCURYO_SECRET_KEY: z.string(),
ALCHEMY_API_KEY: z.string(),
ALCHEMY_API_KEYS: z.string(),
COINGECKO_API_KEY: z.string(),
CRYPTOCOMPARE_API_KEY: z.string(),
CRYPTOCOMPARE_API_KEYS: z.string(),
})
const result = envSchema.safeParse(process.env)
+2 -2
View File
@@ -18,9 +18,9 @@
INFURA_API_KEY=""
NEXT_PUBLIC_INFURA_API_KEY=""
MERCURYO_SECRET_KEY=""
ALCHEMY_API_KEY=""
ALCHEMY_API_KEYS=""
COINGECKO_API_KEY=""
CRYPTOCOMPARE_API_KEY=""
CRYPTOCOMPARE_API_KEYS=""
NEXT_PUBLIC_FEATURE_FLAG_WATCHED_ADDRESSES="true"
#VERCEL_WEB_ANALYTICS_DISABLE_LOGS="true"
#NEXT_PRIVATE_DEBUG_CACHE=1
+2 -2
View File
@@ -17,9 +17,9 @@ export const envSchema = z.object({
])
.optional(),
INFURA_API_KEY: z.string(),
ALCHEMY_API_KEY: z.string(),
ALCHEMY_API_KEYS: z.string(),
COINGECKO_API_KEY: z.string(),
CRYPTOCOMPARE_API_KEY: z.string(),
CRYPTOCOMPARE_API_KEYS: z.string(),
PORT: z.coerce.number().optional(),
MERCURYO_SECRET_KEY: z.string(),
})
-2
View File
@@ -1,2 +0,0 @@
export const QUERY_STALE_TIME_MS = 15 * 1000 // 15 seconds
export const QUERY_GC_TIME_MS = 60 * 60 * 1000 // 1 hour
+4 -3
View File
@@ -1,6 +1,6 @@
import { useInfiniteQuery } from '@tanstack/react-query'
import { QUERY_GC_TIME_MS, QUERY_STALE_TIME_MS } from './constants'
import { useConfigEnv } from './use-config-env'
import type { Activity, NetworkType } from '@status-im/wallet/data'
@@ -57,6 +57,7 @@ const getTransfers = async (
}
export const useActivities = ({ address }: Props) => {
const { data: configEnv } = useConfigEnv()
const searchParams = new URLSearchParams(window.location.search)
const networks = searchParams.get('networks')?.split(',') ?? ['ethereum']
@@ -87,7 +88,7 @@ export const useActivities = ({ address }: Props) => {
},
initialPageParam: {},
staleTime: QUERY_STALE_TIME_MS,
gcTime: QUERY_GC_TIME_MS,
staleTime: configEnv.staleTimeMs,
gcTime: configEnv.gcTimeMs,
})
}
+4 -3
View File
@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query'
import { QUERY_GC_TIME_MS, QUERY_STALE_TIME_MS } from './constants'
import { useConfigEnv } from './use-config-env'
import type { ApiOutput } from '@status-im/wallet/data'
import type { UseQueryResult } from '@tanstack/react-query'
@@ -16,6 +16,7 @@ const useAssets = (
props: Props,
): UseQueryResult<ApiOutput['assets']['all'], Error> => {
const { address, isWalletLoading } = props
const { data: configEnv } = useConfigEnv()
return useQuery({
queryKey: ['assets', address],
@@ -53,8 +54,8 @@ const useAssets = (
return body.result.data.json
},
enabled: !!address && !isWalletLoading,
staleTime: QUERY_STALE_TIME_MS,
gcTime: QUERY_GC_TIME_MS,
staleTime: configEnv.staleTimeMs,
gcTime: configEnv.gcTimeMs,
})
}
+4 -3
View File
@@ -1,6 +1,6 @@
import { useInfiniteQuery } from '@tanstack/react-query'
import { QUERY_GC_TIME_MS, QUERY_STALE_TIME_MS } from './constants'
import { useConfigEnv } from './use-config-env'
import type { NetworkType } from '@status-im/wallet/data'
@@ -75,6 +75,7 @@ const getCollectibles = async (
const useCollectibles = (props: Props) => {
const { address, isWalletLoading } = props
const { data: configEnv } = useConfigEnv()
const searchParams = new URLSearchParams(window.location.search)
const search = searchParams.get('search') ?? undefined
@@ -116,8 +117,8 @@ const useCollectibles = (props: Props) => {
lastPage.hasMore ? lastPage.nextPage : undefined,
initialPageParam: 0,
enabled: !!address && !isWalletLoading,
staleTime: QUERY_STALE_TIME_MS,
gcTime: QUERY_GC_TIME_MS,
staleTime: configEnv.staleTimeMs,
gcTime: configEnv.gcTimeMs,
})
}
+53
View File
@@ -0,0 +1,53 @@
import { useQuery } from '@tanstack/react-query'
import type { ApiOutput } from '@status-im/wallet/data'
import type { UseQueryResult } from '@tanstack/react-query'
type ConfigEnv = ApiOutput['config']['env']
const DEFAULT_ENV: ConfigEnv = {
refreshIntervalMs: 0,
staleTimeMs: Infinity,
gcTimeMs: Infinity,
}
type UseConfigEnvResult = Omit<UseQueryResult<ConfigEnv, never>, 'data'> & {
data: ConfigEnv
}
const useConfigEnv = (): UseConfigEnvResult => {
const query = useQuery<ConfigEnv, never>({
queryKey: ['config-env'],
queryFn: async () => {
try {
const url = new URL(
`${import.meta.env.WXT_STATUS_API_URL}/api/trpc/config.env`,
)
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
if (!response.ok) {
return DEFAULT_ENV
}
const body = await response.json()
return body.result.data.json
} catch {
return DEFAULT_ENV
}
},
initialData: DEFAULT_ENV,
staleTime: 5 * 60 * 1000, // 5 min
gcTime: 60 * 60 * 1000, // 1 hour
refetchOnWindowFocus: false,
retry: false,
})
return { ...query, data: query.data ?? DEFAULT_ENV }
}
export { useConfigEnv }
@@ -1,15 +1,20 @@
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import { useConfigEnv } from './use-config-env'
import { useRefetchToast } from './use-refetch-toast'
const REFRESH_INTERVAL_MS = 15 * 1000
export function useSynchronizedRefetch(address: string) {
const queryClient = useQueryClient()
const [isWindowActive, setIsWindowActive] = useState(true)
const [isAutoRefreshing, setIsAutoRefreshing] = useState(false)
const { data: configEnv } = useConfigEnv()
const refreshIntervalMs = useMemo(
() => configEnv.refreshIntervalMs,
[configEnv.refreshIntervalMs],
)
const refetchQueries = useCallback(async () => {
if (!address) return
@@ -89,12 +94,12 @@ export function useSynchronizedRefetch(address: string) {
}, [address, queryClient, refetchQueries])
useEffect(() => {
if (!isWindowActive) return
if (!isWindowActive || refreshIntervalMs === 0) return
const interval = setInterval(refetchQueries, REFRESH_INTERVAL_MS)
const interval = setInterval(refetchQueries, refreshIntervalMs)
return () => clearInterval(interval)
}, [isWindowActive, address, queryClient, refetchQueries])
}, [isWindowActive, address, queryClient, refetchQueries, refreshIntervalMs])
}
function has429Error(
+2 -2
View File
@@ -49,9 +49,9 @@ export default defineConfig({
vite: () => ({
define: {
'process.env': {
ALCHEMY_API_KEY: 'test',
ALCHEMY_API_KEYS: 'test',
INFURA_API_KEY: 'test',
CRYPTOCOMPARE_API_KEY: 'test',
CRYPTOCOMPARE_API_KEYS: 'test',
COINGECKO_API_KEY: 'test',
MERCURYO_SECRET_KEY: 'test',
VERCEL: 'test',
+2
View File
@@ -2,6 +2,7 @@ import { createCallerFactory, router } from './lib/trpc'
import { activitiesRouter as activities } from './routers/activity'
import { assetsRouter as assets } from './routers/assets'
import { collectiblesRouter as collectibles } from './routers/collectibles'
import { configRouter as config } from './routers/config'
import { nodesRouter as nodes } from './routers/nodes'
export const apiRouter = router({
@@ -9,6 +10,7 @@ export const apiRouter = router({
collectibles,
nodes,
activities,
config,
})
export type ApiRouter = typeof apiRouter
@@ -0,0 +1,34 @@
import { cache } from 'react'
import { publicProcedure, router } from '../lib/trpc'
export const configRouter = router({
env: publicProcedure.query(async () => {
try {
return await getEnvCached()
} catch {
console.error('Failed to load config env')
return {
refreshIntervalMs: 0,
staleTimeMs: Infinity,
gcTimeMs: Infinity,
}
}
}),
})
type ConfigEnv = {
refreshIntervalMs: number
staleTimeMs: number
gcTimeMs: number
}
async function loadEnv(): Promise<ConfigEnv> {
return {
refreshIntervalMs: 15 * 1000, // 15 seconds
staleTimeMs: 15 * 1000, // 15 seconds
gcTimeMs: 60 * 60 * 1000, // 1 hour
}
}
const getEnvCached = cache(loadEnv)