@onairos/react-native 3.0.47 → 3.0.50
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/UniversalOnboarding.js +76 -18
- package/lib/commonjs/components/UniversalOnboarding.js.map +1 -1
- package/lib/commonjs/index.js +29 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/services/platformAuthService.js +350 -51
- package/lib/commonjs/services/platformAuthService.js.map +1 -1
- package/lib/module/components/UniversalOnboarding.js +77 -19
- package/lib/module/components/UniversalOnboarding.js.map +1 -1
- package/lib/module/index.js +11 -9
- package/lib/module/index.js.map +1 -1
- package/lib/module/services/platformAuthService.js +345 -50
- package/lib/module/services/platformAuthService.js.map +1 -1
- package/lib/typescript/components/UniversalOnboarding.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/services/platformAuthService.d.ts +28 -1
- package/lib/typescript/services/platformAuthService.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +5 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/components/UniversalOnboarding.tsx +75 -17
- package/src/index.ts +7 -0
- package/src/services/platformAuthService.ts +351 -51
- package/src/types.ts +6 -1
|
@@ -24,7 +24,7 @@ import { TrainingModal } from './TrainingModal';
|
|
|
24
24
|
import { OAuthWebView } from './onboarding/OAuthWebView';
|
|
25
25
|
import { useConnections } from '../hooks/useConnections';
|
|
26
26
|
import { COLORS, DEEP_LINK_CONFIG } from '../constants';
|
|
27
|
-
import { initiateOAuth, initiateNativeAuth, hasNativeSDK, isOAuthCallback, testApiConnectivity } from '../services/platformAuthService';
|
|
27
|
+
import { initiateOAuth, initiateNativeAuth, hasNativeSDK, isOAuthCallback, testApiConnectivity, handleOAuthCallbackUrl, refreshYouTubeTokens } from '../services/platformAuthService';
|
|
28
28
|
import type { UniversalOnboardingProps, ConnectionStatus } from '../types';
|
|
29
29
|
import {
|
|
30
30
|
init as opacityInit,
|
|
@@ -45,6 +45,9 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
45
45
|
debug = false,
|
|
46
46
|
test = false,
|
|
47
47
|
preferredPlatform,
|
|
48
|
+
inferenceData,
|
|
49
|
+
auto = false,
|
|
50
|
+
partner,
|
|
48
51
|
}) => {
|
|
49
52
|
const [step, setStep] = useState<'connect' | 'pin' | 'training' | 'oauth' | 'success'>('connect');
|
|
50
53
|
const [connections, setConnections] = useState<ConnectionStatus>({});
|
|
@@ -119,6 +122,13 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
119
122
|
// Debug mode for Expo Go
|
|
120
123
|
if (debug || Platform.OS === 'web') {
|
|
121
124
|
console.log('Debug mode enabled - Using mock data for onboarding');
|
|
125
|
+
console.log('Configuration:', {
|
|
126
|
+
auto,
|
|
127
|
+
partner,
|
|
128
|
+
hasInferenceData: !!inferenceData,
|
|
129
|
+
inferenceDataType: typeof inferenceData,
|
|
130
|
+
});
|
|
131
|
+
|
|
122
132
|
// Pre-populate with mock connections in debug mode
|
|
123
133
|
if (test || Platform.OS === 'web') {
|
|
124
134
|
setConnections({
|
|
@@ -264,12 +274,11 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
264
274
|
throw new Error('Invalid or empty Instagram profile data returned from Opacity SDK');
|
|
265
275
|
}
|
|
266
276
|
} else {
|
|
267
|
-
// For all other platforms (non-Instagram),
|
|
268
|
-
// Check if platform has a native SDK
|
|
277
|
+
// For all other platforms (non-Instagram), check if they have native SDK
|
|
269
278
|
if (hasNativeSDK(platformId)) {
|
|
270
279
|
console.log(`📱 Using native SDK for ${platformId}`);
|
|
271
280
|
// Use native SDK for authentication
|
|
272
|
-
const success = await initiateNativeAuth(platformId);
|
|
281
|
+
const success = await initiateNativeAuth(platformId, username);
|
|
273
282
|
if (success) {
|
|
274
283
|
console.log(`✅ Native authentication successful for ${platformId}`);
|
|
275
284
|
// Update platform toggle state
|
|
@@ -283,11 +292,15 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
283
292
|
...prev,
|
|
284
293
|
[platformId]: { userName: username, connected: true }
|
|
285
294
|
}));
|
|
295
|
+
} else {
|
|
296
|
+
throw new Error(`Native authentication failed for ${platformId}`);
|
|
286
297
|
}
|
|
287
298
|
} else {
|
|
299
|
+
// Use OAuth WebView flow
|
|
288
300
|
console.log(`🌐 Initiating OAuth flow for ${platformId}`);
|
|
289
|
-
|
|
301
|
+
|
|
290
302
|
const oauthUrl = await initiateOAuth(platformId, username, AppName);
|
|
303
|
+
|
|
291
304
|
if (oauthUrl) {
|
|
292
305
|
console.log(`✅ Received OAuth URL for ${platformId}:`, oauthUrl);
|
|
293
306
|
setCurrentPlatform(platformId);
|
|
@@ -295,7 +308,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
295
308
|
setStep('oauth');
|
|
296
309
|
} else {
|
|
297
310
|
console.error(`❌ No OAuth URL returned for ${platformId}`);
|
|
298
|
-
|
|
311
|
+
throw new Error(`Failed to get authorization URL for ${platformId}. Please try again.`);
|
|
299
312
|
}
|
|
300
313
|
}
|
|
301
314
|
}
|
|
@@ -347,10 +360,35 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
347
360
|
* Handles OAuth callback URLs
|
|
348
361
|
*/
|
|
349
362
|
const handleOAuthCallback = useCallback((url: string) => {
|
|
350
|
-
console.log('OAuth callback received:', url);
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
363
|
+
console.log('🔗 OAuth callback received:', url);
|
|
364
|
+
|
|
365
|
+
const result = handleOAuthCallbackUrl(url);
|
|
366
|
+
|
|
367
|
+
if (result.success && result.platform && result.code) {
|
|
368
|
+
console.log(`✅ OAuth successful for ${result.platform}`);
|
|
369
|
+
|
|
370
|
+
// Update connections state
|
|
371
|
+
setConnections(prev => ({
|
|
372
|
+
...prev,
|
|
373
|
+
[result.platform!]: { userName: username, connected: true }
|
|
374
|
+
}));
|
|
375
|
+
|
|
376
|
+
// Update platform toggles
|
|
377
|
+
setPlatformToggles(prev => ({
|
|
378
|
+
...prev,
|
|
379
|
+
[result.platform!]: true
|
|
380
|
+
}));
|
|
381
|
+
|
|
382
|
+
// Close OAuth window and return to connect step
|
|
383
|
+
setOauthUrl('');
|
|
384
|
+
setCurrentPlatform('');
|
|
385
|
+
setStep('connect');
|
|
386
|
+
|
|
387
|
+
console.log(`🎉 ${result.platform} successfully connected via OAuth`);
|
|
388
|
+
} else {
|
|
389
|
+
console.error('❌ OAuth callback failed or incomplete');
|
|
390
|
+
}
|
|
391
|
+
}, [username]);
|
|
354
392
|
|
|
355
393
|
/**
|
|
356
394
|
* Handles completion of the OAuth flow
|
|
@@ -427,6 +465,8 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
427
465
|
username,
|
|
428
466
|
timestamp: Date.now(),
|
|
429
467
|
appName: AppName,
|
|
468
|
+
inferenceData: auto ? inferenceData : undefined,
|
|
469
|
+
partner,
|
|
430
470
|
};
|
|
431
471
|
|
|
432
472
|
// Store session data in secure storage for future use
|
|
@@ -455,23 +495,41 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
455
495
|
});
|
|
456
496
|
if (progress >= 1) {
|
|
457
497
|
clearInterval(interval);
|
|
458
|
-
|
|
459
|
-
|
|
498
|
+
|
|
499
|
+
// Prepare completion data
|
|
500
|
+
const completionData = {
|
|
460
501
|
pin: userPin,
|
|
461
502
|
connections,
|
|
462
503
|
platformToggles,
|
|
463
504
|
selectedTier,
|
|
464
505
|
tierData: requestData?.[selectedTier],
|
|
465
|
-
sessionSaved: true,
|
|
466
|
-
|
|
506
|
+
sessionSaved: true,
|
|
507
|
+
// Add inference data if auto mode is enabled
|
|
508
|
+
...(auto && inferenceData && { inferenceData }),
|
|
509
|
+
// Add partner info for special partners
|
|
510
|
+
...(partner && { partner: partner === 'couplebible' ? 'CoupleBible' : partner }),
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
console.log('Completion data prepared:', completionData);
|
|
514
|
+
|
|
515
|
+
// Close overlay and call the original onComplete callback
|
|
516
|
+
onComplete('https://api2.onairos.uk', 'dummy-token', completionData);
|
|
467
517
|
}
|
|
468
518
|
}, 1000); // Update every 1 second
|
|
469
|
-
}, [connections, onComplete, selectedTier, requestData, platformToggles, username, AppName]);
|
|
519
|
+
}, [connections, onComplete, selectedTier, requestData, platformToggles, username, AppName, auto, inferenceData, partner]);
|
|
470
520
|
|
|
471
521
|
const canProceedToPin = useCallback(() => {
|
|
472
522
|
// Check if at least one platform is toggled on
|
|
473
|
-
|
|
474
|
-
|
|
523
|
+
const hasPlatformConnected = Object.values(platformToggles).some(value => value === true);
|
|
524
|
+
|
|
525
|
+
// If auto mode is enabled and partner is not "couplebible", require inferenceData
|
|
526
|
+
if (auto && partner !== 'couplebible' && !inferenceData) {
|
|
527
|
+
console.warn('Auto mode enabled but no inference data provided (and partner is not couplebible)');
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return hasPlatformConnected;
|
|
532
|
+
}, [platformToggles, auto, partner, inferenceData]);
|
|
475
533
|
|
|
476
534
|
const handleProceed = () => {
|
|
477
535
|
console.log('Proceeding to next step');
|
package/src/index.ts
CHANGED
|
@@ -70,6 +70,13 @@ export {
|
|
|
70
70
|
storePlatformConnection,
|
|
71
71
|
} from './services/oauthService';
|
|
72
72
|
|
|
73
|
+
export {
|
|
74
|
+
updateGoogleClientIds,
|
|
75
|
+
refreshYouTubeTokens,
|
|
76
|
+
hasNativeSDK,
|
|
77
|
+
testApiConnectivity,
|
|
78
|
+
} from './services/platformAuthService';
|
|
79
|
+
|
|
73
80
|
// Export API and Services
|
|
74
81
|
export { onairosApi } from './api';
|
|
75
82
|
export { OAuthService } from './services/oauthService';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Platform, Linking } from 'react-native';
|
|
2
|
-
import
|
|
2
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
3
3
|
|
|
4
4
|
// Define types for platform auth configuration
|
|
5
5
|
interface PlatformAuthConfig {
|
|
@@ -7,20 +7,28 @@ interface PlatformAuthConfig {
|
|
|
7
7
|
nativeSDKPackage?: string;
|
|
8
8
|
authEndpoint: string;
|
|
9
9
|
color: string;
|
|
10
|
+
clientId?: string;
|
|
11
|
+
redirectUri?: string;
|
|
12
|
+
scope?: string;
|
|
13
|
+
responseType?: string;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
// Configuration for each platform's authentication
|
|
13
|
-
|
|
17
|
+
let PLATFORM_AUTH_CONFIG: Record<string, PlatformAuthConfig> = {
|
|
14
18
|
instagram: {
|
|
15
19
|
hasNativeSDK: false, // Instagram uses OAuth WebView flow
|
|
16
20
|
authEndpoint: 'https://api2.onairos.uk/instagram/authorize',
|
|
17
21
|
color: '#E1306C',
|
|
18
22
|
},
|
|
19
23
|
youtube: {
|
|
20
|
-
hasNativeSDK: true, //
|
|
24
|
+
hasNativeSDK: true, // Native Google Sign-In SDK enabled
|
|
21
25
|
nativeSDKPackage: '@react-native-google-signin/google-signin',
|
|
22
26
|
authEndpoint: 'https://api2.onairos.uk/youtube/authorize',
|
|
23
27
|
color: '#FF0000',
|
|
28
|
+
clientId: '1030678346906-lovkuds2ouqmoc8eu5qpo98spa6edv4o.apps.googleusercontent.com',
|
|
29
|
+
redirectUri: 'onairosevents://auth/callback',
|
|
30
|
+
scope: 'https://www.googleapis.com/auth/youtube.readonly',
|
|
31
|
+
responseType: 'code',
|
|
24
32
|
},
|
|
25
33
|
reddit: {
|
|
26
34
|
hasNativeSDK: false,
|
|
@@ -197,64 +205,190 @@ export const initiateOAuth = async (platform: string, username: string, appName?
|
|
|
197
205
|
* @param platform The platform to authenticate with
|
|
198
206
|
* @returns A Promise that resolves to the authentication result
|
|
199
207
|
*/
|
|
200
|
-
export const initiateNativeAuth = async (platform: string): Promise<boolean> => {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
208
|
+
export const initiateNativeAuth = async (platform: string, username?: string): Promise<boolean> => {
|
|
209
|
+
if (platform === 'youtube') {
|
|
210
|
+
console.log('🔗 Initiating native Google Sign-In for YouTube');
|
|
211
|
+
try {
|
|
212
|
+
// Import Google Sign-In dynamically to avoid errors if not installed
|
|
213
|
+
const { GoogleSignin, statusCodes } = require('@react-native-google-signin/google-signin');
|
|
214
|
+
|
|
215
|
+
// Configure Google Sign-In
|
|
216
|
+
await GoogleSignin.configure({
|
|
217
|
+
webClientId: '1030678346906-lovkuds2ouqmoc8eu5qpo98spa6edv4o.apps.googleusercontent.com', // Replace with your web client ID
|
|
218
|
+
iosClientId: '1030678346906-lovkuds2ouqmoc8eu5qpo98spa6edv4o.apps.googleusercontent.com', // Replace with your iOS client ID
|
|
219
|
+
scopes: ['https://www.googleapis.com/auth/youtube.readonly'],
|
|
220
|
+
offlineAccess: true, // CRITICAL: This ensures we get refresh tokens
|
|
221
|
+
hostedDomain: '',
|
|
222
|
+
forceCodeForRefreshToken: true, // CRITICAL: Force refresh token on first sign-in
|
|
223
|
+
accountName: '', // Clear to avoid conflicts
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Check if Google Play Services are available
|
|
227
|
+
await GoogleSignin.hasPlayServices();
|
|
228
|
+
|
|
229
|
+
// Sign in with Google
|
|
230
|
+
const userInfo = await GoogleSignin.signIn();
|
|
231
|
+
console.log('✅ Google Sign-In successful:', userInfo.user?.email);
|
|
232
|
+
|
|
233
|
+
// Get access token for API calls
|
|
234
|
+
const tokens = await GoogleSignin.getTokens();
|
|
235
|
+
console.log('🔑 Got Google tokens');
|
|
236
|
+
|
|
237
|
+
// Get current user info with refresh token
|
|
238
|
+
const currentUser = await GoogleSignin.getCurrentUser();
|
|
239
|
+
console.log('👤 Current user info:', currentUser?.user?.email);
|
|
205
240
|
|
|
241
|
+
// Extract refresh token from server auth code
|
|
242
|
+
let refreshToken = null;
|
|
243
|
+
if (currentUser?.serverAuthCode) {
|
|
244
|
+
console.log('🔄 Server auth code available for refresh token');
|
|
245
|
+
refreshToken = currentUser.serverAuthCode;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!refreshToken) {
|
|
249
|
+
console.warn('⚠️ No refresh token available - token refresh may fail later');
|
|
250
|
+
} else {
|
|
251
|
+
console.log('✅ Refresh token available for YouTube connection');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Fetch YouTube channel information
|
|
255
|
+
let channelName = 'Unknown Channel';
|
|
256
|
+
let channelId = null;
|
|
206
257
|
try {
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
webClientId: 'YOUR_ACTUAL_WEB_CLIENT_ID.apps.googleusercontent.com', // Replace with your actual web client ID
|
|
214
|
-
offlineAccess: true,
|
|
215
|
-
hostedDomain: '',
|
|
216
|
-
forceCodeForRefreshToken: true,
|
|
258
|
+
console.log('📺 Fetching YouTube channel information...');
|
|
259
|
+
const channelResponse = await fetch('https://www.googleapis.com/youtube/v3/channels?part=snippet&mine=true', {
|
|
260
|
+
headers: {
|
|
261
|
+
'Authorization': `Bearer ${tokens.accessToken}`,
|
|
262
|
+
'Accept': 'application/json'
|
|
263
|
+
}
|
|
217
264
|
});
|
|
218
265
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// Get access token
|
|
227
|
-
const tokens = await GoogleSignin.getTokens();
|
|
228
|
-
console.log('Google tokens:', tokens);
|
|
229
|
-
|
|
230
|
-
// Here you would typically send the tokens to your backend
|
|
231
|
-
// to associate the YouTube account with the user
|
|
232
|
-
|
|
233
|
-
return true;
|
|
234
|
-
} catch (error: any) {
|
|
235
|
-
console.error('Google Sign-In error:', error);
|
|
236
|
-
|
|
237
|
-
const { statusCodes: StatusCodes } = require('@react-native-google-signin/google-signin');
|
|
238
|
-
|
|
239
|
-
if (error.code === StatusCodes.SIGN_IN_CANCELLED) {
|
|
240
|
-
console.log('User cancelled the sign-in flow');
|
|
241
|
-
} else if (error.code === StatusCodes.IN_PROGRESS) {
|
|
242
|
-
console.log('Sign-in is in progress already');
|
|
243
|
-
} else if (error.code === StatusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
|
|
244
|
-
console.log('Play services not available or outdated');
|
|
245
|
-
} else {
|
|
246
|
-
console.log('Some other error happened');
|
|
266
|
+
if (channelResponse.ok) {
|
|
267
|
+
const channelData = await channelResponse.json();
|
|
268
|
+
if (channelData.items && channelData.items.length > 0) {
|
|
269
|
+
channelName = channelData.items[0].snippet.title;
|
|
270
|
+
channelId = channelData.items[0].id;
|
|
271
|
+
console.log('✅ YouTube channel found:', channelName);
|
|
272
|
+
}
|
|
247
273
|
}
|
|
274
|
+
} catch (channelError) {
|
|
275
|
+
console.log('⚠️ Error fetching YouTube channel info:', channelError);
|
|
276
|
+
channelName = userInfo.user?.name || userInfo.user?.email || 'Unknown Channel';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Get authentication info
|
|
280
|
+
let authToken = await AsyncStorage.getItem('onairos_jwt_token') ||
|
|
281
|
+
await AsyncStorage.getItem('enoch_token') ||
|
|
282
|
+
await AsyncStorage.getItem('auth_token');
|
|
283
|
+
const storedUsername = await AsyncStorage.getItem('onairos_username');
|
|
284
|
+
const finalUsername = storedUsername || username || userInfo.user?.email || 'youtube_user';
|
|
285
|
+
|
|
286
|
+
// Create auth token if needed
|
|
287
|
+
if (!authToken || authToken.trim().length < 20) {
|
|
288
|
+
console.log('🔐 Creating authentication token for YouTube...');
|
|
248
289
|
|
|
290
|
+
try {
|
|
291
|
+
const fallbackEmail = userInfo.user?.email || `${finalUsername}@youtube.temp`;
|
|
292
|
+
|
|
293
|
+
// Create user accounts and get JWT token
|
|
294
|
+
const onairosSignupResponse = await fetch('https://api2.onairos.uk/register/enoch', {
|
|
295
|
+
method: 'POST',
|
|
296
|
+
headers: {
|
|
297
|
+
'Content-Type': 'application/json'
|
|
298
|
+
},
|
|
299
|
+
body: JSON.stringify({
|
|
300
|
+
email: fallbackEmail,
|
|
301
|
+
username: finalUsername,
|
|
302
|
+
name: finalUsername
|
|
303
|
+
})
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (onairosSignupResponse.ok) {
|
|
307
|
+
const onairosResponseData = await onairosSignupResponse.json();
|
|
308
|
+
|
|
309
|
+
// Extract token from response
|
|
310
|
+
authToken = onairosResponseData.token || onairosResponseData.data?.token || onairosResponseData.jwt;
|
|
311
|
+
|
|
312
|
+
if (authToken) {
|
|
313
|
+
// Store tokens
|
|
314
|
+
await AsyncStorage.setItem('onairos_jwt_token', authToken);
|
|
315
|
+
await AsyncStorage.setItem('enoch_token', authToken);
|
|
316
|
+
await AsyncStorage.setItem('auth_token', authToken);
|
|
317
|
+
await AsyncStorage.setItem('onairos_username', onairosResponseData.username || finalUsername);
|
|
318
|
+
|
|
319
|
+
console.log('✅ Successfully created and stored authentication token');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} catch (tokenError) {
|
|
323
|
+
console.warn('⚠️ Error creating auth token:', tokenError);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
console.log('🔗 Linking YouTube data to user:', finalUsername);
|
|
328
|
+
console.log('📺 YouTube channel name:', channelName);
|
|
329
|
+
|
|
330
|
+
// Send tokens to backend for YouTube data processing
|
|
331
|
+
const backendResponse = await fetch('https://api2.onairos.uk/youtube/native-auth', {
|
|
332
|
+
method: 'POST',
|
|
333
|
+
headers: {
|
|
334
|
+
'Content-Type': 'application/json',
|
|
335
|
+
...(authToken && { 'Authorization': `Bearer ${authToken}` })
|
|
336
|
+
},
|
|
337
|
+
body: JSON.stringify({
|
|
338
|
+
session: {
|
|
339
|
+
username: finalUsername,
|
|
340
|
+
platform: 'youtube',
|
|
341
|
+
timestamp: new Date().toISOString(),
|
|
342
|
+
channelName: channelName,
|
|
343
|
+
channelId: channelId
|
|
344
|
+
},
|
|
345
|
+
googleUser: userInfo.user,
|
|
346
|
+
accessToken: tokens.accessToken,
|
|
347
|
+
idToken: tokens.idToken,
|
|
348
|
+
refreshToken: refreshToken, // CRITICAL: Include refresh token
|
|
349
|
+
serverAuthCode: currentUser?.serverAuthCode,
|
|
350
|
+
userAccountInfo: {
|
|
351
|
+
username: finalUsername,
|
|
352
|
+
email: userInfo.user?.email,
|
|
353
|
+
authToken: authToken,
|
|
354
|
+
channelName: channelName,
|
|
355
|
+
channelId: channelId,
|
|
356
|
+
userIdentifier: authToken ? `user-${authToken.substring(0, 10)}` : `youtube-${userInfo.user?.email}`,
|
|
357
|
+
googleId: userInfo.user?.id,
|
|
358
|
+
},
|
|
359
|
+
tokenExpiry: new Date(Date.now() + 3600 * 1000).toISOString(), // 1 hour from now
|
|
360
|
+
requestRefreshToken: true
|
|
361
|
+
})
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
if (backendResponse.ok) {
|
|
365
|
+
const responseData = await backendResponse.json();
|
|
366
|
+
console.log('✅ YouTube connection processed by backend:', responseData);
|
|
367
|
+
return true;
|
|
368
|
+
} else {
|
|
369
|
+
const errorData = await backendResponse.text();
|
|
370
|
+
console.error('❌ Backend processing failed:', backendResponse.status, errorData);
|
|
249
371
|
return false;
|
|
250
372
|
}
|
|
373
|
+
|
|
374
|
+
} catch (error: any) {
|
|
375
|
+
console.error('❌ Google Sign-In error:', error);
|
|
376
|
+
|
|
377
|
+
const { statusCodes: StatusCodes } = require('@react-native-google-signin/google-signin');
|
|
378
|
+
|
|
379
|
+
if (error.code === StatusCodes.SIGN_IN_CANCELLED) {
|
|
380
|
+
console.log('User cancelled Google Sign-In');
|
|
381
|
+
} else if (error.code === StatusCodes.IN_PROGRESS) {
|
|
382
|
+
console.log('Google Sign-In already in progress');
|
|
383
|
+
} else if (error.code === StatusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
|
|
384
|
+
console.log('Google Play Services not available');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return false;
|
|
251
388
|
}
|
|
252
|
-
|
|
253
|
-
throw new Error(`Native authentication not supported for ${platform}`);
|
|
254
|
-
} catch (error) {
|
|
255
|
-
console.error(`Error initiating native auth for ${platform}:`, error);
|
|
256
|
-
throw error;
|
|
257
389
|
}
|
|
390
|
+
|
|
391
|
+
return false;
|
|
258
392
|
};
|
|
259
393
|
|
|
260
394
|
/**
|
|
@@ -313,3 +447,169 @@ export const testApiConnectivity = async (): Promise<{ success: boolean; error?:
|
|
|
313
447
|
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
314
448
|
}
|
|
315
449
|
};
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* 🔄 REFRESH GOOGLE TOKENS
|
|
453
|
+
*/
|
|
454
|
+
export const refreshGoogleTokens = async (): Promise<{ accessToken: string; idToken?: string } | null> => {
|
|
455
|
+
try {
|
|
456
|
+
console.log('🔄 Attempting to refresh Google tokens...');
|
|
457
|
+
|
|
458
|
+
const { GoogleSignin } = require('@react-native-google-signin/google-signin');
|
|
459
|
+
|
|
460
|
+
const currentUser = await GoogleSignin.getCurrentUser();
|
|
461
|
+
if (!currentUser) {
|
|
462
|
+
console.log('❌ User not signed in to Google, cannot refresh tokens');
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const tokens = await GoogleSignin.getTokens();
|
|
467
|
+
console.log('✅ Successfully refreshed Google tokens');
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
accessToken: tokens.accessToken,
|
|
471
|
+
idToken: tokens.idToken
|
|
472
|
+
};
|
|
473
|
+
} catch (error) {
|
|
474
|
+
console.error('❌ Failed to refresh Google tokens:', error);
|
|
475
|
+
|
|
476
|
+
// If refresh fails, try to sign in again
|
|
477
|
+
try {
|
|
478
|
+
console.log('🔄 Refresh failed, attempting re-authentication...');
|
|
479
|
+
const { GoogleSignin } = require('@react-native-google-signin/google-signin');
|
|
480
|
+
const userInfo = await GoogleSignin.signIn();
|
|
481
|
+
const tokens = await GoogleSignin.getTokens();
|
|
482
|
+
|
|
483
|
+
console.log('✅ Re-authentication successful');
|
|
484
|
+
return {
|
|
485
|
+
accessToken: tokens.accessToken,
|
|
486
|
+
idToken: tokens.idToken
|
|
487
|
+
};
|
|
488
|
+
} catch (signInError) {
|
|
489
|
+
console.error('❌ Re-authentication also failed:', signInError);
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* 🔄 REFRESH YOUTUBE TOKENS
|
|
497
|
+
*/
|
|
498
|
+
export const refreshYouTubeTokens = async (): Promise<boolean> => {
|
|
499
|
+
try {
|
|
500
|
+
console.log('🔄 Refreshing YouTube tokens...');
|
|
501
|
+
|
|
502
|
+
// Get fresh tokens from Google SDK
|
|
503
|
+
const freshTokens = await refreshGoogleTokens();
|
|
504
|
+
if (!freshTokens) {
|
|
505
|
+
console.error('❌ Failed to get fresh tokens from Google SDK');
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Get current user info
|
|
510
|
+
const { GoogleSignin } = require('@react-native-google-signin/google-signin');
|
|
511
|
+
const currentUser = await GoogleSignin.getCurrentUser();
|
|
512
|
+
if (!currentUser) {
|
|
513
|
+
console.error('❌ No current Google user found');
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Get stored auth token
|
|
518
|
+
const authToken = await AsyncStorage.getItem('onairos_jwt_token') ||
|
|
519
|
+
await AsyncStorage.getItem('enoch_token') ||
|
|
520
|
+
await AsyncStorage.getItem('auth_token');
|
|
521
|
+
|
|
522
|
+
if (!authToken) {
|
|
523
|
+
console.error('❌ No auth token found for YouTube refresh');
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Send refreshed tokens to backend
|
|
528
|
+
const refreshResponse = await fetch('https://api2.onairos.uk/youtube/refresh-token', {
|
|
529
|
+
method: 'POST',
|
|
530
|
+
headers: {
|
|
531
|
+
'Content-Type': 'application/json',
|
|
532
|
+
'Authorization': `Bearer ${authToken}`
|
|
533
|
+
},
|
|
534
|
+
body: JSON.stringify({
|
|
535
|
+
accessToken: freshTokens.accessToken,
|
|
536
|
+
idToken: freshTokens.idToken,
|
|
537
|
+
refreshToken: currentUser.serverAuthCode,
|
|
538
|
+
userEmail: currentUser.user?.email,
|
|
539
|
+
tokenExpiry: new Date(Date.now() + 3600 * 1000).toISOString(),
|
|
540
|
+
timestamp: new Date().toISOString()
|
|
541
|
+
})
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
if (refreshResponse.ok) {
|
|
545
|
+
const responseData = await refreshResponse.json();
|
|
546
|
+
console.log('✅ YouTube tokens refreshed successfully:', responseData);
|
|
547
|
+
return true;
|
|
548
|
+
} else {
|
|
549
|
+
const errorData = await refreshResponse.text();
|
|
550
|
+
console.error('❌ YouTube token refresh failed:', refreshResponse.status, errorData);
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
} catch (error) {
|
|
554
|
+
console.error('❌ Error refreshing YouTube tokens:', error);
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* 🎯 ENHANCED OAUTH CALLBACK HANDLER
|
|
561
|
+
*/
|
|
562
|
+
export const handleOAuthCallbackUrl = (url: string): { platform?: string; code?: string; success: boolean } => {
|
|
563
|
+
try {
|
|
564
|
+
console.log('🔍 Processing OAuth callback URL:', url);
|
|
565
|
+
|
|
566
|
+
// Parse the URL
|
|
567
|
+
const parsedUrl = new URL(url);
|
|
568
|
+
|
|
569
|
+
// Extract platform and code
|
|
570
|
+
const platform = parsedUrl.searchParams.get('platform') ||
|
|
571
|
+
parsedUrl.pathname.split('/').find(segment =>
|
|
572
|
+
['instagram', 'youtube', 'reddit', 'pinterest', 'email'].includes(segment)
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
const code = parsedUrl.searchParams.get('code');
|
|
576
|
+
const error = parsedUrl.searchParams.get('error');
|
|
577
|
+
|
|
578
|
+
if (error) {
|
|
579
|
+
console.error('❌ OAuth error in callback:', error);
|
|
580
|
+
return { success: false };
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (code && platform) {
|
|
584
|
+
console.log(`✅ OAuth callback processed: ${platform} with code: ${code.substring(0, 10)}...`);
|
|
585
|
+
return { platform, code, success: true };
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
console.warn('⚠️ OAuth callback missing platform or code');
|
|
589
|
+
return { success: false };
|
|
590
|
+
} catch (error) {
|
|
591
|
+
console.error('❌ Error processing OAuth callback:', error);
|
|
592
|
+
return { success: false };
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* 🔧 UPDATE GOOGLE CLIENT IDS
|
|
598
|
+
* Allows apps to configure their own Google client IDs
|
|
599
|
+
*/
|
|
600
|
+
export const updateGoogleClientIds = (config: {
|
|
601
|
+
webClientId?: string;
|
|
602
|
+
iosClientId?: string;
|
|
603
|
+
}) => {
|
|
604
|
+
console.log('🔧 Updating Google client IDs configuration');
|
|
605
|
+
|
|
606
|
+
if (config.webClientId || config.iosClientId) {
|
|
607
|
+
// Update the YouTube configuration
|
|
608
|
+
PLATFORM_AUTH_CONFIG.youtube = {
|
|
609
|
+
...PLATFORM_AUTH_CONFIG.youtube,
|
|
610
|
+
clientId: config.webClientId || PLATFORM_AUTH_CONFIG.youtube.clientId,
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
console.log('✅ Google client IDs updated successfully');
|
|
614
|
+
}
|
|
615
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -21,6 +21,9 @@ export interface UniversalOnboardingProps {
|
|
|
21
21
|
buttonType?: 'default' | 'pill';
|
|
22
22
|
buttonForm?: 'signup' | 'login';
|
|
23
23
|
preferredPlatform?: string;
|
|
24
|
+
inferenceData?: any; // Data used for AI inference API requests
|
|
25
|
+
auto?: boolean; // If true, use inferenceData for automatic API requests
|
|
26
|
+
partner?: string; // Partner identifier (e.g., "couplebible")
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export interface ConnectionStatus {
|
|
@@ -56,7 +59,9 @@ export interface OnairosButtonProps {
|
|
|
56
59
|
preferredPlatform?: string;
|
|
57
60
|
testMode?: boolean;
|
|
58
61
|
autoFetch?: boolean;
|
|
59
|
-
inferenceData?: any;
|
|
62
|
+
inferenceData?: any; // Data used for AI inference API requests
|
|
63
|
+
auto?: boolean; // If true, use inferenceData for automatic API requests
|
|
64
|
+
partner?: string; // Partner identifier (e.g., "couplebible")
|
|
60
65
|
textLayout?: 'left' | 'right' | 'center';
|
|
61
66
|
textColor?: string;
|
|
62
67
|
proofMode?: boolean;
|