@onairos/react-native 3.1.16 → 3.1.18

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 (207) hide show
  1. package/README.md +404 -0
  2. package/lib/commonjs/assets/images/Checkbox.svg +3 -3
  3. package/lib/commonjs/assets/images/EnochE.svg +19 -19
  4. package/lib/commonjs/assets/images/Personalityprofile.svg +3 -3
  5. package/lib/commonjs/assets/images/Personalitytraits.svg +3 -3
  6. package/lib/commonjs/assets/images/Userpreferences.svg +3 -3
  7. package/lib/commonjs/assets/images/arrow.svg +20 -20
  8. package/lib/commonjs/assets/images/basicproficon.svg +43 -43
  9. package/lib/commonjs/assets/images/basicprofile.svg +3 -3
  10. package/lib/commonjs/assets/images/checkmark.svg +4 -4
  11. package/lib/commonjs/assets/images/contentanalysis.svg +3 -3
  12. package/lib/commonjs/assets/images/contenticon.svg +23 -23
  13. package/lib/commonjs/assets/images/personalityicon.svg +18 -18
  14. package/lib/commonjs/assets/images/x-close.svg +3 -3
  15. package/lib/commonjs/components/ModalSheet.js +8 -2
  16. package/lib/commonjs/components/ModalSheet.js.map +1 -1
  17. package/lib/commonjs/components/OnairosButton.js +290 -0
  18. package/lib/commonjs/components/OnairosButton.js.map +1 -0
  19. package/lib/commonjs/components/OnairosSignInButton.js +32 -8
  20. package/lib/commonjs/components/OnairosSignInButton.js.map +1 -1
  21. package/lib/commonjs/components/UniversalOnboarding.js +4 -4
  22. package/lib/commonjs/components/WelcomeScreen.js +29 -13
  23. package/lib/commonjs/components/WelcomeScreen.js.map +1 -1
  24. package/lib/commonjs/config/api.js +2 -2
  25. package/lib/commonjs/hooks/useConnections.js +6 -6
  26. package/lib/commonjs/hooks/useUserConnections.js +10 -10
  27. package/lib/commonjs/index.js +13 -6
  28. package/lib/commonjs/index.js.map +1 -1
  29. package/lib/commonjs/services/apiClient.js +35 -35
  30. package/lib/commonjs/services/apiKeyService.js +99 -99
  31. package/lib/commonjs/services/authService.js +82 -82
  32. package/lib/commonjs/services/biometricPinService.js +10 -10
  33. package/lib/commonjs/services/connectedAccountsService.js +32 -32
  34. package/lib/commonjs/services/googleAuthService.js +15 -15
  35. package/lib/commonjs/services/imageCompressionService.js +15 -15
  36. package/lib/commonjs/services/jwtStorageService.js +59 -59
  37. package/lib/commonjs/services/mobileTrainingService.js +14 -14
  38. package/lib/commonjs/services/pinEncryptionService.js +10 -10
  39. package/lib/commonjs/services/pinStorageUtils.js +15 -15
  40. package/lib/commonjs/services/platformAuthService.js +47 -47
  41. package/lib/commonjs/services/storageService.js +31 -31
  42. package/lib/commonjs/services/trainingApiHelpers.js +33 -33
  43. package/lib/commonjs/services/userConnectionsService.js +24 -24
  44. package/lib/commonjs/utils/Portal.js +4 -4
  45. package/lib/commonjs/utils/api.js +24 -24
  46. package/lib/commonjs/utils/auth.js +18 -18
  47. package/lib/commonjs/utils/crypto.js +13 -13
  48. package/lib/commonjs/utils/encryption.js +12 -12
  49. package/lib/commonjs/utils/eventUtils.js +52 -52
  50. package/lib/commonjs/utils/programmaticFlow.js +16 -16
  51. package/lib/commonjs/utils/retryHelper.js +27 -27
  52. package/lib/module/assets/images/Checkbox.svg +3 -3
  53. package/lib/module/assets/images/EnochE.svg +19 -19
  54. package/lib/module/assets/images/Personalityprofile.svg +3 -3
  55. package/lib/module/assets/images/Personalitytraits.svg +3 -3
  56. package/lib/module/assets/images/Userpreferences.svg +3 -3
  57. package/lib/module/assets/images/arrow.svg +20 -20
  58. package/lib/module/assets/images/basicproficon.svg +43 -43
  59. package/lib/module/assets/images/basicprofile.svg +3 -3
  60. package/lib/module/assets/images/checkmark.svg +4 -4
  61. package/lib/module/assets/images/contentanalysis.svg +3 -3
  62. package/lib/module/assets/images/contenticon.svg +23 -23
  63. package/lib/module/assets/images/personalityicon.svg +18 -18
  64. package/lib/module/assets/images/x-close.svg +3 -3
  65. package/lib/module/components/ModalSheet.js +7 -2
  66. package/lib/module/components/ModalSheet.js.map +1 -1
  67. package/lib/module/components/OnairosButton.js +282 -0
  68. package/lib/module/components/OnairosButton.js.map +1 -0
  69. package/lib/module/components/OnairosSignInButton.js +32 -8
  70. package/lib/module/components/OnairosSignInButton.js.map +1 -1
  71. package/lib/module/components/UniversalOnboarding.js +4 -4
  72. package/lib/module/components/WelcomeScreen.js +25 -10
  73. package/lib/module/components/WelcomeScreen.js.map +1 -1
  74. package/lib/module/config/api.js +2 -2
  75. package/lib/module/hooks/useConnections.js +6 -6
  76. package/lib/module/hooks/useUserConnections.js +10 -10
  77. package/lib/module/index.js +11 -11
  78. package/lib/module/index.js.map +1 -1
  79. package/lib/module/services/apiClient.js +35 -35
  80. package/lib/module/services/apiKeyService.js +99 -99
  81. package/lib/module/services/authService.js +82 -82
  82. package/lib/module/services/biometricPinService.js +10 -10
  83. package/lib/module/services/connectedAccountsService.js +32 -32
  84. package/lib/module/services/googleAuthService.js +15 -15
  85. package/lib/module/services/imageCompressionService.js +15 -15
  86. package/lib/module/services/jwtStorageService.js +59 -59
  87. package/lib/module/services/mobileTrainingService.js +14 -14
  88. package/lib/module/services/pinEncryptionService.js +10 -10
  89. package/lib/module/services/pinStorageUtils.js +15 -15
  90. package/lib/module/services/platformAuthService.js +47 -47
  91. package/lib/module/services/storageService.js +31 -31
  92. package/lib/module/services/trainingApiHelpers.js +33 -33
  93. package/lib/module/services/userConnectionsService.js +24 -24
  94. package/lib/module/utils/Portal.js +4 -4
  95. package/lib/module/utils/api.js +24 -24
  96. package/lib/module/utils/auth.js +18 -18
  97. package/lib/module/utils/crypto.js +13 -13
  98. package/lib/module/utils/encryption.js +12 -12
  99. package/lib/module/utils/eventUtils.js +52 -52
  100. package/lib/module/utils/programmaticFlow.js +16 -16
  101. package/lib/module/utils/retryHelper.js +27 -27
  102. package/lib/typescript/components/ModalSheet.d.ts.map +1 -1
  103. package/lib/typescript/components/OnairosButton.d.ts +37 -0
  104. package/lib/typescript/components/OnairosButton.d.ts.map +1 -0
  105. package/lib/typescript/components/OnairosSignInButton.d.ts.map +1 -1
  106. package/lib/typescript/components/WelcomeScreen.d.ts.map +1 -1
  107. package/lib/typescript/index.d.ts +3 -1
  108. package/lib/typescript/index.d.ts.map +1 -1
  109. package/package.json +145 -163
  110. package/src/api/index.ts +151 -151
  111. package/src/assets/images/Checkbox.svg +3 -3
  112. package/src/assets/images/EnochE.svg +19 -19
  113. package/src/assets/images/Personalityprofile.svg +3 -3
  114. package/src/assets/images/Personalitytraits.svg +3 -3
  115. package/src/assets/images/Userpreferences.svg +3 -3
  116. package/src/assets/images/arrow.svg +20 -20
  117. package/src/assets/images/basicproficon.svg +43 -43
  118. package/src/assets/images/basicprofile.svg +3 -3
  119. package/src/assets/images/checkmark.svg +4 -4
  120. package/src/assets/images/contentanalysis.svg +3 -3
  121. package/src/assets/images/contenticon.svg +23 -23
  122. package/src/assets/images/personalityicon.svg +18 -18
  123. package/src/assets/images/x-close.svg +3 -3
  124. package/src/components/BodyText.tsx +33 -33
  125. package/src/components/BrandMark.tsx +62 -62
  126. package/src/components/CodeInput.tsx +32 -32
  127. package/src/components/DataRequestScreen.tsx +355 -355
  128. package/src/components/EmailInput.tsx +31 -31
  129. package/src/components/EmailVerificationModal.tsx +363 -363
  130. package/src/components/ExistingUserDataConfirmation.tsx +506 -506
  131. package/src/components/GoogleButton.tsx +55 -55
  132. package/src/components/HeadingGroup.tsx +49 -49
  133. package/src/components/ModalHeader.tsx +125 -125
  134. package/src/components/ModalSheet.tsx +59 -57
  135. package/src/components/Onairos.tsx +422 -422
  136. package/src/components/OnairosButton.tsx +339 -0
  137. package/src/components/OnairosSignInButton.tsx +31 -9
  138. package/src/components/Overlay.tsx +506 -506
  139. package/src/components/PersonaImage.tsx +79 -79
  140. package/src/components/PersonaLoadingScreen.tsx +201 -201
  141. package/src/components/PersonalizationConsentScreen.tsx +410 -410
  142. package/src/components/PinCreationScreen.tsx +492 -492
  143. package/src/components/PinInput.tsx +555 -555
  144. package/src/components/PlatformConnectorsStep.tsx +891 -891
  145. package/src/components/PlatformList.tsx +144 -144
  146. package/src/components/PlatformToggle.tsx +226 -226
  147. package/src/components/PrimaryButton.tsx +213 -213
  148. package/src/components/SignInMatchAnimation.tsx +225 -225
  149. package/src/components/SignInStep.tsx +217 -217
  150. package/src/components/TrainingModal.tsx +1047 -1047
  151. package/src/components/UniversalOnboarding.tsx +2887 -2887
  152. package/src/components/VerificationStep.tsx +198 -198
  153. package/src/components/WelcomeScreen.tsx +490 -473
  154. package/src/components/icons/Basicproficon.tsx +30 -30
  155. package/src/components/icons/Basicprofile.tsx +17 -17
  156. package/src/components/icons/Checkbox.tsx +17 -17
  157. package/src/components/icons/Checkmark.tsx +24 -24
  158. package/src/components/icons/Contentanalysis.tsx +17 -17
  159. package/src/components/icons/Contenticon.tsx +30 -30
  160. package/src/components/icons/EnochE.tsx +39 -39
  161. package/src/components/icons/Personalityicon.tsx +22 -22
  162. package/src/components/icons/Personalityprofile.tsx +17 -17
  163. package/src/components/icons/Personalitytraits.tsx +17 -17
  164. package/src/components/icons/Userpreferences.tsx +17 -17
  165. package/src/components/icons/index.ts +12 -12
  166. package/src/components/onboarding/OAuthWebView.tsx +232 -232
  167. package/src/config/api.ts +25 -25
  168. package/src/context/AuthContext.tsx +393 -393
  169. package/src/hooks/useConnectedAccounts.ts +138 -138
  170. package/src/hooks/useConnections.ts +161 -161
  171. package/src/hooks/useCredentials.ts +174 -174
  172. package/src/hooks/useUserConnections.ts +165 -165
  173. package/src/index.js +14 -0
  174. package/src/index.ts +99 -96
  175. package/src/services/apiClient.ts +336 -336
  176. package/src/services/apiKeyService.ts +919 -919
  177. package/src/services/authService.ts +1008 -1008
  178. package/src/services/biometricPinService.ts +192 -192
  179. package/src/services/connectedAccountsService.ts +289 -289
  180. package/src/services/googleAuthService.ts +279 -279
  181. package/src/services/imageCompressionService.ts +302 -302
  182. package/src/services/jwtStorageService.ts +256 -256
  183. package/src/services/mobileTrainingService.ts +203 -203
  184. package/src/services/pinEncryptionService.ts +75 -75
  185. package/src/services/pinStorageUtils.ts +96 -96
  186. package/src/services/platformAuthService.ts +1346 -1346
  187. package/src/services/storageService.ts +451 -451
  188. package/src/services/trainingApiHelpers.ts +66 -66
  189. package/src/services/userConnectionsService.ts +556 -556
  190. package/src/services/youtubeMigrationService.ts +453 -453
  191. package/src/theme/index.ts +239 -239
  192. package/src/types/ambient.d.ts +28 -28
  193. package/src/types/index.ts +265 -265
  194. package/src/types/node-fix.d.ts +18 -18
  195. package/src/types/node-override.d.ts +23 -23
  196. package/src/types/opacity.d.ts +15 -15
  197. package/src/types/types.d.ts +17 -17
  198. package/src/utils/Portal.tsx +82 -82
  199. package/src/utils/api.js +111 -111
  200. package/src/utils/auth.js +103 -103
  201. package/src/utils/crypto.js +59 -59
  202. package/src/utils/encryption.ts +68 -68
  203. package/src/utils/eventUtils.ts +302 -302
  204. package/src/utils/haptics.ts +58 -58
  205. package/src/utils/imagePreloader.ts +2 -2
  206. package/src/utils/programmaticFlow.ts +112 -112
  207. package/src/utils/retryHelper.ts +274 -274
@@ -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
  }