@onairos/react-native 3.0.75 → 3.1.1
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/lib/commonjs/components/EmailVerificationModal.js +7 -5
- package/lib/commonjs/components/EmailVerificationModal.js.map +1 -1
- package/lib/commonjs/index.js +18 -6
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/services/apiKeyService.js +401 -27
- package/lib/commonjs/services/apiKeyService.js.map +1 -1
- package/lib/commonjs/services/platformAuthService.js +130 -299
- package/lib/commonjs/services/platformAuthService.js.map +1 -1
- package/lib/commonjs/utils/onairosApi.js +151 -71
- package/lib/commonjs/utils/onairosApi.js.map +1 -1
- package/lib/commonjs/utils/secureStorage.js +123 -1
- package/lib/commonjs/utils/secureStorage.js.map +1 -1
- package/lib/module/components/EmailVerificationModal.js +7 -5
- package/lib/module/components/EmailVerificationModal.js.map +1 -1
- package/lib/module/index.js +4 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/services/apiKeyService.js +384 -22
- package/lib/module/services/apiKeyService.js.map +1 -1
- package/lib/module/services/platformAuthService.js +127 -295
- package/lib/module/services/platformAuthService.js.map +1 -1
- package/lib/module/utils/onairosApi.js +147 -70
- package/lib/module/utils/onairosApi.js.map +1 -1
- package/lib/module/utils/secureStorage.js +116 -0
- package/lib/module/utils/secureStorage.js.map +1 -1
- package/lib/typescript/components/EmailVerificationModal.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/services/apiKeyService.d.ts +68 -2
- package/lib/typescript/services/apiKeyService.d.ts.map +1 -1
- package/lib/typescript/services/platformAuthService.d.ts +29 -14
- package/lib/typescript/services/platformAuthService.d.ts.map +1 -1
- package/lib/typescript/utils/onairosApi.d.ts +25 -10
- package/lib/typescript/utils/onairosApi.d.ts.map +1 -1
- package/lib/typescript/utils/secureStorage.d.ts +31 -0
- package/lib/typescript/utils/secureStorage.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/EmailVerificationModal.tsx +9 -5
- package/src/index.ts +4 -1
- package/src/services/apiKeyService.ts +412 -18
- package/src/services/platformAuthService.ts +219 -421
- package/src/types/index.d.ts +11 -5
- package/src/utils/onairosApi.ts +162 -74
- package/src/utils/secureStorage.ts +122 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OnairosConfig, ApiKeyValidationResult } from '../types';
|
|
2
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
3
|
|
|
3
4
|
// Admin key for backend validation
|
|
4
5
|
export const ADMIN_API_KEY = 'OnairosIsAUnicorn2025';
|
|
@@ -10,30 +11,33 @@ export enum ApiKeyType {
|
|
|
10
11
|
INVALID = 'invalid'
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
// JWT token storage key
|
|
15
|
+
const JWT_TOKEN_KEY = 'onairos_jwt_token';
|
|
16
|
+
|
|
13
17
|
/**
|
|
14
|
-
*
|
|
18
|
+
* Two-Tier Authentication Service for Onairos React Native SDK
|
|
15
19
|
*
|
|
16
|
-
* This service
|
|
17
|
-
*
|
|
20
|
+
* This service implements the two-tier authentication system:
|
|
21
|
+
* 1. Developer API Keys: For app-level operations (email verification, app registration)
|
|
22
|
+
* 2. JWT User Tokens: For user-level operations (PIN storage, user profile)
|
|
18
23
|
*
|
|
19
24
|
* How it works:
|
|
20
|
-
* 1. Initialize with API key
|
|
21
|
-
* 2.
|
|
22
|
-
* 3.
|
|
23
|
-
* 4.
|
|
24
|
-
* 5. Handle
|
|
25
|
+
* 1. Initialize with developer API key
|
|
26
|
+
* 2. Use API key for email verification requests
|
|
27
|
+
* 3. Store JWT token from email verification response
|
|
28
|
+
* 4. Use JWT token for user-authenticated requests
|
|
29
|
+
* 5. Handle token expiration gracefully
|
|
25
30
|
*
|
|
26
31
|
* Backend Integration:
|
|
27
|
-
* -
|
|
28
|
-
* -
|
|
29
|
-
* - Admin key "OnairosIsAUnicorn2025" has full permissions
|
|
30
|
-
* - Developer keys have limited permissions based on validation response
|
|
32
|
+
* - Developer routes: Authorization: Bearer ${API_KEY}
|
|
33
|
+
* - User routes: Authorization: Bearer ${JWT_TOKEN}
|
|
31
34
|
*/
|
|
32
35
|
|
|
33
36
|
// Global configuration state
|
|
34
37
|
let globalConfig: OnairosConfig | null = null;
|
|
35
38
|
let validationCache: Map<string, { result: ApiKeyValidationResult; timestamp: number }> = new Map();
|
|
36
39
|
let isInitialized = false;
|
|
40
|
+
let userToken: string | null = null;
|
|
37
41
|
|
|
38
42
|
// Cache duration (5 minutes)
|
|
39
43
|
const CACHE_DURATION = 5 * 60 * 1000;
|
|
@@ -46,15 +50,15 @@ const API_ENDPOINTS = {
|
|
|
46
50
|
};
|
|
47
51
|
|
|
48
52
|
/**
|
|
49
|
-
* Initialize the SDK with API key
|
|
50
|
-
* @param config API configuration including API key
|
|
53
|
+
* Initialize the SDK with developer API key
|
|
54
|
+
* @param config API configuration including developer API key
|
|
51
55
|
*/
|
|
52
56
|
export const initializeApiKey = async (config: OnairosConfig): Promise<void> => {
|
|
53
57
|
try {
|
|
54
|
-
console.log('🔑 Initializing Onairos SDK with API key...');
|
|
58
|
+
console.log('🔑 Initializing Onairos SDK with developer API key...');
|
|
55
59
|
|
|
56
60
|
if (!config.apiKey) {
|
|
57
|
-
throw new Error('API key is required for SDK initialization');
|
|
61
|
+
throw new Error('Developer API key is required for SDK initialization');
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
if (config.apiKey.length < 32) {
|
|
@@ -80,17 +84,24 @@ export const initializeApiKey = async (config: OnairosConfig): Promise<void> =>
|
|
|
80
84
|
});
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
// Validate the API key
|
|
87
|
+
// Validate the developer API key
|
|
84
88
|
const validation = await validateApiKey(config.apiKey);
|
|
85
89
|
|
|
86
90
|
if (!validation.isValid) {
|
|
87
|
-
throw new Error(`API key validation failed: ${validation.error}`);
|
|
91
|
+
throw new Error(`Developer API key validation failed: ${validation.error}`);
|
|
88
92
|
}
|
|
89
93
|
|
|
94
|
+
// Try to load existing JWT token
|
|
95
|
+
await loadJWT();
|
|
96
|
+
|
|
90
97
|
isInitialized = true;
|
|
91
98
|
|
|
92
99
|
if (globalConfig.enableLogging) {
|
|
93
100
|
console.log('✅ Onairos SDK initialized successfully');
|
|
101
|
+
console.log('🔑 Developer API key ready for app-level operations');
|
|
102
|
+
if (userToken) {
|
|
103
|
+
console.log('🎫 User JWT token loaded from storage');
|
|
104
|
+
}
|
|
94
105
|
if (validation.permissions) {
|
|
95
106
|
console.log('🔐 API Key Permissions:', validation.permissions);
|
|
96
107
|
}
|
|
@@ -284,6 +295,201 @@ export const isApiKeyInitialized = (): boolean => {
|
|
|
284
295
|
return isInitialized && globalConfig !== null;
|
|
285
296
|
};
|
|
286
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Store JWT token securely after email verification
|
|
300
|
+
* @param token JWT token from email verification response
|
|
301
|
+
*/
|
|
302
|
+
export const storeJWT = async (token: string): Promise<void> => {
|
|
303
|
+
try {
|
|
304
|
+
await AsyncStorage.setItem(JWT_TOKEN_KEY, token);
|
|
305
|
+
userToken = token;
|
|
306
|
+
|
|
307
|
+
if (globalConfig?.enableLogging) {
|
|
308
|
+
console.log('🎫 JWT token stored successfully');
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.error('❌ Failed to store JWT token:', error);
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Load JWT token from storage
|
|
318
|
+
* @returns JWT token or null if not found
|
|
319
|
+
*/
|
|
320
|
+
export const loadJWT = async (): Promise<string | null> => {
|
|
321
|
+
try {
|
|
322
|
+
const token = await AsyncStorage.getItem(JWT_TOKEN_KEY);
|
|
323
|
+
userToken = token;
|
|
324
|
+
return token;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.error('❌ Failed to load JWT token:', error);
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get current JWT token
|
|
333
|
+
* @returns JWT token or null if not available
|
|
334
|
+
*/
|
|
335
|
+
export const getJWT = (): string | null => {
|
|
336
|
+
return userToken;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Clear JWT token (on logout or token expiration)
|
|
341
|
+
*/
|
|
342
|
+
export const clearJWT = async (): Promise<void> => {
|
|
343
|
+
try {
|
|
344
|
+
await AsyncStorage.removeItem(JWT_TOKEN_KEY);
|
|
345
|
+
userToken = null;
|
|
346
|
+
|
|
347
|
+
if (globalConfig?.enableLogging) {
|
|
348
|
+
console.log('🗑️ JWT token cleared');
|
|
349
|
+
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error('❌ Failed to clear JWT token:', error);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* React Native compatible base64 decoder
|
|
357
|
+
* @param str Base64 encoded string
|
|
358
|
+
* @returns Decoded string
|
|
359
|
+
*/
|
|
360
|
+
const base64Decode = (str: string): string => {
|
|
361
|
+
// Simple base64 decoding for React Native
|
|
362
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
363
|
+
let result = '';
|
|
364
|
+
let i = 0;
|
|
365
|
+
|
|
366
|
+
str = str.replace(/[^A-Za-z0-9+/]/g, '');
|
|
367
|
+
|
|
368
|
+
while (i < str.length) {
|
|
369
|
+
const a = chars.indexOf(str.charAt(i++));
|
|
370
|
+
const b = chars.indexOf(str.charAt(i++));
|
|
371
|
+
const c = chars.indexOf(str.charAt(i++));
|
|
372
|
+
const d = chars.indexOf(str.charAt(i++));
|
|
373
|
+
|
|
374
|
+
const bitmap = (a << 18) | (b << 12) | (c << 6) | d;
|
|
375
|
+
|
|
376
|
+
result += String.fromCharCode((bitmap >> 16) & 255);
|
|
377
|
+
if (c !== 64) result += String.fromCharCode((bitmap >> 8) & 255);
|
|
378
|
+
if (d !== 64) result += String.fromCharCode(bitmap & 255);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return result;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Decode JWT token payload (React Native compatible)
|
|
386
|
+
* @param token JWT token string
|
|
387
|
+
* @returns Decoded payload or null if invalid
|
|
388
|
+
*/
|
|
389
|
+
export const decodeJWTPayload = (token: string): any => {
|
|
390
|
+
try {
|
|
391
|
+
// Split JWT token (header.payload.signature)
|
|
392
|
+
const parts = token.split('.');
|
|
393
|
+
if (parts.length !== 3) {
|
|
394
|
+
console.error('❌ Invalid JWT token format');
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Decode payload (base64url to base64)
|
|
399
|
+
const payload = parts[1];
|
|
400
|
+
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
401
|
+
|
|
402
|
+
// Add padding if needed
|
|
403
|
+
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
|
|
404
|
+
|
|
405
|
+
// Decode base64 to JSON using React Native compatible decoder
|
|
406
|
+
const decoded = base64Decode(padded);
|
|
407
|
+
return JSON.parse(decoded);
|
|
408
|
+
} catch (error) {
|
|
409
|
+
console.error('❌ Failed to decode JWT token:', error);
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Extract username from JWT token
|
|
416
|
+
* @param token JWT token (optional, uses stored token if not provided)
|
|
417
|
+
* @returns Username or null if not found
|
|
418
|
+
*/
|
|
419
|
+
export const extractUsernameFromJWT = (token?: string): string | null => {
|
|
420
|
+
try {
|
|
421
|
+
const jwtToken = token || userToken;
|
|
422
|
+
if (!jwtToken) {
|
|
423
|
+
console.warn('⚠️ No JWT token available for username extraction');
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const payload = decodeJWTPayload(jwtToken);
|
|
428
|
+
if (!payload) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Try different possible username fields in order of preference
|
|
433
|
+
const username = payload.userName || payload.username || payload.userId || payload.email;
|
|
434
|
+
|
|
435
|
+
if (globalConfig?.enableLogging) {
|
|
436
|
+
console.log('👤 Extracted username from JWT:', username);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return username || null;
|
|
440
|
+
} catch (error) {
|
|
441
|
+
console.error('❌ Failed to extract username from JWT:', error);
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Extract user data from JWT token
|
|
448
|
+
* @param token JWT token (optional, uses stored token if not provided)
|
|
449
|
+
* @returns User data object or null if not found
|
|
450
|
+
*/
|
|
451
|
+
export const extractUserDataFromJWT = (token?: string): any => {
|
|
452
|
+
try {
|
|
453
|
+
const jwtToken = token || userToken;
|
|
454
|
+
if (!jwtToken) {
|
|
455
|
+
console.warn('⚠️ No JWT token available for user data extraction');
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const payload = decodeJWTPayload(jwtToken);
|
|
460
|
+
if (!payload) {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const userData = {
|
|
465
|
+
id: payload.id,
|
|
466
|
+
email: payload.email,
|
|
467
|
+
userId: payload.userId,
|
|
468
|
+
userName: payload.userName || payload.username,
|
|
469
|
+
verified: payload.verified,
|
|
470
|
+
iat: payload.iat,
|
|
471
|
+
exp: payload.exp
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
if (globalConfig?.enableLogging) {
|
|
475
|
+
console.log('👤 Extracted user data from JWT:', userData);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return userData;
|
|
479
|
+
} catch (error) {
|
|
480
|
+
console.error('❌ Failed to extract user data from JWT:', error);
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Check if user is authenticated with JWT token
|
|
487
|
+
* @returns True if user has valid JWT token
|
|
488
|
+
*/
|
|
489
|
+
export const isUserAuthenticated = (): boolean => {
|
|
490
|
+
return !!userToken;
|
|
491
|
+
};
|
|
492
|
+
|
|
287
493
|
/**
|
|
288
494
|
* Get authenticated headers for API requests
|
|
289
495
|
* @returns Headers object with Authorization and other required headers
|
|
@@ -306,6 +512,46 @@ export const getAuthHeaders = (): Record<string, string> => {
|
|
|
306
512
|
};
|
|
307
513
|
};
|
|
308
514
|
|
|
515
|
+
/**
|
|
516
|
+
* Get authentication headers for developer API requests
|
|
517
|
+
* @returns Headers with developer API key
|
|
518
|
+
*/
|
|
519
|
+
export const getDeveloperAuthHeaders = (): Record<string, string> => {
|
|
520
|
+
if (!globalConfig?.apiKey) {
|
|
521
|
+
throw new Error('SDK not initialized. Call initializeApiKey() first.');
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const keyType = getApiKeyType(globalConfig.apiKey);
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
'Content-Type': 'application/json',
|
|
528
|
+
'Authorization': `Bearer ${globalConfig.apiKey}`,
|
|
529
|
+
'User-Agent': 'OnairosSDK/1.0.0',
|
|
530
|
+
'X-SDK-Version': '3.0.72',
|
|
531
|
+
'X-SDK-Environment': globalConfig.environment || 'production',
|
|
532
|
+
'X-API-Key-Type': keyType,
|
|
533
|
+
'X-Timestamp': new Date().toISOString(),
|
|
534
|
+
};
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Get authentication headers for user JWT requests
|
|
539
|
+
* @returns Headers with user JWT token
|
|
540
|
+
*/
|
|
541
|
+
export const getUserAuthHeaders = (): Record<string, string> => {
|
|
542
|
+
if (!userToken) {
|
|
543
|
+
throw new Error('User not authenticated. Please verify email first.');
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return {
|
|
547
|
+
'Content-Type': 'application/json',
|
|
548
|
+
'Authorization': `Bearer ${userToken}`,
|
|
549
|
+
'User-Agent': 'OnairosSDK/1.0.0',
|
|
550
|
+
'X-SDK-Version': '3.0.72',
|
|
551
|
+
'X-SDK-Environment': globalConfig?.environment || 'production',
|
|
552
|
+
};
|
|
553
|
+
};
|
|
554
|
+
|
|
309
555
|
/**
|
|
310
556
|
* Make an authenticated API request
|
|
311
557
|
* @param endpoint The API endpoint (relative to base URL)
|
|
@@ -380,6 +626,154 @@ export const makeAuthenticatedRequest = async (
|
|
|
380
626
|
}
|
|
381
627
|
};
|
|
382
628
|
|
|
629
|
+
/**
|
|
630
|
+
* Make authenticated request with developer API key
|
|
631
|
+
* @param endpoint The API endpoint
|
|
632
|
+
* @param options Fetch options
|
|
633
|
+
* @returns Response promise
|
|
634
|
+
*/
|
|
635
|
+
export const makeDeveloperRequest = async (
|
|
636
|
+
endpoint: string,
|
|
637
|
+
options: RequestInit = {}
|
|
638
|
+
): Promise<Response> => {
|
|
639
|
+
if (!isApiKeyInitialized()) {
|
|
640
|
+
throw new Error('SDK not initialized. Call initializeApiKey() first.');
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const config = getApiConfig()!;
|
|
644
|
+
const baseUrl = API_ENDPOINTS[config.environment || 'production'];
|
|
645
|
+
const url = `${baseUrl}${endpoint.startsWith('/') ? '' : '/'}${endpoint}`;
|
|
646
|
+
|
|
647
|
+
// Merge developer authentication headers
|
|
648
|
+
const headers = {
|
|
649
|
+
...getDeveloperAuthHeaders(),
|
|
650
|
+
...(options.headers || {}),
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
// Add timeout
|
|
654
|
+
const controller = new AbortController();
|
|
655
|
+
const timeoutId = setTimeout(() => controller.abort(), config.timeout || 30000);
|
|
656
|
+
|
|
657
|
+
try {
|
|
658
|
+
if (config.enableLogging) {
|
|
659
|
+
console.log(`🌐 Making developer request to: ${endpoint}`);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const response = await fetch(url, {
|
|
663
|
+
...options,
|
|
664
|
+
headers,
|
|
665
|
+
signal: controller.signal,
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
clearTimeout(timeoutId);
|
|
669
|
+
|
|
670
|
+
if (config.enableLogging) {
|
|
671
|
+
console.log(`📡 Developer request response: ${response.status} for ${endpoint}`);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Handle API key errors
|
|
675
|
+
if (response.status === 401) {
|
|
676
|
+
console.error('❌ Developer API key authentication failed');
|
|
677
|
+
throw new Error('Invalid or expired API key');
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (response.status === 403) {
|
|
681
|
+
console.error('❌ Developer API key permissions insufficient');
|
|
682
|
+
throw new Error('Insufficient API key permissions');
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (response.status === 429) {
|
|
686
|
+
console.error('❌ API rate limit exceeded');
|
|
687
|
+
throw new Error('Rate limit exceeded');
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return response;
|
|
691
|
+
} catch (error) {
|
|
692
|
+
clearTimeout(timeoutId);
|
|
693
|
+
|
|
694
|
+
if (error.name === 'AbortError') {
|
|
695
|
+
console.error('⏱️ Request timeout for:', endpoint);
|
|
696
|
+
throw new Error('Request timeout');
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
throw error;
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Make authenticated request with user JWT token
|
|
705
|
+
* @param endpoint The API endpoint
|
|
706
|
+
* @param options Fetch options
|
|
707
|
+
* @returns Response promise
|
|
708
|
+
*/
|
|
709
|
+
export const makeUserRequest = async (
|
|
710
|
+
endpoint: string,
|
|
711
|
+
options: RequestInit = {}
|
|
712
|
+
): Promise<Response> => {
|
|
713
|
+
if (!isUserAuthenticated()) {
|
|
714
|
+
await loadJWT(); // Try to load from storage
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (!isUserAuthenticated()) {
|
|
718
|
+
throw new Error('User not authenticated. Please verify email first.');
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const config = getApiConfig() || { environment: 'production', timeout: 30000, enableLogging: false };
|
|
722
|
+
const baseUrl = API_ENDPOINTS[config.environment || 'production'];
|
|
723
|
+
const url = `${baseUrl}${endpoint.startsWith('/') ? '' : '/'}${endpoint}`;
|
|
724
|
+
|
|
725
|
+
// Merge user authentication headers
|
|
726
|
+
const headers = {
|
|
727
|
+
...getUserAuthHeaders(),
|
|
728
|
+
...(options.headers || {}),
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
// Add timeout
|
|
732
|
+
const controller = new AbortController();
|
|
733
|
+
const timeoutId = setTimeout(() => controller.abort(), config.timeout || 30000);
|
|
734
|
+
|
|
735
|
+
try {
|
|
736
|
+
if (config.enableLogging) {
|
|
737
|
+
console.log(`🌐 Making user request to: ${endpoint}`);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const response = await fetch(url, {
|
|
741
|
+
...options,
|
|
742
|
+
headers,
|
|
743
|
+
signal: controller.signal,
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
clearTimeout(timeoutId);
|
|
747
|
+
|
|
748
|
+
if (config.enableLogging) {
|
|
749
|
+
console.log(`📡 User request response: ${response.status} for ${endpoint}`);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Handle JWT token errors
|
|
753
|
+
if (response.status === 401) {
|
|
754
|
+
console.error('❌ JWT token authentication failed - token may be expired');
|
|
755
|
+
await clearJWT(); // Clear expired token
|
|
756
|
+
throw new Error('Authentication expired. Please verify email again.');
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (response.status === 403) {
|
|
760
|
+
console.error('❌ JWT token permissions insufficient');
|
|
761
|
+
throw new Error('Insufficient permissions for this operation');
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return response;
|
|
765
|
+
} catch (error) {
|
|
766
|
+
clearTimeout(timeoutId);
|
|
767
|
+
|
|
768
|
+
if (error.name === 'AbortError') {
|
|
769
|
+
console.error('⏱️ Request timeout for:', endpoint);
|
|
770
|
+
throw new Error('Request timeout');
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
throw error;
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
|
|
383
777
|
/**
|
|
384
778
|
* Clear the API key validation cache
|
|
385
779
|
*/
|