@orsetra/shared-ui 1.0.23 → 1.0.25

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.
Files changed (2) hide show
  1. package/lib/http-client.ts +84 -4
  2. package/package.json +2 -2
@@ -2,6 +2,22 @@
2
2
 
3
3
  export type AuthHeadersProvider = () => Promise<Record<string, string>> | Record<string, string>;
4
4
 
5
+ export class ApiError extends Error {
6
+ status: number;
7
+ code?: number;
8
+ details?: unknown;
9
+ raw?: unknown;
10
+
11
+ constructor(message: string, params: { status: number; code?: number; details?: unknown; raw?: unknown }) {
12
+ super(message);
13
+ this.name = 'ApiError';
14
+ this.status = params.status;
15
+ this.code = params.code;
16
+ this.details = params.details;
17
+ this.raw = params.raw;
18
+ }
19
+ }
20
+
5
21
  class HttpClient {
6
22
  private baseUrl: string;
7
23
  private authHeadersProvider?: AuthHeadersProvider;
@@ -56,7 +72,46 @@ class HttpClient {
56
72
  });
57
73
 
58
74
  if (!response.ok) {
59
- throw new Error(`Request failed: ${response.status}`);
75
+ // Try to extract error message from API response
76
+ let errorMessage = `Request failed: ${response.status}`;
77
+ let errorBody: any = undefined;
78
+ try {
79
+ errorBody = await response.json();
80
+ // Zitadel API returns { code, message, details } on error
81
+ if (errorBody?.message) {
82
+ errorMessage = errorBody.message;
83
+ } else if (errorBody?.error) {
84
+ errorMessage = errorBody.error;
85
+ }
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
+
97
+ // Zitadel step-up requirement
98
+ const isMfaRequired =
99
+ apiError.code === 7 ||
100
+ (typeof errorMessage === 'string' && errorMessage.toLowerCase().includes('mfa required'));
101
+
102
+ if (isMfaRequired && typeof window !== 'undefined') {
103
+ try {
104
+ window.dispatchEvent(
105
+ new CustomEvent('zitadel:mfa-required', {
106
+ detail: { url, status: apiError.status, code: apiError.code, error: apiError.raw },
107
+ })
108
+ );
109
+ } catch {
110
+ // ignore event dispatch issues
111
+ }
112
+ }
113
+
114
+ throw apiError;
60
115
  }
61
116
 
62
117
  if (response.status === 204) {
@@ -121,8 +176,23 @@ class HttpClient {
121
176
  });
122
177
 
123
178
  if (!response.ok) {
124
- const errorText = await response.text();
125
- throw new Error(`Upload failed: ${response.status} ${response.statusText} - ${errorText}`);
179
+ // Try to extract error message from API response
180
+ let errorMessage = `Upload failed: ${response.status} ${response.statusText}`;
181
+ try {
182
+ const errorBody = await response.json();
183
+ if (errorBody?.message) {
184
+ errorMessage = errorBody.message;
185
+ } else if (errorBody?.error) {
186
+ errorMessage = errorBody.error;
187
+ }
188
+ } catch {
189
+ // Try text if JSON fails
190
+ try {
191
+ const errorText = await response.text();
192
+ if (errorText) errorMessage = errorText;
193
+ } catch { /* ignore */ }
194
+ }
195
+ throw new Error(errorMessage);
126
196
  }
127
197
 
128
198
  if (response.status === 204) {
@@ -143,7 +213,17 @@ class HttpClient {
143
213
  });
144
214
 
145
215
  if (!response.ok) {
146
- throw new Error(`Download failed: ${response.status} ${response.statusText}`);
216
+ // Try to extract error message from API response
217
+ let errorMessage = `Download failed: ${response.status} ${response.statusText}`;
218
+ try {
219
+ const errorBody = await response.json();
220
+ if (errorBody?.message) {
221
+ errorMessage = errorBody.message;
222
+ } else if (errorBody?.error) {
223
+ errorMessage = errorBody.error;
224
+ }
225
+ } catch { /* ignore */ }
226
+ throw new Error(errorMessage);
147
227
  }
148
228
 
149
229
  return await response.blob();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orsetra/shared-ui",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "Shared UI components for Orsetra platform",
5
5
  "main": "./index.ts",
6
6
  "types": "./index.ts",
@@ -92,4 +92,4 @@
92
92
  "next": "^16.0.7",
93
93
  "typescript": "^5"
94
94
  }
95
- }
95
+ }