This repository has no description
1/**
2 * Utility functions for API interactions
3 */
4
5/**
6 * Makes a fetch request with proper error handling, timeouts, and authentication.
7 *
8 * @param {string} url - The URL to fetch
9 * @param {Object} options - Fetch options
10 * @param {number} [timeout=10000] - Timeout in milliseconds
11 * @returns {Promise<Object>} - The JSON response or error object
12 */
13export const fetchWithTimeout = async (url, options = {}, timeout = 10000) => {
14 // Always include credentials for session cookies
15 const fetchOptions = {
16 ...options,
17 credentials: 'include',
18 };
19
20 // Set up abort controller for timeout
21 const controller = new AbortController();
22 const timeoutId = setTimeout(() => controller.abort(), timeout);
23
24 try {
25 const response = await fetch(url, {
26 ...fetchOptions,
27 signal: controller.signal,
28 });
29
30 // Clear the timeout regardless of the outcome
31 clearTimeout(timeoutId);
32
33 // Check if the response was ok (status in the range 200-299)
34 if (!response.ok) {
35 // Try to get the error message from the response body
36 let errorMessage;
37 try {
38 const errorData = await response.json();
39 errorMessage = errorData.error || errorData.message || errorData.details;
40 } catch (e) {
41 // If we can't parse the error as JSON, use the status text
42 errorMessage = response.statusText;
43 }
44
45 // Create an error object with useful properties
46 const error = new Error(errorMessage || `HTTP error ${response.status}`);
47 error.status = response.status;
48 error.statusText = response.statusText;
49 error.url = url;
50
51 // Handle auth errors specifically
52 if (response.status === 401) {
53 error.isAuthError = true;
54 }
55
56 throw error;
57 }
58
59 // Parse the JSON response
60 const data = await response.json();
61 return data;
62 } catch (error) {
63 // Clear the timeout if we catch an error before it fires
64 clearTimeout(timeoutId);
65
66 // Enhance error with more context
67 if (error.name === 'AbortError') {
68 error.message = `Request timed out after ${timeout}ms: ${url}`;
69 error.isTimeout = true;
70 } else if (error.message && error.message.includes('Network request failed')) {
71 error.isNetworkError = true;
72 }
73
74 // Add the URL to the error for context
75 error.url = url;
76
77 throw error;
78 }
79};
80
81/**
82 * Fetch data with retries for more reliability
83 *
84 * @param {string} url - The URL to fetch
85 * @param {Object} options - Fetch options
86 * @param {number} [maxRetries=2] - Maximum number of retries
87 * @param {number} [timeout=10000] - Timeout in milliseconds
88 * @returns {Promise<Object>} - The JSON response or error object
89 */
90export const fetchWithRetry = async (url, options = {}, maxRetries = 2, timeout = 10000) => {
91 let lastError;
92
93 for (let attempt = 0; attempt <= maxRetries; attempt++) {
94 try {
95 // If not the first attempt, wait increasing time before retry
96 if (attempt > 0) {
97 const backoffTime = 1000 * attempt; // 1s, 2s, 3s, etc.
98 console.log(`Retry attempt ${attempt}/${maxRetries} for ${url} after ${backoffTime}ms`);
99 await new Promise(resolve => setTimeout(resolve, backoffTime));
100 }
101
102 return await fetchWithTimeout(url, options, timeout);
103 } catch (error) {
104 lastError = error;
105
106 // Don't retry if it's an auth error - those won't go away with retries
107 if (error.status === 401 || error.status === 403) {
108 throw error;
109 }
110
111 // Don't retry if it's a client error (4xx range) except for 408 (timeout) and 429 (rate limit)
112 if (error.status && error.status >= 400 && error.status < 500 &&
113 error.status !== 408 && error.status !== 429) {
114 throw error;
115 }
116
117 // Log the error but continue if we have more retries
118 console.warn(`Fetch attempt ${attempt + 1}/${maxRetries + 1} failed for ${url}:`, error.message);
119
120 // If this was the last attempt, throw the error
121 if (attempt === maxRetries) {
122 error.message = `Failed after ${maxRetries + 1} attempts: ${error.message}`;
123 throw error;
124 }
125 }
126 }
127
128 // We shouldn't get here, but just in case
129 throw lastError || new Error(`Failed to fetch ${url} after ${maxRetries + 1} attempts`);
130};