@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.
Files changed (37) hide show
  1. package/dist/core/apiClient.d.ts +13 -0
  2. package/dist/core/apiClient.d.ts.map +1 -0
  3. package/dist/core/apiClient.js +72 -0
  4. package/dist/core/apiClient.js.map +1 -0
  5. package/dist/core/apiClient.test.d.ts +2 -0
  6. package/dist/core/apiClient.test.d.ts.map +1 -0
  7. package/dist/core/apiClient.test.js +143 -0
  8. package/dist/core/apiClient.test.js.map +1 -0
  9. package/dist/core/errorHandler.d.ts +19 -0
  10. package/dist/core/errorHandler.d.ts.map +1 -0
  11. package/dist/core/errorHandler.js +75 -0
  12. package/dist/core/errorHandler.js.map +1 -0
  13. package/dist/core/errorHandler.test.d.ts +2 -0
  14. package/dist/core/errorHandler.test.d.ts.map +1 -0
  15. package/dist/core/errorHandler.test.js +144 -0
  16. package/dist/core/errorHandler.test.js.map +1 -0
  17. package/dist/core/requestQueue.d.ts +24 -0
  18. package/dist/core/requestQueue.d.ts.map +1 -0
  19. package/dist/core/requestQueue.js +36 -0
  20. package/dist/core/requestQueue.js.map +1 -0
  21. package/dist/core/requestQueue.test.d.ts +2 -0
  22. package/dist/core/requestQueue.test.d.ts.map +1 -0
  23. package/dist/core/requestQueue.test.js +76 -0
  24. package/dist/core/requestQueue.test.js.map +1 -0
  25. package/dist/core/tokenManager.d.ts +20 -0
  26. package/dist/core/tokenManager.d.ts.map +1 -0
  27. package/dist/core/tokenManager.js +58 -0
  28. package/dist/core/tokenManager.js.map +1 -0
  29. package/dist/core/tokenManager.test.d.ts +2 -0
  30. package/dist/core/tokenManager.test.d.ts.map +1 -0
  31. package/dist/core/tokenManager.test.js +89 -0
  32. package/dist/core/tokenManager.test.js.map +1 -0
  33. package/dist/index.d.ts +8 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +7 -0
  36. package/dist/index.js.map +1 -0
  37. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=apiClient.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=errorHandler.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=requestQueue.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tokenManager.test.d.ts.map
@@ -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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"}
@@ -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
+ }