@onairos/react-native 3.0.69 → 3.0.70

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.
@@ -177,49 +177,87 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
177
177
 
178
178
  // Check for specific NSURLErrorDomain codes
179
179
  let errorMessage = 'Failed to load OAuth page.';
180
+ let isRetryable = true;
180
181
 
181
182
  if (nativeEvent.domain === 'NSURLErrorDomain') {
182
183
  switch (nativeEvent.code) {
183
184
  case -1009: // NSURLErrorNotConnectedToInternet
184
185
  errorMessage = 'No internet connection. Please check your network and try again.';
186
+ isRetryable = true;
185
187
  break;
186
188
  case -1003: // NSURLErrorCannotFindHost
187
189
  errorMessage = 'Cannot reach authentication server. Please check your internet connection.';
190
+ isRetryable = true;
188
191
  break;
189
192
  case -1001: // NSURLErrorTimedOut
190
193
  errorMessage = 'Connection timed out. Please try again.';
194
+ isRetryable = true;
191
195
  break;
192
196
  case -1200: // NSURLErrorSecureConnectionFailed
193
197
  errorMessage = 'Secure connection failed. Please try again.';
198
+ isRetryable = true;
194
199
  break;
195
200
  case -1022: // NSURLErrorAppTransportSecurityRequiresSecureConnection
196
201
  errorMessage = 'App Transport Security error. Connection must be secure.';
202
+ isRetryable = false;
197
203
  break;
198
204
  case -1004: // NSURLErrorCannotConnectToHost
199
205
  errorMessage = 'Cannot connect to authentication server. Please try again later.';
206
+ isRetryable = true;
207
+ break;
208
+ case -1005: // NSURLErrorNetworkConnectionLost
209
+ errorMessage = 'Network connection lost. Please check your connection and try again.';
210
+ isRetryable = true;
211
+ break;
212
+ case -1020: // NSURLErrorDataNotAllowed
213
+ errorMessage = 'Data not allowed. Please check your cellular data settings.';
214
+ isRetryable = true;
200
215
  break;
201
216
  default:
202
217
  errorMessage = `Network error (${nativeEvent.code}): ${nativeEvent.description || 'Please check your connection and try again.'}`;
218
+ isRetryable = true;
219
+ }
220
+ } else if (nativeEvent.domain === 'WebKitErrorDomain') {
221
+ switch (nativeEvent.code) {
222
+ case 102: // WebKitErrorFrameLoadInterruptedByPolicyChange
223
+ errorMessage = 'Page load was interrupted. Please try again.';
224
+ isRetryable = true;
225
+ break;
226
+ case 204: // WebKitErrorPlugInLoadFailed
227
+ errorMessage = 'Plugin failed to load. Please try again.';
228
+ isRetryable = true;
229
+ break;
230
+ default:
231
+ errorMessage = `WebKit error (${nativeEvent.code}): ${nativeEvent.description || 'Please try again.'}`;
232
+ isRetryable = true;
203
233
  }
204
234
  }
205
235
 
206
236
  console.error('🔴 OAuth WebView Error:', errorMessage);
207
237
 
208
- if (retryCount < 2) {
209
- console.log(`🔄 Retrying OAuth load (attempt ${retryCount + 1}/2)`);
238
+ // Implement smart retry logic
239
+ if (isRetryable && retryCount < 3) {
240
+ console.log(`🔄 Auto-retrying OAuth load (attempt ${retryCount + 1}/3) in 3 seconds...`);
210
241
  setRetryCount(prev => prev + 1);
211
242
  setIsLoading(true);
212
243
  setError(null);
213
244
 
214
- // Retry after a short delay
245
+ // Retry after a delay with exponential backoff
246
+ const retryDelay = 3000 * Math.pow(2, retryCount); // 3s, 6s, 12s
215
247
  setTimeout(() => {
216
248
  if (webViewRef.current) {
249
+ console.log(`🔄 Attempting retry ${retryCount + 1}...`);
217
250
  webViewRef.current.reload();
218
251
  }
219
- }, 3000);
252
+ }, retryDelay);
220
253
  } else {
221
254
  setError(errorMessage);
222
- setIsLoading(false);
255
+ setIsLoading(false);
256
+
257
+ // Show additional help for persistent errors
258
+ if (retryCount >= 3) {
259
+ console.log('❌ Maximum retries reached, showing error to user');
260
+ }
223
261
  }
224
262
  };
225
263
 
@@ -303,14 +341,66 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
303
341
  <Icon name="error-outline" size={48} color="#FF6B6B" />
