@onairos/react-native 3.0.34 → 3.0.36

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.
@@ -32,44 +32,94 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
32
32
  }) => {
33
33
  const [isLoading, setIsLoading] = useState(true);
34
34
  const [error, setError] = useState<string | null>(null);
35
+ const [retryCount, setRetryCount] = useState(0);
35
36
  const webViewRef = useRef<WebView>(null);
37
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
36
38
 
37
39
  console.log('Opening OAuth WebView with URL:', url);
38
40
 
41
+ // Set up timeout for OAuth flow
42
+ React.useEffect(() => {
43
+ // Clear any existing timeout
44
+ if (timeoutRef.current) {
45
+ clearTimeout(timeoutRef.current);
46
+ }
47
+
48
+ // Set a 60-second timeout for OAuth completion
49
+ timeoutRef.current = setTimeout(() => {
50
+ console.log('OAuth flow timeout - no completion detected');
51
+ setError('OAuth flow is taking too long. Please try again or use the browser option.');
52
+ setIsLoading(false);
53
+ }, 60000);
54
+
55
+ return () => {
56
+ if (timeoutRef.current) {
57
+ clearTimeout(timeoutRef.current);
58
+ }
59
+ };
60
+ }, [url]);
61
+
62
+ // Clear timeout when component unmounts or OAuth completes
63
+ React.useEffect(() => {
64
+ return () => {
65
+ if (timeoutRef.current) {
66
+ clearTimeout(timeoutRef.current);
67
+ }
68
+ };
69
+ }, []);
70
+
39
71
  const handleNavigationStateChange = (navState: any) => {
40
72
  console.log(`Navigation state changed for ${platform}:`, navState.url);
41
73
 
74
+ // Check for error states first
75
+ if (navState.url.includes('error=') || navState.url.includes('access_denied')) {
76
+ console.log('OAuth error detected:', navState.url);
77
+ setError('Authentication was cancelled or failed. Please try again.');
78
+ return;
79
+ }
80
+
42
81
  // Check for the final redirect to onairos.uk domain (this means backend callback completed)
43
82
  const isFinalRedirect = (
44
83
  navState.url.includes('onairos.uk/Home') ||
45
84
  navState.url.includes('onairos.uk/home') ||
46
85
  navState.url.includes('onairos.uk/success') ||
47
- navState.url.startsWith('https://onairos.uk/Home')
86
+ navState.url.startsWith('https://onairos.uk/Home') ||
87
+ navState.url.includes('onairos.uk') && navState.url.includes('success')
48
88
  );
49
89
 
50
- // Platform-specific success patterns
90
+ // Enhanced platform-specific success patterns
51
91
  const platformSuccessPatterns: Record<string, RegExp[]> = {
52
92
  reddit: [
53
93
  /reddit\.com\/api\/v1\/authorize\?done=true/,
54
- /reddit\.com\/api\/v1\/authorize\/success/
94
+ /reddit\.com\/api\/v1\/authorize\/success/,
95
+ /reddit\.com.*code=/
55
96
  ],
56
97
  pinterest: [
57
98
  /pinterest\.com\/oauth\/success/,
58
- /pinterest\.com\/oauth\/complete/
99
+ /pinterest\.com\/oauth\/complete/,
100
+ /pinterest\.com.*code=/,
101
+ /api2\.onairos\.uk\/pinterest\/callback/,
102
+ /onairos\.uk.*pinterest/
59
103
  ],
60
104
  linkedin: [
61
105
  /linkedin\.com\/oauth\/success/,
62
106
  /linkedin\.com\/oauth\/complete/,
63
- /linkedin\.com\/uas\/oauth2\/authorization\/success/
107
+ /linkedin\.com\/uas\/oauth2\/authorization\/success/,
108
+ /linkedin\.com.*code=/
64
109
  ],
65
- email: [/success/],
110
+ email: [/success/, /complete/],
66
111
  instagram: [
67
112
  /instagram\.com\/oauth\/authorize\?done=true/,
68
- /instagram\.com\/oauth\/success/
113
+ /instagram\.com\/oauth\/success/,
114
+ /instagram\.com.*code=/,
115
+ /api2\.onairos\.uk\/instagram\/callback/,
116
+ /onairos\.uk.*instagram/
69
117
  ],
70
118
  youtube: [
71
119
  /accounts\.google\.com\/o\/oauth2\/approval/,
72
- /youtube\.com\/oauth\/success/
120
+ /youtube\.com\/oauth\/success/,
121
+ /accounts\.google\.com.*code=/,
122
+ /api2\.onairos\.uk\/youtube\/callback/
73
123
  ]
74
124
  };
75
125
 
@@ -83,7 +133,9 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
83
133
  navState.url.includes('/callback') ||
84
134
  navState.url.includes('code=') ||
85
135
  navState.url.includes('token=') ||
86
- navState.url.includes('access_token=')
136
+ navState.url.includes('access_token=') ||
137
+ navState.url.includes('api2.onairos.uk') ||
138
+ navState.url.includes('oauth_token=')
87
139
  );
88
140
 
89
141
  // Extract authorization code or token if present
@@ -94,18 +146,38 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
94
146
  // Try to extract code or token using different patterns
95
147
  const codeMatch = navState.url.match(/code=([^&]+)/);
96
148
  const tokenMatch = navState.url.match(/(?:token|access_token)=([^&]+)/);
149
+ const oauthTokenMatch = navState.url.match(/oauth_token=([^&]+)/);
97
150
 
98
151
  if (codeMatch && codeMatch[1]) {
99
- authCode = codeMatch[1];
152
+ authCode = decodeURIComponent(codeMatch[1]);
100
153
  console.log('OAuth code extracted:', authCode);
101
154
  } else if (tokenMatch && tokenMatch[1]) {
102
- authCode = tokenMatch[1];
155
+ authCode = decodeURIComponent(tokenMatch[1]);
156
+ console.log('OAuth token extracted:', authCode);
157
+ } else if (oauthTokenMatch && oauthTokenMatch[1]) {
158
+ authCode = decodeURIComponent(oauthTokenMatch[1]);
103
159
  console.log('OAuth token extracted:', authCode);
104
160
  }
105
161
 
106
162
  // Call onSuccess with the extracted code/token
107
163
  if (authCode) {
164
+ console.log(`Calling onSuccess for ${platform} with code:`, authCode);
165
+
166
+ // Clear timeout since OAuth completed successfully
167
+ if (timeoutRef.current) {
168
+ clearTimeout(timeoutRef.current);
169
+ }
170
+
108
171
  onSuccess(authCode);
172
+
173
+ // Close the OAuth window after a short delay
174
+ setTimeout(() => {
175
+ if (onComplete) {
176
+ console.log('Calling onComplete to close OAuth window');
177
+ onComplete();
178
+ }
179
+ }, 1000);
180
+ return;
109
181
  }
110
182
  }
