@onairos/react-native 3.1.8 → 3.1.11
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/services/apiKeyService.js +202 -90
- package/lib/commonjs/services/apiKeyService.js.map +1 -1
- package/lib/commonjs/utils/retryHelper.js +220 -0
- package/lib/commonjs/utils/retryHelper.js.map +1 -0
- package/lib/module/services/apiKeyService.js +202 -90
- package/lib/module/services/apiKeyService.js.map +1 -1
- package/lib/module/utils/retryHelper.js +211 -0
- package/lib/module/utils/retryHelper.js.map +1 -0
- package/lib/typescript/services/apiKeyService.d.ts.map +1 -1
- package/lib/typescript/utils/retryHelper.d.ts +69 -0
- package/lib/typescript/utils/retryHelper.d.ts.map +1 -0
- package/package.json +3 -1
- package/src/services/apiKeyService.ts +182 -64
- package/src/utils/retryHelper.ts +275 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.NETWORK_RETRY_OPTIONS = exports.DEFAULT_RETRY_OPTIONS = exports.API_RETRY_OPTIONS = void 0;
|
|
7
|
+
exports.fetchWithRetry = fetchWithRetry;
|
|
8
|
+
exports.healthCheck = healthCheck;
|
|
9
|
+
exports.withRetry = withRetry;
|
|
10
|
+
/**
|
|
11
|
+
* 🔄 Retry Helper Utility
|
|
12
|
+
*
|
|
13
|
+
* Provides robust retry logic with exponential backoff for network operations.
|
|
14
|
+
* Used throughout the Onairos SDK for handling transient failures gracefully.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default retry options for the Onairos SDK
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_RETRY_OPTIONS = exports.DEFAULT_RETRY_OPTIONS = {
|
|
21
|
+
maxRetries: 3,
|
|
22
|
+
baseDelay: 1000,
|
|
23
|
+
maxDelay: 5000,
|
|
24
|
+
exponentialBackoff: true,
|
|
25
|
+
enableLogging: false,
|
|
26
|
+
shouldRetry: (error, attempt) => {
|
|
27
|
+
// Don't retry client errors (4xx) except for 408 (timeout) and 429 (rate limit)
|
|
28
|
+
if (error.status >= 400 && error.status < 500 && error.status !== 408 && error.status !== 429) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Retry network errors, timeouts, and server errors (5xx)
|
|
33
|
+
if (error.name === 'AbortError' || error.message.includes('Network request failed') || error.message.includes('fetch') || error.message.includes('ENOTFOUND') || error.message.includes('timeout') || error.status >= 500) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Retry JSON parse errors (likely server issues)
|
|
38
|
+
if (error.message.includes('JSON Parse error') || error.message.includes('Unexpected character')) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Execute a function with retry logic and exponential backoff
|
|
47
|
+
* @param fn Function to execute (should return a Promise)
|
|
48
|
+
* @param options Retry configuration options
|
|
49
|
+
* @returns Promise with retry result
|
|
50
|
+
*/
|
|
51
|
+
async function withRetry(fn, options = {}) {
|
|
52
|
+
const config = {
|
|
53
|
+
...DEFAULT_RETRY_OPTIONS,
|
|
54
|
+
...options
|
|
55
|
+
};
|
|
56
|
+
const startTime = Date.now();
|
|
57
|
+
let lastError = null;
|
|
58
|
+
for (let attempt = 1; attempt <= config.maxRetries + 1; attempt++) {
|
|
59
|
+
try {
|
|
60
|
+
if (config.enableLogging && attempt > 1) {
|
|
61
|
+
console.log(`🔄 Retry attempt ${attempt}/${config.maxRetries + 1}`);
|
|
62
|
+
}
|
|
63
|
+
const result = await fn();
|
|
64
|
+
return {
|
|
65
|
+
success: true,
|
|
66
|
+
data: result,
|
|
67
|
+
attempts: attempt,
|
|
68
|
+
totalDuration: Date.now() - startTime
|
|
69
|
+
};
|
|
70
|
+
} catch (error) {
|
|
71
|
+
lastError = error;
|
|
72
|
+
|
|
73
|
+
// Check if we should retry this error
|
|
74
|
+
const shouldRetryError = config.shouldRetry ? config.shouldRetry(error, attempt) : true;
|
|
75
|
+
|
|
76
|
+
// If this is the last attempt or we shouldn't retry, throw the error
|
|
77
|
+
if (attempt > config.maxRetries || !shouldRetryError) {
|
|
78
|
+
if (config.enableLogging) {
|
|
79
|
+
console.error(`❌ All retry attempts exhausted or error not retryable: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Calculate delay for next attempt
|
|
85
|
+
let delay = config.baseDelay;
|
|
86
|
+
if (config.exponentialBackoff) {
|
|
87
|
+
delay = Math.min(config.baseDelay * Math.pow(2, attempt - 1), config.maxDelay);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Add some jitter to prevent thundering herd
|
|
91
|
+
const jitter = Math.random() * 0.1 * delay;
|
|
92
|
+
delay = Math.floor(delay + jitter);
|
|
93
|
+
if (config.onRetry) {
|
|
94
|
+
config.onRetry(error, attempt, delay);
|
|
95
|
+
}
|
|
96
|
+
if (config.enableLogging) {
|
|
97
|
+
console.log(`⏳ Waiting ${delay}ms before retry (attempt ${attempt}/${config.maxRetries + 1})`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Wait before next attempt
|
|
101
|
+
await new Promise(resolve => setTimeout(() => resolve(), delay));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
error: lastError || new Error('Unknown error'),
|
|
107
|
+
attempts: config.maxRetries + 1,
|
|
108
|
+
totalDuration: Date.now() - startTime
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Retry configuration for API calls
|
|
114
|
+
*/
|
|
115
|
+
const API_RETRY_OPTIONS = exports.API_RETRY_OPTIONS = {
|
|
116
|
+
maxRetries: 3,
|
|
117
|
+
baseDelay: 1000,
|
|
118
|
+
maxDelay: 5000,
|
|
119
|
+
exponentialBackoff: true,
|
|
120
|
+
shouldRetry: (error, attempt) => {
|
|
121
|
+
// Enhanced retry logic for API calls
|
|
122
|
+
|
|
123
|
+
// Never retry authentication errors (401) or permission errors (403)
|
|
124
|
+
if (error.status === 401 || error.status === 403) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Never retry bad request errors (400) or not found (404) unless it's a specific case
|
|
129
|
+
if (error.status === 400 || error.status === 404 && !error.message.includes('validation endpoint')) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Retry rate limiting (429) with longer delays
|
|
134
|
+
if (error.status === 429) {
|
|
135
|
+
return attempt <= 2; // Limit retries for rate limiting
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Retry server errors (5xx)
|
|
139
|
+
if (error.status >= 500) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Retry timeout and network errors
|
|
144
|
+
if (error.name === 'AbortError' || error.message.includes('timeout') || error.message.includes('Network request failed') || error.message.includes('fetch') || error.message.includes('ENOTFOUND')) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Retry JSON parse errors (server returning HTML instead of JSON)
|
|
149
|
+
if (error.message.includes('JSON Parse error') || error.message.includes('Unexpected character') || error.message.includes('HTML page instead of JSON')) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
},
|
|
154
|
+
onRetry: (error, attempt, delay) => {
|
|
155
|
+
console.warn(`⚠️ API call failed (attempt ${attempt}), retrying in ${delay}ms: ${error.message}`);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Specialized retry for network/connectivity issues
|
|
161
|
+
*/
|
|
162
|
+
const NETWORK_RETRY_OPTIONS = exports.NETWORK_RETRY_OPTIONS = {
|
|
163
|
+
maxRetries: 2,
|
|
164
|
+
baseDelay: 2000,
|
|
165
|
+
maxDelay: 8000,
|
|
166
|
+
exponentialBackoff: true,
|
|
167
|
+
shouldRetry: (error, attempt) => {
|
|
168
|
+
// Only retry actual network/connectivity issues
|
|
169
|
+
return error.message.includes('Network request failed') || error.message.includes('ENOTFOUND') || error.message.includes('DNS') || error.name === 'AbortError';
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Create a retry wrapper for fetch requests
|
|
175
|
+
* @param url Request URL
|
|
176
|
+
* @param options Fetch options
|
|
177
|
+
* @param retryOptions Retry configuration
|
|
178
|
+
* @returns Promise with fetch response
|
|
179
|
+
*/
|
|
180
|
+
async function fetchWithRetry(url, options = {}, retryOptions = API_RETRY_OPTIONS) {
|
|
181
|
+
const result = await withRetry(() => fetch(url, options), retryOptions);
|
|
182
|
+
if (!result.success) {
|
|
183
|
+
throw result.error;
|
|
184
|
+
}
|
|
185
|
+
return result.data;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Health check function with retry for testing connectivity
|
|
190
|
+
* @param url URL to check
|
|
191
|
+
* @param timeout Timeout in milliseconds
|
|
192
|
+
* @returns Promise indicating if the service is reachable
|
|
193
|
+
*/
|
|
194
|
+
async function healthCheck(url, timeout = 5000) {
|
|
195
|
+
const startTime = Date.now();
|
|
196
|
+
try {
|
|
197
|
+
const controller = new AbortController();
|
|
198
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
199
|
+
const response = await fetch(url, {
|
|
200
|
+
method: 'GET',
|
|
201
|
+
signal: controller.signal,
|
|
202
|
+
headers: {
|
|
203
|
+
'User-Agent': 'OnairosReactNative/HealthCheck'
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
clearTimeout(timeoutId);
|
|
207
|
+
return {
|
|
208
|
+
reachable: true,
|
|
209
|
+
status: response.status,
|
|
210
|
+
duration: Date.now() - startTime
|
|
211
|
+
};
|
|
212
|
+
} catch (error) {
|
|
213
|
+
return {
|
|
214
|
+
reachable: false,
|
|
215
|
+
error: error.message,
|
|
216
|
+
duration: Date.now() - startTime
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=retryHelper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["DEFAULT_RETRY_OPTIONS","exports","maxRetries","baseDelay","maxDelay","exponentialBackoff","enableLogging","shouldRetry","error","attempt","status","name","message","includes","withRetry","fn","options","config","startTime","Date","now","lastError","console","log","result","success","data","attempts","totalDuration","shouldRetryError","delay","Math","min","pow","jitter","random","floor","onRetry","Promise","resolve","setTimeout","Error","API_RETRY_OPTIONS","warn","NETWORK_RETRY_OPTIONS","fetchWithRetry","url","retryOptions","fetch","healthCheck","timeout","controller","AbortController","timeoutId","abort","response","method","signal","headers","clearTimeout","reachable","duration"],"sourceRoot":"..\\..\\..\\src","sources":["utils/retryHelper.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -87,7 +87,14 @@ export const initializeApiKey = async config => {
|
|
|
87
87
|
// Validate the API key (handles both admin and developer keys)
|
|
88
88
|
const validation = await validateApiKey(config.apiKey);
|
|
89
89
|
if (!validation.isValid) {
|
|
90
|
-
|
|
90
|
+
var _validation$error, _validation$error2, _validation$error3;
|
|
91
|
+
// If it's a network error or JSON parse error, warn but don't fail initialization
|
|
92
|
+
if ((_validation$error = validation.error) !== null && _validation$error !== void 0 && _validation$error.includes('Network error') || (_validation$error2 = validation.error) !== null && _validation$error2 !== void 0 && _validation$error2.includes('JSON Parse error') || (_validation$error3 = validation.error) !== null && _validation$error3 !== void 0 && _validation$error3.includes('API validation endpoint returned')) {
|
|
93
|
+
console.warn('⚠️ API key validation failed due to network/server issues, continuing in offline mode:', validation.error);
|
|
94
|
+
console.warn('📝 SDK will function with limited validation. Ensure your API key is valid for production use.');
|
|
95
|
+
} else {
|
|
96
|
+
throw new Error(`API key validation failed: ${validation.error}`);
|
|
97
|
+
}
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
// Try to load existing JWT token
|
|
@@ -150,7 +157,7 @@ export const isAdminKey = apiKey => {
|
|
|
150
157
|
*/
|
|
151
158
|
export const validateApiKey = async apiKey => {
|
|
152
159
|
try {
|
|
153
|
-
var _globalConfig2, _globalConfig3;
|
|
160
|
+
var _globalConfig2, _globalConfig3, _globalConfig4;
|
|
154
161
|
console.log('🔍 Validating API key...');
|
|
155
162
|
|
|
156
163
|
// Check if it's an admin key
|
|
@@ -190,82 +197,187 @@ export const validateApiKey = async apiKey => {
|
|
|
190
197
|
const environment = ((_globalConfig2 = globalConfig) === null || _globalConfig2 === void 0 ? void 0 : _globalConfig2.environment) || 'production';
|
|
191
198
|
const baseUrl = API_ENDPOINTS[environment];
|
|
192
199
|
const timeout = ((_globalConfig3 = globalConfig) === null || _globalConfig3 === void 0 ? void 0 : _globalConfig3.timeout) || 30000;
|
|
200
|
+
const maxRetries = ((_globalConfig4 = globalConfig) === null || _globalConfig4 === void 0 ? void 0 : _globalConfig4.retryAttempts) || 3;
|
|
201
|
+
|
|
202
|
+
// Retry logic for network failures
|
|
203
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
204
|
+
// Create abort controller for timeout
|
|
205
|
+
const controller = new AbortController();
|
|
206
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
207
|
+
try {
|
|
208
|
+
var _globalConfig5;
|
|
209
|
+
if ((_globalConfig5 = globalConfig) !== null && _globalConfig5 !== void 0 && _globalConfig5.enableLogging && attempt > 1) {
|
|
210
|
+
console.log(`🔄 Retry attempt ${attempt}/${maxRetries} for API key validation`);
|
|
211
|
+
}
|
|
212
|
+
const response = await fetch(`${baseUrl}/auth/validate-key`, {
|
|
213
|
+
method: 'POST',
|
|
214
|
+
headers: {
|
|
215
|
+
'Content-Type': 'application/json',
|
|
216
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
217
|
+
'User-Agent': 'OnairosReactNative/3.1.10',
|
|
218
|
+
'X-API-Key-Type': keyType,
|
|
219
|
+
'X-SDK-Platform': 'react-native',
|
|
220
|
+
'X-Retry-Attempt': attempt.toString()
|
|
221
|
+
},
|
|
222
|
+
body: JSON.stringify({
|
|
223
|
+
environment,
|
|
224
|
+
sdk_version: '3.1.10',
|
|
225
|
+
platform: 'react-native',
|
|
226
|
+
keyType,
|
|
227
|
+
timestamp: new Date().toISOString(),
|
|
228
|
+
attempt
|
|
229
|
+
}),
|
|
230
|
+
signal: controller.signal
|
|
231
|
+
});
|
|
232
|
+
clearTimeout(timeoutId);
|
|
193
233
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
headers
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
// First check if we got a valid response
|
|
235
|
+
if (!response) {
|
|
236
|
+
throw new Error('No response received from server');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check if response is actually JSON before trying to parse
|
|
240
|
+
const contentType = response.headers.get('content-type');
|
|
241
|
+
const isJsonResponse = contentType && contentType.includes('application/json');
|
|
242
|
+
if (!isJsonResponse) {
|
|
243
|
+
const textContent = await response.text();
|
|
244
|
+
const previewText = textContent.substring(0, 200);
|
|
245
|
+
console.error('❌ API endpoint returned non-JSON response:', {
|
|
246
|
+
status: response.status,
|
|
247
|
+
statusText: response.statusText,
|
|
248
|
+
contentType: contentType || 'unknown',
|
|
249
|
+
preview: previewText,
|
|
250
|
+
url: `${baseUrl}/auth/validate-key`,
|
|
251
|
+
attempt: attempt
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Handle specific error cases
|
|
255
|
+
if (response.status === 404) {
|
|
256
|
+
throw new Error(`API validation endpoint not found (404). The endpoint ${baseUrl}/auth/validate-key may not exist or be configured correctly.`);
|
|
257
|
+
} else if (response.status === 500) {
|
|
258
|
+
throw new Error(`Server error (500). The Onairos backend is experiencing issues.`);
|
|
259
|
+
} else if (response.status === 502 || response.status === 503) {
|
|
260
|
+
throw new Error(`Service unavailable (${response.status}). The Onairos backend may be temporarily down.`);
|
|
261
|
+
} else if (textContent.includes('<html') || textContent.includes('<!DOCTYPE')) {
|
|
262
|
+
throw new Error(`Server returned HTML page instead of JSON API response. This often indicates a routing issue or server misconfiguration.`);
|
|
263
|
+
} else {
|
|
264
|
+
throw new Error(`API validation endpoint returned ${response.status} - ${response.statusText}. Expected JSON but got ${contentType || 'unknown content type'}.`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Parse JSON response
|
|
269
|
+
let data;
|
|
270
|
+
try {
|
|
271
|
+
data = await response.json();
|
|
272
|
+
} catch (jsonError) {
|
|
273
|
+
console.error('❌ Failed to parse JSON response:', {
|
|
274
|
+
error: jsonError.message,
|
|
275
|
+
status: response.status,
|
|
276
|
+
contentType,
|
|
277
|
+
attempt: attempt
|
|
278
|
+
});
|
|
279
|
+
throw new Error(`Failed to parse server response as JSON: ${jsonError.message}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Handle successful response
|
|
283
|
+
if (response.ok && data.success) {
|
|
284
|
+
var _globalConfig6;
|
|
285
|
+
const result = {
|
|
286
|
+
isValid: true,
|
|
287
|
+
permissions: data.permissions || [],
|
|
288
|
+
rateLimits: data.rateLimits || null,
|
|
289
|
+
keyType: keyType
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Cache the successful result
|
|
293
|
+
validationCache.set(apiKey, {
|
|
294
|
+
result,
|
|
295
|
+
timestamp: Date.now()
|
|
296
|
+
});
|
|
297
|
+
if ((_globalConfig6 = globalConfig) !== null && _globalConfig6 !== void 0 && _globalConfig6.enableLogging) {
|
|
298
|
+
console.log('✅ API key validation successful');
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
} else {
|
|
302
|
+
// Handle API errors (invalid key, etc.)
|
|
303
|
+
const errorMessage = data.error || data.message || `HTTP ${response.status}: ${response.statusText}`;
|
|
304
|
+
const result = {
|
|
305
|
+
isValid: false,
|
|
306
|
+
error: errorMessage,
|
|
307
|
+
keyType: keyType
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// For client errors (4xx), don't retry
|
|
311
|
+
if (response.status >= 400 && response.status < 500) {
|
|
312
|
+
var _globalConfig7;
|
|
313
|
+
if ((_globalConfig7 = globalConfig) !== null && _globalConfig7 !== void 0 && _globalConfig7.enableLogging) {
|
|
314
|
+
console.error('❌ API key validation failed (client error):', errorMessage);
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// For server errors (5xx), retry
|
|
320
|
+
throw new Error(errorMessage);
|
|
321
|
+
}
|
|
322
|
+
} catch (fetchError) {
|
|
323
|
+
var _globalConfig8;
|
|
324
|
+
clearTimeout(timeoutId);
|
|
325
|
+
if (fetchError.name === 'AbortError') {
|
|
326
|
+
const errorMessage = `API key validation timeout (${timeout}ms)`;
|
|
327
|
+
console.error('⏱️ API key validation timeout');
|
|
328
|
+
if (attempt === maxRetries) {
|
|
329
|
+
return {
|
|
330
|
+
isValid: false,
|
|
331
|
+
error: errorMessage,
|
|
332
|
+
keyType: keyType
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
continue; // Retry timeout errors
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Enhanced error message based on error type
|
|
339
|
+
let errorMessage = `Network error during API key validation: ${fetchError.message}`;
|
|
340
|
+
|
|
341
|
+
// Add specific guidance for common errors
|
|
342
|
+
if (fetchError.message.includes('JSON Parse error') || fetchError.message.includes('Unexpected character')) {
|
|
343
|
+
errorMessage = `Server returned invalid JSON response. This usually indicates the API endpoint returned HTML instead of JSON (often a 404 or server error page). ${fetchError.message}`;
|
|
344
|
+
} else if (fetchError.message.includes('Network request failed') || fetchError.message.includes('fetch')) {
|
|
345
|
+
errorMessage = `Network connectivity issue. Please check internet connection and verify the Onairos API is accessible. ${fetchError.message}`;
|
|
346
|
+
} else if (fetchError.message.includes('DNS') || fetchError.message.includes('ENOTFOUND')) {
|
|
347
|
+
errorMessage = `DNS resolution failed for ${baseUrl}. Please check network settings and domain accessibility. ${fetchError.message}`;
|
|
348
|
+
}
|
|
349
|
+
console.error('🌐 Network error during API key validation:', {
|
|
350
|
+
error: fetchError,
|
|
351
|
+
endpoint: `${baseUrl}/auth/validate-key`,
|
|
352
|
+
attempt: attempt,
|
|
353
|
+
maxRetries: maxRetries,
|
|
354
|
+
retryable: attempt < maxRetries
|
|
230
355
|
});
|
|
231
|
-
|
|
232
|
-
|
|
356
|
+
|
|
357
|
+
// If this is the last attempt, return the error
|
|
358
|
+
if (attempt === maxRetries) {
|
|
359
|
+
return {
|
|
360
|
+
isValid: false,
|
|
361
|
+
error: errorMessage,
|
|
362
|
+
keyType: keyType
|
|
363
|
+
};
|
|
233
364
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
isValid: false,
|
|
240
|
-
error: errorMessage,
|
|
241
|
-
keyType: keyType
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
// Don't cache failed results
|
|
245
|
-
if ((_globalConfig5 = globalConfig) !== null && _globalConfig5 !== void 0 && _globalConfig5.enableLogging) {
|
|
246
|
-
console.error('❌ API key validation failed:', errorMessage);
|
|
365
|
+
|
|
366
|
+
// Wait before retrying (exponential backoff)
|
|
367
|
+
const backoffDelay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
|
|
368
|
+
if ((_globalConfig8 = globalConfig) !== null && _globalConfig8 !== void 0 && _globalConfig8.enableLogging) {
|
|
369
|
+
console.log(`⏳ Waiting ${backoffDelay}ms before retry...`);
|
|
247
370
|
}
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
} catch (fetchError) {
|
|
251
|
-
clearTimeout(timeoutId);
|
|
252
|
-
if (fetchError.name === 'AbortError') {
|
|
253
|
-
const errorMessage = 'API key validation timeout';
|
|
254
|
-
console.error('⏱️ API key validation timeout');
|
|
255
|
-
return {
|
|
256
|
-
isValid: false,
|
|
257
|
-
error: errorMessage,
|
|
258
|
-
keyType: keyType
|
|
259
|
-
};
|
|
371
|
+
await new Promise(resolve => setTimeout(() => resolve(), backoffDelay));
|
|
260
372
|
}
|
|
261
|
-
const errorMessage = `Network error during API key validation: ${fetchError.message}`;
|
|
262
|
-
console.error('🌐 Network error during API key validation:', fetchError);
|
|
263
|
-
return {
|
|
264
|
-
isValid: false,
|
|
265
|
-
error: errorMessage,
|
|
266
|
-
keyType: keyType
|
|
267
|
-
};
|
|
268
373
|
}
|
|
374
|
+
|
|
375
|
+
// This should never be reached, but just in case
|
|
376
|
+
return {
|
|
377
|
+
isValid: false,
|
|
378
|
+
error: 'All retry attempts exhausted',
|
|
379
|
+
keyType: keyType
|
|
380
|
+
};
|
|
269
381
|
} catch (error) {
|
|
270
382
|
const errorMessage = `API key validation error: ${error.message}`;
|
|
271
383
|
console.error('❌ API key validation error:', error);
|
|
@@ -290,8 +402,8 @@ export const getApiConfig = () => {
|
|
|
290
402
|
* @returns Current API key or null if not initialized
|
|
291
403
|
*/
|
|
292
404
|
export const getApiKey = () => {
|
|
293
|
-
var
|
|
294
|
-
return ((
|
|
405
|
+
var _globalConfig9;
|
|
406
|
+
return ((_globalConfig9 = globalConfig) === null || _globalConfig9 === void 0 ? void 0 : _globalConfig9.apiKey) || null;
|
|
295
407
|
};
|
|
296
408
|
|
|
297
409
|
/**
|
|
@@ -308,10 +420,10 @@ export const isApiKeyInitialized = () => {
|
|
|
308
420
|
*/
|
|
309
421
|
export const storeJWT = async token => {
|
|
310
422
|
try {
|
|
311
|
-
var
|
|
423
|
+
var _globalConfig0;
|
|
312
424
|
await AsyncStorage.setItem(JWT_TOKEN_KEY, token);
|
|
313
425
|
userToken = token;
|
|
314
|
-
if ((
|
|
426
|
+
if ((_globalConfig0 = globalConfig) !== null && _globalConfig0 !== void 0 && _globalConfig0.enableLogging) {
|
|
315
427
|
console.log('🎫 JWT token stored successfully');
|
|
316
428
|
}
|
|
317
429
|
} catch (error) {
|
|
@@ -348,10 +460,10 @@ export const getJWT = () => {
|
|
|
348
460
|
*/
|
|
349
461
|
export const clearJWT = async () => {
|
|
350
462
|
try {
|
|
351
|
-
var
|
|
463
|
+
var _globalConfig1;
|
|
352
464
|
await AsyncStorage.removeItem(JWT_TOKEN_KEY);
|
|
353
465
|
userToken = null;
|
|
354
|
-
if ((
|
|
466
|
+
if ((_globalConfig1 = globalConfig) !== null && _globalConfig1 !== void 0 && _globalConfig1.enableLogging) {
|
|
355
467
|
console.log('🗑️ JWT token cleared');
|
|
356
468
|
}
|
|
357
469
|
} catch (error) {
|
|
@@ -420,7 +532,7 @@ export const decodeJWTPayload = token => {
|
|
|
420
532
|
*/
|
|
421
533
|
export const extractUsernameFromJWT = token => {
|
|
422
534
|
try {
|
|
423
|
-
var
|
|
535
|
+
var _globalConfig10;
|
|
424
536
|
const jwtToken = token || userToken;
|
|
425
537
|
if (!jwtToken) {
|
|
426
538
|
console.warn('⚠️ No JWT token available for username extraction');
|
|
@@ -433,7 +545,7 @@ export const extractUsernameFromJWT = token => {
|
|
|
433
545
|
|
|
434
546
|
// Try different possible username fields in order of preference
|
|
435
547
|
const username = payload.userName || payload.username || payload.userId || payload.email;
|
|
436
|
-
if ((
|
|
548
|
+
if ((_globalConfig10 = globalConfig) !== null && _globalConfig10 !== void 0 && _globalConfig10.enableLogging) {
|
|
437
549
|
console.log('👤 Extracted username from JWT:', username);
|
|
438
550
|
}
|
|
439
551
|
return username || null;
|
|
@@ -450,7 +562,7 @@ export const extractUsernameFromJWT = token => {
|
|
|
450
562
|
*/
|
|
451
563
|
export const extractUserDataFromJWT = token => {
|
|
452
564
|
try {
|
|
453
|
-
var
|
|
565
|
+
var _globalConfig11;
|
|
454
566
|
const jwtToken = token || userToken;
|
|
455
567
|
if (!jwtToken) {
|
|
456
568
|
console.warn('⚠️ No JWT token available for user data extraction');
|
|
@@ -469,7 +581,7 @@ export const extractUserDataFromJWT = token => {
|
|
|
469
581
|
iat: payload.iat,
|
|
470
582
|
exp: payload.exp
|
|
471
583
|
};
|
|
472
|
-
if ((
|
|
584
|
+
if ((_globalConfig11 = globalConfig) !== null && _globalConfig11 !== void 0 && _globalConfig11.enableLogging) {
|
|
473
585
|
console.log('👤 Extracted user data from JWT:', userData);
|
|
474
586
|
}
|
|
475
587
|
return userData;
|
|
@@ -492,8 +604,8 @@ export const isUserAuthenticated = () => {
|
|
|
492
604
|
* @returns Headers object with Authorization and other required headers
|
|
493
605
|
*/
|
|
494
606
|
export const getAuthHeaders = () => {
|
|
495
|
-
var
|
|
496
|
-
if (!((
|
|
607
|
+
var _globalConfig12;
|
|
608
|
+
if (!((_globalConfig12 = globalConfig) !== null && _globalConfig12 !== void 0 && _globalConfig12.apiKey)) {
|
|
497
609
|
throw new Error('SDK not initialized. Call initializeApiKey() first.');
|
|
498
610
|
}
|
|
499
611
|
const keyType = getApiKeyType(globalConfig.apiKey);
|
|
@@ -513,8 +625,8 @@ export const getAuthHeaders = () => {
|
|
|
513
625
|
* @returns Headers with developer API key
|
|
514
626
|
*/
|
|
515
627
|
export const getDeveloperAuthHeaders = () => {
|
|
516
|
-
var
|
|
517
|
-
if (!((
|
|
628
|
+
var _globalConfig13;
|
|
629
|
+
if (!((_globalConfig13 = globalConfig) !== null && _globalConfig13 !== void 0 && _globalConfig13.apiKey)) {
|
|
518
630
|
throw new Error('SDK not initialized. Call initializeApiKey() first.');
|
|
519
631
|
}
|
|
520
632
|
const keyType = getApiKeyType(globalConfig.apiKey);
|
|
@@ -534,7 +646,7 @@ export const getDeveloperAuthHeaders = () => {
|
|
|
534
646
|
* @returns Headers with user JWT token
|
|
535
647
|
*/
|
|
536
648
|
export const getUserAuthHeaders = () => {
|
|
537
|
-
var
|
|
649
|
+
var _globalConfig14;
|
|
538
650
|
if (!userToken) {
|
|
539
651
|
throw new Error('User not authenticated. Please verify email first.');
|
|
540
652
|
}
|
|
@@ -543,7 +655,7 @@ export const getUserAuthHeaders = () => {
|
|
|
543
655
|
'Authorization': `Bearer ${userToken}`,
|
|
544
656
|
'User-Agent': 'OnairosSDK/1.0.0',
|
|
545
657
|
'X-SDK-Version': '3.0.72',
|
|
546
|
-
'X-SDK-Environment': ((
|
|
658
|
+
'X-SDK-Environment': ((_globalConfig14 = globalConfig) === null || _globalConfig14 === void 0 ? void 0 : _globalConfig14.environment) || 'production'
|
|
547
659
|
};
|
|
548
660
|
};
|
|
549
661
|
|
|
@@ -738,9 +850,9 @@ export const makeUserRequest = async (endpoint, options = {}) => {
|
|
|
738
850
|
* Clear the API key validation cache
|
|
739
851
|
*/
|
|
740
852
|
export const clearValidationCache = () => {
|
|
741
|
-
var
|
|
853
|
+
var _globalConfig15;
|
|
742
854
|
validationCache.clear();
|
|
743
|
-
if ((
|
|
855
|
+
if ((_globalConfig15 = globalConfig) !== null && _globalConfig15 !== void 0 && _globalConfig15.enableLogging) {
|
|
744
856
|
console.log('🗑️ API key validation cache cleared');
|
|
745
857
|
}
|
|
746
858
|
};
|