@feardread/feature-factory 5.1.2 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.min.js +2 -2
- package/dist/index.esm.js +3 -3
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/rollup.config.mjs +48 -35
- package/src/factory/api.js +44 -325
- package/src/factory/crud.js +177 -0
- package/src/factory/index.js +1 -1
- package/src/factory/state.js +5 -1
- package/src/factory/thunk.js +1 -0
- package/src/factory/utils.js +4 -6
- package/src/index.js +2 -0
package/package.json
CHANGED
package/rollup.config.mjs
CHANGED
|
@@ -4,40 +4,53 @@ import commonjs from "@rollup/plugin-commonjs";
|
|
|
4
4
|
import json from "@rollup/plugin-json";
|
|
5
5
|
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
|
|
6
6
|
|
|
7
|
+
// Shared globals map — used by the IIFE bundle to locate peer deps
|
|
8
|
+
// on the global scope instead of trying to require() them at runtime.
|
|
9
|
+
const globals = {
|
|
10
|
+
'react': 'React',
|
|
11
|
+
'react-dom': 'ReactDOM',
|
|
12
|
+
'react-native': 'ReactNative',
|
|
13
|
+
'react-redux': 'ReactRedux',
|
|
14
|
+
'@reduxjs/toolkit': 'ReduxToolkit',
|
|
15
|
+
'rsuite': 'RSuite',
|
|
16
|
+
'axios': 'axios',
|
|
17
|
+
'qs': 'Qs',
|
|
18
|
+
};
|
|
19
|
+
|
|
7
20
|
const jsconfig = [{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
];
|
|
21
|
+
input: 'src/index.js',
|
|
22
|
+
output: [
|
|
23
|
+
{
|
|
24
|
+
file: 'dist/index.js',
|
|
25
|
+
format: 'cjs',
|
|
26
|
+
exports: 'named',
|
|
27
|
+
sourcemap: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
file: 'dist/index.esm.js',
|
|
31
|
+
format: 'esm',
|
|
32
|
+
exports: 'named',
|
|
33
|
+
sourcemap: true,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
file: 'dist/bundle.min.js',
|
|
37
|
+
format: 'iife',
|
|
38
|
+
name: 'FeatureFactory', // exposes the bundle as window.MyLib
|
|
39
|
+
globals, // tells Rollup which globals map to which peer dep
|
|
40
|
+
plugins: [terser()],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
plugins: [
|
|
44
|
+
peerDepsExternal(), // marks all peerDependencies as external
|
|
45
|
+
resolve({
|
|
46
|
+
browser: true,
|
|
47
|
+
preferBuiltins: false,
|
|
48
|
+
}),
|
|
49
|
+
commonjs(),
|
|
50
|
+
json(),
|
|
51
|
+
terser(),
|
|
52
|
+
],
|
|
53
|
+
context: 'this',
|
|
54
|
+
}];
|
|
42
55
|
|
|
43
|
-
export default jsconfig;
|
|
56
|
+
export default jsconfig;
|
package/src/factory/api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import qs from 'qs';
|
|
3
|
+
import { Platform } from 'react-native';
|
|
3
4
|
import CacheFactory from './cache';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -7,24 +8,19 @@ import CacheFactory from './cache';
|
|
|
7
8
|
*/
|
|
8
9
|
const CONFIG = {
|
|
9
10
|
BASE_URL: null,
|
|
11
|
+
environment: process.env.NODE_ENV,
|
|
10
12
|
// API Base URLs
|
|
11
13
|
baseUrls: {
|
|
12
|
-
production: process.env.API_BASE_URL_PROD || '
|
|
14
|
+
production: process.env.API_BASE_URL_PROD || 'https://fear.dedyn.io/fear/api/',
|
|
13
15
|
development: process.env.API_BASE_URL_DEV || 'http://localhost:4000/fear/api/',
|
|
14
16
|
test: process.env.API_BASE_URL_TEST || 'https://fear.dedyn.io/fear/api/',
|
|
15
17
|
},
|
|
16
|
-
tokenNames: {
|
|
17
|
-
bearer: 'Authorization',
|
|
18
|
-
custom: process.env.REACT_APP_JWT_TOKEN_HEADER || 'x-token',
|
|
19
|
-
},
|
|
20
18
|
cacheKeys: {
|
|
21
|
-
auth: 'auth',
|
|
22
|
-
refreshToken: 'refresh_token',
|
|
23
19
|
userPrefs: 'user_preferences',
|
|
24
20
|
},
|
|
25
|
-
timeout: parseInt(process.env.
|
|
26
|
-
retryAttempts: parseInt(process.env.
|
|
27
|
-
retryDelay: parseInt(process.env.
|
|
21
|
+
timeout: parseInt(process.env.API_TIMEOUT) || 30000,
|
|
22
|
+
retryAttempts: parseInt(process.env.API_RETRY_ATTEMPTS) || 3,
|
|
23
|
+
retryDelay: parseInt(process.env.API_RETRY_DELAY) || 1000,
|
|
28
24
|
};
|
|
29
25
|
|
|
30
26
|
const setBaseUrl = (uri) => {
|
|
@@ -38,86 +34,10 @@ const setBaseUrl = (uri) => {
|
|
|
38
34
|
*/
|
|
39
35
|
const getBaseUrl = () => {
|
|
40
36
|
const env = process.env.NODE_ENV || 'development';
|
|
41
|
-
if (!CONFIG.BASE_URL) CONFIG.BASE_URL = CONFIG.baseUrls[env] || CONFIG.baseUrls.development
|
|
37
|
+
if (!CONFIG.BASE_URL) CONFIG.BASE_URL = CONFIG.baseUrls[env] || CONFIG.baseUrls.development;
|
|
42
38
|
return CONFIG.BASE_URL;
|
|
43
39
|
};
|
|
44
40
|
|
|
45
|
-
/**
|
|
46
|
-
* Gets authentication data from cache
|
|
47
|
-
* @returns {Object|null} Auth data or null
|
|
48
|
-
*/
|
|
49
|
-
const getAuthData = () => {
|
|
50
|
-
try {
|
|
51
|
-
const authData = CacheFactory.local.get(CONFIG.cacheKeys.auth);
|
|
52
|
-
return authData && typeof authData === 'object' ? authData : null;
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.warn('Failed to retrieve auth data:', error);
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Checks if token is expired
|
|
61
|
-
* @param {string} token - JWT token
|
|
62
|
-
* @returns {boolean} Whether token is expired
|
|
63
|
-
*/
|
|
64
|
-
const isTokenExpired = (token) => {
|
|
65
|
-
if (!token) return true;
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
const payload = JSON.parse(atob(token.split('.')[1]));
|
|
69
|
-
const currentTime = Date.now() / 1000;
|
|
70
|
-
return payload.exp < currentTime;
|
|
71
|
-
} catch (error) {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Refreshes the authentication token
|
|
78
|
-
* @returns {Promise<string|null>} New token or null if refresh failed
|
|
79
|
-
*/
|
|
80
|
-
const refreshAuthToken = async () => {
|
|
81
|
-
try {
|
|
82
|
-
const authData = getAuthData();
|
|
83
|
-
const refreshToken = authData?.refreshToken || CacheFactory.local.get(CONFIG.cacheKeys.refreshToken);
|
|
84
|
-
|
|
85
|
-
if (!refreshToken) {
|
|
86
|
-
throw new Error('No refresh token available');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const response = await axios.post(`${getBaseUrl()}auth/refresh`, {
|
|
90
|
-
refreshToken,
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const newAuthData = {
|
|
94
|
-
token: response.data.token,
|
|
95
|
-
refreshToken: response.data.refreshToken || refreshToken,
|
|
96
|
-
user: response.data.user,
|
|
97
|
-
expiresAt: response.data.expiresAt,
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
CacheFactory.local.set(CONFIG.cacheKeys.auth, newAuthData);
|
|
101
|
-
return newAuthData.token;
|
|
102
|
-
} catch (error) {
|
|
103
|
-
console.error('Token refresh failed:', error);
|
|
104
|
-
clearAuthData();
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Clears authentication data from cache
|
|
111
|
-
*/
|
|
112
|
-
const clearAuthData = () => {
|
|
113
|
-
try {
|
|
114
|
-
CacheFactory.local.remove(CONFIG.cacheKeys.auth);
|
|
115
|
-
CacheFactory.local.remove(CONFIG.cacheKeys.refreshToken);
|
|
116
|
-
} catch (error) {
|
|
117
|
-
console.warn('Failed to clear auth data:', error);
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
41
|
/**
|
|
122
42
|
* Creates retry delay with exponential backoff
|
|
123
43
|
* @param {number} attempt - Current attempt number
|
|
@@ -142,7 +62,6 @@ const formatError = (error) => {
|
|
|
142
62
|
};
|
|
143
63
|
|
|
144
64
|
if (error.response) {
|
|
145
|
-
// Server responded with error status
|
|
146
65
|
return {
|
|
147
66
|
...baseError,
|
|
148
67
|
message: error.response.data?.message || `HTTP Error ${error.response.status}`,
|
|
@@ -151,17 +70,15 @@ const formatError = (error) => {
|
|
|
151
70
|
data: error.response.data,
|
|
152
71
|
};
|
|
153
72
|
}
|
|
154
|
-
|
|
73
|
+
|
|
155
74
|
if (error.request) {
|
|
156
|
-
// Request made but no response received
|
|
157
75
|
return {
|
|
158
76
|
...baseError,
|
|
159
77
|
message: 'Network error - no response received',
|
|
160
78
|
code: 'NETWORK_ERROR',
|
|
161
79
|
};
|
|
162
80
|
}
|
|
163
|
-
|
|
164
|
-
// Something else happened
|
|
81
|
+
|
|
165
82
|
return {
|
|
166
83
|
...baseError,
|
|
167
84
|
message: error.message || 'Request configuration error',
|
|
@@ -170,37 +87,18 @@ const formatError = (error) => {
|
|
|
170
87
|
};
|
|
171
88
|
|
|
172
89
|
/**
|
|
173
|
-
* Request interceptor
|
|
90
|
+
* Request interceptor — adds tracking headers
|
|
174
91
|
* @param {Object} config - Axios config
|
|
175
92
|
* @returns {Promise<Object>} Modified config
|
|
176
93
|
*/
|
|
177
94
|
const requestInterceptor = async (config) => {
|
|
178
95
|
try {
|
|
179
|
-
// Set retry metadata
|
|
180
96
|
config.metadata = { startTime: Date.now() };
|
|
181
97
|
config._retry = config._retry || 0;
|
|
182
98
|
|
|
183
|
-
// Get authentication data
|
|
184
|
-
const authData = getAuthData();
|
|
185
|
-
let token = authData?.token;
|
|
186
|
-
|
|
187
|
-
// Check if token needs refresh
|
|
188
|
-
if (token && isTokenExpired(token) && config._retry === 0) {
|
|
189
|
-
token = await refreshAuthToken();
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Set authorization headers
|
|
193
|
-
if (token) {
|
|
194
|
-
config.headers[CONFIG.tokenNames.bearer] = `Bearer ${token}`;
|
|
195
|
-
config.headers[CONFIG.tokenNames.custom] = token;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Add request ID for tracking
|
|
199
99
|
config.headers['X-Request-ID'] = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
200
|
-
|
|
201
|
-
//
|
|
202
|
-
config.headers['X-Client-Version'] = process.env.REACT_APP_VERSION || '1.0.0';
|
|
203
|
-
config.headers['X-Client-Platform'] = 'web';
|
|
100
|
+
config.headers['X-Client-Version'] = process.env.API_VERSION || '1.0.0';
|
|
101
|
+
config.headers['X-Client-Platform'] = Platform.OS; // 'ios' | 'android' | 'web'
|
|
204
102
|
|
|
205
103
|
return config;
|
|
206
104
|
} catch (error) {
|
|
@@ -225,16 +123,12 @@ const requestErrorInterceptor = (error) => {
|
|
|
225
123
|
* @returns {Object} Processed response
|
|
226
124
|
*/
|
|
227
125
|
const responseInterceptor = (response) => {
|
|
228
|
-
// Add response metadata
|
|
229
126
|
response.metadata = {
|
|
230
127
|
responseTime: Date.now() - (response.config.metadata?.startTime || Date.now()),
|
|
231
128
|
requestId: response.config.headers['X-Request-ID'],
|
|
232
129
|
};
|
|
233
130
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
// Log successful responses in development
|
|
237
|
-
if (process.env.NODE_ENV === 'development') {
|
|
131
|
+
if (CONFIG.environment === 'development') {
|
|
238
132
|
console.log(`✅ API Success [${response.status}]:`, {
|
|
239
133
|
url: response.config.url,
|
|
240
134
|
data: response.data,
|
|
@@ -244,20 +138,16 @@ const responseInterceptor = (response) => {
|
|
|
244
138
|
});
|
|
245
139
|
}
|
|
246
140
|
|
|
247
|
-
// Validate response structure
|
|
248
141
|
if (response.data && typeof response.data === 'object') {
|
|
249
|
-
// Handle different success status codes
|
|
250
142
|
if ([200, 201, 202, 204].includes(response.status)) {
|
|
251
143
|
return response;
|
|
252
144
|
}
|
|
253
145
|
}
|
|
254
146
|
|
|
255
|
-
// Handle edge cases
|
|
256
147
|
if (response.status >= 200 && response.status < 300) {
|
|
257
148
|
return response;
|
|
258
149
|
}
|
|
259
150
|
|
|
260
|
-
// Unexpected status code
|
|
261
151
|
return Promise.reject(formatError({
|
|
262
152
|
response,
|
|
263
153
|
message: `Unexpected response status: ${response.status}`,
|
|
@@ -265,18 +155,15 @@ const responseInterceptor = (response) => {
|
|
|
265
155
|
};
|
|
266
156
|
|
|
267
157
|
/**
|
|
268
|
-
* Response error interceptor with retry
|
|
158
|
+
* Response error interceptor with retry logic
|
|
269
159
|
* @param {Object} error - Response error
|
|
270
160
|
* @returns {Promise<Object>} Retry attempt or rejected promise
|
|
271
161
|
*/
|
|
272
162
|
const responseErrorInterceptor = async (error) => {
|
|
273
163
|
const originalRequest = error.config;
|
|
274
|
-
|
|
275
|
-
// Format error for consistent handling
|
|
276
164
|
const formattedError = formatError(error);
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (process.env.NODE_ENV === 'development') {
|
|
165
|
+
|
|
166
|
+
if (CONFIG.environment === 'development') {
|
|
280
167
|
console.error(`❌ API Error [${formattedError.status}]:`, {
|
|
281
168
|
url: originalRequest?.url,
|
|
282
169
|
error: formattedError,
|
|
@@ -286,48 +173,19 @@ const responseErrorInterceptor = async (error) => {
|
|
|
286
173
|
});
|
|
287
174
|
}
|
|
288
175
|
|
|
289
|
-
// Handle
|
|
290
|
-
if (formattedError.status === 401 && !originalRequest._isRetryingAuth) {
|
|
291
|
-
originalRequest._isRetryingAuth = true;
|
|
292
|
-
|
|
293
|
-
try {
|
|
294
|
-
const newToken = await refreshAuthToken();
|
|
295
|
-
if (newToken) {
|
|
296
|
-
// Retry original request with new token
|
|
297
|
-
originalRequest.headers[CONFIG.tokenNames.bearer] = `Bearer ${newToken}`;
|
|
298
|
-
originalRequest.headers[CONFIG.tokenNames.custom] = newToken;
|
|
299
|
-
return apiInstance(originalRequest);
|
|
300
|
-
}
|
|
301
|
-
} catch (refreshError) {
|
|
302
|
-
console.error('Auth refresh failed:', refreshError);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Clear auth data and redirect to login
|
|
306
|
-
clearAuthData();
|
|
307
|
-
|
|
308
|
-
// Dispatch auth failure event
|
|
309
|
-
if (typeof window !== 'undefined') {
|
|
310
|
-
window.dispatchEvent(new CustomEvent('auth:failure', {
|
|
311
|
-
detail: { error: formattedError }
|
|
312
|
-
}));
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Handle retryable errors
|
|
176
|
+
// Handle retryable errors with exponential backoff
|
|
317
177
|
const isRetryable = [408, 429, 500, 502, 503, 504].includes(formattedError.status) ||
|
|
318
|
-
|
|
319
|
-
|
|
178
|
+
formattedError.code === 'NETWORK_ERROR';
|
|
179
|
+
|
|
320
180
|
if (isRetryable && originalRequest._retry < CONFIG.retryAttempts) {
|
|
321
181
|
originalRequest._retry += 1;
|
|
322
|
-
|
|
323
182
|
const delay = getRetryDelay(originalRequest._retry);
|
|
324
183
|
console.log(`⏳ Retrying request (${originalRequest._retry}/${CONFIG.retryAttempts}) in ${delay}ms`);
|
|
325
|
-
|
|
326
184
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
327
185
|
return apiInstance(originalRequest);
|
|
328
186
|
}
|
|
329
187
|
|
|
330
|
-
//
|
|
188
|
+
// Attach retry-after duration when rate-limited
|
|
331
189
|
if (formattedError.status === 429) {
|
|
332
190
|
const retryAfter = error.response?.headers['retry-after'];
|
|
333
191
|
if (retryAfter) {
|
|
@@ -350,26 +208,24 @@ const createApiInstance = () => {
|
|
|
350
208
|
'Content-Type': 'application/json',
|
|
351
209
|
},
|
|
352
210
|
paramsSerializer: {
|
|
353
|
-
serialize: (params) => qs.stringify(params, {
|
|
211
|
+
serialize: (params) => qs.stringify(params, {
|
|
354
212
|
indices: false,
|
|
355
213
|
skipNulls: true,
|
|
356
214
|
arrayFormat: 'brackets',
|
|
357
215
|
}),
|
|
358
216
|
},
|
|
359
|
-
withCredentials
|
|
360
|
-
// Disable automatic JSON parsing for better error handling
|
|
217
|
+
// withCredentials intentionally omitted — cookies are not supported in React Native
|
|
361
218
|
transformResponse: [
|
|
362
219
|
(data) => {
|
|
363
220
|
try {
|
|
364
221
|
return JSON.parse(data);
|
|
365
|
-
} catch
|
|
222
|
+
} catch {
|
|
366
223
|
return data;
|
|
367
224
|
}
|
|
368
|
-
}
|
|
225
|
+
},
|
|
369
226
|
],
|
|
370
227
|
});
|
|
371
228
|
|
|
372
|
-
// Add interceptors
|
|
373
229
|
instance.interceptors.request.use(requestInterceptor, requestErrorInterceptor);
|
|
374
230
|
instance.interceptors.response.use(responseInterceptor, responseErrorInterceptor);
|
|
375
231
|
|
|
@@ -385,17 +241,16 @@ const apiInstance = createApiInstance();
|
|
|
385
241
|
const API = {
|
|
386
242
|
// Main axios instance
|
|
387
243
|
instance: apiInstance,
|
|
388
|
-
|
|
244
|
+
|
|
389
245
|
// Direct access to axios methods
|
|
390
|
-
get:
|
|
391
|
-
post:
|
|
392
|
-
put:
|
|
393
|
-
patch:
|
|
394
|
-
delete:
|
|
395
|
-
head:
|
|
246
|
+
get: apiInstance.get.bind(apiInstance),
|
|
247
|
+
post: apiInstance.post.bind(apiInstance),
|
|
248
|
+
put: apiInstance.put.bind(apiInstance),
|
|
249
|
+
patch: apiInstance.patch.bind(apiInstance),
|
|
250
|
+
delete: apiInstance.delete.bind(apiInstance),
|
|
251
|
+
head: apiInstance.head.bind(apiInstance),
|
|
396
252
|
options: apiInstance.options.bind(apiInstance),
|
|
397
253
|
|
|
398
|
-
// Utility methods
|
|
399
254
|
/**
|
|
400
255
|
* Makes a request with custom configuration
|
|
401
256
|
* @param {Object} config - Request configuration
|
|
@@ -413,65 +268,11 @@ const API = {
|
|
|
413
268
|
...apiInstance.defaults,
|
|
414
269
|
...customConfig,
|
|
415
270
|
});
|
|
416
|
-
|
|
417
271
|
customInstance.interceptors.request.use(requestInterceptor, requestErrorInterceptor);
|
|
418
272
|
customInstance.interceptors.response.use(responseInterceptor, responseErrorInterceptor);
|
|
419
|
-
|
|
420
273
|
return customInstance;
|
|
421
274
|
},
|
|
422
275
|
|
|
423
|
-
/**
|
|
424
|
-
* Sets authentication token
|
|
425
|
-
* @param {string} token - Auth token
|
|
426
|
-
* @param {Object} userData - User data
|
|
427
|
-
* @returns {boolean} Success status
|
|
428
|
-
*/
|
|
429
|
-
setAuth: (token, userData = {}) => {
|
|
430
|
-
try {
|
|
431
|
-
const authData = {
|
|
432
|
-
token,
|
|
433
|
-
user: userData,
|
|
434
|
-
timestamp: Date.now(),
|
|
435
|
-
expiresAt: userData.expiresAt,
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
return CacheFactory.local.set(CONFIG.cacheKeys.auth, authData);
|
|
439
|
-
} catch (error) {
|
|
440
|
-
console.error('Failed to set auth:', error);
|
|
441
|
-
return false;
|
|
442
|
-
}
|
|
443
|
-
},
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Clears authentication
|
|
447
|
-
* @returns {boolean} Success status
|
|
448
|
-
*/
|
|
449
|
-
clearAuth: () => {
|
|
450
|
-
try {
|
|
451
|
-
clearAuthData();
|
|
452
|
-
return true;
|
|
453
|
-
} catch (error) {
|
|
454
|
-
console.error('Failed to clear auth:', error);
|
|
455
|
-
return false;
|
|
456
|
-
}
|
|
457
|
-
},
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Gets current auth status
|
|
461
|
-
* @returns {Object} Auth status
|
|
462
|
-
*/
|
|
463
|
-
getAuthStatus: () => {
|
|
464
|
-
const authData = getAuthData();
|
|
465
|
-
const isAuthenticated = !!(authData?.token && !isTokenExpired(authData.token));
|
|
466
|
-
|
|
467
|
-
return {
|
|
468
|
-
isAuthenticated,
|
|
469
|
-
user: authData?.user || null,
|
|
470
|
-
token: authData?.token || null,
|
|
471
|
-
expiresAt: authData?.expiresAt || null,
|
|
472
|
-
};
|
|
473
|
-
},
|
|
474
|
-
|
|
475
276
|
/**
|
|
476
277
|
* Configures global request defaults
|
|
477
278
|
* @param {Object} config - Configuration object
|
|
@@ -487,17 +288,9 @@ const API = {
|
|
|
487
288
|
healthCheck: async () => {
|
|
488
289
|
try {
|
|
489
290
|
const response = await apiInstance.get('health', { timeout: 5000 });
|
|
490
|
-
return {
|
|
491
|
-
status: 'healthy',
|
|
492
|
-
response: response.data,
|
|
493
|
-
timestamp: Date.now(),
|
|
494
|
-
};
|
|
291
|
+
return { status: 'healthy', response: response.data, timestamp: Date.now() };
|
|
495
292
|
} catch (error) {
|
|
496
|
-
return {
|
|
497
|
-
status: 'unhealthy',
|
|
498
|
-
error: formatError(error),
|
|
499
|
-
timestamp: Date.now(),
|
|
500
|
-
};
|
|
293
|
+
return { status: 'unhealthy', error: formatError(error), timestamp: Date.now() };
|
|
501
294
|
}
|
|
502
295
|
},
|
|
503
296
|
|
|
@@ -510,9 +303,7 @@ const API = {
|
|
|
510
303
|
*/
|
|
511
304
|
uploadFile: (url, formData, onProgress) => {
|
|
512
305
|
return apiInstance.post(url, formData, {
|
|
513
|
-
headers: {
|
|
514
|
-
'Content-Type': 'multipart/form-data',
|
|
515
|
-
},
|
|
306
|
+
headers: { 'Content-Type': 'multipart/form-data' },
|
|
516
307
|
onUploadProgress: (progressEvent) => {
|
|
517
308
|
if (onProgress && progressEvent.total) {
|
|
518
309
|
const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
|
@@ -523,96 +314,24 @@ const API = {
|
|
|
523
314
|
},
|
|
524
315
|
|
|
525
316
|
// Cache utility methods
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
*/
|
|
532
|
-
getCached: (key, defaultValue = null) => {
|
|
533
|
-
return CacheFactory.local.get(key, defaultValue);
|
|
534
|
-
},
|
|
317
|
+
getCached: (key, defaultValue = null) => CacheFactory.local.get(key, defaultValue),
|
|
318
|
+
setCached: (key, value) => CacheFactory.local.set(key, value),
|
|
319
|
+
removeCached: (key) => CacheFactory.local.remove(key),
|
|
320
|
+
clearCache: () => CacheFactory.local.clear(),
|
|
321
|
+
getCacheStats: () => CacheFactory.local.getStats(),
|
|
535
322
|
|
|
536
|
-
/**
|
|
537
|
-
* Sets cached data
|
|
538
|
-
* @param {string} key - Cache key
|
|
539
|
-
* @param {any} value - Value to cache
|
|
540
|
-
* @returns {boolean} Success status
|
|
541
|
-
*/
|
|
542
|
-
setCached: (key, value) => {
|
|
543
|
-
return CacheFactory.local.set(key, value);
|
|
544
|
-
},
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* Removes cached data
|
|
548
|
-
* @param {string} key - Cache key
|
|
549
|
-
* @returns {boolean} Success status
|
|
550
|
-
*/
|
|
551
|
-
removeCached: (key) => {
|
|
552
|
-
return CacheFactory.local.remove(key);
|
|
553
|
-
},
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* Clears all cached data
|
|
557
|
-
* @returns {boolean} Success status
|
|
558
|
-
*/
|
|
559
|
-
clearCache: () => {
|
|
560
|
-
return CacheFactory.local.clear();
|
|
561
|
-
},
|
|
562
|
-
|
|
563
|
-
/**
|
|
564
|
-
* Gets cache statistics
|
|
565
|
-
* @returns {Object} Cache stats
|
|
566
|
-
*/
|
|
567
|
-
getCacheStats: () => {
|
|
568
|
-
return CacheFactory.local.getStats();
|
|
569
|
-
},
|
|
570
|
-
|
|
571
|
-
/**
|
|
572
|
-
* Bulk cache operations
|
|
573
|
-
*/
|
|
323
|
+
/** Bulk cache operations */
|
|
574
324
|
cache: {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
* @returns {Object} Key-value pairs
|
|
579
|
-
*/
|
|
580
|
-
getMultiple: (keys) => {
|
|
581
|
-
return CacheFactory.local.bulk.get(keys);
|
|
582
|
-
},
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* Sets multiple cached items
|
|
586
|
-
* @param {Object} items - Key-value pairs
|
|
587
|
-
* @returns {Object} Success status for each key
|
|
588
|
-
*/
|
|
589
|
-
setMultiple: (items) => {
|
|
590
|
-
return CacheFactory.local.bulk.set(items);
|
|
591
|
-
},
|
|
592
|
-
|
|
593
|
-
/**
|
|
594
|
-
* Removes multiple cached items
|
|
595
|
-
* @param {string[]} keys - Cache keys
|
|
596
|
-
* @returns {Object} Success status for each key
|
|
597
|
-
*/
|
|
598
|
-
removeMultiple: (keys) => {
|
|
599
|
-
return CacheFactory.local.bulk.remove(keys);
|
|
600
|
-
},
|
|
325
|
+
getMultiple: (keys) => CacheFactory.local.bulk.get(keys),
|
|
326
|
+
setMultiple: (items) => CacheFactory.local.bulk.set(items),
|
|
327
|
+
removeMultiple: (keys) => CacheFactory.local.bulk.remove(keys),
|
|
601
328
|
},
|
|
602
329
|
|
|
603
330
|
// Configuration access
|
|
604
331
|
config: CONFIG,
|
|
605
332
|
getBaseUrl,
|
|
606
|
-
setBaseUrl
|
|
333
|
+
setBaseUrl,
|
|
607
334
|
};
|
|
608
335
|
|
|
609
|
-
// Add event listener for auth failures (optional)
|
|
610
|
-
if (typeof window !== 'undefined') {
|
|
611
|
-
window.addEventListener('auth:failure', (event) => {
|
|
612
|
-
console.warn('Authentication failed:', event.detail.error);
|
|
613
|
-
// Could trigger a redirect to login page here
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
|
|
617
336
|
export { API };
|
|
618
337
|
export default API;
|