111
183
 
@@ -115,6 +187,7 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
115
187
 
116
188
  // If we haven't already extracted a code/token, consider this a generic success
117
189
  if (!authCode) {
190
+ console.log(`Calling onSuccess for ${platform} with generic success`);
118
191
  onSuccess('success');
119
192
  }
120
193
 
@@ -124,6 +197,19 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
124
197
  onComplete();
125
198
  }
126
199
  }
200
+
201
+ // Handle specific redirect patterns that indicate completion
202
+ if (navState.url.includes('api2.onairos.uk') &&
203
+ (navState.url.includes('callback') || navState.url.includes('success'))) {
204
+ console.log(`Backend callback detected for ${platform}`);
205
+ onSuccess('backend_callback_success');
206
+
207
+ setTimeout(() => {
208
+ if (onComplete) {
209
+ onComplete();
210
+ }
211
+ }, 1500);
212
+ }
127
213
  };
128
214
 
129
215
  const handleLoadEnd = () => {
@@ -134,16 +220,44 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
134
220
  const handleError = (syntheticEvent: any) => {
135
221
  const { nativeEvent } = syntheticEvent;
136
222
  console.error('WebView error:', nativeEvent);
137
- setError('Failed to load OAuth page. Please try again.');
138
- setIsLoading(false);
223
+
224
+ if (retryCount < 2) {
225
+ console.log(`Retrying OAuth load (attempt ${retryCount + 1})`);
226
+ setRetryCount(prev => prev + 1);
227
+ setIsLoading(true);
228
+ setError(null);
229
+
230
+ // Retry after a short delay
231
+ setTimeout(() => {
232
+ if (webViewRef.current) {
233
+ webViewRef.current.reload();
234
+ }
235
+ }, 2000);
236
+ } else {
237
+ setError('Failed to load OAuth page. Please try again or use the browser option.');
238
+ setIsLoading(false);
239
+ }
139
240
  };
140
241
 
141
242
  const handleOpenInBrowser = () => {
243
+ console.log('Opening OAuth URL in external browser:', url);
142
244
  Linking.openURL(url).catch(err => {
143
245
  console.error('Failed to open URL in browser:', err);
246
+ setError('Unable to open browser. Please check your device settings.');
144
247
  });
145
248
  };
146
249
 
250
+ const handleRetry = () => {
251
+ console.log('Manual retry requested');
252
+ setError(null);
253
+ setIsLoading(true);
254
+ setRetryCount(0);
255
+
256
+ if (webViewRef.current) {
257
+ webViewRef.current.reload();
258
+ }
259
+ };
260
+
147
261
  /**
148
262
  * Get platform-specific icon
149
263
  */
@@ -205,9 +319,14 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
205
319
  <Icon name="error-outline" size={48} color="#FF6B6B" />
206
320
  <Text style={styles.errorTitle}>Connection Error</Text>
207
321
  <Text style={styles.errorMessage}>{error}</Text>
208
- <TouchableOpacity style={styles.retryButton} onPress={handleOpenInBrowser}>
209
- <Text style={styles.retryButtonText}>Open in Browser</Text>
210
- </TouchableOpacity>
322
+ <View style={styles.buttonContainer}>
323
+ <TouchableOpacity style={styles.retryButton} onPress={handleRetry}>
324
+ <Text style={styles.retryButtonText}>Retry</Text>
325
+ </TouchableOpacity>
326
+ <TouchableOpacity style={styles.browserButton} onPress={handleOpenInBrowser}>
327
+ <Text style={styles.browserButtonText}>Open in Browser</Text>
328
+ </TouchableOpacity>
329
+ </View>
211
330
  </View>
212
331
  </SafeAreaView>
213
332
  );
