@cranberry-money/shared-services 1.0.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 (50) hide show
  1. package/README.md +288 -0
  2. package/dist/adapters/MobileApiClient.d.ts +68 -0
  3. package/dist/adapters/MobileApiClient.d.ts.map +1 -0
  4. package/dist/adapters/MobileApiClient.js +240 -0
  5. package/dist/adapters/MobileTokenStorage.d.ts +43 -0
  6. package/dist/adapters/MobileTokenStorage.d.ts.map +1 -0
  7. package/dist/adapters/MobileTokenStorage.js +128 -0
  8. package/dist/adapters/WebApiClient.d.ts +28 -0
  9. package/dist/adapters/WebApiClient.d.ts.map +1 -0
  10. package/dist/adapters/WebApiClient.js +119 -0
  11. package/dist/adapters/WebTokenStorage.d.ts +38 -0
  12. package/dist/adapters/WebTokenStorage.d.ts.map +1 -0
  13. package/dist/adapters/WebTokenStorage.js +86 -0
  14. package/dist/auth/AuthManager.d.ts +81 -0
  15. package/dist/auth/AuthManager.d.ts.map +1 -0
  16. package/dist/auth/AuthManager.js +223 -0
  17. package/dist/auth/createAuthManager.d.ts +63 -0
  18. package/dist/auth/createAuthManager.d.ts.map +1 -0
  19. package/dist/auth/createAuthManager.js +103 -0
  20. package/dist/auth/useAuthManager.d.ts +66 -0
  21. package/dist/auth/useAuthManager.d.ts.map +1 -0
  22. package/dist/auth/useAuthManager.js +133 -0
  23. package/dist/core/BaseApiClient.d.ts +82 -0
  24. package/dist/core/BaseApiClient.d.ts.map +1 -0
  25. package/dist/core/BaseApiClient.js +89 -0
  26. package/dist/core/TokenStorage.d.ts +45 -0
  27. package/dist/core/TokenStorage.d.ts.map +1 -0
  28. package/dist/core/TokenStorage.js +23 -0
  29. package/dist/index.d.ts +19 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +23 -0
  32. package/dist/query/QueryClient.d.ts +82 -0
  33. package/dist/query/QueryClient.d.ts.map +1 -0
  34. package/dist/query/QueryClient.js +136 -0
  35. package/dist/query/useAuth.d.ts +64 -0
  36. package/dist/query/useAuth.d.ts.map +1 -0
  37. package/dist/query/useAuth.js +144 -0
  38. package/dist/query/usePortfolios.d.ts +79 -0
  39. package/dist/query/usePortfolios.d.ts.map +1 -0
  40. package/dist/query/usePortfolios.js +172 -0
  41. package/dist/services/AuthService.d.ts +75 -0
  42. package/dist/services/AuthService.d.ts.map +1 -0
  43. package/dist/services/AuthService.js +83 -0
  44. package/dist/services/BaseService.d.ts +48 -0
  45. package/dist/services/BaseService.d.ts.map +1 -0
  46. package/dist/services/BaseService.js +51 -0
  47. package/dist/services/PortfolioService.d.ts +100 -0
  48. package/dist/services/PortfolioService.d.ts.map +1 -0
  49. package/dist/services/PortfolioService.js +68 -0
  50. package/package.json +56 -0
