@orsetra/shared-ui 1.1.41 → 1.2.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/lib/base-service.ts +4 -4
- package/lib/http-client.ts +157 -133
- package/package.json +6 -5
package/lib/base-service.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import HttpClient, { type AuthHeadersProvider } from './http-client';
|
|
1
|
+
import HttpClient, { type AuthHeadersProvider, type Interceptors } from './http-client';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Classe de base pour tous les services qui utilisent HttpClient
|
|
@@ -6,8 +6,8 @@ import HttpClient, { type AuthHeadersProvider } from './http-client';
|
|
|
6
6
|
export abstract class BaseService {
|
|
7
7
|
protected httpClient: HttpClient;
|
|
8
8
|
|
|
9
|
-
constructor(baseUrl: string, authHeadersProvider?: AuthHeadersProvider) {
|
|
10
|
-
this.httpClient = HttpClient.getInstance(baseUrl, authHeadersProvider);
|
|
9
|
+
constructor(baseUrl: string, authHeadersProvider?: AuthHeadersProvider, interceptors?: Interceptors) {
|
|
10
|
+
this.httpClient = HttpClient.getInstance(baseUrl, authHeadersProvider, interceptors);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
protected handleServiceError(error: any): Error {
|
|
@@ -16,4 +16,4 @@ export abstract class BaseService {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export type { AuthHeadersProvider };
|
|
19
|
+
export type { AuthHeadersProvider, Interceptors };
|
package/lib/http-client.ts
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
// Simplified HTTP client without AWS Amplify dependencies
|
|
2
2
|
|
|
3
|
+
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
|
4
|
+
|
|
3
5
|
export type AuthHeadersProvider = () => Promise<Record<string, string>> | Record<string, string>;
|
|
4
6
|
|
|
7
|
+
export type RequestInterceptor = (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
|
|
8
|
+
export type RequestErrorInterceptor = (error: any) => any;
|
|
9
|
+
export type ResponseInterceptor = (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;
|
|
10
|
+
export type ResponseErrorInterceptor = (error: any) => any;
|
|
11
|
+
|
|
12
|
+
export type Interceptors = {
|
|
13
|
+
onRequest?: RequestInterceptor;
|
|
14
|
+
onRequestError?: RequestErrorInterceptor;
|
|
15
|
+
onResponse?: ResponseInterceptor;
|
|
16
|
+
onResponseError?: ResponseErrorInterceptor;
|
|
17
|
+
};
|
|
18
|
+
|
|
5
19
|
export class ApiError extends Error {
|
|
6
20
|
status: number;
|
|
7
21
|
code?: number;
|
|
@@ -21,32 +35,58 @@ export class ApiError extends Error {
|
|
|
21
35
|
class HttpClient {
|
|
22
36
|
private baseUrl: string;
|
|
23
37
|
private authHeadersProvider?: AuthHeadersProvider;
|
|
38
|
+
private axiosInstance: AxiosInstance;
|
|
24
39
|
|
|
25
|
-
private constructor(baseUrl: string, authHeadersProvider?: AuthHeadersProvider) {
|
|
40
|
+
private constructor(baseUrl: string, authHeadersProvider?: AuthHeadersProvider, interceptors?: Interceptors) {
|
|
26
41
|
this.baseUrl = baseUrl || '';
|
|
27
42
|
this.authHeadersProvider = authHeadersProvider;
|
|
43
|
+
|
|
44
|
+
// Create axios instance with base configuration
|
|
45
|
+
this.axiosInstance = axios.create({
|
|
46
|
+
baseURL: this.baseUrl,
|
|
47
|
+
timeout: 30000, // 30 seconds timeout
|
|
48
|
+
headers: {
|
|
49
|
+
'Content-Type': 'application/json'
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Setup interceptors if provided
|
|
54
|
+
if (interceptors) {
|
|
55
|
+
this.setupInterceptors(interceptors);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private setupInterceptors(interceptors: Interceptors): void {
|
|
60
|
+
// Setup request interceptor
|
|
61
|
+
if (interceptors.onRequest || interceptors.onRequestError) {
|
|
62
|
+
this.axiosInstance.interceptors.request.use(
|
|
63
|
+
interceptors.onRequest || ((config) => config),
|
|
64
|
+
interceptors.onRequestError || ((error) => Promise.reject(error))
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Setup response interceptor
|
|
69
|
+
if (interceptors.onResponse || interceptors.onResponseError) {
|
|
70
|
+
this.axiosInstance.interceptors.response.use(
|
|
71
|
+
interceptors.onResponse || ((response) => response),
|
|
72
|
+
interceptors.onResponseError || ((error) => Promise.reject(error))
|
|
73
|
+
);
|
|
74
|
+
}
|
|
28
75
|
}
|
|
29
76
|
|
|
30
|
-
public static getInstance(baseUrl: string, authHeadersProvider?: AuthHeadersProvider): HttpClient {
|
|
31
|
-
return new HttpClient(baseUrl, authHeadersProvider);
|
|
77
|
+
public static getInstance(baseUrl: string, authHeadersProvider?: AuthHeadersProvider, interceptors?: Interceptors): HttpClient {
|
|
78
|
+
return new HttpClient(baseUrl, authHeadersProvider, interceptors);
|
|
32
79
|
}
|
|
33
80
|
|
|
34
|
-
private getFullUrl(path: string): string {
|
|
35
|
-
const cleanPath = path.startsWith('/') ? path.slice(1) : path;
|
|
36
|
-
return `${this.baseUrl}/${cleanPath}`;
|
|
37
|
-
}
|
|
38
81
|
|
|
39
|
-
private async getAuthHeaders(): Promise<
|
|
40
|
-
const headers =
|
|
41
|
-
headers.set('Content-Type', 'application/json');
|
|
82
|
+
private async getAuthHeaders(): Promise<Record<string, string>> {
|
|
83
|
+
const headers: Record<string, string> = {};
|
|
42
84
|
|
|
43
85
|
if (this.authHeadersProvider) {
|
|
44
86
|
try {
|
|
45
87
|
const authHeaders = await this.authHeadersProvider();
|
|
46
|
-
Object.
|
|
47
|
-
|
|
48
|
-
});
|
|
49
|
-
} catch (error) {
|
|
88
|
+
Object.assign(headers, authHeaders);
|
|
89
|
+
} catch (error: unknown) {
|
|
50
90
|
console.warn('Failed to get auth headers:', error);
|
|
51
91
|
}
|
|
52
92
|
}
|
|
@@ -54,184 +94,168 @@ class HttpClient {
|
|
|
54
94
|
return headers;
|
|
55
95
|
}
|
|
56
96
|
|
|
57
|
-
async request<T>(url: string, options:
|
|
97
|
+
async request<T>(url: string, options: AxiosRequestConfig = {}): Promise<T | null> {
|
|
58
98
|
const headers = await this.getAuthHeaders();
|
|
59
|
-
|
|
60
|
-
// Fusionner avec les headers existants
|
|
61
99
|
if (options.headers) {
|
|
62
|
-
Object.
|
|
63
|
-
headers.set(key, value as string);
|
|
64
|
-
});
|
|
100
|
+
Object.assign(headers, options.headers);
|
|
65
101
|
}
|
|
66
102
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
103
|
+
try {
|
|
104
|
+
const response: AxiosResponse<T> = await this.axiosInstance.request<T>({
|
|
105
|
+
url,
|
|
106
|
+
...options,
|
|
107
|
+
headers,
|
|
108
|
+
});
|
|
73
109
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
110
|
+
// Handle 204 No Content
|
|
111
|
+
if (response.status === 204) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return response.data;
|
|
116
|
+
} catch (error: unknown) {
|
|
117
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
118
|
+
const response = error.response;
|
|
119
|
+
|
|
120
|
+
// Try to extract error message from API response
|
|
121
|
+
let errorMessage = `Request failed: ${response.status}`;
|
|
122
|
+
let errorBody: any = response.data;
|
|
123
|
+
|
|
81
124
|
if (errorBody?.message) {
|
|
82
125
|
errorMessage = errorBody.message;
|
|
83
126
|
} else if (errorBody?.error) {
|
|
84
127
|
errorMessage = errorBody.error;
|
|
85
128
|
}
|
|
86
|
-
} catch {
|
|
87
|
-
// If we can't parse JSON, use the default message
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const apiError = new ApiError(errorMessage, {
|
|
91
|
-
status: response.status,
|
|
92
|
-
code: typeof errorBody?.code === 'number' ? errorBody.code : undefined,
|
|
93
|
-
details: errorBody?.details,
|
|
94
|
-
raw: errorBody,
|
|
95
|
-
});
|
|
96
129
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
})
|
|
108
|
-
);
|
|
109
|
-
} catch {
|
|
110
|
-
// ignore event dispatch issues
|
|
111
|
-
}
|
|
130
|
+
const apiError = new ApiError(errorMessage, {
|
|
131
|
+
status: response.status,
|
|
132
|
+
code: typeof errorBody?.code === 'number' ? errorBody.code : undefined,
|
|
133
|
+
details: errorBody?.details,
|
|
134
|
+
raw: errorBody,
|
|
135
|
+
});
|
|
136
|
+
throw apiError;
|
|
137
|
+
} else {
|
|
138
|
+
// For non-axios errors or network errors
|
|
139
|
+
throw new Error(error instanceof Error ? error.message : 'Unknown error occurred');
|
|
112
140
|
}
|
|
113
|
-
|
|
114
|
-
throw apiError;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (response.status === 204) {
|
|
118
|
-
return null;
|
|
119
141
|
}
|
|
120
|
-
|
|
121
|
-
return response.json();
|
|
122
142
|
}
|
|
123
143
|
|
|
124
|
-
async get<T>(url: string, options:
|
|
144
|
+
async get<T>(url: string, options: AxiosRequestConfig = {}): Promise<T | null> {
|
|
125
145
|
return this.request<T>(url, {
|
|
126
146
|
...options,
|
|
127
147
|
method: 'GET'
|
|
128
148
|
});
|
|
129
149
|
}
|
|
130
150
|
|
|
131
|
-
async post<T>(url: string, body?: any, options:
|
|
151
|
+
async post<T>(url: string, body?: any, options: AxiosRequestConfig = {}): Promise<T | null> {
|
|
132
152
|
return this.request<T>(url, {
|
|
133
153
|
...options,
|
|
134
154
|
method: 'POST',
|
|
135
|
-
|
|
155
|
+
data: body
|
|
136
156
|
});
|
|
137
157
|
}
|
|
138
158
|
|
|
139
|
-
async put<T>(url: string, body?: any, options:
|
|
159
|
+
async put<T>(url: string, body?: any, options: AxiosRequestConfig = {}): Promise<T | null> {
|
|
140
160
|
return this.request<T>(url, {
|
|
141
161
|
...options,
|
|
142
162
|
method: 'PUT',
|
|
143
|
-
|
|
163
|
+
data: body
|
|
144
164
|
});
|
|
145
165
|
}
|
|
146
166
|
|
|
147
|
-
async patch<T>(url: string, body?: any, options:
|
|
167
|
+
async patch<T>(url: string, body?: any, options: AxiosRequestConfig = {}): Promise<T | null> {
|
|
148
168
|
return this.request<T>(url, {
|
|
149
169
|
...options,
|
|
150
170
|
method: 'PATCH',
|
|
151
|
-
|
|
171
|
+
data: body
|
|
152
172
|
});
|
|
153
173
|
}
|
|
154
174
|
|
|
155
|
-
async delete<T>(url: string, options:
|
|
175
|
+
async delete<T>(url: string, options: AxiosRequestConfig = {}): Promise<T | null> {
|
|
156
176
|
return this.request<T>(url, {
|
|
157
177
|
...options,
|
|
158
178
|
method: 'DELETE'
|
|
159
179
|
});
|
|
160
180
|
}
|
|
161
181
|
|
|
162
|
-
async upload<T>(url: string, formData: FormData, options:
|
|
182
|
+
async upload<T>(url: string, formData: FormData, options: AxiosRequestConfig = {}): Promise<T> {
|
|
163
183
|
const headers = await this.getAuthHeaders();
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
try {
|
|
185
|
+
const response: AxiosResponse<T> = await this.axiosInstance.request<T>({
|
|
186
|
+
url,
|
|
187
|
+
method: 'POST',
|
|
188
|
+
data: formData,
|
|
189
|
+
headers,
|
|
190
|
+
...options
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (response.status === 204) {
|
|
194
|
+
return null as unknown as T;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return response.data;
|
|
198
|
+
} catch (error: unknown) {
|
|
199
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
200
|
+
const response = error.response;
|
|
201
|
+
let errorMessage = `Upload failed: ${response.status} ${response.statusText}`;
|
|
202
|
+
|
|
203
|
+
if (response.data) {
|
|
204
|
+
if (typeof response.data === 'object' && response.data?.message) {
|
|
205
|
+
errorMessage = response.data.message;
|
|
206
|
+
} else if (typeof response.data === 'object' && response.data?.error) {
|
|
207
|
+
errorMessage = response.data.error;
|
|
208
|
+
}
|
|
187
209
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (errorText) errorMessage = errorText;
|
|
193
|
-
} catch { /* ignore */ }
|
|
210
|
+
|
|
211
|
+
throw new Error(errorMessage);
|
|
212
|
+
} else {
|
|
213
|
+
throw new Error(error instanceof Error ? error.message : 'Unknown upload error');
|
|
194
214
|
}
|
|
195
|
-
throw new Error(errorMessage);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (response.status === 204) {
|
|
199
|
-
return null as unknown as T;
|
|
200
215
|
}
|
|
201
|
-
|
|
202
|
-
return response.json();
|
|
203
216
|
}
|
|
204
217
|
|
|
205
218
|
async download(url: string): Promise<Blob> {
|
|
206
219
|
const headers = await this.getAuthHeaders();
|
|
207
|
-
const fullUrl = this.getFullUrl(url);
|
|
208
|
-
|
|
209
|
-
const response = await fetch(fullUrl, {
|
|
210
|
-
method: 'GET',
|
|
211
|
-
headers,
|
|
212
|
-
credentials: 'include'
|
|
213
|
-
});
|
|
214
220
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
221
|
+
try {
|
|
222
|
+
const response = await this.axiosInstance.request({
|
|
223
|
+
url,
|
|
224
|
+
method: 'GET',
|
|
225
|
+
headers,
|
|
226
|
+
responseType: 'blob'
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return response.data;
|
|
230
|
+
} catch (error: unknown) {
|
|
231
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
232
|
+
const response = error.response;
|
|
233
|
+
let errorMessage = `Download failed: ${response.status} ${response.statusText}`;
|
|
234
|
+
|
|
235
|
+
if (response.data) {
|
|
236
|
+
try {
|
|
237
|
+
const errorText = await (response.data as Blob).text();
|
|
238
|
+
const errorBody = JSON.parse(errorText);
|
|
239
|
+
if (errorBody?.message) {
|
|
240
|
+
errorMessage = errorBody.message;
|
|
241
|
+
} else if (errorBody?.error) {
|
|
242
|
+
errorMessage = errorBody.error;
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
245
|
+
// If JSON parsing fails, use default message
|
|
246
|
+
}
|
|
224
247
|
}
|
|
225
|
-
|
|
226
|
-
|
|
248
|
+
|
|
249
|
+
throw new Error(errorMessage);
|
|
250
|
+
} else {
|
|
251
|
+
throw new Error(error instanceof Error ? error.message : 'Unknown download error');
|
|
252
|
+
}
|
|
227
253
|
}
|
|
228
|
-
|
|
229
|
-
return await response.blob();
|
|
230
254
|
}
|
|
231
255
|
}
|
|
232
256
|
|
|
233
|
-
export function useHttpClient(baseUrl: string, authHeadersProvider?: AuthHeadersProvider) {
|
|
234
|
-
return HttpClient.getInstance(baseUrl, authHeadersProvider);
|
|
257
|
+
export function useHttpClient(baseUrl: string, authHeadersProvider?: AuthHeadersProvider, interceptors?: Interceptors) {
|
|
258
|
+
return HttpClient.getInstance(baseUrl, authHeadersProvider, interceptors);
|
|
235
259
|
}
|
|
236
260
|
|
|
237
261
|
export default HttpClient;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orsetra/shared-ui",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Shared UI components for Orsetra platform",
|
|
5
5
|
"main": "./index.ts",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -75,6 +75,8 @@
|
|
|
75
75
|
"@radix-ui/react-toggle": "1.1.1",
|
|
76
76
|
"@radix-ui/react-toggle-group": "1.1.1",
|
|
77
77
|
"@radix-ui/react-tooltip": "1.1.6",
|
|
78
|
+
"@types/js-cookie": "^3.0.6",
|
|
79
|
+
"axios": "^1.13.2",
|
|
78
80
|
"class-variance-authority": "^0.7.1",
|
|
79
81
|
"clsx": "^2.1.1",
|
|
80
82
|
"cmdk": "1.0.4",
|
|
@@ -82,15 +84,14 @@
|
|
|
82
84
|
"embla-carousel-react": "8.5.1",
|
|
83
85
|
"i18next": "^23.15.0",
|
|
84
86
|
"input-otp": "1.4.1",
|
|
87
|
+
"js-cookie": "^3.0.5",
|
|
88
|
+
"js-yaml": "^4.1.1",
|
|
85
89
|
"lodash": "^4.17.21",
|
|
86
90
|
"lucide-react": "^0.454.0",
|
|
87
91
|
"moment": "^2.30.1",
|
|
92
|
+
"monaco-editor": "^0.55.1",
|
|
88
93
|
"next-themes": "^0.4.4",
|
|
89
94
|
"react-avatar": "^5.0.3",
|
|
90
|
-
"js-cookie": "^3.0.5",
|
|
91
|
-
"js-yaml": "^4.1.1",
|
|
92
|
-
"monaco-editor": "^0.55.1",
|
|
93
|
-
"@types/js-cookie": "^3.0.6",
|
|
94
95
|
"react-day-picker": "8.10.1",
|
|
95
96
|
"react-easy-crop": "^5.0.8",
|
|
96
97
|
"react-hook-form": "^7.54.0",
|