@@ -238,18 +357,91 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
238
357
  onNavigationStateChange={handleNavigationStateChange}
239
358
  onLoadEnd={handleLoadEnd}
240
359
  onError={handleError}
360
+ onHttpError={(syntheticEvent) => {
361
+ const { nativeEvent } = syntheticEvent;
362
+ console.error('WebView HTTP error:', nativeEvent);
363
+ setError('Failed to load OAuth page. Please check your internet connection.');
364
+ setIsLoading(false);
365
+ }}
241
366
  style={styles.webView}
242
367
  javaScriptEnabled={true}
243
368
  domStorageEnabled={true}
244
- userAgent="Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1"
369
+ startInLoadingState={true}
370
+ scalesPageToFit={true}
371
+ allowsInlineMediaPlayback={true}
372
+ mediaPlaybackRequiresUserAction={false}
373
+ mixedContentMode="compatibility"
374
+ thirdPartyCookiesEnabled={true}
375
+ sharedCookiesEnabled={true}
376
+ cacheEnabled={true}
377
+ allowsBackForwardNavigationGestures={true}
378
+ allowsLinkPreview={false}
379
+ userAgent="Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1"
380
+ onShouldStartLoadWithRequest={(request) => {
381
+ console.log('WebView attempting to load:', request.url);
382
+
383
+ // Block about:srcdoc and other problematic URLs
384
+ if (request.url.startsWith('about:') ||
385
+ request.url.startsWith('data:') ||
386
+ request.url === 'about:blank') {
387
+ console.log('Blocking problematic URL:', request.url);
388
+ return false;
389
+ }
390
+
391
+ // Allow all other requests
392
+ return true;
393
+ }}
394
+ onLoadStart={(syntheticEvent) => {
395
+ const { nativeEvent } = syntheticEvent;
396
+ console.log('WebView load started:', nativeEvent.url);
397
+ setIsLoading(true);
398
+ }}
399
+ onLoadProgress={(syntheticEvent) => {
400
+ const { nativeEvent } = syntheticEvent;
401
+ console.log('WebView load progress:', nativeEvent.progress);
402
+ }}
403
+ renderError={(errorName) => (
404
+ <View style={styles.errorContainer}>
405
+ <Icon name="error-outline" size={48} color="#FF6B6B" />
406
+ <Text style={styles.errorTitle}>WebView Error</Text>
407
+ <Text style={styles.errorMessage}>
408
+ Failed to load the authentication page. Error: {errorName}
409
+ </Text>
410
+ <TouchableOpacity style={styles.retryButton} onPress={handleRetry}>
411
+ <Text style={styles.retryButtonText}>Retry</Text>
412
+ </TouchableOpacity>
413
+ </View>
414
+ )}
415
+ renderLoading={() => (
416
+ <View style={styles.loadingContainer}>
417
+ <ActivityIndicator size="large" color={COLORS.primary} />
418
+ <Text style={styles.loadingText}>Loading {platform}...</Text>
419
+ </View>
420
+ )}
421
+ injectedJavaScript={`
422
+ // Prevent white screen issues
423
+ window.addEventListener('error', function(e) {
424
+ console.log('JavaScript error:', e.message);
425
+ });
426
+
427
+ // Handle OAuth redirects
428
+ window.addEventListener('beforeunload', function(e) {
429
+ console.log('Page unloading:', window.location.href);
430
+ });
431
+
432
+ // Monitor for OAuth completion
433
+ setInterval(function() {
434
+ if (window.location.href.includes('code=') ||
435
+ window.location.href.includes('access_token=') ||
436
+ window.location.href.includes('oauth_token=') ||
437
+ window.location.href.includes('api2.onairos.uk')) {
438
+ console.log('OAuth completion detected:', window.location.href);
439
+ }
440
+ }, 1000);
441
+
442
+ true; // Required for injected JavaScript
443
+ `}
245
444
  />