304
342
  <Text style={styles.errorTitle}>Connection Error</Text>
305
343
  <Text style={styles.errorMessage}>{error}</Text>
344
+
345
+ {/* Help text based on error type */}
346
+ {error.includes('internet') || error.includes('network') ? (
347
+ <View style={styles.helpContainer}>
348
+ <Text style={styles.helpTitle}>💡 Troubleshooting Steps:</Text>
349
+ <Text style={styles.helpText}>• Check your Wi-Fi or cellular connection</Text>
350
+ <Text style={styles.helpText}>• Try switching between Wi-Fi and cellular</Text>
351
+ <Text style={styles.helpText}>• Ensure you're not using a VPN that blocks OAuth</Text>
352
+ <Text style={styles.helpText}>• Close other apps that might be using bandwidth</Text>
353
+ </View>
354
+ ) : error.includes('timeout') ? (
355
+ <View style={styles.helpContainer}>
356
+ <Text style={styles.helpTitle}>⏱️ Connection Timeout:</Text>
357
+ <Text style={styles.helpText}>• Your connection might be slow</Text>
358
+ <Text style={styles.helpText}>• Try again when you have a stronger signal</Text>
359
+ <Text style={styles.helpText}>• Use "Open in Browser" for better reliability</Text>
360
+ </View>
361
+ ) : error.includes('secure') || error.includes('SSL') ? (
362
+ <View style={styles.helpContainer}>
363
+ <Text style={styles.helpTitle}>🔒 Security Error:</Text>
364
+ <Text style={styles.helpText}>• Check your device's date and time settings</Text>
365
+ <Text style={styles.helpText}>• Update your device's operating system</Text>
366
+ <Text style={styles.helpText}>• Try "Open in Browser" as an alternative</Text>
367
+ </View>
368
+ ) : (
369
+ <View style={styles.helpContainer}>
370
+ <Text style={styles.helpTitle}>🔧 General Solutions:</Text>
371
+ <Text style={styles.helpText}>• Close and reopen the app</Text>
372
+ <Text style={styles.helpText}>• Check your internet connection</Text>
373
+ <Text style={styles.helpText}>• Use "Open in Browser" for better reliability</Text>
374
+ </View>
375
+ )}
376
+
306
377
  <View style={styles.buttonContainer}>
307
378
  <TouchableOpacity style={styles.retryButton} onPress={handleRetry}>
379
+ <Icon name="refresh" size={20} color="#fff" style={styles.buttonIcon} />
308
380
  <Text style={styles.retryButtonText}>Retry</Text>
309
381
  </TouchableOpacity>
310
382
  <TouchableOpacity style={styles.browserButton} onPress={handleOpenInBrowser}>
383
+ <Icon name="open-in-browser" size={20} color="#666" style={styles.buttonIcon} />
311
384
  <Text style={styles.browserButtonText}>Open in Browser</Text>
312
- </TouchableOpacity>
385
+ </TouchableOpacity>
313
386
  </View>
387
+
388
+ {retryCount >= 3 && (
389
+ <View style={styles.persistentErrorContainer}>
390
+ <Text style={styles.persistentErrorText}>
391
+ Still having trouble? Try these steps:
392
+ </Text>
393
+ <Text style={styles.persistentErrorStep}>
394
+ 1. Use "Open in Browser" for more reliable authentication
395
+ </Text>
396
+ <Text style={styles.persistentErrorStep}>
397
+ 2. Check if other apps can access the internet
398
+ </Text>
399
+ <Text style={styles.persistentErrorStep}>
400
+ 3. Restart your device if the problem persists
401
+ </Text>
402
+ </View>
403
+ )}
314
404
  </View>
315
405
  </SafeAreaView>
316
406
  );
