Advanced
The Aurelia Fetch Client offers powerful advanced features for enterprise applications, including sophisticated caching, request monitoring, custom interceptor patterns, and integration with complex authentication systems.
Advanced Header Management
Dynamic Authorization Headers
Headers can be functions that are evaluated for each request, perfect for handling token refresh scenarios:
import { IHttpClient } from '@aurelia/fetch-client';
import { resolve } from '@aurelia/kernel';
export class AuthenticatedApiService {
private http = resolve(IHttpClient);
private tokenStorage = new TokenStorage();
constructor() {
this.http.configure(config => config
.withDefaults({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': () => {
const token = this.tokenStorage.getAccessToken();
return token ? `Bearer ${token}` : '';
},
'X-Client-Version': () => this.getClientVersion(),
'X-Request-ID': () => this.generateRequestId()
}
})
);
}
private getClientVersion(): string {
return process.env.APP_VERSION || '1.0.0';
}
private generateRequestId(): string {
return `${Date.now()}-${Math.random().toString(36).substring(2)}`;
}
}
class TokenStorage {
getAccessToken(): string | null {
const tokenData = localStorage.getItem('auth_tokens');
if (!tokenData) return null;
const { accessToken, expiresAt } = JSON.parse(tokenData);
// Check if token is expired
if (Date.now() >= expiresAt) {
this.refreshToken();
return this.getAccessToken(); // Recursive call after refresh
}
return accessToken;
}
private refreshToken(): void {
// Token refresh logic here
// This is synchronous for simplicity, but could be async
}
}Conditional Headers
Apply different headers based on request characteristics:
export class ConditionalHeaderService {
private http = resolve(IHttpClient);
constructor() {
this.http.configure(config => config.withInterceptor({
request(request) {
const url = new URL(request.url);
// Add API key for external APIs
if (url.hostname !== window.location.hostname) {
request.headers.set('X-API-Key', this.getExternalApiKey(url.hostname));
}
// Add CSRF token for state-changing operations
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(request.method)) {
const csrfToken = this.getCsrfToken();
if (csrfToken) {
request.headers.set('X-CSRF-Token', csrfToken);
}
}
// Add correlation ID for internal services
if (url.pathname.startsWith('/api/internal/')) {
request.headers.set('X-Correlation-ID', this.generateCorrelationId());
}
return request;
}
}));
}
private getExternalApiKey(hostname: string): string {
const keyMap = {
'api.external1.com': process.env.EXTERNAL_API_1_KEY,
'api.external2.com': process.env.EXTERNAL_API_2_KEY
};
return keyMap[hostname] || '';
}
private getCsrfToken(): string {
return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
}
private generateCorrelationId(): string {
return `corr-${Date.now()}-${Math.random().toString(36).substring(2)}`;
}
}Built-in Cache Interceptor
The fetch client includes a sophisticated caching system with multiple storage options:
import { IHttpClient, CacheInterceptor } from '@aurelia/fetch-client';
import { DI } from '@aurelia/kernel';
export class CachedApiService {
private http = resolve(IHttpClient);
constructor() {
this.setupCaching();
}
private setupCaching() {
// Create cache interceptor with configuration
const cacheInterceptor = DI.getGlobalContainer().invoke(CacheInterceptor, [{
cacheTime: 300_000, // Cache for 5 minutes
staleTime: 60_000, // Data becomes stale after 1 minute
refreshStaleImmediate: false, // Don't block on stale refresh
refreshInterval: 30_000, // Background refresh every 30 seconds
}]);
this.http.configure(config => config.withInterceptor(cacheInterceptor));
}
// Cache interceptor automatically handles these GET requests
async getUserProfile(userId: string) {
const response = await this.http.get(`/api/users/${userId}`);
return response.json();
}
async getStaticData() {
// This will be cached and refreshed in background
const response = await this.http.get('/api/config/static');
return response.json();
}
}Custom Cache Storage
Use different storage backends for caching:
import {
CacheInterceptor,
BrowserLocalStorage,
BrowserSessionStorage,
BrowserIndexDBStorage,
MemoryStorage
} from '@aurelia/fetch-client';
export class CustomCacheService {
private http = resolve(IHttpClient);
setupPersistentCaching() {
// Use localStorage for persistent caching across sessions
const persistentCache = DI.getGlobalContainer().invoke(CacheInterceptor, [{
cacheTime: 3600_000, // 1 hour
storage: new BrowserLocalStorage()
}]);
this.http.configure(config => config.withInterceptor(persistentCache));
}
setupSessionCaching() {
// Use sessionStorage for session-only caching
const sessionCache = DI.getGlobalContainer().invoke(CacheInterceptor, [{
cacheTime: 1800_000, // 30 minutes
storage: new BrowserSessionStorage()
}]);
this.http.configure(config => config.withInterceptor(sessionCache));
}
setupIndexDBCaching() {
// Use IndexedDB for large data caching
const indexDbCache = DI.getGlobalContainer().invoke(CacheInterceptor, [{
cacheTime: 7200_000, // 2 hours
storage: new BrowserIndexDBStorage()
}]);
this.http.configure(config => config.withInterceptor(indexDbCache));
}
}Request Tracking and Lifecycle Management
The HttpClient provides built-in request tracking capabilities that enable you to monitor active requests and respond to request lifecycle events. This is essential for building loading indicators, progress tracking, and request coordination features.
Built-in Request Properties
The HttpClient exposes two key properties for request tracking:
import { IHttpClient } from '@aurelia/fetch-client';
import { resolve } from '@aurelia/kernel';
export class RequestTrackerService {
private http = resolve(IHttpClient);
checkRequestStatus() {
// Get the current number of active requests
console.log('Active requests:', this.http.activeRequestCount);
// Check if any requests are currently active
console.log('Is requesting:', this.http.isRequesting);
}
async makeTrackedRequest() {
console.log('Before request:', this.http.activeRequestCount); // 0
console.log('Is requesting:', this.http.isRequesting); // false
const promise = this.http.get('/api/data');
console.log('During request:', this.http.activeRequestCount); // 1
console.log('Is requesting:', this.http.isRequesting); // true
await promise;
console.log('After request:', this.http.activeRequestCount); // 0
console.log('Is requesting:', this.http.isRequesting); // false
}
}Key Properties:
activeRequestCount: The current number of active requests (including those being processed by interceptors)isRequesting: Boolean indicating whether one or more requests are currently active
Request Lifecycle Events
The HttpClient can dispatch DOM events for request lifecycle tracking. This requires configuring an event dispatcher using withDispatcher().
Available Events
import { HttpClientEvent } from '@aurelia/fetch-client';
// Available lifecycle events:
HttpClientEvent.started // 'aurelia-fetch-client-request-started'
HttpClientEvent.drained // 'aurelia-fetch-client-requests-drained'started: Fired when the first request starts (whenactiveRequestCountgoes from 0 to 1)drained: Fired when all requests complete (whenactiveRequestCountreturns to 0)
Configuring Event Dispatcher
import { IHttpClient, HttpClientEvent } from '@aurelia/fetch-client';
import { resolve } from '@aurelia/kernel';
export class RequestEventService {
private http = resolve(IHttpClient);
constructor() {
// Configure event dispatcher on a DOM node
this.http.configure(config => config.withDispatcher(document.body));
// Listen for lifecycle events
this.setupEventListeners();
}
private setupEventListeners() {
document.body.addEventListener(HttpClientEvent.started, (event: CustomEvent) => {
console.log('First request started');
// Event fires when activeRequestCount goes from 0 to 1
});
document.body.addEventListener(HttpClientEvent.drained, (event: CustomEvent) => {
console.log('All requests completed');
// Event fires when activeRequestCount returns to 0
});
}
}Building a Loading Indicator
Use request tracking to implement a global loading indicator:
export class LoadingIndicatorService {
private http = resolve(IHttpClient);
private loadingElement: HTMLElement;
constructor() {
this.loadingElement = document.getElementById('loading-indicator');
this.setupLoadingIndicator();
}
private setupLoadingIndicator() {
// Configure event dispatcher
this.http.configure(config => config.withDispatcher(document.body));
// Show loading indicator when requests start
document.body.addEventListener(HttpClientEvent.started, () => {
this.showLoadingIndicator();
});
// Hide loading indicator when all requests complete
document.body.addEventListener(HttpClientEvent.drained, () => {
this.hideLoadingIndicator();
});
}
private showLoadingIndicator() {
this.loadingElement.classList.add('active');
document.body.classList.add('loading');
}
private hideLoadingIndicator() {
this.loadingElement.classList.remove('active');
document.body.classList.remove('loading');
}
}Advanced Request Monitoring
Combine built-in tracking with custom monitoring:
export class AdvancedRequestMonitor {
private http = resolve(IHttpClient);
private requestDetails = new Map<string, {
url: string;
method: string;
startTime: number;
}>();
constructor() {
this.setupComprehensiveMonitoring();
}
private setupComprehensiveMonitoring() {
// Configure event dispatcher
this.http.configure(config => config
.withDispatcher(document.body)
.withInterceptor({
request: (request) => {
const requestId = this.generateRequestId();
request.headers.set('X-Request-ID', requestId);
// Store request details
this.requestDetails.set(requestId, {
url: request.url,
method: request.method,
startTime: Date.now(),
});
console.log(`[${requestId}] Starting: ${request.method} ${request.url}`);
console.log(`Active requests: ${this.http.activeRequestCount}`);
return request;
},
response: (response, request) => {
const requestId = request?.headers.get('X-Request-ID');
if (requestId) {
const details = this.requestDetails.get(requestId);
if (details) {
const duration = Date.now() - details.startTime;
console.log(`[${requestId}] Completed in ${duration}ms: ${response.status}`);
this.requestDetails.delete(requestId);
}
}
console.log(`Remaining requests: ${this.http.activeRequestCount - 1}`);
return response;
},
responseError: (error, request) => {
const requestId = request?.headers.get('X-Request-ID');
if (requestId) {
const details = this.requestDetails.get(requestId);
if (details) {
const duration = Date.now() - details.startTime;
console.error(`[${requestId}] Failed after ${duration}ms`);
this.requestDetails.delete(requestId);
}
}
throw error;
}
})
);
// Listen for lifecycle events
document.body.addEventListener(HttpClientEvent.started, () => {
console.log('🚀 Request activity started');
this.onRequestActivityStarted();
});
document.body.addEventListener(HttpClientEvent.drained, () => {
console.log('✅ Request activity completed');
this.onRequestActivityCompleted();
});
}
private onRequestActivityStarted() {
// Custom logic when requests begin
// This fires only when going from 0 to 1 active requests
}
private onRequestActivityCompleted() {
// Custom logic when all requests complete
// This fires only when going from 1+ to 0 active requests
console.log('All tracked requests completed:', this.requestDetails.size === 0);
}
private generateRequestId(): string {
return `req-${Date.now()}-${Math.random().toString(36).substring(2)}`;
}
// Public API
getActiveRequestCount(): number {
return this.http.activeRequestCount;
}
isRequesting(): boolean {
return this.http.isRequesting;
}
getCurrentRequests(): Array<{ url: string; method: string; duration: number }> {
return Array.from(this.requestDetails.values()).map(details => ({
...details,
duration: Date.now() - details.startTime,
}));
}
}Progress Tracking Component
Build a reactive progress tracker:
export class ProgressTracker {
private http = resolve(IHttpClient);
private progressCallbacks = new Set<(progress: RequestProgress) => void>();
constructor() {
this.setupProgressTracking();
}
private setupProgressTracking() {
this.http.configure(config => config
.withDispatcher(document.body)
.withInterceptor({
request: (request) => {
this.notifyProgress();
return request;
},
response: (response) => {
this.notifyProgress();
return response;
},
responseError: (error) => {
this.notifyProgress();
throw error;
}
})
);
// React to lifecycle events
document.body.addEventListener(HttpClientEvent.started, () => {
this.notifyProgress();
});
document.body.addEventListener(HttpClientEvent.drained, () => {
this.notifyProgress();
});
}
private notifyProgress() {
const progress: RequestProgress = {
activeCount: this.http.activeRequestCount,
isRequesting: this.http.isRequesting,
timestamp: Date.now(),
};
this.progressCallbacks.forEach(callback => callback(progress));
}
subscribe(callback: (progress: RequestProgress) => void): () => void {
this.progressCallbacks.add(callback);
// Return unsubscribe function
return () => {
this.progressCallbacks.delete(callback);
};
}
getCurrentProgress(): RequestProgress {
return {
activeCount: this.http.activeRequestCount,
isRequesting: this.http.isRequesting,
timestamp: Date.now(),
};
}
}
interface RequestProgress {
activeCount: number;
isRequesting: boolean;
timestamp: number;
}Request Queue Visualization
Display active requests in real-time:
export class RequestQueueVisualizer {
private http = resolve(IHttpClient);
private queueDisplay: HTMLElement;
constructor(queueDisplay: HTMLElement) {
this.queueDisplay = queueDisplay;
this.setupVisualization();
}
private setupVisualization() {
this.http.configure(config => config
.withDispatcher(document.body)
.withInterceptor({
request: (request) => {
this.updateDisplay();
return request;
},
response: (response) => {
this.updateDisplay();
return response;
},
responseError: (error) => {
this.updateDisplay();
throw error;
}
})
);
}
private updateDisplay() {
const count = this.http.activeRequestCount;
const status = this.http.isRequesting ? 'active' : 'idle';
this.queueDisplay.innerHTML = `
<div class="request-queue ${status}">
<div class="status">${status.toUpperCase()}</div>
<div class="count">
<span class="number">${count}</span>
<span class="label">${count === 1 ? 'request' : 'requests'} active</span>
</div>
<div class="indicator">
${this.createIndicatorDots(count)}
</div>
</div>
`;
}
private createIndicatorDots(count: number): string {
return Array.from({ length: Math.min(count, 10) }, () =>
'<span class="dot"></span>'
).join('');
}
}Best Practices for Request Tracking
1. Use Events for UI Updates
Prefer lifecycle events over polling for UI updates:
// Good - Event-driven
document.body.addEventListener(HttpClientEvent.started, () => {
showLoadingSpinner();
});
// Avoid - Polling
setInterval(() => {
if (this.http.isRequesting) {
showLoadingSpinner();
}
}, 100);2. Single Event Dispatcher
Configure the dispatcher once during initialization:
export class HttpClientSetup {
static initialize(http: IHttpClient) {
http.configure(config => config
.withDispatcher(document.body)
.withBaseUrl('/api')
// ... other configuration
);
}
}3. Cleanup Event Listeners
Always remove event listeners when components are destroyed:
export class RequestMonitorComponent {
private startedListener: EventListener;
private drainedListener: EventListener;
constructor() {
this.startedListener = () => this.onRequestsStarted();
this.drainedListener = () => this.onRequestsDrained();
document.body.addEventListener(HttpClientEvent.started, this.startedListener);
document.body.addEventListener(HttpClientEvent.drained, this.drainedListener);
}
dispose() {
document.body.removeEventListener(HttpClientEvent.started, this.startedListener);
document.body.removeEventListener(HttpClientEvent.drained, this.drainedListener);
}
}4. Combine with Interceptors
Use interceptors for detailed request tracking:
export class DetailedRequestTracker {
private http = resolve(IHttpClient);
constructor() {
this.http.configure(config => config
.withDispatcher(document.body)
.withInterceptor({
request: (request) => {
// Track individual request start
console.log('Request started:', request.url);
console.log('Total active:', this.http.activeRequestCount);
return request;
},
response: (response, request) => {
// Track individual request completion
console.log('Request completed:', request?.url);
console.log('Remaining active:', this.http.activeRequestCount - 1);
return response;
}
})
);
}
}Summary
Request tracking provides:
activeRequestCount: Number of currently active requestsisRequesting: Boolean indicating if any requests are activeHttpClientEvent.started: Fired when first request startsHttpClientEvent.drained: Fired when all requests completewithDispatcher(node): Configure DOM node for event dispatching
These features enable robust loading indicators, progress tracking, and request coordination in your applications.
Advanced Authentication Patterns
Automatic Token Refresh
Handle token expiration and refresh transparently:
export class TokenRefreshService {
private http = resolve(IHttpClient);
private refreshPromise: Promise<string> | null = null;
constructor() {
this.setupTokenRefresh();
}
private setupTokenRefresh() {
this.http.configure(config => config.withInterceptor({
async responseError(error, request, client) {
if (error instanceof Response && error.status === 401) {
// Token expired, try to refresh
try {
const newToken = await this.refreshAccessToken();
// Retry original request with new token
const newRequest = new Request(request.url, {
method: request.method,
headers: {
...Object.fromEntries(request.headers.entries()),
'Authorization': `Bearer ${newToken}`
},
body: request.body
});
return client.fetch(newRequest);
} catch (refreshError) {
// Refresh failed, redirect to login
this.redirectToLogin();
throw error;
}
}
throw error;
}
}));
}
private async refreshAccessToken(): Promise<string> {
// Prevent multiple simultaneous refresh attempts
if (this.refreshPromise) {
return this.refreshPromise;
}
this.refreshPromise = this.performTokenRefresh();
try {
const token = await this.refreshPromise;
return token;
} finally {
this.refreshPromise = null;
}
}
private async performTokenRefresh(): Promise<string> {
const refreshToken = this.getRefreshToken();
if (!refreshToken) {
throw new Error('No refresh token available');
}
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken })
});
if (!response.ok) {
throw new Error('Token refresh failed');
}
const { accessToken, refreshToken: newRefreshToken } = await response.json();
// Store new tokens
this.storeTokens(accessToken, newRefreshToken);
return accessToken;
}
private getRefreshToken(): string | null {
const tokens = localStorage.getItem('auth_tokens');
return tokens ? JSON.parse(tokens).refreshToken : null;
}
private storeTokens(accessToken: string, refreshToken: string) {
const tokens = {
accessToken,
refreshToken,
expiresAt: Date.now() + (55 * 60 * 1000) // 55 minutes
};
localStorage.setItem('auth_tokens', JSON.stringify(tokens));
}
private redirectToLogin() {
localStorage.removeItem('auth_tokens');
window.location.href = '/login';
}
}Request Batching and Coordination
Request Deduplication
Prevent duplicate concurrent requests:
export class RequestDeduplicationService {
private http = resolve(IHttpClient);
private pendingRequests = new Map<string, Promise<Response>>();
constructor() {
this.setupDeduplication();
}
private setupDeduplication() {
this.http.configure(config => config.withInterceptor({
request: (request) => {
// Only deduplicate GET requests
if (request.method !== 'GET') {
return request;
}
const key = this.getRequestKey(request);
const existingRequest = this.pendingRequests.get(key);
if (existingRequest) {
// Return the existing request's promise
return existingRequest.then(response => response.clone());
}
// Store the request promise
const requestPromise = fetch(request).then(response => {
// Clean up when request completes
this.pendingRequests.delete(key);
return response;
}).catch(error => {
// Clean up on error too
this.pendingRequests.delete(key);
throw error;
});
this.pendingRequests.set(key, requestPromise);
// Return the request to continue normal processing
return request;
}
}));
}
private getRequestKey(request: Request): string {
// Create unique key based on URL and headers
const headers = Array.from(request.headers.entries()).sort();
return `${request.method}:${request.url}:${JSON.stringify(headers)}`;
}
}Request Queuing
Queue and coordinate multiple requests:
export class RequestQueueService {
private http = resolve(IHttpClient);
private requestQueue: Array<() => Promise<any>> = [];
private isProcessing = false;
private maxConcurrent = 3;
private activeRequests = 0;
async queueRequest<T>(requestFn: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.requestQueue.push(async () => {
try {
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
}
});
this.processQueue();
});
}
private async processQueue() {
if (this.isProcessing || this.activeRequests >= this.maxConcurrent) {
return;
}
const request = this.requestQueue.shift();
if (!request) {
return;
}
this.isProcessing = true;
this.activeRequests++;
try {
await request();
} finally {
this.activeRequests--;
this.isProcessing = false;
// Process next request in queue
if (this.requestQueue.length > 0) {
setTimeout(() => this.processQueue(), 0);
}
}
}
// Usage example
async uploadFiles(files: File[]) {
const uploadPromises = files.map(file =>
this.queueRequest(() =>
this.http.post('/api/upload', { body: this.createFormData(file) })
)
);
return Promise.all(uploadPromises);
}
private createFormData(file: File): FormData {
const formData = new FormData();
formData.append('file', file);
return formData;
}
}Performance Optimization
Request Timeout Management
Implement sophisticated timeout handling:
export class TimeoutService {
private http = resolve(IHttpClient);
constructor() {
this.setupTimeouts();
}
private setupTimeouts() {
this.http.configure(config => config.withInterceptor({
request: (request) => {
// Add timeout based on request type
const timeout = this.getTimeoutForRequest(request);
if (timeout > 0) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
// Store cleanup function
(request as any).__timeoutId = timeoutId;
return new Request(request, { signal: controller.signal });
}
return request;
},
response: (response, request) => {
// Clear timeout on successful response
const timeoutId = (request as any).__timeoutId;
if (timeoutId) {
clearTimeout(timeoutId);
}
return response;
},
responseError: (error, request) => {
// Clear timeout on error
const timeoutId = (request as any).__timeoutId;
if (timeoutId) {
clearTimeout(timeoutId);
}
// Convert AbortError to TimeoutError for clarity
if (error.name === 'AbortError') {
throw new Error(`Request timeout: ${request?.url}`);
}
throw error;
}
}));
}
private getTimeoutForRequest(request: Request): number {
const url = new URL(request.url);
// Different timeouts for different types of requests
if (url.pathname.includes('/upload')) {
return 300_000; // 5 minutes for uploads
} else if (url.pathname.includes('/reports')) {
return 120_000; // 2 minutes for reports
} else if (request.method === 'GET') {
return 30_000; // 30 seconds for GET requests
} else {
return 60_000; // 1 minute for other requests
}
}
}Response Compression Handling
Handle compressed responses efficiently:
export class CompressionService {
private http = resolve(IHttpClient);
constructor() {
this.setupCompression();
}
private setupCompression() {
this.http.configure(config => config
.withDefaults({
headers: {
'Accept-Encoding': 'gzip, deflate, br'
}
})
.withInterceptor({
response: (response) => {
const encoding = response.headers.get('content-encoding');
if (encoding) {
console.log(`Response compressed with: ${encoding}`);
// The browser automatically decompresses, but we can log it
const originalSize = response.headers.get('content-length');
if (originalSize) {
console.log(`Compressed size: ${originalSize} bytes`);
}
}
return response;
}
})
);
}
}Testing and Debugging Support
Request/Response Logging
Comprehensive logging for development and debugging:
export class LoggingService {
private http = resolve(IHttpClient);
private isDevelopment = process.env.NODE_ENV === 'development';
constructor() {
if (this.isDevelopment) {
this.setupDetailedLogging();
} else {
this.setupProductionLogging();
}
}
private setupDetailedLogging() {
this.http.configure(config => config.withInterceptor({
request: (request) => {
const requestId = this.generateRequestId();
(request as any).__requestId = requestId;
console.group(`🚀 Request ${requestId}`);
console.log('Method:', request.method);
console.log('URL:', request.url);
console.log('Headers:', Object.fromEntries(request.headers.entries()));
if (request.body) {
this.logRequestBody(request);
}
console.groupEnd();
return request;
},
response: async (response, request) => {
const requestId = (request as any).__requestId;
const responseClone = response.clone();
console.group(`✅ Response ${requestId}`);
console.log('Status:', response.status, response.statusText);
console.log('Headers:', Object.fromEntries(response.headers.entries()));
try {
const body = await responseClone.text();
if (body) {
console.log('Body:', this.formatResponseBody(body, response));
}
} catch (error) {
console.log('Body: (could not read)');
}
console.groupEnd();
return response;
},
responseError: (error, request) => {
const requestId = (request as any).__requestId;
console.group(`❌ Error ${requestId}`);
console.error('Error:', error);
console.groupEnd();
throw error;
}
}));
}
private setupProductionLogging() {
this.http.configure(config => config.withInterceptor({
responseError: (error, request) => {
// Only log errors in production
console.error('HTTP Error:', {
url: request?.url,
method: request?.method,
error: error.message,
timestamp: new Date().toISOString()
});
throw error;
}
}));
}
private logRequestBody(request: Request) {
// Note: This is tricky because Request.body is a stream
// In practice, you might want to log at a higher level
console.log('Body: (stream - cannot log without consuming)');
}
private formatResponseBody(body: string, response: Response): any {
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
try {
return JSON.parse(body);
} catch {
return body;
}
}
return body.length > 200 ? `${body.substring(0, 200)}...` : body;
}
private generateRequestId(): string {
return Math.random().toString(36).substring(2, 8);
}
}These advanced patterns demonstrate the full power of the Aurelia Fetch Client for enterprise applications. The combination of interceptors, event monitoring, caching, and authentication patterns provides a robust foundation for complex HTTP client requirements.
Last updated
Was this helpful?