UNPKG

muspe-cli

Version:

MusPE Advanced Framework v2.1.3 - Mobile User-friendly Simple Progressive Engine with Enhanced CLI Tools, Specialized E-Commerce Templates, Material Design 3, Progressive Enhancement, Mobile Optimizations, Performance Analysis, and Enterprise-Grade Develo

424 lines (351 loc) 11.1 kB
// MusPE Fetch Library - Enhanced HTTP client with modern features class MusPEFetch { constructor(config = {}) { this.baseURL = config.baseURL || ''; this.timeout = config.timeout || 10000; this.headers = { 'Content-Type': 'application/json', ...config.headers }; this.interceptors = { request: [], response: [], error: [] }; this.cache = new Map(); this.retryConfig = { attempts: 3, delay: 1000, ...config.retry }; } // Core HTTP methods async get(url, options = {}) { return this.request(url, { ...options, method: 'GET' }); } async post(url, data, options = {}) { return this.request(url, { ...options, method: 'POST', data }); } async put(url, data, options = {}) { return this.request(url, { ...options, method: 'PUT', data }); } async patch(url, data, options = {}) { return this.request(url, { ...options, method: 'PATCH', data }); } async delete(url, options = {}) { return this.request(url, { ...options, method: 'DELETE' }); } async head(url, options = {}) { return this.request(url, { ...options, method: 'HEAD' }); } async options(url, options = {}) { return this.request(url, { ...options, method: 'OPTIONS' }); } // Main request method async request(url, options = {}) { const config = this.buildConfig(url, options); try { // Apply request interceptors for (const interceptor of this.interceptors.request) { await interceptor(config); } // Check cache if (config.cache && config.method === 'GET') { const cached = this.getFromCache(config.fullURL); if (cached) return cached; } // Make request with retry logic const response = await this.makeRequestWithRetry(config); // Apply response interceptors for (const interceptor of this.interceptors.response) { await interceptor(response, config); } // Cache successful GET requests if (config.cache && config.method === 'GET' && response.ok) { this.addToCache(config.fullURL, response.clone(), config.cacheTime); } return response; } catch (error) { // Apply error interceptors for (const interceptor of this.interceptors.error) { await interceptor(error, config); } throw error; } } // Build request configuration buildConfig(url, options) { const fullURL = this.resolveURL(url); const headers = { ...this.headers, ...options.headers }; // Handle different data types let body = options.data; if (body && typeof body === 'object' && !(body instanceof FormData)) { if (headers['Content-Type']?.includes('application/json')) { body = JSON.stringify(body); } else if (headers['Content-Type']?.includes('application/x-www-form-urlencoded')) { body = new URLSearchParams(body).toString(); } } return { fullURL, method: options.method || 'GET', headers, body, timeout: options.timeout || this.timeout, cache: options.cache !== false, cacheTime: options.cacheTime || 300000, // 5 minutes default retry: options.retry !== false, credentials: options.credentials || 'same-origin', signal: options.signal, ...options }; } // Make request with retry logic async makeRequestWithRetry(config) { let lastError; const maxAttempts = config.retry ? this.retryConfig.attempts : 1; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), config.timeout); const signal = config.signal ? this.combineSignals(config.signal, controller.signal) : controller.signal; const response = await fetch(config.fullURL, { method: config.method, headers: config.headers, body: config.body, credentials: config.credentials, signal }); clearTimeout(timeoutId); if (!response.ok) { throw new MusPEFetchError(response.statusText, response.status, response); } // Add helper methods to response return this.enhanceResponse(response); } catch (error) { lastError = error; // Don't retry on certain errors if (this.shouldNotRetry(error) || attempt === maxAttempts) { break; } // Wait before retry await this.delay(this.retryConfig.delay * attempt); } } throw lastError; } // Enhance response with helper methods enhanceResponse(response) { const enhancedResponse = response.clone(); // Add JSON parsing helper enhancedResponse.json = async () => { try { return await response.json(); } catch (error) { throw new MusPEFetchError('Invalid JSON response', response.status, response); } }; // Add text parsing helper enhancedResponse.text = async () => { return await response.text(); }; // Add blob parsing helper enhancedResponse.blob = async () => { return await response.blob(); }; // Add form data parsing helper enhancedResponse.formData = async () => { return await response.formData(); }; return enhancedResponse; } // URL resolution resolveURL(url) { if (url.startsWith('http://') || url.startsWith('https://')) { return url; } return this.baseURL + (url.startsWith('/') ? url : '/' + url); } // Cache management getFromCache(url) { const cached = this.cache.get(url); if (cached && Date.now() < cached.expiry) { return cached.response.clone(); } this.cache.delete(url); return null; } addToCache(url, response, cacheTime) { this.cache.set(url, { response: response.clone(), expiry: Date.now() + cacheTime }); } clearCache() { this.cache.clear(); } // Interceptors interceptRequest(interceptor) { this.interceptors.request.push(interceptor); return this; } interceptResponse(interceptor) { this.interceptors.response.push(interceptor); return this; } interceptError(interceptor) { this.interceptors.error.push(interceptor); return this; } // Utility methods combineSignals(signal1, signal2) { const controller = new AbortController(); signal1.addEventListener('abort', () => controller.abort()); signal2.addEventListener('abort', () => controller.abort()); return controller.signal; } shouldNotRetry(error) { // Don't retry on client errors (4xx) or abort errors return error.name === 'AbortError' || (error.status >= 400 && error.status < 500); } delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Configuration methods setBaseURL(baseURL) { this.baseURL = baseURL; return this; } setHeader(name, value) { this.headers[name] = value; return this; } setHeaders(headers) { Object.assign(this.headers, headers); return this; } setTimeout(timeout) { this.timeout = timeout; return this; } setRetry(config) { Object.assign(this.retryConfig, config); return this; } // Convenience methods for common patterns async upload(url, file, options = {}) { const formData = new FormData(); formData.append('file', file); if (options.fields) { Object.entries(options.fields).forEach(([key, value]) => { formData.append(key, value); }); } return this.post(url, formData, { ...options, headers: { ...options.headers // Don't set Content-Type, let browser set it with boundary } }); } async download(url, options = {}) { const response = await this.get(url, { ...options, cache: false }); return response.blob(); } async downloadAsFile(url, filename, options = {}) { const blob = await this.download(url, options); const downloadUrl = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = downloadUrl; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(downloadUrl); } // GraphQL support async graphql(query, variables = {}, options = {}) { return this.post('/graphql', { query, variables }, options); } // Batch requests async batch(requests) { const promises = requests.map(req => { if (typeof req === 'string') { return this.get(req); } return this.request(req.url, req.options); }); return Promise.allSettled(promises); } // Server-Sent Events helper createEventSource(url, options = {}) { const fullURL = this.resolveURL(url); return new EventSource(fullURL, options); } } // Custom error class class MusPEFetchError extends Error { constructor(message, status, response) { super(message); this.name = 'MusPEFetchError'; this.status = status; this.response = response; } } // Create default instance const fetch = new MusPEFetch(); // Add common request interceptors fetch.interceptRequest(async (config) => { // Add timestamp to prevent caching in IE if (config.method === 'GET' && !config.cache) { const separator = config.fullURL.includes('?') ? '&' : '?'; config.fullURL += `${separator}_t=${Date.now()}`; } // Add CSRF token if available const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content; if (csrfToken) { config.headers['X-CSRF-Token'] = csrfToken; } }); // Add common response interceptors fetch.interceptResponse(async (response, config) => { // Log requests in development if (process.env.NODE_ENV === 'development' || window.location.hostname === 'localhost') { console.log(`[MusPE Fetch] ${config.method} ${config.fullURL}`, { status: response.status, headers: Object.fromEntries(response.headers.entries()) }); } }); // Add common error interceptors fetch.interceptError(async (error, config) => { // Log errors console.error(`[MusPE Fetch Error] ${config.method} ${config.fullURL}`, error); // Handle common HTTP errors if (error.status === 401) { // Redirect to login or refresh token console.warn('Unauthorized request detected'); } else if (error.status === 403) { console.warn('Forbidden request detected'); } else if (error.status >= 500) { console.error('Server error detected'); } }); // Export for module usage if (typeof module !== 'undefined' && module.exports) { module.exports = { MusPEFetch, MusPEFetchError, fetch }; } // Make available globally if (typeof window !== 'undefined') { window.MusPEFetch = MusPEFetch; window.MusPEFetchError = MusPEFetchError; window.fetch = fetch; // Enhanced fetch window.$http = fetch; // Alternative name }