package/README.md ADDED
@@ -0,0 +1,288 @@
1
+ # @myportfolio/shared-services
2
+
3
+ Shared API services and client abstractions for the MyPortfolio platform, supporting both web (Blueberry) and mobile (Blackberry) applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @myportfolio/shared-services
9
+ ```
10
+
11
+ ## Core Architecture
12
+
13
+ This package provides a platform-agnostic API client architecture with:
14
+
15
+ - **Token Storage Abstraction**: Secure token management across platforms
16
+ - **Base API Client**: Common HTTP request interface
17
+ - **Platform Adapters**: Web (cookies) and Mobile (secure storage) implementations
18
+ - **Offline Support**: Request queuing and retry logic for mobile
19
+ - **Authentication Handling**: Automatic token refresh and error handling
20
+
21
+ ## Usage
22
+
23
+ ### Web Implementation
24
+
25
+ ```typescript
26
+ import { WebApiClient, WebTokenStorage } from '@myportfolio/shared-services';
27
+
28
+ // Create web API client with cookie-based auth
29
+ const apiClient = new WebApiClient({
30
+ baseURL: 'https://api.myportfolio.com',
31
+ withCredentials: true, // Enable cookies
32
+ });
33
+
34
+ // Set up token storage (optional for cookie-based auth)
35
+ const tokenStorage = new WebTokenStorage();
36
+ apiClient.setTokenStorage(tokenStorage);
37
+
38
+ // Make requests
39
+ const response = await apiClient.get('/auth/profile');
40
+ const data = await apiClient.post('/portfolios', { name: 'My Portfolio' });
41
+ ```
42
+
43
+ ### Mobile Implementation
44
+
45
+ ```typescript
46
+ import {
47
+ MobileApiClient,
48
+ MobileTokenStorage,
49
+ createExpoSecureStoreAdapter
50
+ } from '@myportfolio/shared-services';
51
+
52
+ // Create mobile API client with secure token storage
53
+ const apiClient = new MobileApiClient({
54
+ baseURL: 'https://api.myportfolio.com',
55
+ retryAttempts: 3,
56
+ offlineQueueEnabled: true,
57
+ });
58
+
59
+ // Set up secure token storage
60
+ const secureStore = createExpoSecureStoreAdapter();
61
+ const tokenStorage = new MobileTokenStorage(secureStore);
62
+ apiClient.setTokenStorage(tokenStorage);
63
+
64
+ // Handle network state
65
+ apiClient.setNetworkState(isOnline);
66
+
67
+ // Make requests with automatic retry and offline queueing
68
+ const response = await apiClient.get('/auth/profile');
69
+ ```
70
+
71
+ ## Core Components
72
+
73
+ ### TokenStorage Interface
74
+
75
+ Provides secure, cross-platform token management:
76
+
77
+ ```typescript
78
+ interface TokenStorage {
79
+ storeTokens(tokens: { access: string; refresh: string }): Promise<void>;
80
+ retrieveTokens(): Promise<{ access: string; refresh: string } | null>;
81
+ clearTokens(): Promise<void>;
82
+ hasTokens(): Promise<boolean>;
83
+ }
84
+ ```
85
+
86
+ **Implementations:**
87
+ - `WebTokenStorage` - HTTP-only cookies (web)
88
+ - `MobileTokenStorage` - Secure device storage (mobile)
89
+
90
+ ### BaseApiClient
91
+
92
+ Abstract base class providing common API functionality:
93
+
94
+ ```typescript
95
+ abstract class BaseApiClient {
96
+ // HTTP methods
97
+ get<T>(url: string, params?: Record<string, unknown>): Promise<ApiResponse<T>>;
98
+ post<T>(url: string, data?: unknown): Promise<ApiResponse<T>>;
99
+ put<T>(url: string, data?: unknown): Promise<ApiResponse<T>>;
100
+ patch<T>(url: string, data?: unknown): Promise<ApiResponse<T>>;
101
+ delete<T>(url: string): Promise<ApiResponse<T>>;
102
+
103
+ // Configuration
104
+ setTokenStorage(storage: TokenStorage): void;
105
+
106
+ // Abstract methods (implemented by platform adapters)
107
+ abstract request<T>(config: RequestConfig): Promise<ApiResponse<T>>;
108
+ }
109
+ ```
110
+
111
+ ## Platform Adapters
112
+
113
+ ### Web Adapter (`WebApiClient`)
114
+
115
+ Optimized for browser environments:
116
+
117
+ - **Cookie-based authentication** with `withCredentials` support
118
+ - **CORS handling** for cross-origin requests
119
+ - **Fetch API** based implementation
120
+ - **Content-type detection** for response parsing
121
+ - **Token refresh** via `/auth/refresh` endpoint
122
+
123
+ ### Mobile Adapter (`MobileApiClient`)
124
+
125
+ Enhanced for React Native with mobile-specific features:
126
+
127
+ - **Retry logic** with exponential backoff
128
+ - **Offline queue** for when network is unavailable
129
+ - **Network state awareness** with automatic queue processing
130
+ - **Timeout handling** with AbortSignal
131
+ - **Secure token storage** integration
132
+ - **Battery-conscious** request batching
133
+
134
+ ## Authentication Flow
135
+
136
+ ### Web (Cookie-based)
137
+ 1. User signs in → Server sets HTTP-only cookies
138
+ 2. API requests automatically include cookies
139
+ 3. Token refresh handled by server cookie renewal
140
+ 4. Sign out clears cookies on server
141
+
142
+ ### Mobile (Token-based)
143
+ 1. User signs in → Tokens stored in secure storage
144
+ 2. API requests include `Authorization: Bearer <token>` header
145
+ 3. Automatic token refresh with secure storage update
146
+ 4. Sign out clears tokens from secure storage
147
+
148
+ ## Error Handling
149
+
150
+ Consistent error handling across platforms:
151
+
152
+ ```typescript
153
+ interface ApiError {
154
+ message: string;
155
+ status?: number;
156
+ code?: string;
157
+ data?: unknown;
158
+ }
159
+
160
+ // Error codes
161
+ 'NETWORK_ERROR' // Network connectivity issues
162
+ 'TIMEOUT_ERROR' // Request timeout
163
+ 'HTTP_ERROR' // HTTP status errors (4xx, 5xx)
164
+ 'UNAUTHORIZED' // Authentication failure
165
+ 'UNEXPECTED_ERROR' // Unknown errors
166
+ ```
167
+
168
+ ## Offline Support (Mobile)
169
+
170
+ ```typescript
171
+ // Enable offline queue
172
+ const client = new MobileApiClient({
173
+ offlineQueueEnabled: true
174
+ });
175
+
176
+ // Set network state
177
+ client.setNetworkState(false); // Requests queued
178
+ client.setNetworkState(true); // Queue processed
179
+
180
+ // Manual sync
181
+ await client.syncWhenOnline();
182
+ ```
183
+
184
+ ## Token Storage Security
185
+
186
+ ### Web Security
187
+ - HTTP-only cookies prevent XSS attacks
188
+ - SameSite cookie attributes prevent CSRF
189
+ - Secure flag for HTTPS-only transmission
190
+ - Server-side session management
191
+
192
+ ### Mobile Security
193
+ - iOS Keychain / Android Keystore integration
194
+ - Hardware-backed encryption when available
195
+ - Automatic data protection classes
196
+ - Secure deletion on app uninstall
197
+
198
+ ## React Query Integration
199
+
200
+ The package is designed to work seamlessly with React Query:
201
+
202
+ ```typescript
203
+ // Custom hook example
204
+ function useProfile() {
205
+ return useQuery({
206
+ queryKey: ['profile'],
207
+ queryFn: () => apiClient.get('/auth/profile'),
208
+ staleTime: 5 * 60 * 1000, // 5 minutes
209
+ });
210
+ }
211
+
212
+ // Mutation example
213
+ function useSignIn() {
214
+ return useMutation({
215
+ mutationFn: (credentials) => apiClient.post('/auth/signin', credentials),
216
+ onSuccess: (response) => {
217
+ // Handle successful authentication
218
+ },
219
+ });
220
+ }
221
+ ```
222
+
223
+ ## Configuration
224
+
225
+ ### Web Configuration
226
+ ```typescript
227
+ const client = new WebApiClient({
228
+ baseURL: 'https://api.myportfolio.com',
229
+ timeout: 30000,
230
+ withCredentials: true,
231
+ defaultHeaders: {
232
+ 'X-Client-Version': '1.0.0',
233
+ },
234
+ });
235
+ ```
236
+
237
+ ### Mobile Configuration
238
+ ```typescript
239
+ const client = new MobileApiClient({
240
+ baseURL: 'https://api.myportfolio.com',
241
+ timeout: 30000,
242
+ retryAttempts: 3,
243
+ retryDelay: 1000,
244
+ offlineQueueEnabled: true,
245
+ defaultHeaders: {
246
+ 'X-Client-Version': '1.0.0',
247
+ 'X-Platform': 'mobile',
248
+ },
249
+ });
250
+ ```
251
+
252
+ ## Development
253
+
254
+ ```bash
255
+ # Build the package
256
+ npm run build
257
+
258
+ # Watch for changes
259
+ npm run dev
260
+
261
+ # Type check
262
+ npm run typecheck
263
+ ```
264
+
265
+ ## Dependencies
266
+
267
+ - `@myportfolio/shared-types` - Shared type definitions
268
+ - `@myportfolio/shared-constants` - HTTP constants and headers
269
+
270
+ ## Peer Dependencies
271
+
272
+ - `@tanstack/react-query` - For React Query integration (optional)
273
+ - `typescript` - For TypeScript support
274
+
275
+ ## Platform Requirements
276
+
277
+ ### Web
278
+ - Modern browsers with Fetch API support
279
+ - Cookie support for authentication
280
+
281
+ ### Mobile
282
+ - React Native 0.60+
283
+ - Expo SDK 47+ (for Expo SecureStore)
284
+ - iOS 10+ / Android API 21+
285
+
286
+ ## License
287
+
288
+ MIT
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Mobile-specific API client implementation
3
+ *
4
+ * This implementation is designed for React Native environments
5
+ * and includes mobile-specific features like network state awareness,
6
+ * offline capability, and retry logic.
7
+ */
8
+ import { BaseApiClient, type RequestConfig, type ApiResponse, type ApiClientConfig } from '../core/BaseApiClient';
9
+ export interface MobileApiClientConfig extends ApiClientConfig {
10
+ retryAttempts?: number;
11
+ retryDelay?: number;
12
+ offlineQueueEnabled?: boolean;
13
+ }
14
+ export declare class MobileApiClient extends BaseApiClient {
15
+ private retryAttempts;
16
+ private retryDelay;
17
+ private offlineQueueEnabled;
18
+ private offlineQueue;
19
+ private isOnline;
20
+ constructor(config: MobileApiClientConfig);
21
+ request<T = unknown>(config: RequestConfig): Promise<ApiResponse<T>>;
22
+ private executeRequest;
23
+ /**
24
+ * Queue request for when back online
25
+ */
26
+ private queueRequest;
27
+ /**
28
+ * Process offline queue when back online
29
+ */
30
+ syncWhenOnline(): Promise<void>;
31
+ /**
32
+ * Enable offline mode
33
+ */
34
+ enableOfflineMode(): void;
35
+ /**
36
+ * Enable online mode and sync queue
37
+ */
38
+ enableOnlineMode(): Promise<void>;
39
+ /**
40
+ * Set network state
41
+ */
42
+ setNetworkState(isOnline: boolean): void;
43
+ /**
44
+ * Parse response data based on content type
45
+ */
46
+ private parseResponseData;
47
+ /**
48
+ * Convert Headers to plain object
49
+ */
50
+ private parseResponseHeaders;
51
+ /**
52
+ * Check if error is an API error
53
+ */
54
+ private isApiError;
55
+ /**
56
+ * Determine if request should be retried
57
+ */
58
+ private shouldRetry;
59
+ /**
60
+ * Handle token refresh for mobile (secure storage based)
61
+ */
62
+ protected handleTokenRefresh(): Promise<boolean>;
63
+ /**
64
+ * Utility delay function
65
+ */
66
+ private delay;
67
+ }
68
+ //# sourceMappingURL=MobileApiClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MobileApiClient.d.ts","sourceRoot":"","sources":["../../src/adapters/MobileApiClient.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAElH,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,qBAAa,eAAgB,SAAQ,aAAa;IAChD,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,mBAAmB,CAAU;IACrC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,QAAQ,CAAiB;gBAErB,MAAM,EAAE,qBAAqB;IAcnC,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YAS5D,cAAc;IAwF5B;;OAEG;YACW,YAAY;IAU1B;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBrC;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAIzB;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAOvC;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAUxC;;OAEG;YACW,iBAAiB;IAe/B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAQ5B;;OAEG;IACH,OAAO,CAAC,UAAU;IAOlB;;OAEG;IACH,OAAO,CAAC,WAAW;IAsBnB;;OAEG;cACa,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC;IAyBtD;;OAEG;IACH,OAAO,CAAC,KAAK;CAGd"}
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Mobile-specific API client implementation
3
+ *
4
+ * This implementation is designed for React Native environments
5
+ * and includes mobile-specific features like network state awareness,
6
+ * offline capability, and retry logic.
7
+ */
8
+ import { HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_APPLICATION_JSON } from '@myportfolio/shared-constants';
9
+ import { BaseApiClient } from '../core/BaseApiClient';
10
+ export class MobileApiClient extends BaseApiClient {
11
+ constructor(config) {
12
+ super({
13
+ ...config,
14
+ defaultHeaders: {
15
+ [HTTP_HEADER_CONTENT_TYPE]: CONTENT_TYPE_APPLICATION_JSON,
16
+ ...config.defaultHeaders,
17
+ },
18
+ });
19
+ this.offlineQueue = [];
20
+ this.isOnline = true;
21
+ this.retryAttempts = config.retryAttempts || 3;
22
+ this.retryDelay = config.retryDelay || 1000;
23
+ this.offlineQueueEnabled = config.offlineQueueEnabled || false;
24
+ }
25
+ async request(config) {
26
+ // Check if we're offline and should queue the request
27
+ if (!this.isOnline && this.offlineQueueEnabled) {
28
+ return this.queueRequest(config);
29
+ }
30
+ return this.executeRequest(config);
31
+ }
32
+ async executeRequest(config, attempt = 1) {
33
+ const url = this.buildUrl(config.url);
34
+ const headers = await this.buildHeaders(config.headers);
35
+ // Build fetch options
36
+ const fetchOptions = {
37
+ method: config.method || 'GET',
38
+ headers,
39
+ signal: config.timeout ? AbortSignal.timeout(config.timeout) : undefined,
40
+ };
41
+ // Add body for non-GET requests
42
+ if (config.data && config.method !== 'GET') {
43
+ if (headers[HTTP_HEADER_CONTENT_TYPE] === CONTENT_TYPE_APPLICATION_JSON) {
44
+ fetchOptions.body = JSON.stringify(config.data);
45
+ }
46
+ else {
47
+ fetchOptions.body = config.data;
48
+ }
49
+ }
50
+ // Add query parameters for GET requests
51
+ const requestUrl = config.params && config.method === 'GET'
52
+ ? `${url}?${new URLSearchParams(config.params).toString()}`
53
+ : url;
54
+ try {
55
+ const response = await fetch(requestUrl, fetchOptions);
56
+ // Handle non-2xx responses
57
+ if (!response.ok) {
58
+ const errorData = await this.parseResponseData(response);
59
+ // Handle authentication errors
60
+ if (response.status === 401) {
61
+ const refreshed = await this.handleTokenRefresh();
62
+ if (refreshed && attempt === 1) {
63
+ // Retry once with new token
64
+ return this.executeRequest(config, attempt + 1);
65
+ }
66
+ }
67
+ throw this.createError(response.statusText || 'Request failed', response.status, 'HTTP_ERROR', errorData);
68
+ }
69
+ const data = await this.parseResponseData(response);
70
+ return {
71
+ data,
72
+ status: response.status,
73
+ statusText: response.statusText,
74
+ headers: this.parseResponseHeaders(response.headers),
75
+ };
76
+ }
77
+ catch (error) {
78
+ // Handle network errors with retry logic
79
+ if (this.shouldRetry(error, attempt)) {
80
+ await this.delay(this.retryDelay * attempt); // Exponential backoff
81
+ return this.executeRequest(config, attempt + 1);
82
+ }
83
+ // Handle different error types
84
+ if (error instanceof TypeError && error.message.includes('fetch')) {
85
+ throw this.createError('Network error', 0, 'NETWORK_ERROR');
86
+ }
87
+ if (error instanceof DOMException && error.name === 'AbortError') {
88
+ throw this.createError('Request timeout', 0, 'TIMEOUT_ERROR');
89
+ }
90
+ // Re-throw API errors
91
+ if (this.isApiError(error)) {
92
+ throw error;
93
+ }
94
+ // Handle unexpected errors
95
+ throw this.createError(error instanceof Error ? error.message : 'Unknown error', 0, 'UNEXPECTED_ERROR');
96
+ }
97
+ }
98
+ /**
99
+ * Queue request for when back online
100
+ */
101
+ async queueRequest(config) {
102
+ return new Promise((resolve, reject) => {
103
+ this.offlineQueue.push({
104
+ ...config,
105
+ resolve: resolve,
106
+ reject,
107
+ });
108
+ });
109
+ }
110
+ /**
111
+ * Process offline queue when back online
112
+ */
113
+ async syncWhenOnline() {
114
+ if (!this.isOnline || this.offlineQueue.length === 0)
115
+ return;
116
+ const queue = [...this.offlineQueue];
117
+ this.offlineQueue = [];
118
+ for (const request of queue) {
119
+ try {
120
+ const response = await this.executeRequest(request);
121
+ request.resolve(response);
122
+ }
123
+ catch (error) {
124
+ request.reject(error);
125
+ }
126
+ }
127
+ }
128
+ /**
129
+ * Enable offline mode
130
+ */
131
+ enableOfflineMode() {
132
+ this.isOnline = false;
133
+ }
134
+ /**
135
+ * Enable online mode and sync queue
136
+ */
137
+ async enableOnlineMode() {
138
+ this.isOnline = true;
139
+ if (this.offlineQueueEnabled) {
140
+ await this.syncWhenOnline();
141
+ }
142
+ }
143
+ /**
144
+ * Set network state
145
+ */
146
+ setNetworkState(isOnline) {
147
+ const wasOffline = !this.isOnline;
148
+ this.isOnline = isOnline;
149
+ if (wasOffline && isOnline && this.offlineQueueEnabled) {
150
+ // Process queue when coming back online
151
+ this.syncWhenOnline().catch(console.error);
152
+ }
153
+ }
154
+ /**
155
+ * Parse response data based on content type
156
+ */
157
+ async parseResponseData(response) {
158
+ const contentType = response.headers.get('content-type') || '';
159
+ if (contentType.includes('application/json')) {
160
+ return response.json();
161
+ }
162
+ if (contentType.includes('text/')) {
163
+ return response.text();
164
+ }
165
+ // For other content types, return as blob
166
+ return response.blob();
167
+ }
168
+ /**
169
+ * Convert Headers to plain object
170
+ */
171
+ parseResponseHeaders(headers) {
172
+ const headerObj = {};
173
+ headers.forEach((value, key) => {
174
+ headerObj[key] = value;
175
+ });
176
+ return headerObj;
177
+ }
178
+ /**
179
+ * Check if error is an API error
180
+ */
181
+ isApiError(error) {
182
+ return typeof error === 'object' &&
183
+ error !== null &&
184
+ 'message' in error &&
185
+ 'status' in error;
186
+ }
187
+ /**
188
+ * Determine if request should be retried
189
+ */
190
+ shouldRetry(error, attempt) {
191
+ if (attempt >= this.retryAttempts)
192
+ return false;
193
+ // Retry on network errors
194
+ if (error instanceof TypeError && error.message.includes('fetch')) {
195
+ return true;
196
+ }
197
+ // Retry on timeout errors
198
+ if (error instanceof DOMException && error.name === 'AbortError') {
199
+ return true;
200
+ }
201
+ // Retry on certain HTTP status codes
202
+ if (this.isApiError(error)) {
203
+ const status = error.status;
204
+ return status === 429 || (status !== undefined && status >= 500); // Rate limit or server errors
205
+ }
206
+ return false;
207
+ }
208
+ /**
209
+ * Handle token refresh for mobile (secure storage based)
210
+ */
211
+ async handleTokenRefresh() {
212
+ if (!this.tokenStorage)
213
+ return false;
214
+ try {
215
+ const tokens = await this.tokenStorage.retrieveTokens();
216
+ if (!tokens?.refresh)
217
+ return false;
218
+ const response = await this.request({
219
+ url: '/auth/refresh',
220
+ method: 'POST',
221
+ data: { refresh: tokens.refresh },
222
+ });
223
+ if (response.status === 200 && response.data) {
224
+ const newTokens = response.data;
225
+ await this.tokenStorage.storeTokens(newTokens);
226
+ return true;
227
+ }
228
+ return false;
229
+ }
230
+ catch {
231
+ return false;
232
+ }
233
+ }
234
+ /**
235
+ * Utility delay function
236
+ */
237
+ delay(ms) {
238
+ return new Promise(resolve => setTimeout(resolve, ms));
239
+ }
240
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Mobile-specific token storage using secure storage
3
+ *
4
+ * This implementation uses platform-secure storage mechanisms
5
+ * like iOS Keychain and Android Keystore via React Native libraries.
6
+ *
7
+ * For Expo: Uses Expo SecureStore
8
+ * For bare React Native: Would use @react-native-async-storage/async-storage
9
+ * with encryption or react-native-keychain
10
+ */
11
+ import { BaseTokenStorage, type TokenPair } from '../core/TokenStorage';
12
+ export declare class MobileTokenStorage extends BaseTokenStorage {
13
+ private secureStore;
14
+ constructor(secureStore: SecureStoreInterface);
15
+ storeTokens(tokens: TokenPair): Promise<void>;
16
+ retrieveTokens(): Promise<TokenPair | null>;
17
+ clearTokens(): Promise<void>;
18
+ hasTokens(): Promise<boolean>;
19
+ /**
20
+ * Check if secure storage is available
21
+ */
22
+ isAvailable(): Promise<boolean>;
23
+ }
24
+ /**
25
+ * Interface for secure storage implementations
26
+ * This abstracts the specific secure storage library being used
27
+ */
28
+ export interface SecureStoreInterface {
29
+ setItemAsync(key: string, value: string): Promise<void>;
30
+ getItemAsync(key: string): Promise<string | null>;
31
+ deleteItemAsync(key: string): Promise<void>;
32
+ isAvailableAsync?(): Promise<boolean>;
33
+ }
34
+ /**
35
+ * Factory function to create MobileTokenStorage with different backends
36
+ */
37
+ export declare const createMobileTokenStorage: (secureStore: SecureStoreInterface) => MobileTokenStorage;
38
+ /**
39
+ * Expo SecureStore adapter
40
+ * Usage: createMobileTokenStorage(createExpoSecureStoreAdapter())
41
+ */
42
+ export declare const createExpoSecureStoreAdapter: () => SecureStoreInterface;
43
+ //# sourceMappingURL=MobileTokenStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MobileTokenStorage.d.ts","sourceRoot":"","sources":["../../src/adapters/MobileTokenStorage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,gBAAgB,EAAE,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAQxE,qBAAa,kBAAmB,SAAQ,gBAAgB;IACtD,OAAO,CAAC,WAAW,CAAuB;gBAE9B,WAAW,EAAE,oBAAoB;IAKvC,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAe7C,cAAc,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IA0B3C,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAanC;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAStC;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAClD,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,gBAAgB,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACvC;AAED;;GAEG;AACH,eAAO,MAAM,wBAAwB,GAAI,aAAa,oBAAoB,KAAG,kBAE5E,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,QAAO,oBAsB/C,CAAC"}