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
JavaScript
// 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
}