UNPKG

mcp-quiz-server

Version:

🧠 AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.

201 lines • 6.45 kB
import { AuthService } from './AuthService'; export class ApiClient { constructor(baseURL = '') { this.baseURL = baseURL; this.authService = AuthService.getInstance(); } static getInstance() { if (!ApiClient.instance) { ApiClient.instance = new ApiClient(); } return ApiClient.instance; } async get(url, config = {}) { return this.request(url, { ...config, method: 'GET' }); } async post(url, data, config = {}) { return this.request(url, { ...config, method: 'POST', data }); } async put(url, data, config = {}) { return this.request(url, { ...config, method: 'PUT', data }); } async delete(url, config = {}) { return this.request(url, { ...config, method: 'DELETE' }); } async patch(url, data, config = {}) { return this.request(url, { ...config, method: 'PATCH', data }); } async request(url, config) { const fullURL = this.buildURL(url); const requestConfig = await this.buildRequestConfig(config); try { const response = await fetch(fullURL, requestConfig); return await this.handleResponse(response); } catch (error) { throw this.handleError(error); } } buildURL(url) { if (url.startsWith('http://') || url.startsWith('https://')) { return url; } const base = this.baseURL || window.location.origin; const cleanBase = base.replace(/\/$/, ''); const cleanPath = url.replace(/^\//, ''); return `${cleanBase}/${cleanPath}`; } async buildRequestConfig(config) { const headers = new Headers(config.headers); if (!config.skipAuth) { const authHeader = this.authService.getAuthHeader(); if (authHeader) { headers.set('Authorization', authHeader); } } if (config.data && !headers.has('Content-Type')) { headers.set('Content-Type', 'application/json'); } const requestConfig = { method: config.method || 'GET', headers, credentials: 'include', }; if (config.data) { if (config.data instanceof FormData) { requestConfig.body = config.data; headers.delete('Content-Type'); } else { requestConfig.body = JSON.stringify(config.data); } } if (config.signal) { requestConfig.signal = config.signal; } return requestConfig; } async handleResponse(response) { const contentType = response.headers.get('Content-Type') || ''; const isJSON = contentType.includes('application/json'); let data; try { if (isJSON) { data = await response.json(); } else { data = await response.text(); } } catch (error) { data = null; } if (response.status === 401) { await this.handleUnauthorized(); throw new ApiError('Authentication required', 401, data); } if (!response.ok) { const message = this.extractErrorMessage(data); throw new ApiError(message, response.status, data); } return { data, status: response.status, statusText: response.statusText, headers: this.responseHeadersToObject(response.headers), }; } async handleUnauthorized() { try { await this.authService.refreshToken(); } catch (error) { await this.authService.logout(); if (!window.location.pathname.includes('/auth/')) { window.location.href = '/auth/login.html'; } } } extractErrorMessage(data) { if (!data) return 'Request failed'; if (typeof data === 'string') { return data; } if (data.error) { if (typeof data.error === 'string') { return data.error; } if (data.error.message) { return data.error.message; } } if (data.message) { return data.message; } return 'Request failed'; } handleError(error) { if (error instanceof ApiError) { return error; } if (error.name === 'AbortError') { return new ApiError('Request cancelled', 0, error); } if (error.name === 'TypeError' && error.message.includes('fetch')) { return new ApiError('Network error. Please check your connection.', 0, error); } return new ApiError(error.message || 'An unexpected error occurred', 0, error); } responseHeadersToObject(headers) { const result = {}; headers.forEach((value, key) => { result[key] = value; }); return result; } async requestWithTimeout(url, config, timeout = 30000) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await this.request(url, { ...config, signal: controller.signal, }); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); throw error; } } async uploadFile(url, file, config = {}) { const formData = new FormData(); formData.append(config.fieldName || 'file', file); if (config.fields) { Object.entries(config.fields).forEach(([key, value]) => { formData.append(key, value); }); } return this.post(url, formData, { skipAuth: config.skipAuth, headers: config.headers, }); } setBaseURL(baseURL) { this.baseURL = baseURL; } getBaseURL() { return this.baseURL; } } export class ApiError extends Error { constructor(message, status = 0, data = null) { super(message); this.name = 'ApiError'; this.status = status; this.data = data; } } //# sourceMappingURL=ApiClient.js.map