@onairos/react-native 3.0.22 → 3.0.25
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/OnairosButton.js +2 -3
- package/lib/commonjs/components/OnairosButton.js.map +1 -1
- package/lib/commonjs/components/TrainingModal.js +120 -16
- package/lib/commonjs/components/TrainingModal.js.map +1 -1
- package/lib/commonjs/components/UniversalOnboarding.js +242 -39
- package/lib/commonjs/components/UniversalOnboarding.js.map +1 -1
- package/lib/commonjs/components/onboarding/OAuthWebView.js +138 -27
- package/lib/commonjs/components/onboarding/OAuthWebView.js.map +1 -1
- package/lib/commonjs/constants/index.js +3 -2
- package/lib/commonjs/constants/index.js.map +1 -1
- package/lib/commonjs/services/platformAuthService.js +227 -0
- package/lib/commonjs/services/platformAuthService.js.map +1 -0
- package/lib/module/components/OnairosButton.js +2 -3
- package/lib/module/components/OnairosButton.js.map +1 -1
- package/lib/module/components/TrainingModal.js +120 -17
- package/lib/module/components/TrainingModal.js.map +1 -1
- package/lib/module/components/UniversalOnboarding.js +243 -40
- package/lib/module/components/UniversalOnboarding.js.map +1 -1
- package/lib/module/components/onboarding/OAuthWebView.js +139 -28
- package/lib/module/components/onboarding/OAuthWebView.js.map +1 -1
- package/lib/module/constants/index.js +3 -2
- package/lib/module/constants/index.js.map +1 -1
- package/lib/module/services/platformAuthService.js +213 -0
- package/lib/module/services/platformAuthService.js.map +1 -0
- package/lib/typescript/components/OnairosButton.d.ts.map +1 -1
- package/lib/typescript/components/TrainingModal.d.ts.map +1 -1
- package/lib/typescript/components/UniversalOnboarding.d.ts.map +1 -1
- package/lib/typescript/components/onboarding/OAuthWebView.d.ts.map +1 -1
- package/lib/typescript/constants/index.d.ts +1 -0
- package/lib/typescript/constants/index.d.ts.map +1 -1
- package/lib/typescript/services/platformAuthService.d.ts +45 -0
- package/lib/typescript/services/platformAuthService.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/components/OnairosButton.tsx +1 -4
- package/src/components/TrainingModal.tsx +202 -61
- package/src/components/UniversalOnboarding.tsx +232 -35
- package/src/components/onboarding/OAuthWebView.tsx +135 -25
- package/src/constants/index.ts +3 -2
- package/src/services/platformAuthService.ts +241 -0
|
@@ -14,13 +14,16 @@ import {
|
|
|
14
14
|
ScrollView,
|
|
15
15
|
Image,
|
|
16
16
|
Switch,
|
|
17
|
+
Linking,
|
|
17
18
|
} from 'react-native';
|
|
18
19
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
19
20
|
import { PlatformList } from './PlatformList';
|
|
20
21
|
import { PinInput } from './PinInput';
|
|
21
22
|
import { TrainingModal } from './TrainingModal';
|
|
23
|
+
import { OAuthWebView } from './onboarding/OAuthWebView';
|
|
22
24
|
import { useConnections } from '../hooks/useConnections';
|
|
23
|
-
import { COLORS } from '../constants';
|
|
25
|
+
import { COLORS, DEEP_LINK_CONFIG } from '../constants';
|
|
26
|
+
import { initiateOAuth, initiateNativeAuth, hasNativeSDK, isOAuthCallback } from '../services/platformAuthService';
|
|
24
27
|
import type { UniversalOnboardingProps, ConnectionStatus } from '../types';
|
|
25
28
|
|
|
26
29
|
const { height, width } = Dimensions.get('window');
|
|
@@ -37,7 +40,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
37
40
|
test = false,
|
|
38
41
|
preferredPlatform,
|
|
39
42
|
}) => {
|
|
40
|
-
const [step, setStep] = useState<'connect' | 'pin' | 'training'>('connect');
|
|
43
|
+
const [step, setStep] = useState<'connect' | 'pin' | 'training' | 'oauth'>('connect');
|
|
41
44
|
const [connections, setConnections] = useState<ConnectionStatus>({});
|
|
42
45
|
const [pin, setPin] = useState<string>('');
|
|
43
46
|
const [selectedTier, setSelectedTier] = useState<'Small' | 'Medium' | 'Large'>('Medium');
|
|
@@ -47,6 +50,11 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
47
50
|
}>({ progress: 0, eta: '' });
|
|
48
51
|
const [slideAnim] = useState(new Animated.Value(height));
|
|
49
52
|
const [platformToggles, setPlatformToggles] = useState<{[key: string]: boolean}>({});
|
|
53
|
+
const [oauthUrl, setOauthUrl] = useState<string>('');
|
|
54
|
+
const [currentPlatform, setCurrentPlatform] = useState<string>('');
|
|
55
|
+
const [userEmail, setUserEmail] = useState<string>('user@example.com'); // Use proper email instead of random username
|
|
56
|
+
const [isConnectingPlatform, setIsConnectingPlatform] = useState<boolean>(false);
|
|
57
|
+
const [connectingPlatform, setConnectingPlatform] = useState<string | null>(null);
|
|
50
58
|
|
|
51
59
|
const platforms = [
|
|
52
60
|
{ id: 'instagram', name: 'Instagram', icon: require('../assets/images/instagram.png') },
|
|
@@ -73,6 +81,27 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
73
81
|
bounciness: 0,
|
|
74
82
|
}).start();
|
|
75
83
|
|
|
84
|
+
// Set up deep link listener for OAuth callbacks
|
|
85
|
+
// Using the subscription pattern for React Native's Linking API
|
|
86
|
+
const subscription = Linking.addListener('url', ({ url }) => {
|
|
87
|
+
if (isOAuthCallback(url)) {
|
|
88
|
+
handleOAuthCallback(url);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Check for initial URL (app was opened via deep link)
|
|
93
|
+
Linking.getInitialURL().then(initialUrl => {
|
|
94
|
+
if (initialUrl && isOAuthCallback(initialUrl)) {
|
|
95
|
+
handleOAuthCallback(initialUrl);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Return cleanup function
|
|
100
|
+
return () => {
|
|
101
|
+
// Remove event listener using the subscription
|
|
102
|
+
subscription.remove();
|
|
103
|
+
};
|
|
104
|
+
|
|
76
105
|
// Initialize platform toggles
|
|
77
106
|
const initialToggles: { [key: string]: boolean } = {};
|
|
78
107
|
platforms.forEach((platform) => {
|
|
@@ -127,35 +156,156 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
127
156
|
setConnections(status);
|
|
128
157
|
}, [getConnectionStatus]);
|
|
129
158
|
|
|
130
|
-
const togglePlatform = useCallback((platformId: string) => {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
159
|
+
const togglePlatform = useCallback(async (platformId: string) => {
|
|
160
|
+
if (!platformToggles[platformId]) {
|
|
161
|
+
// Attempt to connect platform
|
|
162
|
+
try {
|
|
163
|
+
setIsConnectingPlatform(true);
|
|
164
|
+
setConnectingPlatform(platformId);
|
|
165
|
+
console.log(`Initiating connection for ${platformId}`);
|
|
166
|
+
|
|
167
|
+
// Check if platform has a native SDK
|
|
168
|
+
if (hasNativeSDK(platformId)) {
|
|
169
|
+
console.log(`Using native SDK for ${platformId}`);
|
|
170
|
+
// Use native SDK for authentication
|
|
171
|
+
const success = await initiateNativeAuth(platformId);
|
|
172
|
+
if (success) {
|
|
173
|
+
console.log(`Native authentication successful for ${platformId}`);
|
|
174
|
+
// Update platform toggle state
|
|
175
|
+
setPlatformToggles(prev => ({
|
|
176
|
+
...prev,
|
|
177
|
+
[platformId]: true
|
|
178
|
+
}));
|
|
179
|
+
|
|
180
|
+
// Update connections state
|
|
181
|
+
setConnections(prev => ({
|
|
182
|
+
...prev,
|
|
183
|
+
[platformId]: { userName: userEmail, connected: true }
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
console.log(`Initiating OAuth flow for ${platformId}`);
|
|
188
|
+
// Use OAuth flow through proxy server
|
|
189
|
+
const oauthUrl = await initiateOAuth(platformId, userEmail);
|
|
190
|
+
if (oauthUrl) {
|
|
191
|
+
console.log(`Received OAuth URL for ${platformId}: ${oauthUrl}`);
|
|
192
|
+
setCurrentPlatform(platformId);
|
|
193
|
+
setOauthUrl(oauthUrl);
|
|
194
|
+
setStep('oauth');
|
|
195
|
+
} else {
|
|
196
|
+
console.error(`No OAuth URL returned for ${platformId}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error(`Error connecting ${platformId}:`, error);
|
|
201
|
+
// Reset toggle state on error
|
|
202
|
+
setPlatformToggles(prev => ({
|
|
203
|
+
...prev,
|
|
204
|
+
[platformId]: false
|
|
205
|
+
}));
|
|
206
|
+
} finally {
|
|
207
|
+
setIsConnectingPlatform(false);
|
|
208
|
+
setConnectingPlatform(null);
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
// Disconnect platform
|
|
212
|
+
console.log(`Disconnecting ${platformId}`);
|
|
213
|
+
setPlatformToggles(prev => ({
|
|
214
|
+
...prev,
|
|
215
|
+
[platformId]: false
|
|
216
|
+
}));
|
|
217
|
+
|
|
218
|
+
// Update connections state
|
|
219
|
+
setConnections(prev => {
|
|
220
|
+
const newConnections = { ...prev };
|
|
221
|
+
delete newConnections[platformId];
|
|
222
|
+
return newConnections;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}, [platformToggles, userEmail]);
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Handles OAuth callback URLs
|
|
229
|
+
*/
|
|
230
|
+
const handleOAuthCallback = useCallback((url: string) => {
|
|
231
|
+
try {
|
|
232
|
+
// Extract the authorization code from the URL
|
|
233
|
+
const parsedUrl = new URL(url);
|
|
234
|
+
const code = parsedUrl.searchParams.get('code');
|
|
235
|
+
const platform = parsedUrl.searchParams.get('platform') || currentPlatform;
|
|
236
|
+
|
|
237
|
+
if (code && platform) {
|
|
238
|
+
// Update connections state
|
|
239
|
+
setConnections(prev => ({
|
|
240
|
+
...prev,
|
|
241
|
+
[platform]: { userName: userEmail, connected: true }
|
|
242
|
+
}));
|
|
243
|
+
|
|
244
|
+
// Update platform toggles
|
|
245
|
+
setPlatformToggles(prev => ({
|
|
246
|
+
...prev,
|
|
247
|
+
[platform]: true
|
|
248
|
+
}));
|
|
249
|
+
|
|
250
|
+
// Return to the connect step
|
|
251
|
+
setStep('connect');
|
|
252
|
+
}
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error('Error handling OAuth callback:', error);
|
|
255
|
+
}
|
|
256
|
+
}, [currentPlatform, userEmail]);
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Handles completion of the OAuth flow
|
|
260
|
+
*/
|
|
261
|
+
const handleOAuthSuccess = useCallback((code: string) => {
|
|
262
|
+
console.log(`OAuth success for ${currentPlatform} with code: ${code}`);
|
|
263
|
+
|
|
264
|
+
// Update connections for the current platform
|
|
265
|
+
if (currentPlatform) {
|
|
266
|
+
// Update connections state
|
|
267
|
+
setConnections(prev => ({
|
|
268
|
+
...prev,
|
|
269
|
+
[currentPlatform]: { userName: userEmail, connected: true }
|
|
270
|
+
}));
|
|
271
|
+
|
|
272
|
+
// Update platform toggles
|
|
273
|
+
setPlatformToggles(prev => ({
|
|
274
|
+
...prev,
|
|
275
|
+
[currentPlatform]: true
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
// In a real implementation, we would send the code to our server
|
|
279
|
+
// to exchange it for an access token and store it securely
|
|
280
|
+
console.log(`Simulating token exchange for ${currentPlatform}`);
|
|
281
|
+
|
|
282
|
+
// For demo purposes, we'll just simulate a successful token exchange
|
|
283
|
+
if (debug || test) {
|
|
284
|
+
console.log('Debug mode: Simulating successful token exchange');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Close OAuth window and return to connect step
|
|
289
|
+
setOauthUrl('');
|
|
290
|
+
setStep('connect');
|
|
291
|
+
}, [currentPlatform, userEmail, debug, test]);
|
|
136
292
|
|
|
137
293
|
const handlePinSubmit = useCallback(async (userPin: string) => {
|
|
138
294
|
setPin(userPin);
|
|
139
295
|
setStep('training');
|
|
140
|
-
// Simulate training progress
|
|
296
|
+
// Simulate training progress over 10 seconds
|
|
141
297
|
let progress = 0;
|
|
142
298
|
const interval = setInterval(() => {
|
|
143
|
-
progress +=
|
|
299
|
+
progress += 1; // 10% progress every second for 1 seconds total
|
|
144
300
|
setTraining({
|
|
145
301
|
progress,
|
|
146
|
-
eta: `${Math.round((1 - progress) *
|
|
302
|
+
eta: `${Math.round((1 - progress) * 10)} seconds remaining`,
|
|
147
303
|
});
|
|
148
304
|
if (progress >= 1) {
|
|
149
305
|
clearInterval(interval);
|
|
150
|
-
|
|
151
|
-
pin: userPin,
|
|
152
|
-
connections,
|
|
153
|
-
platformToggles,
|
|
154
|
-
selectedTier,
|
|
155
|
-
tierData: requestData?.[selectedTier],
|
|
156
|
-
});
|
|
306
|
+
// Training complete - TrainingModal will handle email input and final completion
|
|
157
307
|
}
|
|
158
|
-
}, 1000);
|
|
308
|
+
}, 1000); // 1 second intervals for 10 total seconds
|
|
159
309
|
}, [connections, onComplete, selectedTier, requestData, platformToggles]);
|
|
160
310
|
|
|
161
311
|
const canProceedToPin = useCallback(() => {
|
|
@@ -223,26 +373,41 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
223
373
|
|
|
224
374
|
{/* Platform connection options */}
|
|
225
375
|
<View style={styles.platformsContainer}>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
376
|
+
{platforms.map((platform) => (
|
|
377
|
+
<View key={platform.id} style={[
|
|
378
|
+
styles.platformItem,
|
|
379
|
+
connections[platform.id]?.connected && styles.platformItemConnected
|
|
380
|
+
]}>
|
|
381
|
+
<View style={styles.platformInfo}>
|
|
382
|
+
<Image
|
|
383
|
+
source={platform.icon}
|
|
384
|
+
style={styles.platformIcon}
|
|
385
|
+
resizeMode="contain"
|
|
386
|
+
/>
|
|
387
|
+
<View style={styles.platformTextContainer}>
|
|
234
388
|
<Text style={styles.platformName}>
|
|
235
389
|
{platform.name}
|
|
236
390
|
</Text>
|
|
391
|
+
{connections[platform.id]?.connected && (
|
|
392
|
+
<Text style={styles.connectedText}>Connected</Text>
|
|
393
|
+
)}
|
|
237
394
|
</View>
|
|
238
|
-
<Switch
|
|
239
|
-
value={platformToggles[platform.id]}
|
|
240
|
-
onValueChange={() => togglePlatform(platform.id)}
|
|
241
|
-
trackColor={{ false: '#767577', true: '#81b0ff' }}
|
|
242
|
-
thumbColor={platformToggles[platform.id] ? '#2196F3' : '#f4f3f4'}
|
|
243
|
-
/>
|
|
244
395
|
</View>
|
|
245
|
-
|
|
396
|
+
<View style={styles.platformActions}>
|
|
397
|
+
{connectingPlatform === platform.id ? (
|
|
398
|
+
<ActivityIndicator size="small" color={COLORS.primary} />
|
|
399
|
+
) : (
|
|
400
|
+
<Switch
|
|
401
|
+
value={platformToggles[platform.id] || connections[platform.id]?.connected || false}
|
|
402
|
+
onValueChange={() => togglePlatform(platform.id)}
|
|
403
|
+
trackColor={{ false: '#767577', true: '#81b0ff' }}
|
|
404
|
+
thumbColor={platformToggles[platform.id] || connections[platform.id]?.connected ? '#2196F3' : '#f4f3f4'}
|
|
405
|
+
disabled={connectingPlatform !== null}
|
|
406
|
+
/>
|
|
407
|
+
)}
|
|
408
|
+
</View>
|
|
409
|
+
</View>
|
|
410
|
+
))}
|
|
246
411
|
</View>
|
|
247
412
|
</ScrollView>
|
|
248
413
|
|
|
@@ -291,10 +456,24 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
291
456
|
platformToggles,
|
|
292
457
|
selectedTier,
|
|
293
458
|
tierData: requestData?.[selectedTier],
|
|
459
|
+
userEmail,
|
|
294
460
|
});
|
|
295
461
|
}}
|
|
296
462
|
modelKey="onairosTrainingModel"
|
|
297
|
-
username=
|
|
463
|
+
username={userEmail}
|
|
464
|
+
/>
|
|
465
|
+
)}
|
|
466
|
+
|
|
467
|
+
{step === 'oauth' && oauthUrl && (
|
|
468
|
+
<OAuthWebView
|
|
469
|
+
url={oauthUrl}
|
|
470
|
+
platform={currentPlatform}
|
|
471
|
+
onClose={() => {
|
|
472
|
+
setStep('connect');
|
|
473
|
+
setOauthUrl('');
|
|
474
|
+
}}
|
|
475
|
+
onSuccess={handleOAuthSuccess}
|
|
476
|
+
onComplete={() => setStep('connect')}
|
|
298
477
|
/>
|
|
299
478
|
)}
|
|
300
479
|
</SafeAreaView>
|
|
@@ -422,6 +601,24 @@ const styles = StyleSheet.create({
|
|
|
422
601
|
fontWeight: '500',
|
|
423
602
|
color: '#000',
|
|
424
603
|
},
|
|
604
|
+
platformTextContainer: {
|
|
605
|
+
flex: 1,
|
|
606
|
+
},
|
|
607
|
+
connectedText: {
|
|
608
|
+
fontSize: 12,
|
|
609
|
+
color: '#4CAF50',
|
|
610
|
+
fontWeight: '500',
|
|
611
|
+
marginTop: 2,
|
|
612
|
+
},
|
|
613
|
+
platformActions: {
|
|
614
|
+
alignItems: 'center',
|
|
615
|
+
justifyContent: 'center',
|
|
616
|
+
},
|
|
617
|
+
platformItemConnected: {
|
|
618
|
+
backgroundColor: '#F8F9FA',
|
|
619
|
+
borderColor: '#4CAF50',
|
|
620
|
+
borderWidth: 1,
|
|
621
|
+
},
|
|
425
622
|
footer: {
|
|
426
623
|
flexDirection: 'row',
|
|
427
624
|
alignItems: 'center',
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useCallback, useState } from 'react';
|
|
2
|
-
import { View, StyleSheet, ActivityIndicator, SafeAreaView, TouchableOpacity } from 'react-native';
|
|
2
|
+
import { View, StyleSheet, ActivityIndicator, SafeAreaView, TouchableOpacity, Text } from 'react-native';
|
|
3
3
|
import { WebView, WebViewNavigation } from 'react-native-webview';
|
|
4
4
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
5
5
|
import { COLORS } from '../../constants';
|
|
6
|
+
import { isOAuthSuccess, handleOAuthCallback } from '../../services/platformAuthService';
|
|
6
7
|
import type { OAuthWebViewProps } from '../../types';
|
|
7
8
|
|
|
8
9
|
export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
|
|
@@ -13,41 +14,83 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
|
|
|
13
14
|
onSuccess,
|
|
14
15
|
}) => {
|
|
15
16
|
const [loading, setLoading] = useState(true);
|
|
17
|
+
const [currentUrl, setCurrentUrl] = useState(url);
|
|
16
18
|
|
|
17
19
|
const handleNavigationStateChange = useCallback(
|
|
18
20
|
(navState: WebViewNavigation) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
console.log(`[OAuthWebView] Navigation state changed for ${platform}:`, navState.url);
|
|
22
|
+
setCurrentUrl(navState.url);
|
|
23
|
+
|
|
24
|
+
// Check for final redirect to onairos.uk/Home (indicates proxy has processed the callback)
|
|
25
|
+
if (navState.url.includes('onairos.uk/Home')) {
|
|
26
|
+
console.log(`[OAuthWebView] Detected final redirect to onairos.uk/Home for ${platform}`);
|
|
27
|
+
|
|
28
|
+
// Add a small delay to ensure the redirect is fully processed
|
|
29
|
+
setTimeout(() => {
|
|
30
|
+
onSuccess('success');
|
|
31
|
+
if (onComplete) {
|
|
32
|
+
onComplete();
|
|
33
|
+
}
|
|
34
|
+
}, 500);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check for platform-specific success patterns using the service
|
|
39
|
+
if (platform && isOAuthSuccess(navState.url, platform)) {
|
|
40
|
+
console.log(`[OAuthWebView] Detected platform-specific success pattern for ${platform}`);
|
|
41
|
+
|
|
42
|
+
// For YouTube/Google, wait for the final redirect
|
|
43
|
+
if (platform === 'youtube') {
|
|
44
|
+
console.log(`[OAuthWebView] YouTube success detected, waiting for final redirect...`);
|
|
45
|
+
// Don't close immediately for YouTube, wait for the onairos.uk/Home redirect
|
|
46
|
+
return;
|
|
25
47
|
}
|
|
26
48
|
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
// For other platforms, close immediately
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
onSuccess('success');
|
|
52
|
+
if (onComplete) {
|
|
53
|
+
onComplete();
|
|
54
|
+
}
|
|
55
|
+
}, 300);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check for callback URLs with authorization codes
|
|
60
|
+
if (navState.url.includes('/callback') || navState.url.includes('code=')) {
|
|
61
|
+
console.log(`[OAuthWebView] Detected callback URL for ${platform}:`, navState.url);
|
|
62
|
+
|
|
63
|
+
// Extract the authorization code
|
|
64
|
+
const authCode = handleOAuthCallback(navState.url);
|
|
65
|
+
if (authCode) {
|
|
66
|
+
console.log(`[OAuthWebView] Extracted auth code for ${platform}:`, authCode);
|
|
67
|
+
onSuccess(authCode);
|
|
68
|
+
if (onComplete) {
|
|
69
|
+
onComplete();
|
|
70
|
+
}
|
|
30
71
|
}
|
|
31
72
|
return;
|
|
32
73
|
}
|
|
33
74
|
},
|
|
34
|
-
[onComplete, onSuccess]
|
|
75
|
+
[platform, onComplete, onSuccess]
|
|
35
76
|
);
|
|
36
77
|
|
|
78
|
+
const handleLoadStart = useCallback(() => {
|
|
79
|
+
setLoading(true);
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
37
82
|
const handleLoadEnd = useCallback(() => {
|
|
38
83
|
setLoading(false);
|
|
39
84
|
}, []);
|
|
40
85
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
};
|
|
86
|
+
const handleError = useCallback((syntheticEvent: any) => {
|
|
87
|
+
const { nativeEvent } = syntheticEvent;
|
|
88
|
+
console.error(`[OAuthWebView] WebView error for ${platform}:`, nativeEvent);
|
|
89
|
+
setLoading(false);
|
|
90
|
+
}, [platform]);
|
|
91
|
+
|
|
92
|
+
// Get iOS Safari user agent for better compatibility
|
|
93
|
+
const userAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1';
|
|
51
94
|
|
|
52
95
|
return (
|
|
53
96
|
<SafeAreaView style={styles.container}>
|
|
@@ -61,21 +104,56 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
|
|
|
61
104
|
size={20}
|
|
62
105
|
color={getPlatformColor(platform || 'default')}
|
|
63
106
|
/>
|
|
107
|
+
<Text style={styles.platformTitle}>
|
|
108
|
+
Connect {platform ? platform.charAt(0).toUpperCase() + platform.slice(1) : 'Account'}
|
|
109
|
+
</Text>
|
|
64
110
|
</View>
|
|
111
|
+
<View style={styles.placeholder} />
|
|
65
112
|
</View>
|
|
66
113
|
|
|
67
114
|
<WebView
|
|
68
115
|
source={{ uri: url }}
|
|
69
116
|
onNavigationStateChange={handleNavigationStateChange}
|
|
117
|
+
onLoadStart={handleLoadStart}
|
|
70
118
|
onLoadEnd={handleLoadEnd}
|
|
119
|
+
onError={handleError}
|
|
71
120
|
startInLoadingState={true}
|
|
72
121
|
renderLoading={() => <View />}
|
|
73
122
|
style={styles.webView}
|
|
123
|
+
// Essential WebView configuration for OAuth
|
|
124
|
+
javaScriptEnabled={true}
|
|
125
|
+
domStorageEnabled={true}
|
|
126
|
+
cacheEnabled={true}
|
|
127
|
+
incognito={false} // Allow session persistence
|
|
128
|
+
sharedCookiesEnabled={true} // Critical for Google/YouTube OAuth
|
|
129
|
+
thirdPartyCookiesEnabled={true} // Required for OAuth flows
|
|
130
|
+
userAgent={userAgent} // iOS Safari for better compatibility
|
|
131
|
+
// Additional settings for better OAuth support
|
|
132
|
+
allowsInlineMediaPlayback={true}
|
|
133
|
+
mediaPlaybackRequiresUserAction={false}
|
|
134
|
+
allowsBackForwardNavigationGestures={true}
|
|
135
|
+
// Security settings
|
|
136
|
+
allowsLinkPreview={false}
|
|
137
|
+
allowFileAccess={false}
|
|
138
|
+
allowUniversalAccessFromFileURLs={false}
|
|
139
|
+
mixedContentMode="compatibility"
|
|
74
140
|
/>
|
|
75
141
|
|
|
76
142
|
{loading && (
|
|
77
143
|
<View style={styles.loadingContainer}>
|
|
78
144
|
<ActivityIndicator size="large" color={COLORS.primary} />
|
|
145
|
+
<Text style={styles.loadingText}>
|
|
146
|
+
Connecting to {platform ? platform.charAt(0).toUpperCase() + platform.slice(1) : 'platform'}...
|
|
147
|
+
</Text>
|
|
148
|
+
</View>
|
|
149
|
+
)}
|
|
150
|
+
|
|
151
|
+
{/* Debug info in development */}
|
|
152
|
+
{__DEV__ && (
|
|
153
|
+
<View style={styles.debugContainer}>
|
|
154
|
+
<Text style={styles.debugText} numberOfLines={1}>
|
|
155
|
+
{currentUrl}
|
|
156
|
+
</Text>
|
|
79
157
|
</View>
|
|
80
158
|
)}
|
|
81
159
|
</SafeAreaView>
|
|
@@ -95,6 +173,8 @@ const getPlatformIcon = (platform: string): string => {
|
|
|
95
173
|
return 'push-pin';
|
|
96
174
|
case 'reddit':
|
|
97
175
|
return 'forum';
|
|
176
|
+
case 'email':
|
|
177
|
+
return 'email';
|
|
98
178
|
default:
|
|
99
179
|
return 'link';
|
|
100
180
|
}
|
|
@@ -113,6 +193,8 @@ const getPlatformColor = (platform: string): string => {
|
|
|
113
193
|
return '#E60023';
|
|
114
194
|
case 'reddit':
|
|
115
195
|
return '#FF4500';
|
|
196
|
+
case 'email':
|
|
197
|
+
return '#4285F4';
|
|
116
198
|
default:
|
|
117
199
|
return COLORS.primary;
|
|
118
200
|
}
|
|
@@ -134,21 +216,49 @@ const styles = StyleSheet.create({
|
|
|
134
216
|
},
|
|
135
217
|
titleContainer: {
|
|
136
218
|
flex: 1,
|
|
219
|
+
flexDirection: 'row',
|
|
137
220
|
alignItems: 'center',
|
|
221
|
+
justifyContent: 'center',
|
|
222
|
+
},
|
|
223
|
+
platformTitle: {
|
|
224
|
+
fontSize: 16,
|
|
225
|
+
fontWeight: '600',
|
|
226
|
+
color: '#000',
|
|
227
|
+
marginLeft: 8,
|
|
138
228
|
},
|
|
139
229
|
closeButton: {
|
|
140
230
|
padding: 8,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
231
|
+
width: 40,
|
|
232
|
+
},
|
|
233
|
+
placeholder: {
|
|
234
|
+
width: 40,
|
|
144
235
|
},
|
|
145
236
|
webView: {
|
|
146
237
|
flex: 1,
|
|
147
238
|
},
|
|
148
239
|
loadingContainer: {
|
|
149
240
|
...StyleSheet.absoluteFillObject,
|
|
150
|
-
backgroundColor: 'rgba(255, 255, 255, 0.
|
|
241
|
+
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
151
242
|
alignItems: 'center',
|
|
152
243
|
justifyContent: 'center',
|
|
153
244
|
},
|
|
245
|
+
loadingText: {
|
|
246
|
+
fontSize: 16,
|
|
247
|
+
color: '#666',
|
|
248
|
+
marginTop: 16,
|
|
249
|
+
textAlign: 'center',
|
|
250
|
+
},
|
|
251
|
+
debugContainer: {
|
|
252
|
+
position: 'absolute',
|
|
253
|
+
bottom: 0,
|
|
254
|
+
left: 0,
|
|
255
|
+
right: 0,
|
|
256
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
257
|
+
padding: 8,
|
|
258
|
+
},
|
|
259
|
+
debugText: {
|
|
260
|
+
color: '#fff',
|
|
261
|
+
fontSize: 12,
|
|
262
|
+
fontFamily: 'monospace',
|
|
263
|
+
},
|
|
154
264
|
});
|
package/src/constants/index.ts
CHANGED