From ab10d43f41ae56811ef5216b21a6b7135df32b15 Mon Sep 17 00:00:00 2001 From: Ntein Precious <92717513+NteinPrecious@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:24:15 +0100 Subject: [PATCH 1/5] feat: add module augmentation for custom Axios config fields (#800) --- src/types/axios.d.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/types/axios.d.ts diff --git a/src/types/axios.d.ts b/src/types/axios.d.ts new file mode 100644 index 0000000..d33cf10 --- /dev/null +++ b/src/types/axios.d.ts @@ -0,0 +1,33 @@ +// src/types/axios.d.ts +// Module augmentation for custom Axios request config properties. +// This is the single source of truth — no more `as any` casts needed. + +import 'axios'; + +declare module 'axios' { + interface InternalAxiosRequestConfig { + /** + * Set to `true` once a 401 has triggered a token-refresh attempt for this + * request, preventing infinite refresh loops. + */ + _retry?: boolean; + + /** + * Running count of retry attempts for 429 (rate-limit) and 5xx (server + * error) responses. Starts at 0 and increments before each retry. + */ + _retryCount?: number; + + /** + * Epoch milliseconds recorded when the request was dispatched. Used to + * calculate response latency in the response interceptor. + */ + _requestStartMs?: number; + + /** + * Performance timing finish callback injected by the request interceptor. + * Set to `undefined` after first use to avoid double-reporting on retries. + */ + _timingFinish?: ((success: boolean, statusCode?: number) => unknown) | undefined; + } +} From 6daaf8c0728d51be01584385660b63f7c8b99b62 Mon Sep 17 00:00:00 2001 From: Ntein Precious <92717513+NteinPrecious@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:24:19 +0100 Subject: [PATCH 2/5] fix: add generic type params to post/put/patch (#801) --- src/services/api/index.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/services/api/index.ts b/src/services/api/index.ts index 86f0348..e46373c 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -38,9 +38,11 @@ export const apiService = { ) ); }, - post: (url: string, data: any) => apiClient.post(url, data), - put: (url: string, data: any) => apiClient.put(url, data), - delete: (url: string) => apiClient.delete(url), + /** #801: Generic response type so callers can infer the response shape. */ + post: (url: string, data: unknown) => apiClient.post(url, data), + put: (url: string, data: unknown) => apiClient.put(url, data), + patch: (url: string, data: unknown) => apiClient.patch(url, data), + delete: (url: string) => apiClient.delete(url), }; export { default as apiClient } from './axios.config'; From 300d5472a0bf369845a7529d5afd36bde439e2b0 Mon Sep 17 00:00:00 2001 From: Ntein Precious <92717513+NteinPrecious@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:24:20 +0100 Subject: [PATCH 3/5] feat: add ApiClient interface to replace any in RequestQueue (#802) --- src/types/apiClient.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/types/apiClient.ts diff --git a/src/types/apiClient.ts b/src/types/apiClient.ts new file mode 100644 index 0000000..6b357d2 --- /dev/null +++ b/src/types/apiClient.ts @@ -0,0 +1,20 @@ +// src/types/apiClient.ts +// Typed interface that RequestQueue uses to replay queued requests. +// The exported `apiClient` from axios.config.ts satisfies this interface. + +import type { AxiosResponse, InternalAxiosRequestConfig } from 'axios'; + +/** + * Minimum contract required by RequestQueue to execute queued HTTP requests. + * + * Typed as a callable (for replaying raw configs) plus named method overloads + * so TypeScript can verify the actual apiClient satisfies it at compile time. + */ +export interface ApiClient { + (config: InternalAxiosRequestConfig): Promise; + get(url: string, config?: object): Promise>; + post(url: string, data?: unknown, config?: object): Promise>; + put(url: string, data?: unknown, config?: object): Promise>; + patch(url: string, data?: unknown, config?: object): Promise>; + delete(url: string, config?: object): Promise>; +} From 71eb9428ca78ce65db10fed95d2e409bc4614b6b Mon Sep 17 00:00:00 2001 From: Ntein Precious <92717513+NteinPrecious@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:24:26 +0100 Subject: [PATCH 4/5] fix: replace any with ApiClient type in RequestQueue (#802) --- src/services/api/requestQueue.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/services/api/requestQueue.ts b/src/services/api/requestQueue.ts index 519e57b..04955f4 100644 --- a/src/services/api/requestQueue.ts +++ b/src/services/api/requestQueue.ts @@ -1,4 +1,5 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; +import type { ApiClient } from '../../types/apiClient'; import { InternalAxiosRequestConfig } from 'axios'; import * as Network from 'expo-network'; @@ -59,7 +60,8 @@ class RequestQueue { private listeners: ((count: number) => void)[] = []; private monitoringInterval: ReturnType | null = null; private networkListener: (() => void) | null = null; - private apiClient: any | null = null; + // #802: typed as ApiClient to prevent passing arbitrary objects + private apiClient: ApiClient | null = null; private metrics: QueueMetrics = { totalQueued: 0, byPriority: { critical: 0, high: 0, normal: 0, low: 0 }, @@ -187,7 +189,7 @@ class RequestQueue { } } - async processQueue(apiClient?: any): Promise { + async processQueue(apiClient?: ApiClient): Promise { if (this.isProcessing) return; if (apiClient) { @@ -268,7 +270,7 @@ class RequestQueue { .catch(() => {}); } - startMonitoring(apiClient?: any): void { + startMonitoring(apiClient?: ApiClient): void { if (apiClient) { this.apiClient = apiClient; } From 15c90ad334c89d101083f7918d29e6ab89635073 Mon Sep 17 00:00:00 2001 From: Ntein Precious <92717513+NteinPrecious@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:24:30 +0100 Subject: [PATCH 5/5] fix: explicitly type persist onRehydrateStorage error callback (#803) --- src/store/createStore.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/store/createStore.ts b/src/store/createStore.ts index 2cfd0ea..122291b 100644 --- a/src/store/createStore.ts +++ b/src/store/createStore.ts @@ -43,12 +43,17 @@ export const createStore = ( storage: createJSONStorage(() => asyncMMKVStorage), partialize, migrate, - onRehydrateStorage: (state) => { - return (state, error) => { + onRehydrateStorage: (_initialState) => { + // #803: explicitly type both params so implicit `any` is eliminated. + // `error` is `Error | undefined` per the Zustand persist middleware signature. + return (_rehydratedState: T | undefined, error: Error | undefined) => { if (error) { - logger.errorSync('an error happened during hydration', error as Error) + logger.errorSync('an error happened during hydration', error); + // Reset persisted state to defaults on corrupt storage to avoid + // leaving the app in a broken half-hydrated state. + logger.warnSync('Resetting corrupted persisted state for store: ' + name); } - } + }; }, }), { name: `Teach-This-${name}` }