@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.
- package/lib/commonjs/components/onboarding/OAuthWebView.js +228 -30
- package/lib/commonjs/components/onboarding/OAuthWebView.js.map +1 -1
- package/lib/commonjs/services/platformAuthService.js +1 -1
- package/lib/commonjs/services/platformAuthService.js.map +1 -1
- package/lib/module/components/onboarding/OAuthWebView.js +228 -30
- package/lib/module/components/onboarding/OAuthWebView.js.map +1 -1
- package/lib/module/services/platformAuthService.js +1 -1
- package/lib/module/services/platformAuthService.js.map +1 -1
- package/lib/typescript/components/onboarding/OAuthWebView.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/onboarding/OAuthWebView.tsx +234 -28
- package/src/services/platformAuthService.ts +1 -1
|
@@ -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
|
-
//
|
|
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
|
-
|
|
138
|
-
|
|
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
|
-
<
|
|
209
|
-
<
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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: '
|
|
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,
|