@onairos/react-native 3.1.16 → 3.1.17
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/README.md +404 -0
- package/lib/commonjs/assets/images/Checkbox.svg +3 -3
- package/lib/commonjs/assets/images/EnochE.svg +19 -19
- package/lib/commonjs/assets/images/Personalityprofile.svg +3 -3
- package/lib/commonjs/assets/images/Personalitytraits.svg +3 -3
- package/lib/commonjs/assets/images/Userpreferences.svg +3 -3
- package/lib/commonjs/assets/images/arrow.svg +20 -20
- package/lib/commonjs/assets/images/basicproficon.svg +43 -43
- package/lib/commonjs/assets/images/basicprofile.svg +3 -3
- package/lib/commonjs/assets/images/checkmark.svg +4 -4
- package/lib/commonjs/assets/images/contentanalysis.svg +3 -3
- package/lib/commonjs/assets/images/contenticon.svg +23 -23
- package/lib/commonjs/assets/images/personalityicon.svg +18 -18
- package/lib/commonjs/assets/images/x-close.svg +3 -3
- package/lib/commonjs/components/OnairosButton.js +290 -0
- package/lib/commonjs/components/OnairosButton.js.map +1 -0
- package/lib/commonjs/components/OnairosSignInButton.js +30 -8
- package/lib/commonjs/components/OnairosSignInButton.js.map +1 -1
- package/lib/commonjs/components/UniversalOnboarding.js +4 -4
- package/lib/commonjs/config/api.js +2 -2
- package/lib/commonjs/hooks/useConnections.js +6 -6
- package/lib/commonjs/hooks/useUserConnections.js +10 -10
- package/lib/commonjs/index.js +9 -10
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/services/apiClient.js +35 -35
- package/lib/commonjs/services/apiKeyService.js +99 -99
- package/lib/commonjs/services/authService.js +82 -82
- package/lib/commonjs/services/biometricPinService.js +10 -10
- package/lib/commonjs/services/connectedAccountsService.js +32 -32
- package/lib/commonjs/services/googleAuthService.js +15 -15
- package/lib/commonjs/services/imageCompressionService.js +15 -15
- package/lib/commonjs/services/jwtStorageService.js +59 -59
- package/lib/commonjs/services/mobileTrainingService.js +14 -14
- package/lib/commonjs/services/pinEncryptionService.js +10 -10
- package/lib/commonjs/services/pinStorageUtils.js +15 -15
- package/lib/commonjs/services/platformAuthService.js +47 -47
- package/lib/commonjs/services/storageService.js +31 -31
- package/lib/commonjs/services/trainingApiHelpers.js +33 -33
- package/lib/commonjs/services/userConnectionsService.js +24 -24
- package/lib/commonjs/utils/Portal.js +4 -4
- package/lib/commonjs/utils/api.js +24 -24
- package/lib/commonjs/utils/auth.js +18 -18
- package/lib/commonjs/utils/crypto.js +13 -13
- package/lib/commonjs/utils/encryption.js +12 -12
- package/lib/commonjs/utils/eventUtils.js +52 -52
- package/lib/commonjs/utils/programmaticFlow.js +16 -16
- package/lib/commonjs/utils/retryHelper.js +27 -27
- package/lib/module/assets/images/Checkbox.svg +3 -3
- package/lib/module/assets/images/EnochE.svg +19 -19
- package/lib/module/assets/images/Personalityprofile.svg +3 -3
- package/lib/module/assets/images/Personalitytraits.svg +3 -3
- package/lib/module/assets/images/Userpreferences.svg +3 -3
- package/lib/module/assets/images/arrow.svg +20 -20
- package/lib/module/assets/images/basicproficon.svg +43 -43
- package/lib/module/assets/images/basicprofile.svg +3 -3
- package/lib/module/assets/images/checkmark.svg +4 -4
- package/lib/module/assets/images/contentanalysis.svg +3 -3
- package/lib/module/assets/images/contenticon.svg +23 -23
- package/lib/module/assets/images/personalityicon.svg +18 -18
- package/lib/module/assets/images/x-close.svg +3 -3
- package/lib/module/components/OnairosButton.js +282 -0
- package/lib/module/components/OnairosButton.js.map +1 -0
- package/lib/module/components/OnairosSignInButton.js +30 -8
- package/lib/module/components/OnairosSignInButton.js.map +1 -1
- package/lib/module/components/UniversalOnboarding.js +4 -4
- package/lib/module/config/api.js +2 -2
- package/lib/module/hooks/useConnections.js +6 -6
- package/lib/module/hooks/useUserConnections.js +10 -10
- package/lib/module/index.js +8 -10
- package/lib/module/index.js.map +1 -1
- package/lib/module/services/apiClient.js +35 -35
- package/lib/module/services/apiKeyService.js +99 -99
- package/lib/module/services/authService.js +82 -82
- package/lib/module/services/biometricPinService.js +10 -10
- package/lib/module/services/connectedAccountsService.js +32 -32
- package/lib/module/services/googleAuthService.js +15 -15
- package/lib/module/services/imageCompressionService.js +15 -15
- package/lib/module/services/jwtStorageService.js +59 -59
- package/lib/module/services/mobileTrainingService.js +14 -14
- package/lib/module/services/pinEncryptionService.js +10 -10
- package/lib/module/services/pinStorageUtils.js +15 -15
- package/lib/module/services/platformAuthService.js +47 -47
- package/lib/module/services/storageService.js +31 -31
- package/lib/module/services/trainingApiHelpers.js +33 -33
- package/lib/module/services/userConnectionsService.js +24 -24
- package/lib/module/utils/Portal.js +4 -4
- package/lib/module/utils/api.js +24 -24
- package/lib/module/utils/auth.js +18 -18
- package/lib/module/utils/crypto.js +13 -13
- package/lib/module/utils/encryption.js +12 -12
- package/lib/module/utils/eventUtils.js +52 -52
- package/lib/module/utils/programmaticFlow.js +16 -16
- package/lib/module/utils/retryHelper.js +27 -27
- package/lib/typescript/components/OnairosButton.d.ts +37 -0
- package/lib/typescript/components/OnairosButton.d.ts.map +1 -0
- package/lib/typescript/components/OnairosSignInButton.d.ts +2 -1
- package/lib/typescript/components/OnairosSignInButton.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +3 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +163 -163
- package/src/api/index.ts +151 -151
- package/src/assets/images/Checkbox.svg +3 -3
- package/src/assets/images/EnochE.svg +19 -19
- package/src/assets/images/Personalityprofile.svg +3 -3
- package/src/assets/images/Personalitytraits.svg +3 -3
- package/src/assets/images/Userpreferences.svg +3 -3
- package/src/assets/images/arrow.svg +20 -20
- package/src/assets/images/basicproficon.svg +43 -43
- package/src/assets/images/basicprofile.svg +3 -3
- package/src/assets/images/checkmark.svg +4 -4
- package/src/assets/images/contentanalysis.svg +3 -3
- package/src/assets/images/contenticon.svg +23 -23
- package/src/assets/images/personalityicon.svg +18 -18
- package/src/assets/images/x-close.svg +3 -3
- package/src/components/BodyText.tsx +33 -33
- package/src/components/BrandMark.tsx +62 -62
- package/src/components/CodeInput.tsx +32 -32
- package/src/components/DataRequestScreen.tsx +355 -355
- package/src/components/EmailInput.tsx +31 -31
- package/src/components/EmailVerificationModal.tsx +363 -363
- package/src/components/ExistingUserDataConfirmation.tsx +506 -506
- package/src/components/GoogleButton.tsx +55 -55
- package/src/components/HeadingGroup.tsx +49 -49
- package/src/components/ModalHeader.tsx +125 -125
- package/src/components/ModalSheet.tsx +57 -57
- package/src/components/Onairos.tsx +422 -422
- package/src/components/OnairosButton.tsx +339 -0
- package/src/components/OnairosSignInButton.tsx +30 -10
- package/src/components/Overlay.tsx +506 -506
- package/src/components/PersonaImage.tsx +79 -79
- package/src/components/PersonaLoadingScreen.tsx +201 -201
- package/src/components/PersonalizationConsentScreen.tsx +410 -410
- package/src/components/PinCreationScreen.tsx +492 -492
- package/src/components/PinInput.tsx +555 -555
- package/src/components/PlatformConnectorsStep.tsx +891 -891
- package/src/components/PlatformList.tsx +144 -144
- package/src/components/PlatformToggle.tsx +226 -226
- package/src/components/PrimaryButton.tsx +213 -213
- package/src/components/SignInMatchAnimation.tsx +225 -225
- package/src/components/SignInStep.tsx +217 -217
- package/src/components/TrainingModal.tsx +1047 -1047
- package/src/components/UniversalOnboarding.tsx +2887 -2887
- package/src/components/VerificationStep.tsx +198 -198
- package/src/components/WelcomeScreen.tsx +473 -473
- package/src/components/icons/Basicproficon.tsx +30 -30
- package/src/components/icons/Basicprofile.tsx +17 -17
- package/src/components/icons/Checkbox.tsx +17 -17
- package/src/components/icons/Checkmark.tsx +24 -24
- package/src/components/icons/Contentanalysis.tsx +17 -17
- package/src/components/icons/Contenticon.tsx +30 -30
- package/src/components/icons/EnochE.tsx +39 -39
- package/src/components/icons/Personalityicon.tsx +22 -22
- package/src/components/icons/Personalityprofile.tsx +17 -17
- package/src/components/icons/Personalitytraits.tsx +17 -17
- package/src/components/icons/Userpreferences.tsx +17 -17
- package/src/components/icons/index.ts +12 -12
- package/src/components/onboarding/OAuthWebView.tsx +232 -232
- package/src/config/api.ts +25 -25
- package/src/context/AuthContext.tsx +393 -393
- package/src/hooks/useConnectedAccounts.ts +138 -138
- package/src/hooks/useConnections.ts +161 -161
- package/src/hooks/useCredentials.ts +174 -174
- package/src/hooks/useUserConnections.ts +165 -165
- package/src/index.js +14 -0
- package/src/index.ts +94 -96
- package/src/services/apiClient.ts +336 -336
- package/src/services/apiKeyService.ts +919 -919
- package/src/services/authService.ts +1008 -1008
- package/src/services/biometricPinService.ts +192 -192
- package/src/services/connectedAccountsService.ts +289 -289
- package/src/services/googleAuthService.ts +279 -279
- package/src/services/imageCompressionService.ts +302 -302
- package/src/services/jwtStorageService.ts +256 -256
- package/src/services/mobileTrainingService.ts +203 -203
- package/src/services/pinEncryptionService.ts +75 -75
- package/src/services/pinStorageUtils.ts +96 -96
- package/src/services/platformAuthService.ts +1346 -1346
- package/src/services/storageService.ts +451 -451
- package/src/services/trainingApiHelpers.ts +66 -66
- package/src/services/userConnectionsService.ts +556 -556
- package/src/services/youtubeMigrationService.ts +453 -453
- package/src/theme/index.ts +239 -239
- package/src/types/ambient.d.ts +28 -28
- package/src/types/index.ts +265 -265
- package/src/types/node-fix.d.ts +18 -18
- package/src/types/node-override.d.ts +23 -23
- package/src/types/opacity.d.ts +15 -15
- package/src/types/types.d.ts +17 -17
- package/src/utils/Portal.tsx +82 -82
- package/src/utils/api.js +111 -111
- package/src/utils/auth.js +103 -103
- package/src/utils/crypto.js +59 -59
- package/src/utils/encryption.ts +68 -68
- package/src/utils/eventUtils.ts +302 -302
- package/src/utils/haptics.ts +58 -58
- package/src/utils/imagePreloader.ts +2 -2
- package/src/utils/programmaticFlow.ts +112 -112
- package/src/utils/retryHelper.ts +274 -274
package/src/utils/retryHelper.ts
CHANGED
|
@@ -1,275 +1,275 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🔄 Retry Helper Utility
|
|
3
|
-
*
|
|
4
|
-
* Provides robust retry logic with exponential backoff for network operations.
|
|
5
|
-
* Used throughout the Onairos SDK for handling transient failures gracefully.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface RetryOptions {
|
|
9
|
-
/** Maximum number of retry attempts */
|
|
10
|
-
maxRetries: number;
|
|
11
|
-
/** Base delay between retries in milliseconds */
|
|
12
|
-
baseDelay: number;
|
|
13
|
-
/** Maximum delay between retries in milliseconds */
|
|
14
|
-
maxDelay: number;
|
|
15
|
-
/** Whether to use exponential backoff */
|
|
16
|
-
exponentialBackoff: boolean;
|
|
17
|
-
/** Function to determine if an error should trigger a retry */
|
|
18
|
-
shouldRetry?: (error: any, attempt: number) => boolean;
|
|
19
|
-
/** Function called before each retry attempt */
|
|
20
|
-
onRetry?: (error: any, attempt: number, nextDelay: number) => void;
|
|
21
|
-
/** Enable logging of retry attempts */
|
|
22
|
-
enableLogging: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface RetryResult<T> {
|
|
26
|
-
success: boolean;
|
|
27
|
-
data?: T;
|
|
28
|
-
error?: Error;
|
|
29
|
-
attempts: number;
|
|
30
|
-
totalDuration: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Default retry options for the Onairos SDK
|
|
35
|
-
*/
|
|
36
|
-
export const DEFAULT_RETRY_OPTIONS: RetryOptions = {
|
|
37
|
-
maxRetries: 3,
|
|
38
|
-
baseDelay: 1000,
|
|
39
|
-
maxDelay: 5000,
|
|
40
|
-
exponentialBackoff: true,
|
|
41
|
-
enableLogging: false,
|
|
42
|
-
shouldRetry: (error: any, attempt: number) => {
|
|
43
|
-
// Don't retry client errors (4xx) except for 408 (timeout) and 429 (rate limit)
|
|
44
|
-
if (error.status >= 400 && error.status < 500 && error.status !== 408 && error.status !== 429) {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Retry network errors, timeouts, and server errors (5xx)
|
|
49
|
-
if (error.name === 'AbortError' ||
|
|
50
|
-
error.message.includes('Network request failed') ||
|
|
51
|
-
error.message.includes('fetch') ||
|
|
52
|
-
error.message.includes('ENOTFOUND') ||
|
|
53
|
-
error.message.includes('timeout') ||
|
|
54
|
-
(error.status >= 500)) {
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Retry JSON parse errors (likely server issues)
|
|
59
|
-
if (error.message.includes('JSON Parse error') || error.message.includes('Unexpected character')) {
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Execute a function with retry logic and exponential backoff
|
|
69
|
-
* @param fn Function to execute (should return a Promise)
|
|
70
|
-
* @param options Retry configuration options
|
|
71
|
-
* @returns Promise with retry result
|
|
72
|
-
*/
|
|
73
|
-
export async function withRetry<T>(
|
|
74
|
-
fn: () => Promise<T>,
|
|
75
|
-
options: Partial<RetryOptions> = {}
|
|
76
|
-
): Promise<RetryResult<T>> {
|
|
77
|
-
const config = { ...DEFAULT_RETRY_OPTIONS, ...options };
|
|
78
|
-
const startTime = Date.now();
|
|
79
|
-
let lastError: Error | null = null;
|
|
80
|
-
|
|
81
|
-
for (let attempt = 1; attempt <= config.maxRetries + 1; attempt++) {
|
|
82
|
-
try {
|
|
83
|
-
if (config.enableLogging && attempt > 1) {
|
|
84
|
-
console.log(`🔄 Retry attempt ${attempt}/${config.maxRetries + 1}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const result = await fn();
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
success: true,
|
|
91
|
-
data: result,
|
|
92
|
-
attempts: attempt,
|
|
93
|
-
totalDuration: Date.now() - startTime
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
} catch (error: any) {
|
|
97
|
-
lastError = error;
|
|
98
|
-
|
|
99
|
-
// Check if we should retry this error
|
|
100
|
-
const shouldRetryError = config.shouldRetry ? config.shouldRetry(error, attempt) : true;
|
|
101
|
-
|
|
102
|
-
// If this is the last attempt or we shouldn't retry, throw the error
|
|
103
|
-
if (attempt > config.maxRetries || !shouldRetryError) {
|
|
104
|
-
if (config.enableLogging) {
|
|
105
|
-
console.error(`❌ All retry attempts exhausted or error not retryable: ${error.message}`);
|
|
106
|
-
}
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Calculate delay for next attempt
|
|
111
|
-
let delay = config.baseDelay;
|
|
112
|
-
if (config.exponentialBackoff) {
|
|
113
|
-
delay = Math.min(config.baseDelay * Math.pow(2, attempt - 1), config.maxDelay);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Add some jitter to prevent thundering herd
|
|
117
|
-
const jitter = Math.random() * 0.1 * delay;
|
|
118
|
-
delay = Math.floor(delay + jitter);
|
|
119
|
-
|
|
120
|
-
if (config.onRetry) {
|
|
121
|
-
config.onRetry(error, attempt, delay);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (config.enableLogging) {
|
|
125
|
-
console.log(`⏳ Waiting ${delay}ms before retry (attempt ${attempt}/${config.maxRetries + 1})`);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Wait before next attempt
|
|
129
|
-
await new Promise<void>(resolve => setTimeout(() => resolve(), delay));
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
success: false,
|
|
135
|
-
error: lastError || new Error('Unknown error'),
|
|
136
|
-
attempts: config.maxRetries + 1,
|
|
137
|
-
totalDuration: Date.now() - startTime
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Retry configuration for API calls
|
|
143
|
-
*/
|
|
144
|
-
export const API_RETRY_OPTIONS: Partial<RetryOptions> = {
|
|
145
|
-
maxRetries: 3,
|
|
146
|
-
baseDelay: 1000,
|
|
147
|
-
maxDelay: 5000,
|
|
148
|
-
exponentialBackoff: true,
|
|
149
|
-
shouldRetry: (error: any, attempt: number) => {
|
|
150
|
-
// Enhanced retry logic for API calls
|
|
151
|
-
|
|
152
|
-
// Never retry authentication errors (401) or permission errors (403)
|
|
153
|
-
if (error.status === 401 || error.status === 403) {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Never retry bad request errors (400) or not found (404) unless it's a specific case
|
|
158
|
-
if (error.status === 400 || (error.status === 404 && !error.message.includes('validation endpoint'))) {
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Retry rate limiting (429) with longer delays
|
|
163
|
-
if (error.status === 429) {
|
|
164
|
-
return attempt <= 2; // Limit retries for rate limiting
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Retry server errors (5xx)
|
|
168
|
-
if (error.status >= 500) {
|
|
169
|
-
return true;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Retry timeout and network errors
|
|
173
|
-
if (error.name === 'AbortError' ||
|
|
174
|
-
error.message.includes('timeout') ||
|
|
175
|
-
error.message.includes('Network request failed') ||
|
|
176
|
-
error.message.includes('fetch') ||
|
|
177
|
-
error.message.includes('ENOTFOUND')) {
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Retry JSON parse errors (server returning HTML instead of JSON)
|
|
182
|
-
if (error.message.includes('JSON Parse error') ||
|
|
183
|
-
error.message.includes('Unexpected character') ||
|
|
184
|
-
error.message.includes('HTML page instead of JSON')) {
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return false;
|
|
189
|
-
},
|
|
190
|
-
onRetry: (error: any, attempt: number, delay: number) => {
|
|
191
|
-
console.warn(`⚠️ API call failed (attempt ${attempt}), retrying in ${delay}ms: ${error.message}`);
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Specialized retry for network/connectivity issues
|
|
197
|
-
*/
|
|
198
|
-
export const NETWORK_RETRY_OPTIONS: Partial<RetryOptions> = {
|
|
199
|
-
maxRetries: 2,
|
|
200
|
-
baseDelay: 2000,
|
|
201
|
-
maxDelay: 8000,
|
|
202
|
-
exponentialBackoff: true,
|
|
203
|
-
shouldRetry: (error: any, attempt: number) => {
|
|
204
|
-
// Only retry actual network/connectivity issues
|
|
205
|
-
return error.message.includes('Network request failed') ||
|
|
206
|
-
error.message.includes('ENOTFOUND') ||
|
|
207
|
-
error.message.includes('DNS') ||
|
|
208
|
-
error.name === 'AbortError';
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Create a retry wrapper for fetch requests
|
|
214
|
-
* @param url Request URL
|
|
215
|
-
* @param options Fetch options
|
|
216
|
-
* @param retryOptions Retry configuration
|
|
217
|
-
* @returns Promise with fetch response
|
|
218
|
-
*/
|
|
219
|
-
export async function fetchWithRetry(
|
|
220
|
-
url: string,
|
|
221
|
-
options: RequestInit = {},
|
|
222
|
-
retryOptions: Partial<RetryOptions> = API_RETRY_OPTIONS
|
|
223
|
-
): Promise<Response> {
|
|
224
|
-
const result = await withRetry(
|
|
225
|
-
() => fetch(url, options),
|
|
226
|
-
retryOptions
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
if (!result.success) {
|
|
230
|
-
throw result.error;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return result.data!;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Health check function with retry for testing connectivity
|
|
238
|
-
* @param url URL to check
|
|
239
|
-
* @param timeout Timeout in milliseconds
|
|
240
|
-
* @returns Promise indicating if the service is reachable
|
|
241
|
-
*/
|
|
242
|
-
export async function healthCheck(
|
|
243
|
-
url: string,
|
|
244
|
-
timeout: number = 5000
|
|
245
|
-
): Promise<{ reachable: boolean; status?: number; error?: string; duration: number }> {
|
|
246
|
-
const startTime = Date.now();
|
|
247
|
-
|
|
248
|
-
try {
|
|
249
|
-
const controller = new AbortController();
|
|
250
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
251
|
-
|
|
252
|
-
const response = await fetch(url, {
|
|
253
|
-
method: 'GET',
|
|
254
|
-
signal: controller.signal,
|
|
255
|
-
headers: {
|
|
256
|
-
'User-Agent': 'OnairosReactNative/HealthCheck'
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
clearTimeout(timeoutId);
|
|
261
|
-
|
|
262
|
-
return {
|
|
263
|
-
reachable: true,
|
|
264
|
-
status: response.status,
|
|
265
|
-
duration: Date.now() - startTime
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
} catch (error: any) {
|
|
269
|
-
return {
|
|
270
|
-
reachable: false,
|
|
271
|
-
error: error.message,
|
|
272
|
-
duration: Date.now() - startTime
|
|
273
|
-
};
|
|
274
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 🔄 Retry Helper Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides robust retry logic with exponential backoff for network operations.
|
|
5
|
+
* Used throughout the Onairos SDK for handling transient failures gracefully.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface RetryOptions {
|
|
9
|
+
/** Maximum number of retry attempts */
|
|
10
|
+
maxRetries: number;
|
|
11
|
+
/** Base delay between retries in milliseconds */
|
|
12
|
+
baseDelay: number;
|
|
13
|
+
/** Maximum delay between retries in milliseconds */
|
|
14
|
+
maxDelay: number;
|
|
15
|
+
/** Whether to use exponential backoff */
|
|
16
|
+
exponentialBackoff: boolean;
|
|
17
|
+
/** Function to determine if an error should trigger a retry */
|
|
18
|
+
shouldRetry?: (error: any, attempt: number) => boolean;
|
|
19
|
+
/** Function called before each retry attempt */
|
|
20
|
+
onRetry?: (error: any, attempt: number, nextDelay: number) => void;
|
|
21
|
+
/** Enable logging of retry attempts */
|
|
22
|
+
enableLogging: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RetryResult<T> {
|
|
26
|
+
success: boolean;
|
|
27
|
+
data?: T;
|
|
28
|
+
error?: Error;
|
|
29
|
+
attempts: number;
|
|
30
|
+
totalDuration: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Default retry options for the Onairos SDK
|
|
35
|
+
*/
|
|
36
|
+
export const DEFAULT_RETRY_OPTIONS: RetryOptions = {
|
|
37
|
+
maxRetries: 3,
|
|
38
|
+
baseDelay: 1000,
|
|
39
|
+
maxDelay: 5000,
|
|
40
|
+
exponentialBackoff: true,
|
|
41
|
+
enableLogging: false,
|
|
42
|
+
shouldRetry: (error: any, attempt: number) => {
|
|
43
|
+
// Don't retry client errors (4xx) except for 408 (timeout) and 429 (rate limit)
|
|
44
|
+
if (error.status >= 400 && error.status < 500 && error.status !== 408 && error.status !== 429) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Retry network errors, timeouts, and server errors (5xx)
|
|
49
|
+
if (error.name === 'AbortError' ||
|
|
50
|
+
error.message.includes('Network request failed') ||
|
|
51
|
+
error.message.includes('fetch') ||
|
|
52
|
+
error.message.includes('ENOTFOUND') ||
|
|
53
|
+
error.message.includes('timeout') ||
|
|
54
|
+
(error.status >= 500)) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Retry JSON parse errors (likely server issues)
|
|
59
|
+
if (error.message.includes('JSON Parse error') || error.message.includes('Unexpected character')) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Execute a function with retry logic and exponential backoff
|
|
69
|
+
* @param fn Function to execute (should return a Promise)
|
|
70
|
+
* @param options Retry configuration options
|
|
71
|
+
* @returns Promise with retry result
|
|
72
|
+
*/
|
|
73
|
+
export async function withRetry<T>(
|
|
74
|
+
fn: () => Promise<T>,
|
|
75
|
+
options: Partial<RetryOptions> = {}
|
|
76
|
+
): Promise<RetryResult<T>> {
|
|
77
|
+
const config = { ...DEFAULT_RETRY_OPTIONS, ...options };
|
|
78
|
+
const startTime = Date.now();
|
|
79
|
+
let lastError: Error | null = null;
|
|
80
|
+
|
|
81
|
+
for (let attempt = 1; attempt <= config.maxRetries + 1; attempt++) {
|
|
82
|
+
try {
|
|
83
|
+
if (config.enableLogging && attempt > 1) {
|
|
84
|
+
console.log(`🔄 Retry attempt ${attempt}/${config.maxRetries + 1}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const result = await fn();
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
data: result,
|
|
92
|
+
attempts: attempt,
|
|
93
|
+
totalDuration: Date.now() - startTime
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
} catch (error: any) {
|
|
97
|
+
lastError = error;
|
|
98
|
+
|
|
99
|
+
// Check if we should retry this error
|
|
100
|
+
const shouldRetryError = config.shouldRetry ? config.shouldRetry(error, attempt) : true;
|
|
101
|
+
|
|
102
|
+
// If this is the last attempt or we shouldn't retry, throw the error
|
|
103
|
+
if (attempt > config.maxRetries || !shouldRetryError) {
|
|
104
|
+
if (config.enableLogging) {
|
|
105
|
+
console.error(`❌ All retry attempts exhausted or error not retryable: ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Calculate delay for next attempt
|
|
111
|
+
let delay = config.baseDelay;
|
|
112
|
+
if (config.exponentialBackoff) {
|
|
113
|
+
delay = Math.min(config.baseDelay * Math.pow(2, attempt - 1), config.maxDelay);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Add some jitter to prevent thundering herd
|
|
117
|
+
const jitter = Math.random() * 0.1 * delay;
|
|
118
|
+
delay = Math.floor(delay + jitter);
|
|
119
|
+
|
|
120
|
+
if (config.onRetry) {
|
|
121
|
+
config.onRetry(error, attempt, delay);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (config.enableLogging) {
|
|
125
|
+
console.log(`⏳ Waiting ${delay}ms before retry (attempt ${attempt}/${config.maxRetries + 1})`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Wait before next attempt
|
|
129
|
+
await new Promise<void>(resolve => setTimeout(() => resolve(), delay));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
error: lastError || new Error('Unknown error'),
|
|
136
|
+
attempts: config.maxRetries + 1,
|
|
137
|
+
totalDuration: Date.now() - startTime
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Retry configuration for API calls
|
|
143
|
+
*/
|
|
144
|
+
export const API_RETRY_OPTIONS: Partial<RetryOptions> = {
|
|
145
|
+
maxRetries: 3,
|
|
146
|
+
baseDelay: 1000,
|
|
147
|
+
maxDelay: 5000,
|
|
148
|
+
exponentialBackoff: true,
|
|
149
|
+
shouldRetry: (error: any, attempt: number) => {
|
|
150
|
+
// Enhanced retry logic for API calls
|
|
151
|
+
|
|
152
|
+
// Never retry authentication errors (401) or permission errors (403)
|
|
153
|
+
if (error.status === 401 || error.status === 403) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Never retry bad request errors (400) or not found (404) unless it's a specific case
|
|
158
|
+
if (error.status === 400 || (error.status === 404 && !error.message.includes('validation endpoint'))) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Retry rate limiting (429) with longer delays
|
|
163
|
+
if (error.status === 429) {
|
|
164
|
+
return attempt <= 2; // Limit retries for rate limiting
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Retry server errors (5xx)
|
|
168
|
+
if (error.status >= 500) {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Retry timeout and network errors
|
|
173
|
+
if (error.name === 'AbortError' ||
|
|
174
|
+
error.message.includes('timeout') ||
|
|
175
|
+
error.message.includes('Network request failed') ||
|
|
176
|
+
error.message.includes('fetch') ||
|
|
177
|
+
error.message.includes('ENOTFOUND')) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Retry JSON parse errors (server returning HTML instead of JSON)
|
|
182
|
+
if (error.message.includes('JSON Parse error') ||
|
|
183
|
+
error.message.includes('Unexpected character') ||
|
|
184
|
+
error.message.includes('HTML page instead of JSON')) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return false;
|
|
189
|
+
},
|
|
190
|
+
onRetry: (error: any, attempt: number, delay: number) => {
|
|
191
|
+
console.warn(`⚠️ API call failed (attempt ${attempt}), retrying in ${delay}ms: ${error.message}`);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Specialized retry for network/connectivity issues
|
|
197
|
+
*/
|
|
198
|
+
export const NETWORK_RETRY_OPTIONS: Partial<RetryOptions> = {
|
|
199
|
+
maxRetries: 2,
|
|
200
|
+
baseDelay: 2000,
|
|
201
|
+
maxDelay: 8000,
|
|
202
|
+
exponentialBackoff: true,
|
|
203
|
+
shouldRetry: (error: any, attempt: number) => {
|
|
204
|
+
// Only retry actual network/connectivity issues
|
|
205
|
+
return error.message.includes('Network request failed') ||
|
|
206
|
+
error.message.includes('ENOTFOUND') ||
|
|
207
|
+
error.message.includes('DNS') ||
|
|
208
|
+
error.name === 'AbortError';
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Create a retry wrapper for fetch requests
|
|
214
|
+
* @param url Request URL
|
|
215
|
+
* @param options Fetch options
|
|
216
|
+
* @param retryOptions Retry configuration
|
|
217
|
+
* @returns Promise with fetch response
|
|
218
|
+
*/
|
|
219
|
+
export async function fetchWithRetry(
|
|
220
|
+
url: string,
|
|
221
|
+
options: RequestInit = {},
|
|
222
|
+
retryOptions: Partial<RetryOptions> = API_RETRY_OPTIONS
|
|
223
|
+
): Promise<Response> {
|
|
224
|
+
const result = await withRetry(
|
|
225
|
+
() => fetch(url, options),
|
|
226
|
+
retryOptions
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
if (!result.success) {
|
|
230
|
+
throw result.error;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return result.data!;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Health check function with retry for testing connectivity
|
|
238
|
+
* @param url URL to check
|
|
239
|
+
* @param timeout Timeout in milliseconds
|
|
240
|
+
* @returns Promise indicating if the service is reachable
|
|
241
|
+
*/
|
|
242
|
+
export async function healthCheck(
|
|
243
|
+
url: string,
|
|
244
|
+
timeout: number = 5000
|
|
245
|
+
): Promise<{ reachable: boolean; status?: number; error?: string; duration: number }> {
|
|
246
|
+
const startTime = Date.now();
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const controller = new AbortController();
|
|
250
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
251
|
+
|
|
252
|
+
const response = await fetch(url, {
|
|
253
|
+
method: 'GET',
|
|
254
|
+
signal: controller.signal,
|
|
255
|
+
headers: {
|
|
256
|
+
'User-Agent': 'OnairosReactNative/HealthCheck'
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
clearTimeout(timeoutId);
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
reachable: true,
|
|
264
|
+
status: response.status,
|
|
265
|
+
duration: Date.now() - startTime
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
} catch (error: any) {
|
|
269
|
+
return {
|
|
270
|
+
reachable: false,
|
|
271
|
+
error: error.message,
|
|
272
|
+
duration: Date.now() - startTime
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
275
|
}
|