fix(tradein-frontend): остановить retry-storm на 401 (#800) #1304

Merged
bot-backend merged 1 commit from fix/tradein-retry-storm-800 into main 2026-06-13 11:53:09 +00:00
2 changed files with 21 additions and 2 deletions

View file

@ -11,13 +11,24 @@ export function Providers({ children }: { children: React.ReactNode }) {
queries: {
staleTime: 5 * 60_000,
refetchOnWindowFocus: false,
// #770 finding #26: не ретраить 4xx (включая 429) — иначе на rate-limit
// дефолтные 3 ретрая превращаются в шторм. Ретраим только 5xx/сеть, ≤2 раза.
// #800: errored query + дефолтный refetchOnMount=true → каждый новый
// observer (Topbar/UserMenu/useBrand все дёргают useMe) ре-фетчит при
// mount → re-subscribe storm на стойком 401. Выключаем глобально: данные
// и так staleTime-кэшируются, а auth/quota не должны бить бэк на ремаунт.
refetchOnMount: false,
refetchOnReconnect: false,
// #770 finding #26 + #800: не ретраить 4xx (401/403/429) — иначе на
// rate-limit/протухшей сессии дефолтные 3 ретрая превращаются в шторм
// в собственный бэкенд. Ретраим только 5xx/сеть, ≤2 раза.
retry: (failureCount, error) => {
const status = (error as { status?: number } | null)?.status;
if (typeof status === "number" && status >= 400 && status < 500) return false;
return failureCount < 2;
},
// #800: экспоненциальный backoff на сетевых/5xx ретраях (1s, 2s, 4s…
// capped 30s) — никаких немедленных повторов, мягко к бэкенду.
retryDelay: (attemptIndex) =>
Math.min(1000 * 2 ** attemptIndex, 30_000),
},
mutations: {
retry: false,

View file

@ -10,6 +10,11 @@ const BASE = "/api/v1/trade-in";
* GET /api/v1/trade-in/quota
* Fetches the monthly evaluation quota status for the current session/account.
* Fail-open: if the query errors, the UI must NOT block the user (treat as unlimited).
*
* #800: `retry:false` на 401/протухшей сессии quota НЕ должна ре-стучаться в бэк.
* `refetchOnMount/Reconnect/WindowFocus:false` иначе errored quota ре-фетчит на
* каждый ремаунт владельца вклад в retry-storm. Освежается явным
* `invalidateQueries(["trade-in","quota"])` после успешной оценки (см. page.tsx).
*/
export function useQuota() {
return useQuery<QuotaStatus>({
@ -17,5 +22,8 @@ export function useQuota() {
queryFn: () => apiFetch<QuotaStatus>(`${BASE}/quota`),
staleTime: 30_000,
retry: false,
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
});
}