@@ -469,6 +559,9 @@ const styles = StyleSheet.create({
469
559
  paddingHorizontal: 24,
470
560
  paddingVertical: 12,
471
561
  borderRadius: 8,
562
+ flexDirection: 'row',
563
+ alignItems: 'center',
564
+ justifyContent: 'center',
472
565
  },
473
566
  webView: {
474
567
  flex: 1,
@@ -509,6 +602,9 @@ const styles = StyleSheet.create({
509
602
  paddingVertical: 12,
510
603
  borderRadius: 8,
511
604
  marginRight: 10,
605
+ flexDirection: 'row',
606
+ alignItems: 'center',
607
+ justifyContent: 'center',
512
608
  },
513
609
  retryButtonText: {
514
610
  color: '#fff',
@@ -526,4 +622,39 @@ const styles = StyleSheet.create({
526
622
  fontSize: 16,
527
623
  fontWeight: '600',
528
624
  },
625
+ helpContainer: {
626
+ marginBottom: 20,
627
+ },
628
+ helpTitle: {
629
+ fontSize: 18,
630
+ fontWeight: '600',
631
+ color: '#333',
632
+ marginBottom: 8,
633
+ },
634
+ helpText: {
635
+ fontSize: 14,
636
+ color: '#666',
637
+ textAlign: 'center',
638
+ },
639
+ buttonIcon: {
640
+ marginRight: 10,
641
+ },
642
+ persistentErrorContainer: {
643
+ marginTop: 20,
644
+ padding: 20,
645
+ backgroundColor: '#f8f8f8',
646
+ borderRadius: 8,
647
+ },
648
+ persistentErrorText: {
649
+ fontSize: 16,
650
+ fontWeight: '600',
651
+ color: '#333',
652
+ marginBottom: 10,
653
+ },
654
+ persistentErrorStep: {
655
+ fontSize: 14,
656
+ color: '#666',
657
+ textAlign: 'center',
658
+ marginBottom: 5,
659
+ },
529
660
  });
@@ -100,6 +100,17 @@ export const initiateOAuth = async (platform: string, username: string, appName?
100
100
  console.log('🌐 Platform uses OAuth WebView flow');
101
101
  console.log('🔗 Auth endpoint:', PLATFORM_AUTH_CONFIG[platform].authEndpoint);
102
102
 
103
+ // Pre-flight connectivity check
104
+ console.log('🔍 Performing pre-flight connectivity check...');
105
+ const connectivityResult = await testApiConnectivity();
106
+
107
+ if (!connectivityResult.success) {
108
+ console.error('❌ Pre-flight connectivity check failed:', connectivityResult.error);
109
+ throw new Error(`Cannot reach authentication server: ${connectivityResult.error}`);
110
+ }
111
+
112
+ console.log('✅ Pre-flight connectivity check passed');
113
+
103
114
  // Handle Instagram with specific API format
104
115
  if (platform === 'instagram') {
105
116
  const state = 'djksbfds';
@@ -112,13 +123,31 @@ export const initiateOAuth = async (platform: string, username: string, appName?
112
123
 
113
124
  console.log('📤 Sending Instagram OAuth request:', jsonData);
114
125
 
115
- const response = await fetch('https://api2.onairos.uk/instagram/authorize', {
116
- method: 'POST',
117
- headers: {
118
- 'Content-Type': 'application/json',
119
- },
120
- body: JSON.stringify(jsonData),
121
- });
126
+ const controller = new AbortController();
127
+ const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
128
+
129
+ let response: Response;
130
+ try {
131
+ response = await fetch('https://api2.onairos.uk/instagram/authorize', {
132
+ method: 'POST',
133
+ headers: {
134
+ 'Content-Type': 'application/json',
135
+ 'User-Agent': 'OnairosReactNative/1.0',
136
+ },
137
+ body: JSON.stringify(jsonData),
138
+ signal: controller.signal,
139
+ });
140
+ } catch (fetchError) {
141
+ clearTimeout(timeoutId);
142
+
143
+ if (fetchError.name === 'AbortError') {
144
+ throw new Error(`Request timeout: Instagram OAuth server took too long to respond`);
145
+ }
146
+
147
+ throw new Error(`Network error: ${fetchError.message || 'Failed to connect to Instagram OAuth server'}`);
148
+ }
149
+
150
+ clearTimeout(timeoutId);
122
151
 
123
152
  console.log('📡 Instagram OAuth response status:', response.status);
124
153
  console.log('📡 Instagram OAuth response headers:', response.headers);
@@ -134,7 +163,13 @@ export const initiateOAuth = async (platform: string, username: string, appName?
134
163
 
135
164
  if (responseData.instagramURL) {
136
165
  console.log('✅ Instagram OAuth URL received:', responseData.instagramURL);
137
- return responseData.instagramURL;
166
+
167
+ // Validate the URL before returning
168
+ if (await validateOAuthUrl(responseData.instagramURL)) {
169
+ return responseData.instagramURL;
170
+ } else {
171
+ throw new Error('Received invalid Instagram OAuth URL');
172
+ }
138
173
  }
139
174
 
140
175
  console.error('❌ No Instagram URL found in response');
@@ -152,14 +187,32 @@ export const initiateOAuth = async (platform: string, username: string, appName?
152
187
 
153
188
  console.log(`📤 Sending ${platform} OAuth request:`, jsonData);
154
189
 
155
- // Make the request to get the OAuth URL
156
- const response = await fetch(PLATFORM_AUTH_CONFIG[platform].authEndpoint, {
157
- method: 'POST',
158
- headers: {
159
- 'Content-Type': 'application/json',
160
- },
161
- body: JSON.stringify(jsonData),
162
- });
190
+ // Make the request to get the OAuth URL with enhanced error handling
191
+ const controller = new AbortController();
192
+ const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
193
+
194
+ let response: Response;
195
+ try {
196
+ response = await fetch(PLATFORM_AUTH_CONFIG[platform].authEndpoint, {
197
+ method: 'POST',
198
+ headers: {
199
+ 'Content-Type': 'application/json',
200
+ 'User-Agent': 'OnairosReactNative/1.0',
201
+ },
202
+ body: JSON.stringify(jsonData),
203
+ signal: controller.signal,
204
+ });
205
+ } catch (fetchError) {
206
+ clearTimeout(timeoutId);
207
+
208
+ if (fetchError.name === 'AbortError') {
209
+ throw new Error(`Request timeout: ${platform} OAuth server took too long to respond`);
210
+ }
211
+
212
+ throw new Error(`Network error: ${fetchError.message || 'Failed to connect to OAuth server'}`);
213
+ }
214
+
215
+ clearTimeout(timeoutId);
163
216
 
164
217
  console.log(`📡 ${platform} OAuth response status:`, response.status);
165
218
  console.log(`📡 ${platform} OAuth response headers:`, response.headers);
@@ -181,36 +234,108 @@ export const initiateOAuth = async (platform: string, username: string, appName?
181
234
  }
182
235
 
183
236
  // Check if the response contains the OAuth URL based on platform
237
+ let oauthUrl: string | null = null;
238
+
184
239
  switch (platform) {
185
240
  case 'reddit':
186
- if (data.redditURL) return data.redditURL;
241
+ oauthUrl = data.redditURL;
187
242
  break;
188
243
  case 'pinterest':
189
- if (data.pinterestURL) return data.pinterestURL;
244
+ oauthUrl = data.pinterestURL;
190
245
  break;
191
246
  case 'youtube':
192
- if (data.youtubeURL) return data.youtubeURL;
247
+ oauthUrl = data.youtubeURL;
193
248
  break;
194
249
  case 'email':
195
250
  // Gmail might return under different field names
196
- if (data.emailURL) return data.emailURL;
197
- if (data.gmailURL) return data.gmailURL;
198
- if (data.authUrl) return data.authUrl;
199
- if (data.url) return data.url;
251
+ oauthUrl = data.emailURL || data.gmailURL || data.authUrl || data.url;
200
252
  break;
201
253
  default:
202
- if (data.url) return data.url;
254
+ oauthUrl = data.url;
203
255
  break;
204
256
  }
205
257
 
206
- console.error(`❌ No OAuth URL found in response for ${platform}. Response:`, data);
207
- throw new Error(`No OAuth URL found in response for ${platform}`);
258
+ if (!oauthUrl) {
259
+ console.error(`❌ No OAuth URL found in response for ${platform}. Response:`, data);
260
+ throw new Error(`No OAuth URL found in response for ${platform}`);
261
+ }
262
+
263
+ console.log(`✅ ${platform} OAuth URL received:`, oauthUrl);
264
+
265
+ // Validate the URL before returning
266
+ if (await validateOAuthUrl(oauthUrl)) {
267
+ return oauthUrl;
268
+ } else {
269
+ throw new Error(`Received invalid ${platform} OAuth URL`);
270
+ }
208
271
  } catch (error) {
209
272
  console.error(`Error initiating OAuth for ${platform}:`, error);
210
273
  throw error;
211
274
  }
212
275
  };
213
276
 
277
+ /**
278
+ * Validates an OAuth URL to ensure it's reachable
279
+ * @param url The OAuth URL to validate
280
+ * @returns Promise<boolean> indicating if the URL is valid and reachable
281
+ */
282
+ const validateOAuthUrl = async (url: string): Promise<boolean> => {
283
+ try {
284
+ console.log('🔍 Validating OAuth URL:', url);
285
+
286
+ // Basic URL format validation
287
+ if (!url || typeof url !== 'string') {
288
+ console.error('❌ Invalid URL format:', url);
289
+ return false;
290
+ }
291
+
292
+ // Check if URL starts with https
293
+ if (!url.startsWith('https://')) {
294
+ console.error('❌ URL must use HTTPS:', url);
295
+ return false;
296
+ }
297
+
298
+ // Try to parse the URL
299
+ try {
300
+ new URL(url);
301
+ } catch (parseError) {
302
+ console.error('❌ URL parsing failed:', parseError);
303
+ return false;
304
+ }
305
+
306
+ // Make a HEAD request to check if the URL is reachable
307
+ const controller = new AbortController();
308
+ const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
309
+
310
+ try {
311
+ const response = await fetch(url, {
312
+ method: 'HEAD',
313
+ signal: controller.signal,
314
+ });
315
+
316
+ clearTimeout(timeoutId);
317
+
318
+ // Accept any response that's not a network error
319
+ // OAuth pages might return various status codes
320
+ console.log('✅ OAuth URL validation successful, status:', response.status);
321
+ return true;
322
+ } catch (fetchError) {
323
+ clearTimeout(timeoutId);
324
+
325
+ if (fetchError.name === 'AbortError') {
326
+ console.warn('⚠️ OAuth URL validation timeout, but proceeding anyway');
327
+ return true; // Allow timeout as URLs might be slow to respond
328
+ }
329
+
330
+ console.warn('⚠️ OAuth URL validation failed, but proceeding anyway:', fetchError.message);
331
+ return true; // Allow network errors as they might be temporary
332
+ }
333
+ } catch (error) {
334
+ console.warn('⚠️ OAuth URL validation error, but proceeding anyway:', error);
335
+ return true; // Allow validation errors to not block the flow
336
+ }
337
+ };
338
+
214
339
  /**
215
340
  * Initiates the native SDK authentication flow for a platform
216
341
  * @param platform The platform to authenticate with
@@ -473,22 +598,62 @@ export const testApiConnectivity = async (): Promise<{ success: boolean; error?:
473
598
  try {
474
599
  console.log('🔍 Testing connectivity to Onairos API...');
475
600
 
476
- const response = await fetch('https://api2.onairos.uk/health', {
477
- method: 'GET',
478
- headers: {
479
- 'Content-Type': 'application/json',
480
- },
481
- });
601
+ // Test multiple endpoints for better reliability
602
+ const testEndpoints = [
603
+ 'https://api2.onairos.uk/health',
604
+ 'https://api2.onairos.uk', // Base URL
605
+ ];
482
606
 
483
- console.log('🌐 API connectivity test response:', response.status);
607
+ let lastError: string | null = null;
484
608
 
485
- if (response.ok) {
486
- console.log('✅ API server is reachable');
487
- return { success: true };
488
- } else {
489
- console.log('⚠️ API server responded with error:', response.status);
490
- return { success: false, error: `API server error: ${response.status}` };
609
+ for (const endpoint of testEndpoints) {
610
+ try {
611
+ console.log(`🔍 Testing endpoint: ${endpoint}`);
612
+
613
+ const controller = new AbortController();
614
+ const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
615
+
616
+ const response = await fetch(endpoint, {
617
+ method: 'GET',
618
+ headers: {
619
+ 'Content-Type': 'application/json',
620
+ 'User-Agent': 'OnairosReactNative/1.0',
621
+ },
622
+ signal: controller.signal,
623
+ });
624
+
625
+ clearTimeout(timeoutId);
626
+
627
+ console.log(`🌐 API connectivity test response for ${endpoint}:`, response.status);
628
+
629
+ if (response.ok || response.status === 404) {
630
+ // Accept 404 as it means the server is reachable
631
+ console.log('✅ API server is reachable');
632
+ return { success: true };
633
+ } else {
634
+ console.log(`⚠️ API server responded with status ${response.status} for ${endpoint}`);
635
+ lastError = `API server error: ${response.status}`;
636
+ // Continue to next endpoint
637
+ }
638
+ } catch (fetchError) {
639
+ console.log(`❌ Failed to reach ${endpoint}:`, fetchError);
640
+
641
+ if (fetchError.name === 'AbortError') {
642
+ lastError = 'Connection timeout - API server took too long to respond';
643
+ } else if (fetchError.message.includes('Network request failed')) {
644
+ lastError = 'Network error - check your internet connection';
645
+ } else if (fetchError.message.includes('not connected to the internet')) {
646
+ lastError = 'No internet connection';
647
+ } else {
648
+ lastError = `Network error: ${fetchError.message}`;
649
+ }
650
+
651
+ // Continue to next endpoint
652
+ }
491
653
  }
654
+
655
+ console.error('❌ All API connectivity tests failed');
656
+ return { success: false, error: lastError || 'Cannot reach API server' };
492
657
  } catch (error) {
493
658
  console.error('❌ API connectivity test failed:', error);
494
659
  return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };