@rapidraptor/auth-client 0.2.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/dist/core/apiClient.d.ts +13 -0
- package/dist/core/apiClient.d.ts.map +1 -0
- package/dist/core/apiClient.js +72 -0
- package/dist/core/apiClient.js.map +1 -0
- package/dist/core/apiClient.test.d.ts +2 -0
- package/dist/core/apiClient.test.d.ts.map +1 -0
- package/dist/core/apiClient.test.js +143 -0
- package/dist/core/apiClient.test.js.map +1 -0
- package/dist/core/errorHandler.d.ts +19 -0
- package/dist/core/errorHandler.d.ts.map +1 -0
- package/dist/core/errorHandler.js +75 -0
- package/dist/core/errorHandler.js.map +1 -0
- package/dist/core/errorHandler.test.d.ts +2 -0
- package/dist/core/errorHandler.test.d.ts.map +1 -0
- package/dist/core/errorHandler.test.js +144 -0
- package/dist/core/errorHandler.test.js.map +1 -0
- package/dist/core/requestQueue.d.ts +24 -0
- package/dist/core/requestQueue.d.ts.map +1 -0
- package/dist/core/requestQueue.js +36 -0
- package/dist/core/requestQueue.js.map +1 -0
- package/dist/core/requestQueue.test.d.ts +2 -0
- package/dist/core/requestQueue.test.d.ts.map +1 -0
- package/dist/core/requestQueue.test.js +76 -0
- package/dist/core/requestQueue.test.js.map +1 -0
- package/dist/core/tokenManager.d.ts +20 -0
- package/dist/core/tokenManager.d.ts.map +1 -0
- package/dist/core/tokenManager.js +58 -0
- package/dist/core/tokenManager.js.map +1 -0
- package/dist/core/tokenManager.test.d.ts +2 -0
- package/dist/core/tokenManager.test.d.ts.map +1 -0
- package/dist/core/tokenManager.test.js +89 -0
- package/dist/core/tokenManager.test.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AxiosInstance } from 'axios';
|
|
2
|
+
import type { ApiClientConfig } from '@rapidraptor/auth-shared';
|
|
3
|
+
/**
|
|
4
|
+
* Extended AxiosInstance with logout method
|
|
5
|
+
*/
|
|
6
|
+
export interface ApiClient extends AxiosInstance {
|
|
7
|
+
logout: () => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Create API client with automatic token injection and error handling
|
|
11
|
+
*/
|
|
12
|
+
export declare function createApiClient(config: ApiClientConfig): ApiClient;
|
|
13
|
+
//# sourceMappingURL=apiClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiClient.d.ts","sourceRoot":"","sources":["../../src/core/apiClient.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,aAAa,EAAmC,MAAM,OAAO,CAAC;AAGnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAKhE;;GAEG;AACH,MAAM,WAAW,SAAU,SAAQ,aAAa;IAC9C,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAqFlE"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { DEFAULTS } from '@rapidraptor/auth-shared';
|
|
3
|
+
import { ErrorHandler } from './errorHandler.js';
|
|
4
|
+
import { TokenManager } from './tokenManager.js';
|
|
5
|
+
/**
|
|
6
|
+
* Create API client with automatic token injection and error handling
|
|
7
|
+
*/
|
|
8
|
+
export function createApiClient(config) {
|
|
9
|
+
const { baseURL, auth, onLogout, maxRetries = DEFAULTS.MAX_RETRIES, timeout = DEFAULTS.API_TIMEOUT_MS, logoutEndpoint = '/auth/logout', } = config;
|
|
10
|
+
const client = axios.create({
|
|
11
|
+
baseURL,
|
|
12
|
+
timeout,
|
|
13
|
+
headers: {
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
Accept: 'application/json',
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
const tokenManager = new TokenManager(auth);
|
|
19
|
+
const errorHandler = new ErrorHandler(onLogout);
|
|
20
|
+
// Request interceptor - inject token
|
|
21
|
+
client.interceptors.request.use(async (config) => {
|
|
22
|
+
// Get current user token
|
|
23
|
+
const token = await tokenManager.getToken(false);
|
|
24
|
+
if (token && config.headers) {
|
|
25
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
26
|
+
}
|
|
27
|
+
return config;
|
|
28
|
+
}, (error) => {
|
|
29
|
+
return Promise.reject(error);
|
|
30
|
+
});
|
|
31
|
+
// Response interceptor - handle errors
|
|
32
|
+
client.interceptors.response.use((response) => response, async (error) => {
|
|
33
|
+
if (error.response?.status === 401) {
|
|
34
|
+
return errorHandler.handle401Error(error, tokenManager, client, maxRetries);
|
|
35
|
+
}
|
|
36
|
+
return Promise.reject(error);
|
|
37
|
+
});
|
|
38
|
+
// Add logout method to client
|
|
39
|
+
const apiClient = client;
|
|
40
|
+
apiClient.logout = async () => {
|
|
41
|
+
try {
|
|
42
|
+
// Attempt to clear server-side session
|
|
43
|
+
// Get token for logout request
|
|
44
|
+
const token = await tokenManager.getToken(false);
|
|
45
|
+
if (token) {
|
|
46
|
+
try {
|
|
47
|
+
// Call logout endpoint to clear server session
|
|
48
|
+
await client.post(logoutEndpoint, {}, {
|
|
49
|
+
headers: {
|
|
50
|
+
Authorization: `Bearer ${token}`,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
// Log but don't fail - graceful degradation
|
|
56
|
+
// If server logout fails, still proceed with client-side logout
|
|
57
|
+
console.warn('Failed to clear server session:', error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// Log but don't fail - graceful degradation
|
|
63
|
+
console.warn('Failed to get token for logout:', error);
|
|
64
|
+
}
|
|
65
|
+
// Always perform client-side logout (even if server logout failed)
|
|
66
|
+
if (onLogout) {
|
|
67
|
+
await onLogout();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
return apiClient;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=apiClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiClient.js","sourceRoot":"","sources":["../../src/core/apiClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiClient.test.d.ts","sourceRoot":"","sources":["../../src/core/apiClient.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { createApiClient } from './apiClient.js';
|
|
3
|
+
describe('createApiClient', () => {
|
|
4
|
+
let mockAuth;
|
|
5
|
+
let mockUser;
|
|
6
|
+
let onLogout;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
onLogout = vi.fn();
|
|
9
|
+
mockUser = {
|
|
10
|
+
getIdToken: vi.fn().mockResolvedValue('test-token'),
|
|
11
|
+
};
|
|
12
|
+
mockAuth = {
|
|
13
|
+
get currentUser() {
|
|
14
|
+
return mockUser;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
it('should create axios instance', () => {
|
|
19
|
+
const client = createApiClient({
|
|
20
|
+
baseURL: '/api',
|
|
21
|
+
auth: mockAuth,
|
|
22
|
+
onLogout,
|
|
23
|
+
});
|
|
24
|
+
expect(client).toBeDefined();
|
|
25
|
+
expect(typeof client.get).toBe('function');
|
|
26
|
+
expect(typeof client.post).toBe('function');
|
|
27
|
+
});
|
|
28
|
+
it('should inject token in request interceptor', async () => {
|
|
29
|
+
const client = createApiClient({
|
|
30
|
+
baseURL: '/api',
|
|
31
|
+
auth: mockAuth,
|
|
32
|
+
onLogout,
|
|
33
|
+
});
|
|
34
|
+
// The interceptor is set up internally
|
|
35
|
+
// We can verify by checking that the client was created
|
|
36
|
+
expect(client).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
it('should use custom timeout when provided', () => {
|
|
39
|
+
const client = createApiClient({
|
|
40
|
+
baseURL: '/api',
|
|
41
|
+
auth: mockAuth,
|
|
42
|
+
onLogout,
|
|
43
|
+
timeout: 60000,
|
|
44
|
+
});
|
|
45
|
+
expect(client).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
it('should use custom maxRetries when provided', () => {
|
|
48
|
+
const client = createApiClient({
|
|
49
|
+
baseURL: '/api',
|
|
50
|
+
auth: mockAuth,
|
|
51
|
+
onLogout,
|
|
52
|
+
maxRetries: 3,
|
|
53
|
+
});
|
|
54
|
+
// maxRetries is used in error handler, which is tested separately
|
|
55
|
+
expect(client).toBeDefined();
|
|
56
|
+
});
|
|
57
|
+
describe('logout', () => {
|
|
58
|
+
it('should have logout method', () => {
|
|
59
|
+
const client = createApiClient({
|
|
60
|
+
baseURL: '/api',
|
|
61
|
+
auth: mockAuth,
|
|
62
|
+
onLogout,
|
|
63
|
+
});
|
|
64
|
+
expect(typeof client.logout).toBe('function');
|
|
65
|
+
});
|
|
66
|
+
it('should call logout endpoint and onLogout callback on success', async () => {
|
|
67
|
+
const client = createApiClient({
|
|
68
|
+
baseURL: '/api',
|
|
69
|
+
auth: mockAuth,
|
|
70
|
+
onLogout,
|
|
71
|
+
logoutEndpoint: '/auth/logout',
|
|
72
|
+
});
|
|
73
|
+
// Mock successful POST request
|
|
74
|
+
const postSpy = vi.spyOn(client, 'post').mockResolvedValue({
|
|
75
|
+
status: 200,
|
|
76
|
+
data: { message: 'Logged out successfully' },
|
|
77
|
+
});
|
|
78
|
+
await client.logout();
|
|
79
|
+
expect(postSpy).toHaveBeenCalledWith('/auth/logout', {}, {
|
|
80
|
+
headers: {
|
|
81
|
+
Authorization: 'Bearer test-token',
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
expect(onLogout).toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
it('should use default logout endpoint if not specified', async () => {
|
|
87
|
+
const client = createApiClient({
|
|
88
|
+
baseURL: '/api',
|
|
89
|
+
auth: mockAuth,
|
|
90
|
+
onLogout,
|
|
91
|
+
});
|
|
92
|
+
const postSpy = vi.spyOn(client, 'post').mockResolvedValue({
|
|
93
|
+
status: 200,
|
|
94
|
+
data: { message: 'Logged out successfully' },
|
|
95
|
+
});
|
|
96
|
+
await client.logout();
|
|
97
|
+
expect(postSpy).toHaveBeenCalledWith('/auth/logout', {}, expect.any(Object));
|
|
98
|
+
});
|
|
99
|
+
it('should call onLogout even if server logout fails (graceful degradation)', async () => {
|
|
100
|
+
const client = createApiClient({
|
|
101
|
+
baseURL: '/api',
|
|
102
|
+
auth: mockAuth,
|
|
103
|
+
onLogout,
|
|
104
|
+
});
|
|
105
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
106
|
+
const postSpy = vi.spyOn(client, 'post').mockRejectedValue(new Error('Network error'));
|
|
107
|
+
await client.logout();
|
|
108
|
+
expect(postSpy).toHaveBeenCalled();
|
|
109
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith('Failed to clear server session:', expect.any(Error));
|
|
110
|
+
expect(onLogout).toHaveBeenCalled();
|
|
111
|
+
consoleWarnSpy.mockRestore();
|
|
112
|
+
});
|
|
113
|
+
it('should call onLogout even if token retrieval fails', async () => {
|
|
114
|
+
const client = createApiClient({
|
|
115
|
+
baseURL: '/api',
|
|
116
|
+
auth: mockAuth,
|
|
117
|
+
onLogout,
|
|
118
|
+
});
|
|
119
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
120
|
+
vi.spyOn(mockUser, 'getIdToken').mockRejectedValue(new Error('Token error'));
|
|
121
|
+
await client.logout();
|
|
122
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith('Failed to get token for logout:', expect.any(Error));
|
|
123
|
+
expect(onLogout).toHaveBeenCalled();
|
|
124
|
+
consoleWarnSpy.mockRestore();
|
|
125
|
+
});
|
|
126
|
+
it('should handle logout when no user is authenticated', async () => {
|
|
127
|
+
const authWithoutUser = {
|
|
128
|
+
get currentUser() {
|
|
129
|
+
return null;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
const client = createApiClient({
|
|
133
|
+
baseURL: '/api',
|
|
134
|
+
auth: authWithoutUser,
|
|
135
|
+
onLogout,
|
|
136
|
+
});
|
|
137
|
+
await client.logout();
|
|
138
|
+
// Should still call onLogout even without token
|
|
139
|
+
expect(onLogout).toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
//# sourceMappingURL=apiClient.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiClient.test.js","sourceRoot":"","sources":["../../src/core/apiClient.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AxiosError, AxiosInstance } from 'axios';
|
|
2
|
+
import { TokenManager } from './tokenManager.js';
|
|
3
|
+
/**
|
|
4
|
+
* Error handler for 401 responses
|
|
5
|
+
* Detects SESSION_EXPIRED vs TOKEN_EXPIRED and handles appropriately
|
|
6
|
+
*/
|
|
7
|
+
export declare class ErrorHandler {
|
|
8
|
+
private onLogout?;
|
|
9
|
+
constructor(onLogout?: () => void | Promise<void>);
|
|
10
|
+
/**
|
|
11
|
+
* Handle logout if callback is provided
|
|
12
|
+
*/
|
|
13
|
+
private performLogout;
|
|
14
|
+
/**
|
|
15
|
+
* Handle 401 errors
|
|
16
|
+
*/
|
|
17
|
+
handle401Error(error: AxiosError, tokenManager: TokenManager, client: AxiosInstance, maxRetries: number): Promise<any>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=errorHandler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/core/errorHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAA8B,MAAM,OAAO,CAAC;AAInF,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,CAA6B;gBAElC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjD;;OAEG;YACW,aAAa;IAM3B;;OAEG;IACG,cAAc,CAClB,KAAK,EAAE,UAAU,EACjB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,GAAG,CAAC;CAyDhB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ERROR_CODES } from '@rapidraptor/auth-shared';
|
|
2
|
+
/**
|
|
3
|
+
* Error handler for 401 responses
|
|
4
|
+
* Detects SESSION_EXPIRED vs TOKEN_EXPIRED and handles appropriately
|
|
5
|
+
*/
|
|
6
|
+
export class ErrorHandler {
|
|
7
|
+
onLogout;
|
|
8
|
+
constructor(onLogout) {
|
|
9
|
+
this.onLogout = onLogout;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Handle logout if callback is provided
|
|
13
|
+
*/
|
|
14
|
+
async performLogout() {
|
|
15
|
+
if (this.onLogout) {
|
|
16
|
+
await this.onLogout();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Handle 401 errors
|
|
21
|
+
*/
|
|
22
|
+
async handle401Error(error, tokenManager, client, maxRetries) {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
+
const errorData = error.response?.data?.error;
|
|
25
|
+
// Step 1: Handle SESSION_EXPIRED
|
|
26
|
+
if (errorData?.code === ERROR_CODES.SESSION_EXPIRED) {
|
|
27
|
+
// Session expired - logout
|
|
28
|
+
await this.performLogout();
|
|
29
|
+
return Promise.reject({
|
|
30
|
+
code: ERROR_CODES.SESSION_EXPIRED,
|
|
31
|
+
sessionExpired: true,
|
|
32
|
+
message: 'Session has expired',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// Step 2: Handle TOKEN_EXPIRED
|
|
36
|
+
if (errorData?.code !== ERROR_CODES.TOKEN_EXPIRED) {
|
|
37
|
+
// Other 401 errors - reject as-is
|
|
38
|
+
return Promise.reject(error);
|
|
39
|
+
}
|
|
40
|
+
// Token expired - refresh and retry
|
|
41
|
+
// Track retry count using a custom property on the error config
|
|
42
|
+
const config = error.config;
|
|
43
|
+
const retryCount = config._retryCount || 0;
|
|
44
|
+
// Step 3: Check if max retries exceeded
|
|
45
|
+
if (retryCount >= maxRetries) {
|
|
46
|
+
// Max retries exceeded - logout
|
|
47
|
+
await this.performLogout();
|
|
48
|
+
return Promise.reject({
|
|
49
|
+
code: ERROR_CODES.TOKEN_EXPIRED,
|
|
50
|
+
message: 'Token refresh failed after retries',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Step 4: Attempt token refresh and retry
|
|
54
|
+
try {
|
|
55
|
+
// Refresh token (may throw if refresh fails)
|
|
56
|
+
const newToken = await tokenManager.refreshToken();
|
|
57
|
+
// Retry original request with new token
|
|
58
|
+
config._retryCount = retryCount + 1;
|
|
59
|
+
if (config.headers) {
|
|
60
|
+
config.headers.Authorization = `Bearer ${newToken}`;
|
|
61
|
+
}
|
|
62
|
+
return client.request(config);
|
|
63
|
+
}
|
|
64
|
+
catch (refreshError) {
|
|
65
|
+
// Token refresh failed - logout
|
|
66
|
+
await this.performLogout();
|
|
67
|
+
return Promise.reject({
|
|
68
|
+
code: ERROR_CODES.TOKEN_EXPIRED,
|
|
69
|
+
message: 'Token refresh failed',
|
|
70
|
+
originalError: refreshError,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=errorHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/core/errorHandler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIvD;;;GAGG;AACH,MAAM,OAAO,YAAY;IACf,QAAQ,CAA8B;IAE9C,YAAY,QAAqC;QAC/C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,aAAa;QACzB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc,CAClB,KAAiB,EACjB,YAA0B,EAC1B,MAAqB,EACrB,UAAkB;QAElB,8DAA8D;QAC9D,MAAM,SAAS,GAAI,KAAK,CAAC,QAAQ,EAAE,IAAY,EAAE,KAAK,CAAC;QAEvD,iCAAiC;QACjC,IAAI,SAAS,EAAE,IAAI,KAAK,WAAW,CAAC,eAAe,EAAE,CAAC;YACpD,2BAA2B;YAC3B,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;YAC3B,OAAO,OAAO,CAAC,MAAM,CAAC;gBACpB,IAAI,EAAE,WAAW,CAAC,eAAe;gBACjC,cAAc,EAAE,IAAI;gBACpB,OAAO,EAAE,qBAAqB;aAC/B,CAAC,CAAC;QACL,CAAC;QAED,+BAA+B;QAC/B,IAAI,SAAS,EAAE,IAAI,KAAK,WAAW,CAAC,aAAa,EAAE,CAAC;YAClD,kCAAkC;YAClC,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAED,oCAAoC;QACpC,gEAAgE;QAChE,MAAM,MAAM,GAAG,KAAK,CAAC,MAA+D,CAAC;QACrF,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,IAAI,CAAC,CAAC;QAE3C,wCAAwC;QACxC,IAAI,UAAU,IAAI,UAAU,EAAE,CAAC;YAC7B,gCAAgC;YAChC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;YAC3B,OAAO,OAAO,CAAC,MAAM,CAAC;gBACpB,IAAI,EAAE,WAAW,CAAC,aAAa;gBAC/B,OAAO,EAAE,oCAAoC;aAC9C,CAAC,CAAC;QACL,CAAC;QAED,0CAA0C;QAC1C,IAAI,CAAC;YACH,6CAA6C;YAC7C,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,YAAY,EAAE,CAAC;YAEnD,wCAAwC;YACxC,MAAM,CAAC,WAAW,GAAG,UAAU,GAAG,CAAC,CAAC;YACpC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,MAAM,CAAC,OAAO,CAAC,aAAa,GAAG,UAAU,QAAQ,EAAE,CAAC;YACtD,CAAC;YACD,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,YAAY,EAAE,CAAC;YACtB,gCAAgC;YAChC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;YAC3B,OAAO,OAAO,CAAC,MAAM,CAAC;gBACpB,IAAI,EAAE,WAAW,CAAC,aAAa;gBAC/B,OAAO,EAAE,sBAAsB;gBAC/B,aAAa,EAAE,YAAY;aAC5B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorHandler.test.d.ts","sourceRoot":"","sources":["../../src/core/errorHandler.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { ErrorHandler } from './errorHandler.js';
|
|
3
|
+
import { ERROR_CODES } from '@rapidraptor/auth-shared';
|
|
4
|
+
describe('ErrorHandler', () => {
|
|
5
|
+
let errorHandler;
|
|
6
|
+
let tokenManager;
|
|
7
|
+
let mockClient;
|
|
8
|
+
let onLogout;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
onLogout = vi.fn();
|
|
11
|
+
errorHandler = new ErrorHandler(onLogout);
|
|
12
|
+
// Mock TokenManager
|
|
13
|
+
tokenManager = {
|
|
14
|
+
refreshToken: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
// Mock Axios client
|
|
17
|
+
mockClient = {
|
|
18
|
+
request: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
describe('handle401Error', () => {
|
|
22
|
+
describe('SESSION_EXPIRED', () => {
|
|
23
|
+
it('should call onLogout and reject with SESSION_EXPIRED', async () => {
|
|
24
|
+
const error = {
|
|
25
|
+
response: {
|
|
26
|
+
status: 401,
|
|
27
|
+
data: {
|
|
28
|
+
error: {
|
|
29
|
+
code: ERROR_CODES.SESSION_EXPIRED,
|
|
30
|
+
message: 'Session expired',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
await expect(errorHandler.handle401Error(error, tokenManager, mockClient, 1)).rejects.toMatchObject({
|
|
36
|
+
code: ERROR_CODES.SESSION_EXPIRED,
|
|
37
|
+
sessionExpired: true,
|
|
38
|
+
});
|
|
39
|
+
expect(onLogout).toHaveBeenCalledOnce();
|
|
40
|
+
expect(tokenManager.refreshToken).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('TOKEN_EXPIRED', () => {
|
|
44
|
+
it('should refresh token and retry request on first attempt', async () => {
|
|
45
|
+
const newToken = 'new-token';
|
|
46
|
+
tokenManager.refreshToken.mockResolvedValue(newToken);
|
|
47
|
+
mockClient.request.mockResolvedValue({ data: 'success' });
|
|
48
|
+
const error = {
|
|
49
|
+
response: {
|
|
50
|
+
status: 401,
|
|
51
|
+
data: {
|
|
52
|
+
error: {
|
|
53
|
+
code: ERROR_CODES.TOKEN_EXPIRED,
|
|
54
|
+
message: 'Token expired',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
config: {
|
|
59
|
+
headers: {},
|
|
60
|
+
_retryCount: 0,
|
|
61
|
+
},
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
+
};
|
|
64
|
+
const result = await errorHandler.handle401Error(error, tokenManager, mockClient, 1);
|
|
65
|
+
expect(tokenManager.refreshToken).toHaveBeenCalledOnce();
|
|
66
|
+
expect(mockClient.request).toHaveBeenCalledWith(expect.objectContaining({
|
|
67
|
+
headers: expect.objectContaining({
|
|
68
|
+
Authorization: `Bearer ${newToken}`,
|
|
69
|
+
}),
|
|
70
|
+
_retryCount: 1,
|
|
71
|
+
}));
|
|
72
|
+
expect(result).toEqual({ data: 'success' });
|
|
73
|
+
expect(onLogout).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
it('should call onLogout when max retries exceeded', async () => {
|
|
76
|
+
const error = {
|
|
77
|
+
response: {
|
|
78
|
+
status: 401,
|
|
79
|
+
data: {
|
|
80
|
+
error: {
|
|
81
|
+
code: ERROR_CODES.TOKEN_EXPIRED,
|
|
82
|
+
message: 'Token expired',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
config: {
|
|
87
|
+
headers: {},
|
|
88
|
+
_retryCount: 1, // Already at max retries
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
await expect(errorHandler.handle401Error(error, tokenManager, mockClient, 1)).rejects.toMatchObject({
|
|
92
|
+
code: ERROR_CODES.TOKEN_EXPIRED,
|
|
93
|
+
});
|
|
94
|
+
expect(onLogout).toHaveBeenCalledOnce();
|
|
95
|
+
expect(tokenManager.refreshToken).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
it('should call onLogout when token refresh fails', async () => {
|
|
98
|
+
const refreshError = new Error('Refresh failed');
|
|
99
|
+
tokenManager.refreshToken.mockRejectedValue(refreshError);
|
|
100
|
+
const error = {
|
|
101
|
+
response: {
|
|
102
|
+
status: 401,
|
|
103
|
+
data: {
|
|
104
|
+
error: {
|
|
105
|
+
code: ERROR_CODES.TOKEN_EXPIRED,
|
|
106
|
+
message: 'Token expired',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
config: {
|
|
111
|
+
headers: {},
|
|
112
|
+
_retryCount: 0,
|
|
113
|
+
},
|
|
114
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
115
|
+
};
|
|
116
|
+
await expect(errorHandler.handle401Error(error, tokenManager, mockClient, 1)).rejects.toMatchObject({
|
|
117
|
+
code: ERROR_CODES.TOKEN_EXPIRED,
|
|
118
|
+
originalError: refreshError,
|
|
119
|
+
});
|
|
120
|
+
expect(onLogout).toHaveBeenCalledOnce();
|
|
121
|
+
expect(mockClient.request).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('other 401 errors', () => {
|
|
125
|
+
it('should reject with original error', async () => {
|
|
126
|
+
const error = {
|
|
127
|
+
response: {
|
|
128
|
+
status: 401,
|
|
129
|
+
data: {
|
|
130
|
+
error: {
|
|
131
|
+
code: ERROR_CODES.AUTH_FAILED,
|
|
132
|
+
message: 'Authentication failed',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
await expect(errorHandler.handle401Error(error, tokenManager, mockClient, 1)).rejects.toBe(error);
|
|
138
|
+
expect(onLogout).not.toHaveBeenCalled();
|
|
139
|
+
expect(tokenManager.refreshToken).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
//# sourceMappingURL=errorHandler.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorHandler.test.js","sourceRoot":"","sources":["../../src/core/errorHandler.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request queue for token refresh
|
|
3
|
+
* Queues requests during token refresh and flushes them with the new token
|
|
4
|
+
*/
|
|
5
|
+
export declare class RequestQueue {
|
|
6
|
+
private queuedRequests;
|
|
7
|
+
/**
|
|
8
|
+
* Queue a request to wait for token refresh
|
|
9
|
+
*/
|
|
10
|
+
queue(resolve: (token: string) => void, reject: (error: unknown) => void): void;
|
|
11
|
+
/**
|
|
12
|
+
* Flush all queued requests with new token
|
|
13
|
+
*/
|
|
14
|
+
flush(token: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Reject all queued requests (on refresh failure)
|
|
17
|
+
*/
|
|
18
|
+
rejectAll(error: unknown): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Get current queue size
|
|
21
|
+
*/
|
|
22
|
+
getSize(): number;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=requestQueue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestQueue.d.ts","sourceRoot":"","sources":["../../src/core/requestQueue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,cAAc,CAGd;IAER;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI;IAI/E;;OAEG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMzC;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9C;;OAEG;IACH,OAAO,IAAI,MAAM;CAGlB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request queue for token refresh
|
|
3
|
+
* Queues requests during token refresh and flushes them with the new token
|
|
4
|
+
*/
|
|
5
|
+
export class RequestQueue {
|
|
6
|
+
queuedRequests = [];
|
|
7
|
+
/**
|
|
8
|
+
* Queue a request to wait for token refresh
|
|
9
|
+
*/
|
|
10
|
+
queue(resolve, reject) {
|
|
11
|
+
this.queuedRequests.push({ resolve, reject });
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Flush all queued requests with new token
|
|
15
|
+
*/
|
|
16
|
+
async flush(token) {
|
|
17
|
+
const requests = [...this.queuedRequests];
|
|
18
|
+
this.queuedRequests = [];
|
|
19
|
+
requests.forEach(({ resolve }) => resolve(token));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Reject all queued requests (on refresh failure)
|
|
23
|
+
*/
|
|
24
|
+
async rejectAll(error) {
|
|
25
|
+
const requests = [...this.queuedRequests];
|
|
26
|
+
this.queuedRequests = [];
|
|
27
|
+
requests.forEach(({ reject }) => reject(error));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get current queue size
|
|
31
|
+
*/
|
|
32
|
+
getSize() {
|
|
33
|
+
return this.queuedRequests.length;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=requestQueue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestQueue.js","sourceRoot":"","sources":["../../src/core/requestQueue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,OAAO,YAAY;IACf,cAAc,GAGjB,EAAE,CAAC;IAER;;OAEG;IACH,KAAK,CAAC,OAAgC,EAAE,MAAgC;QACtE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAChD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK,CAAC,KAAa;QACvB,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;QAC1C,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;QACzB,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACpD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS,CAAC,KAAc;QAC5B,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;QAC1C,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;QACzB,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,OAAO;QACL,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;IACpC,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestQueue.test.d.ts","sourceRoot":"","sources":["../../src/core/requestQueue.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { RequestQueue } from './requestQueue.js';
|
|
3
|
+
describe('RequestQueue', () => {
|
|
4
|
+
let queue;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
queue = new RequestQueue();
|
|
7
|
+
});
|
|
8
|
+
describe('queue', () => {
|
|
9
|
+
it('should queue requests', () => {
|
|
10
|
+
const resolve = vi.fn();
|
|
11
|
+
const reject = vi.fn();
|
|
12
|
+
queue.queue(resolve, reject);
|
|
13
|
+
expect(queue.getSize()).toBe(1);
|
|
14
|
+
});
|
|
15
|
+
it('should queue multiple requests', () => {
|
|
16
|
+
queue.queue(vi.fn(), vi.fn());
|
|
17
|
+
queue.queue(vi.fn(), vi.fn());
|
|
18
|
+
expect(queue.getSize()).toBe(2);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe('flush', () => {
|
|
22
|
+
it('should flush all queued requests with token', async () => {
|
|
23
|
+
const resolve1 = vi.fn();
|
|
24
|
+
const resolve2 = vi.fn();
|
|
25
|
+
const reject1 = vi.fn();
|
|
26
|
+
const reject2 = vi.fn();
|
|
27
|
+
queue.queue(resolve1, reject1);
|
|
28
|
+
queue.queue(resolve2, reject2);
|
|
29
|
+
const token = 'new-token';
|
|
30
|
+
await queue.flush(token);
|
|
31
|
+
expect(resolve1).toHaveBeenCalledWith(token);
|
|
32
|
+
expect(resolve2).toHaveBeenCalledWith(token);
|
|
33
|
+
expect(reject1).not.toHaveBeenCalled();
|
|
34
|
+
expect(reject2).not.toHaveBeenCalled();
|
|
35
|
+
expect(queue.getSize()).toBe(0);
|
|
36
|
+
});
|
|
37
|
+
it('should clear queue after flush', async () => {
|
|
38
|
+
queue.queue(vi.fn(), vi.fn());
|
|
39
|
+
await queue.flush('token');
|
|
40
|
+
expect(queue.getSize()).toBe(0);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('rejectAll', () => {
|
|
44
|
+
it('should reject all queued requests with error', async () => {
|
|
45
|
+
const resolve1 = vi.fn();
|
|
46
|
+
const resolve2 = vi.fn();
|
|
47
|
+
const reject1 = vi.fn();
|
|
48
|
+
const reject2 = vi.fn();
|
|
49
|
+
queue.queue(resolve1, reject1);
|
|
50
|
+
queue.queue(resolve2, reject2);
|
|
51
|
+
const error = new Error('Refresh failed');
|
|
52
|
+
await queue.rejectAll(error);
|
|
53
|
+
expect(reject1).toHaveBeenCalledWith(error);
|
|
54
|
+
expect(reject2).toHaveBeenCalledWith(error);
|
|
55
|
+
expect(resolve1).not.toHaveBeenCalled();
|
|
56
|
+
expect(resolve2).not.toHaveBeenCalled();
|
|
57
|
+
expect(queue.getSize()).toBe(0);
|
|
58
|
+
});
|
|
59
|
+
it('should clear queue after rejectAll', async () => {
|
|
60
|
+
queue.queue(vi.fn(), vi.fn());
|
|
61
|
+
await queue.rejectAll(new Error('test'));
|
|
62
|
+
expect(queue.getSize()).toBe(0);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('getSize', () => {
|
|
66
|
+
it('should return 0 for empty queue', () => {
|
|
67
|
+
expect(queue.getSize()).toBe(0);
|
|
68
|
+
});
|
|
69
|
+
it('should return correct size', () => {
|
|
70
|
+
queue.queue(vi.fn(), vi.fn());
|
|
71
|
+
queue.queue(vi.fn(), vi.fn());
|
|
72
|
+
expect(queue.getSize()).toBe(2);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
//# sourceMappingURL=requestQueue.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestQueue.test.js","sourceRoot":"","sources":["../../src/core/requestQueue.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { FirebaseAuth } from '@rapidraptor/auth-shared';
|
|
2
|
+
/**
|
|
3
|
+
* Token manager with request queuing during refresh
|
|
4
|
+
*/
|
|
5
|
+
export declare class TokenManager {
|
|
6
|
+
private auth;
|
|
7
|
+
private refreshPromise;
|
|
8
|
+
private requestQueue;
|
|
9
|
+
constructor(auth: FirebaseAuth);
|
|
10
|
+
/**
|
|
11
|
+
* Get token (with optional force refresh)
|
|
12
|
+
*/
|
|
13
|
+
getToken(forceRefresh?: boolean): Promise<string | null>;
|
|
14
|
+
/**
|
|
15
|
+
* Refresh token with queuing
|
|
16
|
+
* If refresh is already in progress, returns the same promise
|
|
17
|
+
*/
|
|
18
|
+
refreshToken(): Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=tokenManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenManager.d.ts","sourceRoot":"","sources":["../../src/core/tokenManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,cAAc,CAAgC;IACtD,OAAO,CAAC,YAAY,CAAe;gBAEvB,IAAI,EAAE,YAAY;IAK9B;;OAEG;IACG,QAAQ,CAAC,YAAY,GAAE,OAAe,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASrE;;;OAGG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;CAgCtC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { RequestQueue } from './requestQueue.js';
|
|
2
|
+
/**
|
|
3
|
+
* Token manager with request queuing during refresh
|
|
4
|
+
*/
|
|
5
|
+
export class TokenManager {
|
|
6
|
+
auth;
|
|
7
|
+
refreshPromise = null;
|
|
8
|
+
requestQueue;
|
|
9
|
+
constructor(auth) {
|
|
10
|
+
this.auth = auth;
|
|
11
|
+
this.requestQueue = new RequestQueue();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get token (with optional force refresh)
|
|
15
|
+
*/
|
|
16
|
+
async getToken(forceRefresh = false) {
|
|
17
|
+
const user = this.auth.currentUser;
|
|
18
|
+
if (!user) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return user.getIdToken(forceRefresh);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Refresh token with queuing
|
|
25
|
+
* If refresh is already in progress, returns the same promise
|
|
26
|
+
*/
|
|
27
|
+
async refreshToken() {
|
|
28
|
+
// If refresh already in progress, return the existing promise
|
|
29
|
+
if (this.refreshPromise) {
|
|
30
|
+
return this.refreshPromise;
|
|
31
|
+
}
|
|
32
|
+
// Start refresh
|
|
33
|
+
this.refreshPromise = (async () => {
|
|
34
|
+
try {
|
|
35
|
+
// Force token refresh
|
|
36
|
+
const user = this.auth.currentUser;
|
|
37
|
+
if (!user) {
|
|
38
|
+
throw new Error('No user authenticated');
|
|
39
|
+
}
|
|
40
|
+
const token = await user.getIdToken(true);
|
|
41
|
+
// Flush queued requests with new token
|
|
42
|
+
await this.requestQueue.flush(token);
|
|
43
|
+
return token;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
// Token refresh failed - reject all queued requests
|
|
47
|
+
await this.requestQueue.rejectAll(error);
|
|
48
|
+
throw error; // Re-throw to trigger logout in error handler
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
// Clear refresh promise
|
|
52
|
+
this.refreshPromise = null;
|
|
53
|
+
}
|
|
54
|
+
})();
|
|
55
|
+
return this.refreshPromise;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=tokenManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenManager.js","sourceRoot":"","sources":["../../src/core/tokenManager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;GAEG;AACH,MAAM,OAAO,YAAY;IACf,IAAI,CAAe;IACnB,cAAc,GAA2B,IAAI,CAAC;IAC9C,YAAY,CAAe;IAEnC,YAAY,IAAkB;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,YAAY,GAAG,IAAI,YAAY,EAAE,CAAC;IACzC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,eAAwB,KAAK;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC;QACnC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IACvC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,YAAY;QAChB,8DAA8D;QAC9D,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,cAAc,CAAC;QAC7B,CAAC;QAED,gBAAgB;QAChB,IAAI,CAAC,cAAc,GAAG,CAAC,KAAK,IAAI,EAAE;YAChC,IAAI,CAAC;gBACH,sBAAsB;gBACtB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC;gBACnC,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;gBAC3C,CAAC;gBACD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;gBAE1C,uCAAuC;gBACvC,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAErC,OAAO,KAAK,CAAC;YACf,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,oDAAoD;gBACpD,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;gBACzC,MAAM,KAAK,CAAC,CAAC,8CAA8C;YAC7D,CAAC;oBAAS,CAAC;gBACT,wBAAwB;gBACxB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;YAC7B,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;QAEL,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenManager.test.d.ts","sourceRoot":"","sources":["../../src/core/tokenManager.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { TokenManager } from './tokenManager.js';
|
|
3
|
+
describe('TokenManager', () => {
|
|
4
|
+
let tokenManager;
|
|
5
|
+
let mockAuth;
|
|
6
|
+
let mockUser;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
mockUser = {
|
|
9
|
+
getIdToken: vi.fn(),
|
|
10
|
+
};
|
|
11
|
+
// Use a getter/setter to allow changing currentUser in tests
|
|
12
|
+
let currentUserValue = mockUser;
|
|
13
|
+
mockAuth = {
|
|
14
|
+
get currentUser() {
|
|
15
|
+
return currentUserValue;
|
|
16
|
+
},
|
|
17
|
+
set currentUser(value) {
|
|
18
|
+
currentUserValue = value;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
tokenManager = new TokenManager(mockAuth);
|
|
22
|
+
});
|
|
23
|
+
describe('getToken', () => {
|
|
24
|
+
it('should return null when no user is authenticated', async () => {
|
|
25
|
+
mockAuth.currentUser = null;
|
|
26
|
+
const token = await tokenManager.getToken();
|
|
27
|
+
expect(token).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
it('should get token from current user', async () => {
|
|
30
|
+
const expectedToken = 'test-token';
|
|
31
|
+
mockUser.getIdToken.mockResolvedValue(expectedToken);
|
|
32
|
+
const token = await tokenManager.getToken();
|
|
33
|
+
expect(token).toBe(expectedToken);
|
|
34
|
+
expect(mockUser.getIdToken).toHaveBeenCalledWith(false);
|
|
35
|
+
});
|
|
36
|
+
it('should force refresh when requested', async () => {
|
|
37
|
+
const expectedToken = 'refreshed-token';
|
|
38
|
+
mockUser.getIdToken.mockResolvedValue(expectedToken);
|
|
39
|
+
const token = await tokenManager.getToken(true);
|
|
40
|
+
expect(token).toBe(expectedToken);
|
|
41
|
+
expect(mockUser.getIdToken).toHaveBeenCalledWith(true);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe('refreshToken', () => {
|
|
45
|
+
it('should refresh token and return new token', async () => {
|
|
46
|
+
const newToken = 'new-token';
|
|
47
|
+
mockUser.getIdToken.mockResolvedValue(newToken);
|
|
48
|
+
const token = await tokenManager.refreshToken();
|
|
49
|
+
expect(token).toBe(newToken);
|
|
50
|
+
expect(mockUser.getIdToken).toHaveBeenCalledWith(true);
|
|
51
|
+
});
|
|
52
|
+
it('should throw error when no user is authenticated', async () => {
|
|
53
|
+
mockAuth.currentUser = null;
|
|
54
|
+
await expect(tokenManager.refreshToken()).rejects.toThrow('No user authenticated');
|
|
55
|
+
});
|
|
56
|
+
it('should handle concurrent refresh requests', async () => {
|
|
57
|
+
const newToken = 'new-token';
|
|
58
|
+
mockUser.getIdToken.mockResolvedValue(newToken);
|
|
59
|
+
// Start two concurrent refresh requests
|
|
60
|
+
const promise1 = tokenManager.refreshToken();
|
|
61
|
+
const promise2 = tokenManager.refreshToken();
|
|
62
|
+
const [token1, token2] = await Promise.all([promise1, promise2]);
|
|
63
|
+
// Both should get the same token
|
|
64
|
+
expect(token1).toBe(newToken);
|
|
65
|
+
expect(token2).toBe(newToken);
|
|
66
|
+
// getIdToken should only be called once (not twice)
|
|
67
|
+
expect(mockUser.getIdToken).toHaveBeenCalledTimes(1);
|
|
68
|
+
});
|
|
69
|
+
it('should clear refresh promise after completion', async () => {
|
|
70
|
+
const newToken = 'new-token';
|
|
71
|
+
mockUser.getIdToken.mockResolvedValue(newToken);
|
|
72
|
+
await tokenManager.refreshToken();
|
|
73
|
+
// Second call should trigger a new refresh
|
|
74
|
+
await tokenManager.refreshToken();
|
|
75
|
+
expect(mockUser.getIdToken).toHaveBeenCalledTimes(2);
|
|
76
|
+
});
|
|
77
|
+
it('should clear refresh promise after error', async () => {
|
|
78
|
+
const error = new Error('Refresh failed');
|
|
79
|
+
mockUser.getIdToken.mockRejectedValue(error);
|
|
80
|
+
await expect(tokenManager.refreshToken()).rejects.toThrow('Refresh failed');
|
|
81
|
+
// Second call should trigger a new refresh attempt
|
|
82
|
+
mockUser.getIdToken.mockResolvedValue('new-token');
|
|
83
|
+
const token = await tokenManager.refreshToken();
|
|
84
|
+
expect(token).toBe('new-token');
|
|
85
|
+
expect(mockUser.getIdToken).toHaveBeenCalledTimes(2);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
//# sourceMappingURL=tokenManager.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenManager.test.js","sourceRoot":"","sources":["../../src/core/tokenManager.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createApiClient } from './core/apiClient.js';
|
|
2
|
+
export type { ApiClient } from './core/apiClient.js';
|
|
3
|
+
export { TokenManager } from './core/tokenManager.js';
|
|
4
|
+
export { ErrorHandler } from './core/errorHandler.js';
|
|
5
|
+
export { RequestQueue } from './core/requestQueue.js';
|
|
6
|
+
export type { SessionInfo, ErrorResponse, ErrorCode, ApiClientConfig, } from '@rapidraptor/auth-shared';
|
|
7
|
+
export { ERROR_CODES, DEFAULTS } from '@rapidraptor/auth-shared';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAGtD,YAAY,EACV,WAAW,EACX,aAAa,EACb,SAAS,EACT,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Core API client
|
|
2
|
+
export { createApiClient } from './core/apiClient.js';
|
|
3
|
+
export { TokenManager } from './core/tokenManager.js';
|
|
4
|
+
export { ErrorHandler } from './core/errorHandler.js';
|
|
5
|
+
export { RequestQueue } from './core/requestQueue.js';
|
|
6
|
+
export { ERROR_CODES, DEFAULTS } from '@rapidraptor/auth-shared';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kBAAkB;AAClB,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAUtD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rapidraptor/auth-client",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Client-side authentication library for React applications",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"test": "vitest"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"axios": "^1.0.0",
|
|
26
|
+
"firebase": "^11.0.0"
|
|
27
|
+
},
|
|
28
|
+
"optionalPeerDependencies": {
|
|
29
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@rapidraptor/auth-shared": "file:../shared"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.12.12",
|
|
36
|
+
"typescript": "^5.4.5",
|
|
37
|
+
"vitest": "^1.6.1"
|
|
38
|
+
}
|
|
39
|
+
}
|