-
Notifications
You must be signed in to change notification settings - Fork 0
Stage 5: HTTP Client Integration
mandla-enkosi edited this page Apr 5, 2025
·
1 revision
-
Objective: Develop the main
httpClient(as an Axios wrapper) and integrate core functionalities including:- Axios interceptors for authentication (token attachment, refresh trigger based on 401/403) and error handling.
- A request queue to hold requests during token refresh.
- Standardized error handling (defining and using
StandardError). - A configurable retry strategy for transient network/server errors.
- Request cancellation support (via
AbortController). - Caching and request deduplication features.
- Ensuring behavior correctly adapts based on the configured
authType.
-
Expected Result: A robust, exported
httpClientinstance that:- Abstracts authentication complexities (token attachment, refresh).
- Handles common network/server errors gracefully through retries.
- Provides a standardized error format (
StandardError). - Supports request cancellation.
- Offers performance enhancements (caching, deduplication).
-
Pre-Stage Requirements:
- Authentication Manager providing
getToken,_triggerRefresh, auth state. - Platform Abstraction Layer providing
INetworkInfo. - Internal State Management providing store setters.
- Authentication Manager providing
- External Dependencies: Axios.
-
Environment Configurations:
- Axios defaults (baseURL, timeout).
- Retry config (count, backoff).
- Cache/dedupe config.
-
Visual Aids:
HTTP Request Flow with Token Refresh
LoadingsequenceDiagram participant App as Application participant HTTPClient as IHttpClient participant AxiosInst as Axios Instance participant Interceptors as Request/Response Interceptors participant AuthMgr as IAuthenticationManager participant ReqQueue as RequestQueue participant RetryHandler as RetryHandler participant ErrorHandler as ErrorHandler participant Backend as Backend API participant StateStore as InternalStateStore App->>HTTPClient: request(config) HTTPClient->>Interceptors: Request Interceptor(config) alt Requires Auth (Default) & AuthType is JWT/OAuth2 Interceptors->>AuthMgr: getToken() AuthMgr-->>Interceptors: token (or null/expired) Interceptors->>Interceptors: Add 'Authorization: Bearer token' header else AuthType is Session Interceptors->>Interceptors: Ensure 'withCredentials: true' end Interceptors-->>AxiosInst: Modified Config AxiosInst->>Backend: HTTP Request Backend-->>AxiosInst: HTTP Response AxiosInst-->>Interceptors: Response Interceptor(response/error) alt Success Response (2xx) Interceptors-->>HTTPClient: Processed Response HTTPClient-->>App: Promise Resolved (Data) else Error Response alt Auth Error (401/403) & AuthType is JWT/OAuth2 & Not Skip Refresh Interceptors->>ReqQueue: enqueue(originalRequest) Interceptors->>AuthMgr: _triggerRefresh() AuthMgr-->>Interceptors: refreshPromise (resolves/rejects later) Interceptors-->>HTTPClient: Returns refreshPromise linked to queued request Note over HTTPClient, App: App awaits... alt Refresh Succeeds AuthMgr->>ReqQueue: processQueue() ReqQueue->>Interceptors: Re-execute original request via interceptors Note over Interceptors: Gets new token, retries... else Refresh Fails AuthMgr->>ReqQueue: clearQueue(refreshError) ReqQueue-->>Interceptors: Reject queued request promises Interceptors->>ErrorHandler: handleAxiosError(original 401 error, refreshError context) ErrorHandler-->>Interceptors: StandardError (Auth Refresh Failed) Interceptors-->>HTTPClient: Error HTTPClient-->>App: Promise Rejected (StandardError) end else Transient Error (Network, 5xx) & Retryable Interceptors->>RetryHandler: shouldRetry(error, config) RetryHandler-->>Interceptors: true (with delay) Interceptors->>Interceptors: Schedule Retry via setTimeout(axiosInst.request(...)) else Non-Retryable Error / Retry Limit Reached Interceptors->>ErrorHandler: handleAxiosError(error) / handleNetworkError(error) ErrorHandler-->>Interceptors: StandardError Interceptors-->>HTTPClient: Error HTTPClient-->>App: Promise Rejected (StandardError) end end
-
Modules/Files:
-
/packages/core/src/http/index.ts: ExporthttpClient. -
/packages/core/src/http/httpClient.ts: Create/configure Axios instance, attach interceptors, implementIHttpClientwrapper methods, handleconfigureupdates. -
/packages/core/src/http/interceptors.ts: Implement detailed request interceptor (auth header,withCredentials, cancellation) and response interceptor (error detection: 401/403, transient; triggering refresh/retry; queue interaction; error handling). -
/packages/core/src/http/requestQueue.ts: Implement queue logic (add, process, clear, timeout). -
/packages/core/src/http/errorHandler.ts: DefineStandardError, implement error creation functions (handleAxiosError,handleNetworkError), define standard error codes enum/type. Retrieves and calls the global onError hook. -
/packages/core/src/http/retryHandler.ts: Implement retry decision logic, backoff calculation (using/polling/backoffStrategies), track retry attempts. -
/packages/core/src/http/caching.ts: Implement cache store and interceptor logic. -
/packages/core/src/http/deduplication.ts: Implement in-flight request tracking and interceptor logic. -
/packages/core/src/http/types.ts: DefineHttpClientRequestConfig,StandardErrorCodeenum/type. -
/packages/core/src/auth/index.ts: ConsumeIAuthenticationManagerinterface. -
/packages/core/src/auth/authManager.ts: Consumed by interceptors (getToken,_triggerRefresh). -
/packages/core/src/state/store.ts: Updated withOperationState(pending requests, refresh status). -
/packages/core/src/state/hooks.ts: ImplementuseOperationState. -
/packages/core/src/platform/index.ts: Consumed to getINetworkInfo.
-
-
Functionalities:
- Axios instance setup and configuration.
- Request Interception: Attach token or set
withCredentialsbased onauthType. Handle cancellation. - Response Interception: Handle 401/403 by triggering refresh and queuing. Handle transient errors by triggering retries. Normalize errors.
- Request Queuing during refresh.
- Configurable Retry Strategy for transient errors.
- Standardized Error Handling.
- Optional Caching & Deduplication.
- Update internal operational state.
- Provide
useOperationStatehook. - Error Handler:
- Creates StandardError from Axios errors, network errors, etc.
- Provides classification (retryable, auth error).
- Invokes the global onError callback.
// --- HTTP Types (/packages/core/src/http/types.ts) ---
/** Standardized Error Code values. */
export type StandardErrorCode =
// Authentication Errors
| 'AUTH_UNAUTHORIZED' // General 401 from backend API
| 'AUTH_FORBIDDEN' // General 403 from backend API
| 'AUTH_TOKEN_EXPIRED' // Specifically detected expired token
| 'AUTH_REFRESH_FAILED' // Token refresh attempt failed
| 'AUTH_LOGIN_REQUIRED' // Action requires authentication
| 'AUTH_CONFIG_ERROR' // Invalid auth configuration
| 'AUTH_REQUEST_FAILED' // Generic auth request failure
| 'AUTH_SERVICE_UNAVAILABLE' // Auth server unavailable
| 'AUTH_STATE_MISMATCH' // OAuth state validation failed
| 'AUTH_PKCE_ERROR' // PKCE verification failed
| 'AUTH_REDIRECT_ERROR' // OAuth redirect handling failed
// Network Errors
| 'NETWORK_ERROR' // Generic connection issue
| 'NETWORK_TIMEOUT' // Request timed out
| 'NETWORK_OFFLINE' // Client is offline
// HTTP Server Errors
| 'HTTP_BAD_REQUEST' // 400
| 'HTTP_NOT_FOUND' // 404
| 'HTTP_METHOD_NOT_ALLOWED' // 405
| 'HTTP_CONFLICT' // 409
| 'HTTP_UNPROCESSABLE_ENTITY' // 422 (Validation errors)
| 'HTTP_TOO_MANY_REQUESTS' // 429
| 'HTTP_INTERNAL_SERVER_ERROR' // 500
| 'HTTP_SERVICE_UNAVAILABLE' // 503
| 'HTTP_GATEWAY_TIMEOUT' // 504
| 'HTTP_UNKNOWN_ERROR' // Other non-2xx status
// Request/Client Errors
| 'REQUEST_CANCELLED' // Cancelled via AbortController
| 'REQUEST_QUEUE_TIMEOUT' // Timed out in refresh queue
| 'RETRY_LIMIT_EXCEEDED' // Max retries reached
| 'CACHE_ERROR' // Cache-related error
// Platform Errors
| 'PLATFORM_STORAGE_ERROR' // Error in IStorage operations
| 'PLATFORM_NAVIGATION_ERROR' // Error in INavigation operations
| 'PLATFORM_NETWORK_ERROR' // Error in INetworkInfo operations
// Polling Errors
| 'POLLING_MAX_ATTEMPTS' // Polling max attempts reached
| 'POLLING_TIMEOUT' // Polling overall timeout reached
| 'POLLING_CANCELLED' // Polling was cancelled
// General
| 'UNKNOWN_ERROR'; // Fallback for unclassified errors
/** Standardized Error structure used throughout the kit. */
export interface StandardError extends Error { // Extend Error for stack trace etc.
code: StandardErrorCode; // Machine-readable code
message: string; // Human-readable message (can be generic)
messageKey?: string; // Optional I18n key
originalError?: any; // The original caught error (AxiosError, Error, etc.)
metadata?: { // Contextual information
statusCode?: number;
url?: string;
method?: string;
config?: HttpClientRequestConfig; // Request config that failed
// Other relevant info (e.g., from platform layer)
};
severity: 'info' | 'warning' | 'error' | 'critical'; // Severity level
retryable: boolean; // Can this operation be retried?
name: 'StandardError'; // For type identification
}
/** Extends AxiosRequestConfig with kit-specific options. */
export interface HttpClientRequestConfig extends AxiosRequestConfig {
/**
* Does this request require authentication? If true, attempts to attach token.
* If false, skips token attachment. If authType is 'session', ignored (browser handles cookies).
* @default true
*/
requireAuth?: boolean;
/**
* If true, skip the automatic token refresh mechanism if this request fails with 401/403.
* The error will be returned directly. Useful for login/refresh endpoints themselves.
* @default false
*/
skipRefresh?: boolean;
// Caching control (Optional Feature)
/** Force use of cache if available and valid, bypass network. */
// useCache?: boolean;
/** Override global cache TTL (in ms) for this request. */
// cacheTTL?: number;
/** Bypass cache read and force network request; update cache on success. */
// forceRefresh?: boolean;
// Queue control
/** Priority for processing if request gets queued during token refresh. Lower number = higher priority. */
queuePriority?: number;
// Deduplication control (Optional Feature)
/** If true (and feature enabled), check for identical in-flight GET requests. */
// deduplicate?: boolean;
// Retry control
/** Override global max retry count for transient errors for this request. */
maxRetries?: number;
/** Provide specific backoff options for retries of this request. */
// retryBackoffOptions?: BackoffOptions; // Reuse from polling types
/** Internal usage for tracking retries. */
_retryCount?: number;
/** Internal usage for identifying queued requests. */
_queuedId?: string;
}
// --- HTTP Client Interface (/packages/core/src/http/index.ts) ---
import { AxiosInstance, AxiosResponse } from 'axios';
export interface IHttpClient {
/** Makes an HTTP request using the configured Axios instance and interceptors. */
request<T = any, R = AxiosResponse<T>, D = any>(config: HttpClientRequestConfig): Promise<R>;
get<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: HttpClientRequestConfig): Promise<R>;
delete<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: HttpClientRequestConfig): Promise<R>;
head<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: HttpClientRequestConfig): Promise<R>;
options<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: HttpClientRequestConfig): Promise<R>;
post<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: HttpClientRequestConfig): Promise<R>;
put<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: HttpClientRequestConfig): Promise<R>;
patch<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: HttpClientRequestConfig): Promise<R>;
// Configuration Methods
setBaseURL(url: string): void;
setTimeout(timeout: number): void;
setDefaultHeaders(headers: Record<string, string>): void;
// Cache Management (Optional Feature)
/** Clears cached responses. Accepts optional matcher for URL targeting. */
// clearCache(urlMatcher?: string | RegExp): void;
/** Returns the underlying Axios instance for advanced use cases (use cautiously). */
getAxiosInstance(): AxiosInstance;
}
// --- Internal Interfaces ---
/** Internal handler for deciding if/when to retry requests. */
interface IRetryHandler {
/** Determines if an error warrants a retry based on config and error type. */
shouldRetry(error: StandardError, config: HttpClientRequestConfig): boolean;
/** Calculates the delay before the next retry attempt. */
getRetryDelay(attempt: number, config: HttpClientRequestConfig): number;
}
/** Internal handler for normalizing errors into StandardError. */
interface IErrorHandler {
/** Converts an AxiosError into a StandardError, potentially calling global error hook. */
handleAxiosError(error: AxiosError, config: HttpClientRequestConfig): StandardError;
/** Converts generic errors (network, etc.) into a StandardError, potentially calling global error hook. */
handleGenericError(error: any, config?: HttpClientRequestConfig): StandardError;
/** Internal helper to create and potentially report a StandardError. */
// createAndReportError(options: ...): StandardError; // Maybe refactor reporting logic here
}
/** Internal queue for requests pending token refresh. */
interface IRequestQueue {
/** Adds a request function (that returns a promise) to the queue. Returns a promise that resolves/rejects when the request eventually executes. */
enqueue<T>(requestExecutor: () => Promise<T>, config: HttpClientRequestConfig): Promise<T>;
/** Executes all queued requests. Should be called on successful refresh. */
processQueue(): Promise<void[]>; // Returns array of promises for queued requests
/** Rejects all queued requests with the provided error. Should be called on failed refresh. */
clearQueue(reason: StandardError): void;
/** Returns the number of requests currently in the queue. */
getQueueLength(): number;
}-
Unit Tests:
- Test interceptors thoroughly (auth logic per
authType, 401/403 trigger, retry trigger, error mapping). - Test request queue.
- Test error handler mapping.
- Test retry handler backoff/decision logic.
- Test cancellation propagation.
- Test optional cache/dedupe.
- Verify that the global onError hook (if provided during setup) is called exactly once with the created StandardError object.
- Test cases where the global hook might throw an error itself (should be caught gracefully).
- Test interceptors thoroughly (auth logic per
-
Integration Tests:
- Test full request lifecycle including successful auth.
- Test full refresh cycle (401 -> refresh -> queue -> retry).
- Test retry cycle (5xx -> retry).
- Test cancellation of in-flight/queued requests.
- Test interaction with mocked
AuthenticationManagerandINetworkInfo. - Test state updates via
useOperationState.
- Edge Cases: Concurrent 401s, network errors during refresh, queue limits, non-standard errors, cancellation races.
-
AuthManager Interaction:
- Verify the Request Interceptor correctly calls
AuthenticationManager.getToken()based onrequireAuthconfiguration andauthType. - Verify the Response Interceptor correctly calls
AuthenticationManager._triggerRefresh()(or equivalent public method) upon detecting relevant 401/403 errors for token-basedauthTypes. - Confirm the
AuthenticationManager's concurrency lock handles simultaneous_triggerRefreshcalls originating from multiple failed requests within thehttpClient.
- Verify the Request Interceptor correctly calls
-
Request Handling & Flow:
- Verify the Request Interceptor correctly attaches the
Authorizationheader (for JWT/OAuth2) or ensureswithCredentials: trueis set (for Session) based onauthType. - Verify the
RequestQueuecorrectly enqueues requests when triggered by the interceptor during refresh and processes/clears them based on the refresh outcome notified byAuthenticationManager. - Verify the
RetryHandlercorrectly identifies retryableStandardErrors (transient network/server errors) and applies configured backoff delays before rescheduling the request attempt via the interceptor/Axios. - Verify request cancellation using
AbortControllersignals works for both in-flight and queued requests.
- Verify the Request Interceptor correctly attaches the
-
Error Handling & State:
- Verify the
ErrorHandlerconsistently createsStandardErrorobjects from various Axios and network errors. - Confirm that
StandardErrorobjects are propagated correctly via Promise rejections and are received by the optional globalonErrorhook configured inAuthenticationManager. - Verify the
OperationStateslice (pendingRequests,isRefreshingToken) in the internal store is accurately updated by the interceptors/client logic. - Confirm that state changes are correctly reflected when using the
useOperationStatehook.
- Verify the
-
Platform Interaction:
- Verify the
httpClient(likely via interceptors) usesINetworkInfofrom the Platform Abstraction Layer to check network status before making requests, potentially failing fast if offline.
- Verify the
- Internal: Comments detailing interceptor logic, queue state management, retry decision tree, error mapping.
-
External:
-
Usage Guide: How to import and use
httpClient(get,post, etc.). Basic configuration (baseURL). -
Authentication: How
httpClientinteracts with auth (requireAuth, automatic refresh). -
Error Handling: Explaining
StandardError, common codes, how to catch errors. -
Configuration: Detailing
HttpClientRequestConfigoptions (retries, cache, etc.). -
Cancellation: How to use
AbortController. -
Session Auth: Note on
withCredentials.
-
Usage Guide: How to import and use
-
Changelog: Document
httpClientAPI, configuration options, error codes, retry behavior.
- Interceptor performance overhead should be minimal.
- Robustness of the refresh-trigger and queue interaction is critical.
- Retry strategy should be predictable and configurable.
- Error codes list (
StandardErrorCode) should be comprehensive and documented. - Features (cache/dedupe) should default off and be simple to configure/understand.
-
httpClientis exported for application use, distinct from the auth module's internal requester. - Ensure the global
onErrorhook is called reliably from the central error handler without causing infinite loops if the hook itself throws an error.
-
Core Logic:
- Platform-agnostic using Axios.
-
Web:
- Use native
AbortController. - Error handling might consider CORS specifically.
- Use native
-
React Native:
- Consider mobile-specific transient error codes for retry logic.
- Integrate
INetworkInfofor offline checks. - Cancellation needs testing.
-
Configuration: May set different default
timeoutvalues for Web vs RN builds (web default timeouts typically shorter than mobile). -
Dependencies: Relies on platform-provided
INetworkInfo.