246
-
247
- {isLoading && (
248
- <View style={styles.loadingContainer}>
249
- <ActivityIndicator size="large" color={COLORS.primary} />
250
- <Text style={styles.loadingText}>Loading {platform}...</Text>
251
- </View>
252
- )}
253
445
  </SafeAreaView>
254
446
  );
255
447
  };
@@ -287,10 +479,12 @@ const styles = StyleSheet.create({
287
479
  zIndex: 10,
288
480
  },
289
481
  browserButton: {
290
- padding: 8,
291
- position: 'absolute',
292
- right: 16,
293
- zIndex: 10,
482
+ backgroundColor: 'transparent',
483
+ borderWidth: 1,
484
+ borderColor: '#666',
485
+ paddingHorizontal: 24,
486
+ paddingVertical: 12,
487
+ borderRadius: 8,
294
488
  },
295
489
  webView: {
296
490
  flex: 1,
@@ -330,10 +524,22 @@ const styles = StyleSheet.create({
330
524
  paddingHorizontal: 24,
331
525
  paddingVertical: 12,
332
526
  borderRadius: 8,
527
+ marginRight: 10,
333
528
  },
334
529
  retryButtonText: {
335
530
  color: '#fff',
336
531
  fontSize: 16,
337
532
  fontWeight: '600',
338
533
  },
534
+ buttonContainer: {
535
+ flexDirection: 'row',
536
+ justifyContent: 'center',
537
+ alignItems: 'center',
538
+ marginTop: 10,
539
+ },
540
+ browserButtonText: {
541
+ color: '#666',
542
+ fontSize: 16,
543
+ fontWeight: '600',
544
+ },
339
545
  });
@@ -175,7 +175,7 @@ export const initiateNativeAuth = async (platform: string): Promise<boolean> =>
175
175
  // Configure Google Sign-In
176
176
  await GoogleSignin.configure({
177
177
  scopes: ['https://www.googleapis.com/auth/youtube.readonly'],
178
- webClientId: 'YOUR_WEB_CLIENT_ID', // Replace with your actual web client ID
178
+ webClientId: 'YOUR_ACTUAL_WEB_CLIENT_ID.apps.googleusercontent.com', // Replace with your actual web client ID
179
179
  offlineAccess: true,
180
180
  hostedDomain: '',
181
181
  forceCodeForRefreshToken: true,