@arow-software/auth-client 1.1.0
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/README.md +342 -0
- package/dist/index.d.mts +251 -0
- package/dist/index.d.ts +251 -0
- package/dist/index.js +548 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +528 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +57 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
7
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
8
|
+
}) : x)(function(x) {
|
|
9
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
10
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// src/tokenManager.ts
|
|
14
|
+
var DEFAULT_STORAGE_PREFIX = "arowauth";
|
|
15
|
+
var ACCESS_TOKEN_KEY = "access_token";
|
|
16
|
+
var REFRESH_TOKEN_KEY = "refresh_token";
|
|
17
|
+
var EXPIRY_BUFFER_SECONDS = 300;
|
|
18
|
+
var config = null;
|
|
19
|
+
var isRefreshing = false;
|
|
20
|
+
var refreshPromise = null;
|
|
21
|
+
function initTokenManager(authConfig) {
|
|
22
|
+
config = authConfig;
|
|
23
|
+
}
|
|
24
|
+
function getStorage() {
|
|
25
|
+
if (typeof window === "undefined") {
|
|
26
|
+
return {
|
|
27
|
+
getItem: () => null,
|
|
28
|
+
setItem: () => {
|
|
29
|
+
},
|
|
30
|
+
removeItem: () => {
|
|
31
|
+
},
|
|
32
|
+
clear: () => {
|
|
33
|
+
},
|
|
34
|
+
key: () => null,
|
|
35
|
+
length: 0
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return config?.useSessionStorage ? sessionStorage : localStorage;
|
|
39
|
+
}
|
|
40
|
+
function getKey(key) {
|
|
41
|
+
const prefix = config?.storagePrefix || DEFAULT_STORAGE_PREFIX;
|
|
42
|
+
return `${prefix}_${key}`;
|
|
43
|
+
}
|
|
44
|
+
function decodeJwt(token) {
|
|
45
|
+
try {
|
|
46
|
+
const parts = token.split(".");
|
|
47
|
+
if (parts.length !== 3) return null;
|
|
48
|
+
const payload = parts[1];
|
|
49
|
+
const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
50
|
+
const jsonPayload = decodeURIComponent(
|
|
51
|
+
atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
|
|
52
|
+
);
|
|
53
|
+
return JSON.parse(jsonPayload);
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function getAccessToken() {
|
|
59
|
+
return getStorage().getItem(getKey(ACCESS_TOKEN_KEY));
|
|
60
|
+
}
|
|
61
|
+
function getRefreshToken() {
|
|
62
|
+
return getStorage().getItem(getKey(REFRESH_TOKEN_KEY));
|
|
63
|
+
}
|
|
64
|
+
function setTokens(accessToken, refreshToken) {
|
|
65
|
+
const storage = getStorage();
|
|
66
|
+
storage.setItem(getKey(ACCESS_TOKEN_KEY), accessToken);
|
|
67
|
+
storage.setItem(getKey(REFRESH_TOKEN_KEY), refreshToken);
|
|
68
|
+
}
|
|
69
|
+
function clearTokens() {
|
|
70
|
+
const storage = getStorage();
|
|
71
|
+
storage.removeItem(getKey(ACCESS_TOKEN_KEY));
|
|
72
|
+
storage.removeItem(getKey(REFRESH_TOKEN_KEY));
|
|
73
|
+
}
|
|
74
|
+
function isTokenExpired(token) {
|
|
75
|
+
const accessToken = token ?? getAccessToken();
|
|
76
|
+
if (!accessToken) return true;
|
|
77
|
+
const payload = decodeJwt(accessToken);
|
|
78
|
+
if (!payload || !payload.exp) return true;
|
|
79
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
80
|
+
return payload.exp <= now + EXPIRY_BUFFER_SECONDS;
|
|
81
|
+
}
|
|
82
|
+
function hasValidToken() {
|
|
83
|
+
const token = getAccessToken();
|
|
84
|
+
return token !== null && !isTokenExpired(token);
|
|
85
|
+
}
|
|
86
|
+
function getUserFromToken() {
|
|
87
|
+
const token = getAccessToken();
|
|
88
|
+
if (!token) return null;
|
|
89
|
+
return decodeJwt(token);
|
|
90
|
+
}
|
|
91
|
+
async function refreshTokens() {
|
|
92
|
+
if (!config) {
|
|
93
|
+
throw new Error("TokenManager not initialized. Call initTokenManager first.");
|
|
94
|
+
}
|
|
95
|
+
const refreshToken = getRefreshToken();
|
|
96
|
+
if (!refreshToken) {
|
|
97
|
+
clearTokens();
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
if (isRefreshing && refreshPromise) {
|
|
101
|
+
return refreshPromise;
|
|
102
|
+
}
|
|
103
|
+
isRefreshing = true;
|
|
104
|
+
refreshPromise = performRefresh(refreshToken);
|
|
105
|
+
try {
|
|
106
|
+
const result = await refreshPromise;
|
|
107
|
+
return result;
|
|
108
|
+
} finally {
|
|
109
|
+
isRefreshing = false;
|
|
110
|
+
refreshPromise = null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function performRefresh(refreshToken) {
|
|
114
|
+
if (!config) return null;
|
|
115
|
+
try {
|
|
116
|
+
const response = await fetch(`${config.ssoBaseUrl}/api/auth/refresh`, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: {
|
|
119
|
+
"Content-Type": "application/json"
|
|
120
|
+
},
|
|
121
|
+
body: JSON.stringify({ refreshToken })
|
|
122
|
+
});
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
clearTokens();
|
|
125
|
+
config.onAuthError?.(new Error("Token refresh failed"));
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const data = await response.json();
|
|
129
|
+
const tokens = {
|
|
130
|
+
accessToken: data.accessToken || data.access_token,
|
|
131
|
+
refreshToken: data.refreshToken || data.refresh_token,
|
|
132
|
+
expiresIn: data.expiresIn || data.expires_in,
|
|
133
|
+
tokenType: data.tokenType || data.token_type || "Bearer"
|
|
134
|
+
};
|
|
135
|
+
setTokens(tokens.accessToken, tokens.refreshToken);
|
|
136
|
+
config.onTokenRefresh?.(tokens);
|
|
137
|
+
return tokens;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
clearTokens();
|
|
140
|
+
config.onAuthError?.(error instanceof Error ? error : new Error("Token refresh failed"));
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function getValidAccessToken() {
|
|
145
|
+
const token = getAccessToken();
|
|
146
|
+
if (token && !isTokenExpired(token)) {
|
|
147
|
+
return token;
|
|
148
|
+
}
|
|
149
|
+
const refreshed = await refreshTokens();
|
|
150
|
+
return refreshed?.accessToken ?? null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/apiInterceptor.ts
|
|
154
|
+
var isRefreshing2 = false;
|
|
155
|
+
var requestQueue = [];
|
|
156
|
+
function processQueue(token, error = null) {
|
|
157
|
+
requestQueue.forEach((request) => {
|
|
158
|
+
if (error) {
|
|
159
|
+
request.reject(error);
|
|
160
|
+
} else {
|
|
161
|
+
request.resolve(token);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
requestQueue = [];
|
|
165
|
+
}
|
|
166
|
+
function createAuthClient(axiosInstance, config2) {
|
|
167
|
+
axiosInstance.interceptors.request.use(
|
|
168
|
+
async (requestConfig) => {
|
|
169
|
+
if (requestConfig.url?.includes("/api/auth/refresh")) {
|
|
170
|
+
return requestConfig;
|
|
171
|
+
}
|
|
172
|
+
const token = getAccessToken();
|
|
173
|
+
if (token) {
|
|
174
|
+
requestConfig.headers = requestConfig.headers || {};
|
|
175
|
+
requestConfig.headers.Authorization = `Bearer ${token}`;
|
|
176
|
+
}
|
|
177
|
+
return requestConfig;
|
|
178
|
+
},
|
|
179
|
+
(error) => Promise.reject(error)
|
|
180
|
+
);
|
|
181
|
+
axiosInstance.interceptors.response.use(
|
|
182
|
+
(response) => response,
|
|
183
|
+
async (error) => {
|
|
184
|
+
const originalRequest = error.config;
|
|
185
|
+
if (error.response?.status !== 401) {
|
|
186
|
+
return Promise.reject(error);
|
|
187
|
+
}
|
|
188
|
+
if (originalRequest._retry || !originalRequest) {
|
|
189
|
+
clearTokens();
|
|
190
|
+
config2.onAuthError?.(new Error("Authentication failed"));
|
|
191
|
+
return Promise.reject(error);
|
|
192
|
+
}
|
|
193
|
+
if (originalRequest.url?.includes("/api/auth/refresh")) {
|
|
194
|
+
clearTokens();
|
|
195
|
+
config2.onAuthError?.(new Error("Token refresh failed"));
|
|
196
|
+
return Promise.reject(error);
|
|
197
|
+
}
|
|
198
|
+
if (isRefreshing2) {
|
|
199
|
+
return new Promise((resolve, reject) => {
|
|
200
|
+
requestQueue.push({
|
|
201
|
+
resolve: (token) => {
|
|
202
|
+
if (token) {
|
|
203
|
+
originalRequest.headers = originalRequest.headers || {};
|
|
204
|
+
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
205
|
+
resolve(axiosInstance(originalRequest));
|
|
206
|
+
} else {
|
|
207
|
+
reject(new Error("Token refresh failed"));
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
reject
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
originalRequest._retry = true;
|
|
215
|
+
isRefreshing2 = true;
|
|
216
|
+
try {
|
|
217
|
+
const token = await getValidAccessToken();
|
|
218
|
+
if (!token) {
|
|
219
|
+
processQueue(null, new Error("Token refresh failed"));
|
|
220
|
+
clearTokens();
|
|
221
|
+
config2.onAuthError?.(new Error("Token refresh failed"));
|
|
222
|
+
return Promise.reject(error);
|
|
223
|
+
}
|
|
224
|
+
processQueue(token);
|
|
225
|
+
originalRequest.headers = originalRequest.headers || {};
|
|
226
|
+
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
227
|
+
return axiosInstance(originalRequest);
|
|
228
|
+
} catch (refreshError) {
|
|
229
|
+
processQueue(null, refreshError instanceof Error ? refreshError : new Error("Token refresh failed"));
|
|
230
|
+
clearTokens();
|
|
231
|
+
config2.onAuthError?.(refreshError instanceof Error ? refreshError : new Error("Token refresh failed"));
|
|
232
|
+
return Promise.reject(refreshError);
|
|
233
|
+
} finally {
|
|
234
|
+
isRefreshing2 = false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
return axiosInstance;
|
|
239
|
+
}
|
|
240
|
+
function createApiClient(config2) {
|
|
241
|
+
const axios = __require("axios");
|
|
242
|
+
const instance = axios.create({
|
|
243
|
+
baseURL: config2.apiBaseUrl,
|
|
244
|
+
headers: {
|
|
245
|
+
"Content-Type": "application/json"
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
return createAuthClient(instance, config2);
|
|
249
|
+
}
|
|
250
|
+
var AuthContext = react.createContext(void 0);
|
|
251
|
+
function parseUserFromToken(token) {
|
|
252
|
+
const payload = decodeJwt(token);
|
|
253
|
+
if (!payload) return null;
|
|
254
|
+
return {
|
|
255
|
+
id: payload.sub,
|
|
256
|
+
email: payload.email,
|
|
257
|
+
firstName: payload.given_name,
|
|
258
|
+
lastName: payload.family_name,
|
|
259
|
+
displayName: payload.name,
|
|
260
|
+
avatarUrl: payload.picture,
|
|
261
|
+
emailVerified: payload.email_verified ?? false,
|
|
262
|
+
roles: payload.roles,
|
|
263
|
+
permissions: payload.permissions
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function buildAuthUrl(ssoBaseUrl, clientId, redirectUri, scopes = ["openid", "email", "profile"], redirectPath) {
|
|
267
|
+
const finalRedirectUri = redirectUri || `${window.location.origin}/callback`;
|
|
268
|
+
if (redirectPath) {
|
|
269
|
+
sessionStorage.setItem("arowauth_redirect_path", redirectPath);
|
|
270
|
+
}
|
|
271
|
+
const params = new URLSearchParams({
|
|
272
|
+
client_id: clientId,
|
|
273
|
+
redirect_uri: finalRedirectUri,
|
|
274
|
+
response_type: "token",
|
|
275
|
+
scope: scopes.join(" ")
|
|
276
|
+
});
|
|
277
|
+
return `${ssoBaseUrl}/oauth/authorize?${params.toString()}`;
|
|
278
|
+
}
|
|
279
|
+
function parseTokensFromHash() {
|
|
280
|
+
if (typeof window === "undefined") return null;
|
|
281
|
+
const hash = window.location.hash;
|
|
282
|
+
if (!hash) return null;
|
|
283
|
+
const params = new URLSearchParams(hash.substring(1));
|
|
284
|
+
const accessToken = params.get("access_token") || params.get("token");
|
|
285
|
+
const refreshToken = params.get("refresh_token");
|
|
286
|
+
if (accessToken && refreshToken) {
|
|
287
|
+
return {
|
|
288
|
+
accessToken,
|
|
289
|
+
refreshToken,
|
|
290
|
+
expiresIn: params.get("expires_in") ? parseInt(params.get("expires_in"), 10) : void 0,
|
|
291
|
+
tokenType: params.get("token_type") || "Bearer"
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
function AuthProvider(props) {
|
|
297
|
+
const { children, onTokenRefresh, onAuthError, onLogout, ...config2 } = props;
|
|
298
|
+
const [state, setState] = react.useState({
|
|
299
|
+
user: null,
|
|
300
|
+
isAuthenticated: false,
|
|
301
|
+
isLoading: true,
|
|
302
|
+
error: null
|
|
303
|
+
});
|
|
304
|
+
react.useEffect(() => {
|
|
305
|
+
const fullConfig = {
|
|
306
|
+
...config2,
|
|
307
|
+
onTokenRefresh,
|
|
308
|
+
onAuthError: (error) => {
|
|
309
|
+
setState((prev) => ({ ...prev, error: error.message }));
|
|
310
|
+
onAuthError?.(error);
|
|
311
|
+
},
|
|
312
|
+
onLogout
|
|
313
|
+
};
|
|
314
|
+
initTokenManager(fullConfig);
|
|
315
|
+
}, [config2.ssoBaseUrl, config2.clientId, config2.apiBaseUrl, onTokenRefresh, onAuthError, onLogout]);
|
|
316
|
+
react.useEffect(() => {
|
|
317
|
+
const initAuth = async () => {
|
|
318
|
+
try {
|
|
319
|
+
const hashTokens = parseTokensFromHash();
|
|
320
|
+
if (hashTokens) {
|
|
321
|
+
setTokens(hashTokens.accessToken, hashTokens.refreshToken);
|
|
322
|
+
window.history.replaceState(null, "", window.location.pathname + window.location.search);
|
|
323
|
+
const redirectPath = sessionStorage.getItem("arowauth_redirect_path");
|
|
324
|
+
if (redirectPath) {
|
|
325
|
+
sessionStorage.removeItem("arowauth_redirect_path");
|
|
326
|
+
window.history.replaceState(null, "", redirectPath);
|
|
327
|
+
}
|
|
328
|
+
onTokenRefresh?.(hashTokens);
|
|
329
|
+
}
|
|
330
|
+
const token = getAccessToken();
|
|
331
|
+
if (token && !isTokenExpired(token)) {
|
|
332
|
+
const user = parseUserFromToken(token);
|
|
333
|
+
setState({
|
|
334
|
+
user,
|
|
335
|
+
isAuthenticated: true,
|
|
336
|
+
isLoading: false,
|
|
337
|
+
error: null
|
|
338
|
+
});
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (token) {
|
|
342
|
+
const newToken = await getValidAccessToken();
|
|
343
|
+
if (newToken) {
|
|
344
|
+
const user = parseUserFromToken(newToken);
|
|
345
|
+
setState({
|
|
346
|
+
user,
|
|
347
|
+
isAuthenticated: true,
|
|
348
|
+
isLoading: false,
|
|
349
|
+
error: null
|
|
350
|
+
});
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
setState({
|
|
355
|
+
user: null,
|
|
356
|
+
isAuthenticated: false,
|
|
357
|
+
isLoading: false,
|
|
358
|
+
error: null
|
|
359
|
+
});
|
|
360
|
+
} catch (error) {
|
|
361
|
+
setState({
|
|
362
|
+
user: null,
|
|
363
|
+
isAuthenticated: false,
|
|
364
|
+
isLoading: false,
|
|
365
|
+
error: error instanceof Error ? error.message : "Authentication error"
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
initAuth();
|
|
370
|
+
}, [onTokenRefresh]);
|
|
371
|
+
const login = react.useCallback((redirectPath) => {
|
|
372
|
+
const authUrl = buildAuthUrl(
|
|
373
|
+
config2.ssoBaseUrl,
|
|
374
|
+
config2.clientId,
|
|
375
|
+
config2.redirectUri,
|
|
376
|
+
config2.scopes,
|
|
377
|
+
redirectPath || window.location.pathname
|
|
378
|
+
);
|
|
379
|
+
window.location.href = authUrl;
|
|
380
|
+
}, [config2.ssoBaseUrl, config2.clientId, config2.redirectUri, config2.scopes]);
|
|
381
|
+
const logout = react.useCallback(async () => {
|
|
382
|
+
clearTokens();
|
|
383
|
+
setState({
|
|
384
|
+
user: null,
|
|
385
|
+
isAuthenticated: false,
|
|
386
|
+
isLoading: false,
|
|
387
|
+
error: null
|
|
388
|
+
});
|
|
389
|
+
onLogout?.();
|
|
390
|
+
}, [onLogout]);
|
|
391
|
+
const refreshUser = react.useCallback(async () => {
|
|
392
|
+
const token = await getValidAccessToken();
|
|
393
|
+
if (token) {
|
|
394
|
+
const user = parseUserFromToken(token);
|
|
395
|
+
setState((prev) => ({
|
|
396
|
+
...prev,
|
|
397
|
+
user,
|
|
398
|
+
isAuthenticated: true,
|
|
399
|
+
error: null
|
|
400
|
+
}));
|
|
401
|
+
} else {
|
|
402
|
+
setState({
|
|
403
|
+
user: null,
|
|
404
|
+
isAuthenticated: false,
|
|
405
|
+
isLoading: false,
|
|
406
|
+
error: null
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}, []);
|
|
410
|
+
const contextValue = react.useMemo(
|
|
411
|
+
() => ({
|
|
412
|
+
...state,
|
|
413
|
+
login,
|
|
414
|
+
logout,
|
|
415
|
+
refreshUser
|
|
416
|
+
}),
|
|
417
|
+
[state, login, logout, refreshUser]
|
|
418
|
+
);
|
|
419
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AuthContext.Provider, { value: contextValue, children });
|
|
420
|
+
}
|
|
421
|
+
function useAuth() {
|
|
422
|
+
const context = react.useContext(AuthContext);
|
|
423
|
+
if (context === void 0) {
|
|
424
|
+
throw new Error("useAuth must be used within an AuthProvider");
|
|
425
|
+
}
|
|
426
|
+
return context;
|
|
427
|
+
}
|
|
428
|
+
function useIsAuthenticated() {
|
|
429
|
+
const { isAuthenticated } = useAuth();
|
|
430
|
+
return isAuthenticated;
|
|
431
|
+
}
|
|
432
|
+
function useUser() {
|
|
433
|
+
const { user } = useAuth();
|
|
434
|
+
return user;
|
|
435
|
+
}
|
|
436
|
+
function useAuthClient() {
|
|
437
|
+
const context = react.useContext(AuthContext);
|
|
438
|
+
if (context === void 0) {
|
|
439
|
+
throw new Error("useAuthClient must be used within an AuthProvider");
|
|
440
|
+
}
|
|
441
|
+
const client = react.useMemo(() => {
|
|
442
|
+
const axios = __require("axios");
|
|
443
|
+
const instance = axios.create({
|
|
444
|
+
headers: {
|
|
445
|
+
"Content-Type": "application/json"
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
let isRefreshing3 = false;
|
|
449
|
+
let requestQueue2 = [];
|
|
450
|
+
const processQueue2 = (token, error = null) => {
|
|
451
|
+
requestQueue2.forEach((req) => {
|
|
452
|
+
if (error) {
|
|
453
|
+
req.reject(error);
|
|
454
|
+
} else {
|
|
455
|
+
req.resolve(token);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
requestQueue2 = [];
|
|
459
|
+
};
|
|
460
|
+
instance.interceptors.request.use(
|
|
461
|
+
async (config2) => {
|
|
462
|
+
if (config2.url?.includes("/api/auth/refresh")) {
|
|
463
|
+
return config2;
|
|
464
|
+
}
|
|
465
|
+
const token = getAccessToken();
|
|
466
|
+
if (token) {
|
|
467
|
+
config2.headers = config2.headers || {};
|
|
468
|
+
config2.headers.Authorization = `Bearer ${token}`;
|
|
469
|
+
}
|
|
470
|
+
return config2;
|
|
471
|
+
},
|
|
472
|
+
(error) => Promise.reject(error)
|
|
473
|
+
);
|
|
474
|
+
instance.interceptors.response.use(
|
|
475
|
+
(response) => response,
|
|
476
|
+
async (error) => {
|
|
477
|
+
const originalRequest = error.config;
|
|
478
|
+
if (error.response?.status !== 401 || originalRequest._retry) {
|
|
479
|
+
return Promise.reject(error);
|
|
480
|
+
}
|
|
481
|
+
if (originalRequest.url?.includes("/api/auth/refresh")) {
|
|
482
|
+
clearTokens();
|
|
483
|
+
return Promise.reject(error);
|
|
484
|
+
}
|
|
485
|
+
if (isRefreshing3) {
|
|
486
|
+
return new Promise((resolve, reject) => {
|
|
487
|
+
requestQueue2.push({
|
|
488
|
+
resolve: (token) => {
|
|
489
|
+
if (token) {
|
|
490
|
+
originalRequest.headers = originalRequest.headers || {};
|
|
491
|
+
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
492
|
+
resolve(instance(originalRequest));
|
|
493
|
+
} else {
|
|
494
|
+
reject(new Error("Token refresh failed"));
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
reject
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
originalRequest._retry = true;
|
|
502
|
+
isRefreshing3 = true;
|
|
503
|
+
try {
|
|
504
|
+
const token = await getValidAccessToken();
|
|
505
|
+
if (!token) {
|
|
506
|
+
processQueue2(null, new Error("Token refresh failed"));
|
|
507
|
+
clearTokens();
|
|
508
|
+
return Promise.reject(error);
|
|
509
|
+
}
|
|
510
|
+
processQueue2(token);
|
|
511
|
+
originalRequest.headers = originalRequest.headers || {};
|
|
512
|
+
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
513
|
+
return instance(originalRequest);
|
|
514
|
+
} catch (refreshError) {
|
|
515
|
+
processQueue2(null, refreshError instanceof Error ? refreshError : new Error("Refresh failed"));
|
|
516
|
+
clearTokens();
|
|
517
|
+
return Promise.reject(refreshError);
|
|
518
|
+
} finally {
|
|
519
|
+
isRefreshing3 = false;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
);
|
|
523
|
+
return instance;
|
|
524
|
+
}, []);
|
|
525
|
+
return client;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
exports.AuthContext = AuthContext;
|
|
529
|
+
exports.AuthProvider = AuthProvider;
|
|
530
|
+
exports.clearTokens = clearTokens;
|
|
531
|
+
exports.createApiClient = createApiClient;
|
|
532
|
+
exports.createAuthClient = createAuthClient;
|
|
533
|
+
exports.decodeJwt = decodeJwt;
|
|
534
|
+
exports.getAccessToken = getAccessToken;
|
|
535
|
+
exports.getRefreshToken = getRefreshToken;
|
|
536
|
+
exports.getUserFromToken = getUserFromToken;
|
|
537
|
+
exports.getValidAccessToken = getValidAccessToken;
|
|
538
|
+
exports.hasValidToken = hasValidToken;
|
|
539
|
+
exports.initTokenManager = initTokenManager;
|
|
540
|
+
exports.isTokenExpired = isTokenExpired;
|
|
541
|
+
exports.refreshTokens = refreshTokens;
|
|
542
|
+
exports.setTokens = setTokens;
|
|
543
|
+
exports.useAuth = useAuth;
|
|
544
|
+
exports.useAuthClient = useAuthClient;
|
|
545
|
+
exports.useIsAuthenticated = useIsAuthenticated;
|
|
546
|
+
exports.useUser = useUser;
|
|
547
|
+
//# sourceMappingURL=index.js.map
|
|
548
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tokenManager.ts","../src/apiInterceptor.ts","../src/AuthProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useAuthClient.ts"],"names":["isRefreshing","config","createContext","useState","useEffect","useCallback","useMemo","useContext","requestQueue","processQueue"],"mappings":"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","file":"index.js","sourcesContent":["import type { JwtPayload, TokenPair, AuthConfig } from './types';\n\nconst DEFAULT_STORAGE_PREFIX = 'arowauth';\nconst ACCESS_TOKEN_KEY = 'access_token';\nconst REFRESH_TOKEN_KEY = 'refresh_token';\n\n// Buffer time before expiry to trigger refresh (5 minutes)\nconst EXPIRY_BUFFER_SECONDS = 300;\n\nlet config: AuthConfig | null = null;\nlet isRefreshing = false;\nlet refreshPromise: Promise<TokenPair | null> | null = null;\n\n/**\n * Initialize token manager with configuration\n */\nexport function initTokenManager(authConfig: AuthConfig): void {\n config = authConfig;\n}\n\n/**\n * Get the storage instance (localStorage or sessionStorage)\n */\nfunction getStorage(): Storage {\n if (typeof window === 'undefined') {\n // SSR fallback - return a no-op storage\n return {\n getItem: () => null,\n setItem: () => {},\n removeItem: () => {},\n clear: () => {},\n key: () => null,\n length: 0,\n };\n }\n return config?.useSessionStorage ? sessionStorage : localStorage;\n}\n\n/**\n * Get storage key with prefix\n */\nfunction getKey(key: string): string {\n const prefix = config?.storagePrefix || DEFAULT_STORAGE_PREFIX;\n return `${prefix}_${key}`;\n}\n\n/**\n * Decode a JWT token without verification\n */\nexport function decodeJwt(token: string): JwtPayload | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n \n const payload = parts[1];\n // Handle URL-safe base64\n const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split('')\n .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join('')\n );\n \n return JSON.parse(jsonPayload) as JwtPayload;\n } catch {\n return null;\n }\n}\n\n/**\n * Get the access token from storage\n */\nexport function getAccessToken(): string | null {\n return getStorage().getItem(getKey(ACCESS_TOKEN_KEY));\n}\n\n/**\n * Get the refresh token from storage\n */\nexport function getRefreshToken(): string | null {\n return getStorage().getItem(getKey(REFRESH_TOKEN_KEY));\n}\n\n/**\n * Store tokens in storage\n */\nexport function setTokens(accessToken: string, refreshToken: string): void {\n const storage = getStorage();\n storage.setItem(getKey(ACCESS_TOKEN_KEY), accessToken);\n storage.setItem(getKey(REFRESH_TOKEN_KEY), refreshToken);\n}\n\n/**\n * Clear all tokens from storage\n */\nexport function clearTokens(): void {\n const storage = getStorage();\n storage.removeItem(getKey(ACCESS_TOKEN_KEY));\n storage.removeItem(getKey(REFRESH_TOKEN_KEY));\n}\n\n/**\n * Check if the access token is expired or about to expire\n */\nexport function isTokenExpired(token?: string | null): boolean {\n const accessToken = token ?? getAccessToken();\n if (!accessToken) return true;\n \n const payload = decodeJwt(accessToken);\n if (!payload || !payload.exp) return true;\n \n // Check if token expires within buffer period\n const now = Math.floor(Date.now() / 1000);\n return payload.exp <= now + EXPIRY_BUFFER_SECONDS;\n}\n\n/**\n * Check if we have a valid (non-expired) access token\n */\nexport function hasValidToken(): boolean {\n const token = getAccessToken();\n return token !== null && !isTokenExpired(token);\n}\n\n/**\n * Get user info from the current access token\n */\nexport function getUserFromToken(): JwtPayload | null {\n const token = getAccessToken();\n if (!token) return null;\n return decodeJwt(token);\n}\n\n/**\n * Refresh tokens using the refresh token\n * Handles concurrent refresh requests by returning the same promise\n */\nexport async function refreshTokens(): Promise<TokenPair | null> {\n if (!config) {\n throw new Error('TokenManager not initialized. Call initTokenManager first.');\n }\n\n const refreshToken = getRefreshToken();\n if (!refreshToken) {\n clearTokens();\n return null;\n }\n\n // If already refreshing, return the existing promise\n if (isRefreshing && refreshPromise) {\n return refreshPromise;\n }\n\n isRefreshing = true;\n refreshPromise = performRefresh(refreshToken);\n\n try {\n const result = await refreshPromise;\n return result;\n } finally {\n isRefreshing = false;\n refreshPromise = null;\n }\n}\n\n/**\n * Perform the actual token refresh request\n */\nasync function performRefresh(refreshToken: string): Promise<TokenPair | null> {\n if (!config) return null;\n\n try {\n const response = await fetch(`${config.ssoBaseUrl}/api/auth/refresh`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ refreshToken }),\n });\n\n if (!response.ok) {\n clearTokens();\n config.onAuthError?.(new Error('Token refresh failed'));\n return null;\n }\n\n const data = await response.json();\n const tokens: TokenPair = {\n accessToken: data.accessToken || data.access_token,\n refreshToken: data.refreshToken || data.refresh_token,\n expiresIn: data.expiresIn || data.expires_in,\n tokenType: data.tokenType || data.token_type || 'Bearer',\n };\n\n setTokens(tokens.accessToken, tokens.refreshToken);\n config.onTokenRefresh?.(tokens);\n\n return tokens;\n } catch (error) {\n clearTokens();\n config.onAuthError?.(error instanceof Error ? error : new Error('Token refresh failed'));\n return null;\n }\n}\n\n/**\n * Get a valid access token, refreshing if necessary\n */\nexport async function getValidAccessToken(): Promise<string | null> {\n const token = getAccessToken();\n \n if (token && !isTokenExpired(token)) {\n return token;\n }\n\n const refreshed = await refreshTokens();\n return refreshed?.accessToken ?? null;\n}\n","import type { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';\nimport { getAccessToken, getValidAccessToken, clearTokens } from './tokenManager';\nimport type { AuthConfig } from './types';\n\ninterface QueuedRequest {\n resolve: (token: string | null) => void;\n reject: (error: Error) => void;\n}\n\nlet isRefreshing = false;\nlet requestQueue: QueuedRequest[] = [];\n\n/**\n * Process queued requests after token refresh\n */\nfunction processQueue(token: string | null, error: Error | null = null): void {\n requestQueue.forEach((request) => {\n if (error) {\n request.reject(error);\n } else {\n request.resolve(token);\n }\n });\n requestQueue = [];\n}\n\n/**\n * Create and configure axios instance with auth interceptors\n */\nexport function createAuthClient(\n axiosInstance: AxiosInstance,\n config: AuthConfig\n): AxiosInstance {\n // Request interceptor - attach Bearer token\n axiosInstance.interceptors.request.use(\n async (requestConfig: InternalAxiosRequestConfig): Promise<InternalAxiosRequestConfig> => {\n // Skip auth for refresh endpoint to avoid infinite loop\n if (requestConfig.url?.includes('/api/auth/refresh')) {\n return requestConfig;\n }\n\n const token = getAccessToken();\n if (token) {\n requestConfig.headers = requestConfig.headers || {};\n requestConfig.headers.Authorization = `Bearer ${token}`;\n }\n return requestConfig;\n },\n (error: unknown) => Promise.reject(error)\n );\n\n // Response interceptor - handle 401 and refresh token\n axiosInstance.interceptors.response.use(\n (response: AxiosResponse) => response,\n async (error: AxiosError): Promise<AxiosResponse> => {\n const originalRequest = error.config as InternalAxiosRequestConfig & {\n _retry?: boolean;\n };\n\n // Only handle 401 errors\n if (error.response?.status !== 401) {\n return Promise.reject(error);\n }\n\n // Don't retry if already retried or no config\n if (originalRequest._retry || !originalRequest) {\n clearTokens();\n config.onAuthError?.(new Error('Authentication failed'));\n return Promise.reject(error);\n }\n\n // Don't retry refresh endpoint\n if (originalRequest.url?.includes('/api/auth/refresh')) {\n clearTokens();\n config.onAuthError?.(new Error('Token refresh failed'));\n return Promise.reject(error);\n }\n\n // If already refreshing, queue this request\n if (isRefreshing) {\n return new Promise((resolve, reject) => {\n requestQueue.push({\n resolve: (token) => {\n if (token) {\n originalRequest.headers = originalRequest.headers || {};\n originalRequest.headers.Authorization = `Bearer ${token}`;\n resolve(axiosInstance(originalRequest));\n } else {\n reject(new Error('Token refresh failed'));\n }\n },\n reject,\n });\n });\n }\n\n originalRequest._retry = true;\n isRefreshing = true;\n\n try {\n const token = await getValidAccessToken();\n \n if (!token) {\n processQueue(null, new Error('Token refresh failed'));\n clearTokens();\n config.onAuthError?.(new Error('Token refresh failed'));\n return Promise.reject(error);\n }\n\n processQueue(token);\n \n originalRequest.headers = originalRequest.headers || {};\n originalRequest.headers.Authorization = `Bearer ${token}`;\n \n return axiosInstance(originalRequest);\n } catch (refreshError) {\n processQueue(null, refreshError instanceof Error ? refreshError : new Error('Token refresh failed'));\n clearTokens();\n config.onAuthError?.(refreshError instanceof Error ? refreshError : new Error('Token refresh failed'));\n return Promise.reject(refreshError);\n } finally {\n isRefreshing = false;\n }\n }\n );\n\n return axiosInstance;\n}\n\n/**\n * Create a new axios instance with auth interceptors\n */\nexport function createApiClient(config: AuthConfig): AxiosInstance {\n // Dynamic import to avoid bundling axios if not used\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const axios = require('axios');\n \n const instance = axios.create({\n baseURL: config.apiBaseUrl,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n return createAuthClient(instance, config);\n}\n","import React, { createContext, useEffect, useState, useCallback, useMemo } from 'react';\nimport type { User, AuthState, AuthContextValue, AuthProviderProps, TokenPair, AuthConfig } from './types';\nimport {\n initTokenManager,\n getAccessToken,\n setTokens,\n clearTokens,\n isTokenExpired,\n decodeJwt,\n getValidAccessToken,\n} from './tokenManager';\n\n// Create context with undefined default\nexport const AuthContext = createContext<AuthContextValue | undefined>(undefined);\n\n/**\n * Parse user from JWT payload\n */\nfunction parseUserFromToken(token: string): User | null {\n const payload = decodeJwt(token);\n if (!payload) return null;\n\n return {\n id: payload.sub,\n email: payload.email,\n firstName: payload.given_name,\n lastName: payload.family_name,\n displayName: payload.name,\n avatarUrl: payload.picture,\n emailVerified: payload.email_verified ?? false,\n roles: payload.roles,\n permissions: payload.permissions,\n };\n}\n\n/**\n * Build the SSO authorization URL\n */\nfunction buildAuthUrl(\n ssoBaseUrl: string,\n clientId: string,\n redirectUri: string | undefined,\n scopes: string[] = ['openid', 'email', 'profile'],\n redirectPath?: string\n): string {\n const finalRedirectUri = redirectUri || `${window.location.origin}/callback`;\n \n // Store the original path to redirect back after login\n if (redirectPath) {\n sessionStorage.setItem('arowauth_redirect_path', redirectPath);\n }\n\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: finalRedirectUri,\n response_type: 'token',\n scope: scopes.join(' '),\n });\n\n return `${ssoBaseUrl}/oauth/authorize?${params.toString()}`;\n}\n\n/**\n * Parse tokens from URL hash fragment (SSO callback)\n */\nfunction parseTokensFromHash(): TokenPair | null {\n if (typeof window === 'undefined') return null;\n \n const hash = window.location.hash;\n if (!hash) return null;\n\n // Parse hash fragment: #access_token=xxx&refresh_token=yyy&...\n const params = new URLSearchParams(hash.substring(1));\n \n const accessToken = params.get('access_token') || params.get('token');\n const refreshToken = params.get('refresh_token');\n\n if (accessToken && refreshToken) {\n return {\n accessToken,\n refreshToken,\n expiresIn: params.get('expires_in') ? parseInt(params.get('expires_in')!, 10) : undefined,\n tokenType: params.get('token_type') || 'Bearer',\n };\n }\n\n return null;\n}\n\n/**\n * AuthProvider component - wraps app with auth context\n */\nexport function AuthProvider(props: AuthProviderProps): React.ReactElement {\n const { children, onTokenRefresh, onAuthError, onLogout, ...config } = props;\n\n const [state, setState] = useState<AuthState>({\n user: null,\n isAuthenticated: false,\n isLoading: true,\n error: null,\n });\n\n // Initialize token manager with config\n useEffect(() => {\n const fullConfig: AuthConfig = {\n ...config,\n onTokenRefresh,\n onAuthError: (error: Error) => {\n setState((prev: AuthState) => ({ ...prev, error: error.message }));\n onAuthError?.(error);\n },\n onLogout,\n };\n initTokenManager(fullConfig);\n }, [config.ssoBaseUrl, config.clientId, config.apiBaseUrl, onTokenRefresh, onAuthError, onLogout]);\n\n // Handle SSO callback and initial auth check\n useEffect(() => {\n const initAuth = async () => {\n try {\n // Check for tokens in URL hash (SSO callback)\n const hashTokens = parseTokensFromHash();\n if (hashTokens) {\n setTokens(hashTokens.accessToken, hashTokens.refreshToken);\n \n // Clean up URL\n window.history.replaceState(null, '', window.location.pathname + window.location.search);\n \n // Redirect to original path if stored\n const redirectPath = sessionStorage.getItem('arowauth_redirect_path');\n if (redirectPath) {\n sessionStorage.removeItem('arowauth_redirect_path');\n window.history.replaceState(null, '', redirectPath);\n }\n\n onTokenRefresh?.(hashTokens);\n }\n\n // Check for existing valid token\n const token = getAccessToken();\n if (token && !isTokenExpired(token)) {\n const user = parseUserFromToken(token);\n setState({\n user,\n isAuthenticated: true,\n isLoading: false,\n error: null,\n });\n return;\n }\n\n // Try to refresh if we have a token but it's expired\n if (token) {\n const newToken = await getValidAccessToken();\n if (newToken) {\n const user = parseUserFromToken(newToken);\n setState({\n user,\n isAuthenticated: true,\n isLoading: false,\n error: null,\n });\n return;\n }\n }\n\n // No valid auth\n setState({\n user: null,\n isAuthenticated: false,\n isLoading: false,\n error: null,\n });\n } catch (error) {\n setState({\n user: null,\n isAuthenticated: false,\n isLoading: false,\n error: error instanceof Error ? error.message : 'Authentication error',\n });\n }\n };\n\n initAuth();\n }, [onTokenRefresh]);\n\n // Login - redirect to SSO\n const login = useCallback((redirectPath?: string) => {\n const authUrl = buildAuthUrl(\n config.ssoBaseUrl,\n config.clientId,\n config.redirectUri,\n config.scopes,\n redirectPath || window.location.pathname\n );\n window.location.href = authUrl;\n }, [config.ssoBaseUrl, config.clientId, config.redirectUri, config.scopes]);\n\n // Logout - clear tokens and optionally call SSO logout\n const logout = useCallback(async () => {\n clearTokens();\n setState({\n user: null,\n isAuthenticated: false,\n isLoading: false,\n error: null,\n });\n onLogout?.();\n }, [onLogout]);\n\n // Refresh user data from token\n const refreshUser = useCallback(async () => {\n const token = await getValidAccessToken();\n if (token) {\n const user = parseUserFromToken(token);\n setState((prev: AuthState) => ({\n ...prev,\n user,\n isAuthenticated: true,\n error: null,\n }));\n } else {\n setState({\n user: null,\n isAuthenticated: false,\n isLoading: false,\n error: null,\n });\n }\n }, []);\n\n // Memoize context value\n const contextValue = useMemo<AuthContextValue>(\n () => ({\n ...state,\n login,\n logout,\n refreshUser,\n }),\n [state, login, logout, refreshUser]\n );\n\n return (\n <AuthContext.Provider value={contextValue}>\n {children}\n </AuthContext.Provider>\n );\n}\n","import { useContext } from 'react';\nimport { AuthContext } from '../AuthProvider';\nimport type { AuthContextValue, User } from '../types';\n\n/**\n * Hook to access auth state and actions\n * \n * @returns Auth context value with user, isAuthenticated, login, logout, etc.\n * @throws Error if used outside of AuthProvider\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { user, isAuthenticated, login, logout, isLoading } = useAuth();\n * \n * if (isLoading) return <div>Loading...</div>;\n * \n * if (!isAuthenticated) {\n * return <button onClick={() => login()}>Login</button>;\n * }\n * \n * return (\n * <div>\n * <p>Welcome, {user?.displayName || user?.email}!</p>\n * <button onClick={logout}>Logout</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useAuth(): AuthContextValue {\n const context = useContext(AuthContext);\n \n if (context === undefined) {\n throw new Error('useAuth must be used within an AuthProvider');\n }\n \n return context;\n}\n\n/**\n * Hook to check if user is authenticated (convenience wrapper)\n * \n * @returns Boolean indicating if user is authenticated\n */\nexport function useIsAuthenticated(): boolean {\n const { isAuthenticated } = useAuth();\n return isAuthenticated;\n}\n\n/**\n * Hook to get current user (convenience wrapper)\n * \n * @returns Current user or null\n */\nexport function useUser(): User | null {\n const { user } = useAuth();\n return user;\n}\n","import { useMemo, useContext } from 'react';\nimport type { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';\nimport { AuthContext } from '../AuthProvider';\nimport { getAccessToken, getValidAccessToken, clearTokens } from '../tokenManager';\n\ninterface QueuedRequest {\n resolve: (token: string | null) => void;\n reject: (error: Error) => void;\n}\n\n/**\n * Hook to get a configured axios instance with auth interceptors\n * \n * The returned axios instance will:\n * - Automatically attach Bearer token to requests\n * - Handle 401 responses by refreshing tokens and retrying\n * - Queue concurrent requests during token refresh\n * \n * @returns Configured axios instance\n * @throws Error if used outside of AuthProvider\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const client = useAuthClient();\n * \n * const fetchData = async () => {\n * try {\n * const response = await client.get('/api/data');\n * console.log(response.data);\n * } catch (error) {\n * console.error('Failed to fetch data', error);\n * }\n * };\n * \n * return <button onClick={fetchData}>Fetch Data</button>;\n * }\n * ```\n */\nexport function useAuthClient(): AxiosInstance {\n const context = useContext(AuthContext);\n \n if (context === undefined) {\n throw new Error('useAuthClient must be used within an AuthProvider');\n }\n\n const client = useMemo(() => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const axios = require('axios');\n \n const instance: AxiosInstance = axios.create({\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n let isRefreshing = false;\n let requestQueue: QueuedRequest[] = [];\n\n const processQueue = (token: string | null, error: Error | null = null): void => {\n requestQueue.forEach((req) => {\n if (error) {\n req.reject(error);\n } else {\n req.resolve(token);\n }\n });\n requestQueue = [];\n };\n\n // Request interceptor\n instance.interceptors.request.use(\n async (config: InternalAxiosRequestConfig): Promise<InternalAxiosRequestConfig> => {\n if (config.url?.includes('/api/auth/refresh')) {\n return config;\n }\n const token = getAccessToken();\n if (token) {\n config.headers = config.headers || {};\n config.headers.Authorization = `Bearer ${token}`;\n }\n return config;\n },\n (error: unknown) => Promise.reject(error)\n );\n\n // Response interceptor\n instance.interceptors.response.use(\n (response: AxiosResponse) => response,\n async (error: AxiosError): Promise<AxiosResponse> => {\n const originalRequest = error.config as InternalAxiosRequestConfig & {\n _retry?: boolean;\n };\n\n if (error.response?.status !== 401 || originalRequest._retry) {\n return Promise.reject(error);\n }\n\n if (originalRequest.url?.includes('/api/auth/refresh')) {\n clearTokens();\n return Promise.reject(error);\n }\n\n if (isRefreshing) {\n return new Promise((resolve, reject) => {\n requestQueue.push({\n resolve: (token) => {\n if (token) {\n originalRequest.headers = originalRequest.headers || {};\n originalRequest.headers.Authorization = `Bearer ${token}`;\n resolve(instance(originalRequest));\n } else {\n reject(new Error('Token refresh failed'));\n }\n },\n reject,\n });\n });\n }\n\n originalRequest._retry = true;\n isRefreshing = true;\n\n try {\n const token = await getValidAccessToken();\n if (!token) {\n processQueue(null, new Error('Token refresh failed'));\n clearTokens();\n return Promise.reject(error);\n }\n\n processQueue(token);\n originalRequest.headers = originalRequest.headers || {};\n originalRequest.headers.Authorization = `Bearer ${token}`;\n return instance(originalRequest);\n } catch (refreshError) {\n processQueue(null, refreshError instanceof Error ? refreshError : new Error('Refresh failed'));\n clearTokens();\n return Promise.reject(refreshError);\n } finally {\n isRefreshing = false;\n }\n }\n );\n\n return instance;\n }, []);\n\n return client;\n}\n"]}
|